본문 바로가기
스프링과 JPA 기반 웹 어플리케이션 개발/4부 스터디

53. 스터디 설정 - 배너

by Backchus 2020. 4. 24.

배너

 

배너 화면 요청을 처리하는 핸들러 생성

package me.weekbelt.studyolle.study;

@RequiredArgsConstructor
@RequestMapping("/study/{path}/settings")
@Controller
public class StudySettingsController {

    // ......
    
    @GetMapping("/banner")
    public String studyImageForm(@CurrentAccount Account account, 
                                 @PathVariable String path, Model model) {
        Study study = studyService.getStudyToUpdate(account, path);
        model.addAttribute(account);
        model.addAttribute(study);
        return "study/settings/banner";
    }

}

 

배너 화면 구현(study/settings/banner.html)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments.html::head"></head>
<body class="bg-light">
<div th:replace="fragments.html::main-nav"></div>
<div th:replace="fragments.html::study-banner"></div>
<div class="container">
    <div th:replace="fragments.html::study-info"></div>
    <div th:replace="fragments.html::study-menu(studyMenu='settings')"></div>
    <div class="row mt-3 justify-content-center">
        <div class="col-2">
            <div th:replace="fragments.html::study-settings-menu(currentMenu='image')"></div>
        </div>
        <div class="col-8">
            <div th:replace="fragments.html::message"></div>
            <div class="row">
                <h2 class="col-sm-12">배너 이미지 사용</h2>
            </div>
            <form th:if="${!study.useBanner}" action="#"
                  th:action="@{'/study/' + ${study.getPath()} + '/settings/banner/enable'}" method="post" novalidate>
                <div class="alert alert-primary" role="alert">
                    스터디 메뉴에서 스터디 배너 이미지를 사용합니다. 스터디 배너 이미지를 아직 설정하지 않았다면, 기본 배너 이미지를 사용합니다.
                </div>
                <div class="form-group">
                    <button class="btn btn-outline-primary btn-block" type="submit" aria-describedby="submitHelp">배너 이미지
                        사용하기
                    </button>
                </div>
            </form>
            <form th:if="${study.useBanner}" action="#"
                  th:action="@{'/study/' + ${study.getPath()} + '/settings/banner/disable'}" method="post" novalidate>
                <div class="alert alert-primary" role="alert">
                    스터디 메뉴에서 스터디 배너 이미지를 사용하지 않습니다. 스터디 목록에서는 배너 이미지를 사용합니다.
                </div>
                <div class="form-group">
                    <button class="btn btn-outline-primary btn-block" type="submit" aria-describedby="submitHelp">배너 이미지
                        사용하지 않기
                    </button>
                </div>
            </form>
            <hr/>
            <div class="row">
                <h2 class="col-sm-12">배너 이미지 변경</h2>
            </div>
            <form id="imageForm" action="#" th:action="@{'/study/' + ${study.getPath()} + '/settings/banner'}"
                  method="post" novalidate>
                <div class="form-group">
                    <input id="studyImage" type="hidden" name="image" class="form-control"/>
                </div>
            </form>
            <div class="card text-center">
                <div id="current-study-image" class="mt-3">
                    <img class="rounded" th:src="${study.image}" width="640" alt="name" th:alt="${study.title}"/>
                </div>
                <div id="new-study-image" class="mt-3"></div>
                <div class="card-body">
                    <div class="custom-file">
                        <input type="file" class="custom-file-input" id="study-image-file">
                        <label class="custom-file-label" for="study-image-file"></label>
                    </div>
                    <div id="new-study-image-control" class="mt-3">
                        <button class="btn btn-outline-primary btn-block" id="cut-button">자르기</button>
                        <button class="btn btn-outline-success btn-block" id="confirm-button">확인</button>
                        <button class="btn btn-primary btn-block" id="save-button">저장</button>
                        <button class="btn btn-outline-warning btn-block" id="reset-button">취소</button>
                    </div>
                    <div id="cropped-new-study-image" class="mt-3"></div>
                </div>
            </div>
        </div>
    </div>
    <div th:replace="fragments.html::footer"></div>
