[Spring Boot] 14. 홈페이지 만들기 ⑤ 내 정보 보기, 내 정보 변경, 탈퇴

shr·2022년 2월 25일
0

Spring

목록 보기
13/23
post-thumbnail

내 정보 보기


  1. src/main/java - com.example.demo.dto - MemberDto - Read 클래스 추가
        @Data
        @AllArgsConstructor
        @Builder
        public static class Read {
            private String name;
            private String irum;
            private String email;
            // 사이트의 표준 날짜 형식으로 변환할 것이므로 String 타입을 지정해 준다.
            private String birthday;
            private String joinday;
            private String levels;
            private Long days;
        }

  1. src/main/java - com.example.demo.entity - Member - toRead 메소드 추가
    public MemberDto.Read toRead() {
            MemberDto.Read dto = MemberDto.Read.builder().username(username).email(email).irum(irum).levels(levels.name()).build();
            DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
            dto.setBirthday(dtf.format(birthday));
            dto.setJoinday(dtf.format(joinday));
            dto.setDays(ChronoUnit.DAYS.between(joinday, LocalDate.now()));
            return dto;
    }

  1. src/main/java - com.example.demo.controller.mvc - MemberController - checkPassword, read 메소드 추가

        // 내 정보 보기 전 비밀번호 확인
        @PreAuthorize("isAuthenticated()")
        @GetMapping("/member/check_password")
        public void checkPassword() {
        }
    
        @PreAuthorize("isAuthenticated()")
        @PostMapping("/member/check_password")
        public String checkPassword(@NotEmpty String password, Principal principal, HttpSession session) {
            Boolean result = service.checkPassword(password, principal.getName());
            if (result == false)
                return "redirect:/member/check_password?error";
            session.setAttribute("isPasswordCheck", true);
            return "redirect:/member/read";
        }
    
    @PreAuthorize("isAuthenticated()")
        @GetMapping("/member/read")
        public ModelAndView read(Principal principal, HttpSession session) {
            if (session.getAttribute("isPasswordCheck") == null)
                return new ModelAndView("redirect:/member/check_password");
            MemberDto.Read dto = service.read(principal.getName());
            return new ModelAndView("member/read").addObject("member", dto);
        }

  1. src/main/java - com.example.demo.service - MemberService - checkPassword, read 메소드 추가

    public Boolean checkPassword(@NotEmpty String password, String loginId) {
            Member member = dao.findById(loginId);
            return passwordEncoder.matches(password, member.getPassword());
        }
    
    public MemberDto.Read read(String loginId) {
            return dao.findById(loginId).toRead();
    }

  1. src/main/resources - templates - member - check_password.html 생성
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
    <title>Insert title here</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
    <link rel="stylesheet" href="/css/main.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>
    <script th:inline="javascript">
    $(document).ready(function() {
        const querystring = location.search.substr(1);
        if(querystring=="error")
            alert("비밀번호를 다시 확인하세요.");
    });
    </script>
    </head>
    <body>
    <span id="msg" th:value="${msg}" style="display:none;"></span>
    <div id="page">
        <header id="header" th:replace="/fragments/header">
        </header>
        <nav id="nav" th:replace="/fragments/nav">
        </nav>
        <div id="main">
            <aside id="aside" th:replace="/fragments/aside">
            </aside>
            <section id="section">
                <h1>비밀번호 확인</h1>
                <form id="check_password_form" method="post" action="/member/check_password">
                    <input type="hidden" name="_csrf" th:value="${_csrf.token}">
                    <div class="form-group">
                        <label for="check_password_email">비밀번호</label>
                        <input id="check_password_email" type="password" name="password" class="form-control">
                        <span class="helper-text" id="check_password_email_msg"></span>
                    </div>
                    <button class="btn btn-primary" id="checkPassword">확인</button>
                </form>
            </section>
        </div>
        <footer id="footer" th:replace="/fragments/footer">
        </footer>
    </div>
    </body>
    </html>

