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;
}
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;
}
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);
}
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();
}
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>
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일 때는 통과, ""는 걸린다.
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";
}
MemberService
- update
메소드 추가public void update(MemberDto.update dto, String loginId) {
// 사용자가 값을 입력하지 않은 <input> 요소에 해당하는 커맨드 객체의 필드 값은 ""이다.
// 우리가 업데이트할 때 값을 변경하지 않으려면 null을 가져야 한다.
Member member = dto.toEntity();
member.setUsername(loginId);
dao.update(member);
}
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>
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">
@
<input type="text" name="email2" id="email2" disabled="disabled">
<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>
<button type="button" class="btn btn-success" id="resign">탈퇴하기</button>
</section>
</div>
<footer th:replace="/fragments/footer.html">
</footer>
</div>
</body>
</html>
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:/";
}
MemberService
- resign
메소드 추가public void resign(String loginId) {
dao.deleteById(loginId);
}
MemberDao
- deleteById
메소드 추가public void deleteById(String loginId);
memberMapper.xml
- <delete>
추가<delete id="deleteById">
delete from member where username=#{username}
</delete>
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();