</div>
<script th:replace="fragments.html::tooltip"></script>
<link href="/node_modules/cropper/dist/cropper.min.css" rel="stylesheet">
<script src="/node_modules/cropper/dist/cropper.min.js"></script>
<script src="/node_modules/jquery-cropper/dist/jquery-cropper.min.js"></script>
<script type="application/javascript">
    $(function () {
        cropper = '';
        let $confirmBtn = $("#confirm-button");
        let $resetBtn = $("#reset-button");
        let $cutBtn = $("#cut-button");
        let $saveBtn = $("#save-button");
        let $newStudyImage = $("#new-study-image");
        let $currentStudyImage = $("#current-study-image");
        let $resultImage = $("#cropped-new-study-image");
        let $studyImage = $("#studyImage");

        $newStudyImage.hide();
        $cutBtn.hide();
        $resetBtn.hide();
        $confirmBtn.hide();
        $saveBtn.hide();

        $("#study-image-file").change(function (e) {
            if (e.target.files.length === 1) {
                const reader = new FileReader();
                reader.onload = e => {
                    if (e.target.result) {
                        if (!e.target.result.startsWith("data:image")) {
                            alert("이미지 파일을 선택하세요.");
                            return;
                        }

                        let img = document.createElement("img");
                        img.id = 'new-study';
                        img.src = e.target.result;
                        img.setAttribute('width', '100%');

                        $newStudyImage.html(img);
                        $newStudyImage.show();
                        $currentStudyImage.hide();

                        let $newImage = $(img);
                        $newImage.cropper({aspectRatio: 13 / 2});
                        cropper = $newImage.data('cropper');

                        $cutBtn.show();
                        $confirmBtn.hide();
                        $resetBtn.show();
                    }
                };

                reader.readAsDataURL(e.target.files[0]);
            }
        });

        $resetBtn.click(function () {
            $currentStudyImage.show();
            $newStudyImage.hide();
            $resultImage.hide();
            $resetBtn.hide();
            $cutBtn.hide();
            $confirmBtn.hide();
            $saveBtn.hide();
            $studyImage.val('');
        });

        $cutBtn.click(function () {
            let dataUrl = cropper.getCroppedCanvas().toDataURL();

            if (dataUrl.length > 1000 * 1024) {
                alert("이미지 파일이 너무 큽니다. 1024000 보다 작은 파일을 사용하세요. 현재 이미지 사이즈 " + dataUrl.length);
                return;
            }

            let newImage = document.createElement("img");
            newImage.id = "cropped-new-study-image";
            newImage.src = dataUrl;
            newImage.width = 640;
            $resultImage.html(newImage);
            $resultImage.show();
            $confirmBtn.show();

            $confirmBtn.click(function () {
                $newStudyImage.html(newImage);
                $cutBtn.hide();
                $confirmBtn.hide();
                $studyImage.val(dataUrl);
                $saveBtn.show();
            });
        });

        $saveBtn.click(function () {
            $("#imageForm").submit();
        })
    });
</script>
</body>
</html>

 

이미지 파일 업로드시 고려할 점

이미지 파일인지 확인 (이미지가 아닌 파일을 업로드 하려는건 아닌지 확인)

settings/profile.html에서 js부분 수정 (이미지 파일인지 확인)

// ....

<script type="application/javascript">
    $(function() {
    
        // .......

        $("#profile-image-file").change(function(e) {
            if (e.target.files.length === 1) {
                const reader = new FileReader();
                reader.onload = e => {
                    // 추가 ---------
                    if (e.target.result) {
                        if (!e.target.result.startsWith("data:image")) {
                            alert("이미지 파일을 선택하세요.");
                            return;
                        }

                       // ...........
                    }
                };

                reader.readAsDataURL(e.target.files[0]);
            }
        });

        // ...........
    });
</script>
</body>
</html>

 

 

이미지 크기 확인 (너무 큰 이미지 업로드 하지 않도록)

settings/profile.html에서 js 부분 수정

// .....