내 정보 변경


  1. src/main/java - com.example.demo.dto - MemberDto - update 클래스 추가, 유효성 검증 설정 변경

    package com.example.demo.dto;
    
    import java.time.LocalDate;
    
    import javax.validation.constraints.*;
    
    import com.example.demo.entity.Member;
    
    import lombok.*;
    
    @NoArgsConstructor(access = AccessLevel.PRIVATE)
    public class MemberDto {
        @Data
        public static class Join {
            @NotNull
            @Pattern(regexp = "^[A-Z0-9]{8,10}$", message = "아이디는 대문자나 숫자 8-10자입니다.")
            private String username;
            @NotNull
            @Pattern(regexp = "(?=.*[!@#$%^&*])^[A-Za-z0-9!@#$%^&*]{8,10}$", message = "비밀번호는 필수 입력입니다.")
            private String password;
            @NotNull
            @Pattern(regexp = "^[가-힣]{2,10}$", message = "이름은 필수 입력입니다.")
            private String irum;
            @NotNull
            @Pattern(regexp = "(?i)^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$", message = "이메일은 필수 입력입니다.")
            private String email;
            private LocalDate birthday;
    
            public Member toEntity() {
                return Member.builder().username(username).password(password).irum(irum).email(email).birthday(birthday).build();
            }
        }
    
        @Data
        @AllArgsConstructor
        @Builder
        public static class Read {
            private String username;
            private String irum;
            private String email;
            private String birthday;
            private String joinday;
            private String levels;
            private Long days;
        }
    
        @Data
        public static class update {
            // irum, email은 모두 선택 입력이다. @Pattern을 걸면 ""이 통과가 안 된다.
            // @Pattern(regexp="^[가-힣]{2,10}$")
            private String irum;
            // @Pattern(regexp="(?i)^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$")
            private String email;
    
            public Member toEntity() {
                Member member = new Member();
                if (irum.equals("") == false)
                    member.setIrum(irum);
                if (email.equals("") == false)
                    member.setEmail(email);
                return member;
            }
        }
    }

    toEntity를 함께 생성해 주었다.

    📝 @Pattern으로 검증하면 null일 때는 통과, ""는 걸린다.


  1. src/main/java - com.example.demo.controller.mvc - MemberController - update 메소드 추가

    // 내 정보 변경
        @PreAuthorize("isAuthenticated()")
        @PostMapping("/member/update")
        public String update(@Valid MemberDto.update dto, BindingResult bindingResult, RedirectAttributes ra, Principal principal) {
            // 이메일도 이름도 필수 입력이 아니다. 하지만 둘 중 하나는 있어야 한다.
            // 이메일이 null, "", "@" 중의 하나이면서 이름도 null, "" 중 하나라면 변경할 값이 없기 때문이다.
            if ((dto.getEmail() == "" || dto.getEmail() == null || dto.getEmail().equals("@")) 
                    && (dto.getIrum() == "") || dto.getIrum() == null) {
                ra.addFlashAttribute("변경할 값이 없습니다.");
                return "redirect:/member/read";
            }
    
            // 수동으로 패턴 테스트를 한다.
            String emailPattern = "^[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*@[0-9a-zA-Z]([-_.]?[0-9a-zA-Z])*.[a-zA-Z]{2,3}$";
            if (Pattern.matches(emailPattern, dto.getEmail()) == false)
                throw new ConstraintViolationException("이메일을 정확히 입력하세요.", null);
    
            String irumPattern = "^[가-힣]{2,10}$";
            if (Pattern.matches(irumPattern, dto.getIrum()) == false)
                throw new ConstraintViolationException("이름을 정확히 입력하세요.", null);
    
            service.update(dto, principal.getName());
            return "redirect:/member/read";
        }

  1. src/main/java - com.example.demo.service - MemberService - update 메소드 추가
    public void update(MemberDto.update dto, String loginId) {
            // 사용자가 값을 입력하지 않은 <input> 요소에 해당하는 커맨드 객체의 필드 값은 ""이다.
            // 우리가 업데이트할 때 값을 변경하지 않으려면 null을 가져야 한다.
            Member member = dto.toEntity();
            member.setUsername(loginId);
            dao.update(member);
        }

  1. src/main/resources - mapper - memberMapper.xml - <update> 추가
    <update id="update">
            update member 
            <trim suffixOverrides="," prefix="set"> 
                <if test="irum!=null">irum=#{irum}</if>
                <if test="password!=null">password=#{password},</if>
                <if test="email!=null">email=#{email},</if>
                <if test="enabled!=null">enabled=#{enabled},</if>
                <if test="authority!=null">authority=#{authority},</if>
                <if test="checkcode!=null">checkcode=null,</if>
                <if test="count!=null">count=#{count},</if>
                <if test="levels!=null">levels=#{levels},</if>
                <if test="loginFailCnt!=null">loginFailCnt=#{loginFailCnt},</if>
            </trim>
            where username=#{username}
        </update>

  1. src/main/resources - templates - member - read.html 생성

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
        <title>Insert title here</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
        <link rel="stylesheet" href="/css/main.css">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
        <!-- sweeetalert2 -->
        <script src="//cdn.jsdelivr.net/npm/sweetalert2@11"></script>
        <script th:inline="javascript">
        // 서버에서 이메일, 이름, 토큰 받아 오기
        const Email = /*[[${member.email}]]*/
        const irum = /*[[${member.irum}]]*/
        const _csrf = /*[[${_csrf.token}]]*/
    
        // 이메일 출력하기
        function printEmail() {
            const emails = Email.split("@");
            $('#email1').val(emails[0]);
            $('#email2').val(emails[1]);
            let isEmailServerFind = false;
            const $emailServers = $('#selectEmail option');
            $.each($emailServers, function(idx, option) {
                // ★ $(option)으로 써 주어야 한다. 앞에 $ 까먹지 않기!
                if ($(option).text() == emails[1]) {
                    $(option).prop("selected", true);
                    isEmailServerFind = true;
                }
            })
            if (isEmailServerFind == false) {
                $('#email2').prop("disabled", false);
                $($emailServers[0]).prop("selected", true);
            }
            }
    
        function changeEmail() {
            const $choiceEmail = $('#selectEmail').val();
            if ($choiceEmail == "직접 입력")
                $('#email2').prop('disabled', false).val('').attr('placeholder', '직접 입력해 주세요.').focus();
            else
                $('#email2').val($choiceEmail).prop('disabled',true);
        }
    
        // 변경한 정보 보내기 (이름이랑 이메일만 변경 가능, 변경이 있을 때만 update 가능)
        function update() {
            const $inputIrum = $('#irum').val();
            const $inputEmail = $('#email1').val() + "@" + $('#email2').val();
            if (irum == $inputIrum && email == $inputEmail) {
                alert("변경된 정보가 없습니다.");
                return false;
            }
            const $form = $('<form>').attr('action', '/member/update').attr('method', 'post').appendTo($('body'));
            $('<input>').attr('type', 'hidden').attr('name', '_csrf').val(_csrf).appendTo($form);
            $('<input>').attr('type', 'hidden').attr('name', 'name').val($inputIrum).appendTo($form);
            $('<input>').attr('type', 'hidden').attr('name', 'email').val($InputEmail).appendTo($form);
        };
    
        $(document).ready(function() {
            printEmail();
            $('#selectEmail').change(changeEmail);
            $('#update').click(update);
        })
        </script>
    </head>
    <body>
    <div id="page">
        <header th:replace="/fragments/header.html">
        </header>
        <nav th:replace="/fragments/nav.html">
        </nav>
        <div id="main">
            <aside th:replace="/fragments/aside.html">
            </aside>
            <section>
                    <table class="table table-hover">
                    <colgroup>
                        <col width="30%">
                        <col width="70%">
                    </colgroup>
                    <tr>
                        <td class="first">이름</td>
                        <td >
                            <input type="text" name="irum" id="irum" th:value="${member.irum}">
                        </td>
                    </tr>
                    <tr>
                        <td class="first">아이디</td>
                        <td id="username" th:text="${member.username}"></td>
                    </tr>
                    <tr>
                        <td class="first">생년월일</td>
                        <td id="birthday" th:text="${member.birthday}"></td>
                    </tr>
                    <tr>
                        <td class="first">가입일</td>
                        <td colspan="2" id="joinday" th:text="${member.joinday}"></td>
                    </tr>
                    <tr><td class="first">비밀번호</td>
                        <td colspan="2">
                            <a type="button" class="btn btn-info" href="/member/change_password">비밀번호 수정</a>
                        </td></tr>
                    <tr>
                        <td class="first">이메일</td>
                        <td>
                            <input type="text" name="email1" id="email1">
                                &nbsp;@&nbsp;
                            <input type="text" name="email2" id="email2" disabled="disabled">&nbsp;&nbsp;
                            <select id="selectEmail">
                                <option selected="selected">직접 입력</option>
                                <option>naver.com</option>
                                <option>daum.net</option>
                                <option>gmail.com</option>
                            </select>
                        </td>
                    </tr>
                    <tr>
                        <td class="first">회원정보</td>
                        <td>
                            가입기간 : <span id="days" th:text="${member.days}"></span><br>
                            레벨 : <span id="level" th:text="${member.levels}"></span>
                        </td></tr>
                </table>
                <button type="button" class="btn btn-success" id="update">변경하기</button>&nbsp;&nbsp;&nbsp;&nbsp;
                <button type="button" class="btn btn-success" id="resign">탈퇴하기</button>	
            </section>
        </div>
        <footer th:replace="/fragments/footer.html">
        </footer>
    </div>
    </body>
    </html>

탈퇴


  1. src/main/java - com.example.demo.controller.mvc - MemberController - resign 메소드 추가

    @PreAuthorize("isAuthenticated()")
        @PostMapping("/member/resign")
        public String resign(SecurityContextLogoutHandler handler, HttpServletRequest req, HttpServletResponse res,
                Authentication authentication) {
            // LogoutHandler가 Authentication을 파라미터로 요구한다. 어차피 써야 하니까 굳이 Principal을 또 받아 오지 않는다.
            service.resign(authentication.getName());
    
            // 탈퇴 후 로그아웃
            handler.logout(req, res, authentication);
            return "redirect:/";
        }

  1. src/main/java - com.example.demo.service - MemberService - resign 메소드 추가
    public void resign(String loginId) {
            dao.deleteById(loginId);
        }

  1. src/main/java - com.example.demo.dao - MemberDao - deleteById 메소드 추가
    public void deleteById(String loginId);

  1. src/main/resources - mapper - memberMapper.xml - <delete> 추가
    <delete id="deleteById">
            delete from member where username=#{username}
        </delete>

  1. src/main/resources - templates - member - read.html - <script>에 코드 추가
            $('#resign').click(function() {
                const choice = confirm("탈퇴하시겠습니까?");
                if (choice == false)
                    return false;
                const $form = $('<form>').attr('action', '/member/resign').attr('method', 'post').appendTo($('body'));
                $('<input>').attr('type', 'hidden').attr('name', '_csrf').val(_csrf).appendTo($form);
                $form.submit();
profile
못하다 보면 잘하게 되는 거야 ・ᴗ・̥̥̥

0개의 댓글