<script type="application/javascript">
    $(function() {
        // ........
        
        $cutBtn.click(function () {
            let dataUrl = cropper.getCroppedCanvas().toDataURL();

            // 추가
            if (dataUrl.length > 1000 * 1024) {
                alert("이미지 파일이 너무 큽니다. 1024000 보다 작은 파일을 사용하세요. 현재 이미지 사이즈 " + dataUrl.length);
                return;
            }

            // ...............
        });
    });
</script>
</body>
</html>

 

서버 요청 크기 설정

# 톰캣 기본 요청 사이즈는 2MB 입니다. 그것보다 큰 요청을 받고 싶은 경우에 이 값을 조정해야 합니다.
server.tomcat.max-http-form-post-size=5MB

 

application.properties에 추가

// .......

# 톰캣 기본 요청 사이즈는 2MB 입니다. 그것보다 큰 요청을 받고 싶은 경우에 이 값을 조정해야 합니다.
server.tomcat.max-http-form-post-size=5MB

// .......

 

스터디 배너 업데이트 처리 핸들러 작성

package me.weekbelt.studyolle.study;

@RequiredArgsConstructor
@RequestMapping("/study/{path}/settings")
@Controller
public class StudySettingsController {

    // ......

    @PostMapping("/banner")
    public String studyImageSubmit(@CurrentAccount Account account, @PathVariable String path,
                                   String image, RedirectAttributes attributes){
        Study study = studyService.getStudyToUpdate(account, path);
        studyService.updateStudyImage(study, image);
        attributes.addFlashAttribute("message", "스터디 이미지를 수정했습니다.");
        return "redirect:/study/" + getPath(path) + "/settings/banner";
    }

}

 

배너 이미지 업데이트 처리를 위한 updateStudyImage메소드 작성

package me.weekbelt.studyolle.study;

@RequiredArgsConstructor
@Transactional
@Service
public class StudyService {

    // .......

    public void updateStudyImage(Study study, String image) {
        study.setImage(image);
    }
}

 

배너 이미지 사용/비사용 요청을 받을 핸들러 작성

package me.weekbelt.studyolle.study;

@RequiredArgsConstructor
@RequestMapping("/study/{path}/settings")
@Controller
public class StudySettingsController {

    // .........
    
    @PostMapping("/banner/enable")
    public String enableStudyBanner(@CurrentAccount Account account, @PathVariable String path) {
        Study study = studyService.getStudyToUpdate(account, path);
        studyService.enableStudyBanner(study);
        return "redirect:/study/" + getPath(path) + "/settings/banner";
    }

    @PostMapping("/banner/disable")
    public String disableStudyBanner(@CurrentAccount Account account, @PathVariable String path) {
        Study study = studyService.getStudyToUpdate(account, path);
        studyService.disableStudyBanner(study);
        return "redirect:/study/" + getPath(path) + "/settings/banner";
    }

}

 

배너 이미지 사용/비사용 처리를 위한 enableStudyBanner, disableStudyBanner 메소드 작성

package me.weekbelt.studyolle.study;


@RequiredArgsConstructor
@Transactional
@Service
public class StudyService {

    // ..........

    public void enableStudyBanner(Study study) {
        study.setUseBanner(true);
    }

    public void disableStudyBanner(Study study) {
        study.setUseBanner(false);
    }
}

 

 

참고: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1#

 

스프링과 JPA 기반 웹 애플리케이션 개발 - 인프런

이 강좌에서 여러분은 실제로 운영 중인 서비스를 스프링, JPA 그리고 타임리프를 비롯한 여러 자바 기반의 여러 오픈 소스 기술을 사용하여 웹 애플리케이션을 개발하는 과정을 학습할 수 있습니다. 이 강좌를 충분히 학습한다면 여러분 만의 웹 서비스를 만들거나 취직에 도움이 될만한 포트폴리오를 만들 수 있을 겁니다. 활용 웹 개발 프레임워크 및 라이브러리 Java Spring Spring Boot Spring Data JPA Thymeleaf 온라인 강의 스

www.inflearn.com