CommonMemberRepository - interface
public interface CommonMemberRepository {
Member save(Member member);
}
public interface MemberRepository extends Repository<Member, Long>, CommonMemberRepository {
}
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
@Slf4j
public class MemberServiceImpl implements MemberService {
private final MemberRoleRepository memberRoleRepository;
private final RoleCommonRepository roleRepository;
private final MembershipRepository membershipRepository;
private final MembershipHistoryRepository membershipHistoryRepository;
private final QuerydslMemberRepository querydslMemberRepository;
private final MemberRepository memberRepository;
/**
* 회원등록 기능
* 회원 가입 시 회원 등급(Membership)과 회원 권한(MemberRole)을 부여해줍니다.
* @param createRequest DTO로 넘겨받은 정보를 가지고 연관된 테이블까지 전부 정보를 저장할 수 있도록 합니다.
* @return 해당 entity를 넘겨주는 형식이 아닌 response DTO를 두어 반환할 수 있게 합니다.
* */
@Transactional
@Override
public MemberCreateResponse createMember(MemberCreateRequest createRequest) {
//1. 입력받은 request dto에서 해당 중복 사항이 없다면 회원 생성, 저장(연관관계에 있는 데이터들을 모두 넣어주기) 작업 계속 진행
checkExistMember(createRequest);
//2. member 생성, 이때 OneToOne 관계로 둘을 같이 save 해준다.
Membership membership = Membership.createMembership();
Member member = createRequest.toEntity(membership);
Member savedMember = memberRepository.save(member);
membershipRepository.save(membership);
MembershipHistory membershipHistory = createMembershipHistory(membership,savedMember);
membershipHistoryRepository.save(membershipHistory);
//1 = ROLE_ADMIN, 2 = ROLE_USER .... 이미 저장된 값을 가지고 작업한다.
Long roleId = 2L;
Role role = roleRepository.findByRoleId(roleId).orElseThrow(NotFoundMemberRoleException::new);
MemberRole memberRole = createMemberRole(savedMember, roleId, role);
memberRoleRepository.save(memberRole);
MemberCreateResponse response = MemberCreateResponse.fromEntity(savedMember, role);
log.info("dto = {}", createRequest.getPassword());
return response;
}
private MembershipHistory createMembershipHistory(Membership membership, Member member) {
//회원가입시에만 사용하는 멤버쉽 히스토리
return MembershipHistory.builder()
.updateTime(LocalDateTime.now())
.previousPaidAmount(0L)
.membership(membership)
.member(member)
.build();
}
private MemberRole createMemberRole(Member savedMember, Long roleId, Role roleMember) {
//회원가입시에만 사용하는 회원권한
return MemberRole.builder()
.memberRolePk(new MemberRolePk(savedMember.getMemberId(), roleId))
.member(savedMember)
.role(roleMember)
.build();
}
//회원 가입시 중복되는 id, nickname, emailAddress, phoneNumber 은 예외를 던져 처리하고 회원 가입을 하지 못하게 한다.
private void checkExistMember(MemberCreateRequest createRequest){
if(querydslMemberRepository.existMemberByNickname(createRequest.getNickname())){
throw new AlreadyExistsMember();
}
if(querydslMemberRepository.existMemberByEmail(createRequest.getEmailAddress())){
throw new AlreadyExistsMember();
}
if (querydslMemberRepository.existMemberByPhoneNumber(createRequest.getPhoneNumber())){
throw new AlreadyExistsMember();
}
if (querydslMemberRepository.existMemberByLoginId(createRequest.getId())) {
throw new AlreadyDeletedAddressException();
}
}
@PostMapping("/sign-up")
@ResponseStatus(HttpStatus.CREATED)
public ResponseDto<MemberCreateResponse> signUpMember(@Valid @RequestBody MemberCreateRequest createRequest) throws URISyntaxException {
MemberCreateResponse response = memberService.createMember(createRequest);
log.info("dto = {}", response);
return ResponseDto.<MemberCreateResponse>builder()
.status(HttpStatus.CREATED)
.success(true)
.data(response)
.build();
}
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Builder
public class MemberCreateRequest {
@NotBlank(message = "공백과 빈값은 허용하지 않습니다. 올바르게 입력해주세요.")
@Size(min = 8, max = 15)
@Pattern(regexp = "^[a-zA-Z0-9]{8,15}$", message ="아이디는 영문과 숫자로만 가능하며 숫자로 시작할 수 없습니다. 올바르게 입력해주세요.")
private String id;
@NotBlank(message = "공백과 빈값은 허용하지 않습니다. 올바르게 입력해주세요.")
@Size(min = 2, max = 30)
@Pattern(regexp = "^[가-힣ㄱ-ㅎa-zA-Z0-9._-]{2,15}$", message = "닉네임은 한글과 영문만 입력해주세요.")
private String nickname;
@Size(min = 2, max = 50)
@NotBlank(message = "공백과 빈값은 허용하지 않습니다. 올바르게 입력해주세요.")
private String name;
@NotBlank(message = "공백과 빈값은 허용하지 않습니다. 올바르게 입력해주세요.")
private String gender;
@NotBlank(message = "공백과 빈값은 허용하지 않습니다. 올바르게 입력해주세요.")
@Pattern(regexp = "^[0-9]{8}", message = "ex) 1999090 숫자로만 8글자 작성해주세요")
private String birthday;
@NotBlank
private String password;
@NotBlank(message = "공백과 빈값은 허용하지 않습니다. 올바르게 입력해주세요.")
@Size(min = 11, max = 30)
private String phoneNumber;
@Email
@NotBlank(message = "공백과 빈값은 허용하지 않습니다. 올바르게 입력해주세요.")
@Size(max = 100)
private String emailAddress;
public Member toEntity(Membership membership){
return Member.builder()
.membership(membership)
.id(id)
.nickname(nickname)
.name(name)
.genderCoder(GenderCode.valueOf(gender))
.birthday(birthday)
.password(password)
.phoneNumber(phoneNumber)
.emailAddress(emailAddress)
.memberCreatedAt(LocalDateTime.now())
.isSocialMember(false)
.isSoftDelete(false)
.memberSoftDeletedAt(null)
.isSleepAccount(false)
.build();
}
}
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class MemberCreateResponse {
private Long memberId;
private String name;
private String nickname;
private String id;
private String grade;
private String role;
/**
* @param
* @return 결과로 반환된 엔티티의 일부 데이터를 사용해서 Response DTO로 반환
* 회원 가입 시 회원의 등급은 WHITE 등급으로 고정
* 이 DTO의 경우엔 회원 가입 기능에서만 사용된다.
*
* */
public static MemberCreateResponse fromEntity(Member member, Role role){
return new MemberCreateResponse(
member.getMemberId(),
member.getName(),
member.getNickname(),
member.getId(),
Grade.WHITE.getName(),
role.getRoleType().getRoleName()
);
}
}
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="keywords" content="">
<meta name="author" content="Codescandy">
<title>yalooStore</title>
<!-- Favicon icon-->
<link rel="shortcut icon" type="image/x-icon" href="../assets/images/favicon/favicon.ico">
<!-- Libs CSS -->
<link rel="stylesheet" href="../assets/libs/bootstrap-icons/font/bootstrap-icons.css">
<!-- Theme CSS -->
<link rel="stylesheet" href="../assets/css/theme.min.css">
<!-- Bootstrap CSS CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We"
crossorigin="anonymous">
<!-- Bootstrap icons-->
<!-- Core theme CSS (includes Bootstrap)-->
<link th:href="@{../../static/css/styles.css}" rel="stylesheet"/>
<link href="../assets/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"
integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js"
integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF"
crossorigin="anonymous"></script>
</head>
<body class="bg-light">
<script type="text/javascript" th:src="@{/js/member/signUp.js}"></script>
<div th:replace="~{common/fragments/header-nonbtn::fragment-header-nonbtn}"></div>
<!-- sign up -->
<div class="min-vh-100 d-flex align-items-center ">
<!--style="background:url(../assets/images/register-img.jpg)no-repeat; background-size: cover;">-->
<div class="container ">
<div class="row">
<div class="offset-lg-3 col-lg-6 col-12">
<a href="../index.html" class="mb-4 d-flex justify-content-center"><img
src="../assets/images/logo.svg" alt=""></a>
<div class="bg-white p-4 p-lg-8 rounded-3 shadow-lg p-3 mb-5 bg-white rounded">
<form class="signup-form"
id="signup-form"
action="/members/signup"
method="post"
th:object="${member}">
<h1 class="mb-2 text-dark text-center pb-2 h3">Sign up</h1>
<p class="mb-4 text-center">Please fill in this form to create account!</p>
<!--이름-->
<div class="mb-3">
<label for="name" class="form-label text-dark">Name </label>
<div class="row">
<div class="col-9">
<input type="text" id="name" th:name="name" class="form-control border-1 col-9" placeholder="이름을 입력해주세요."
pattern="^.*(?=.*[가-힣a-z])(?=^.{2,50}).*$"
required="" autocomplete="off">
<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="fieldError"> 이름은 한글로 2~50자까지만 허용합니다 올바르게 작성해주세요 </p>
</div>
<div class="col-3">
</div>
</div>
</div>
<!--이름-->
<div class="mb-3">
<label for="id" class="form-label text-dark">Login Id </label>
<div class="row">
<div class="col-9">
<input type="text" id="id" th:name="id" class="form-control border-1" placeholder="회원 아이디를 입력해주세요."
required="">
<p th:if="${#fields.hasErrors('id')}" th:errors="*{id}" class="fieldError"> 회원 로그인 아이디는 영문과 하나 이상의 숫자의 조합으로 작성해주세요. </p>
</div>
<div class="col-3 d-flex justify-content-around">
<button id="checkLoginIdBtn" class="btn btn-light" type="button" onclick="checkLoginId()">중복 확인</button>
</div>
</div>
</div>
<div class="mb-3 mb-4">
<label for="password" class="form-label text-dark">Password</label>
<div class="row">
<div class="col-9">
<input type="password" id="password" th:name="password" class="form-control border-1"
placeholder="Password" required="">
<p th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="fieldError">비밀번호는 숫자와 영문 그리고 특수기호를 하나 포함하여야 합니다. 다시 작성해주세요 </p>
</div>
<div class="col-3">
</div>
</div>
</div>
<div class="mb-3 mb-4">
<label for="nickname" class="form-label text-dark">Nickname </label>
<div class="row">
<div class="col-9">
<input type="text" id="nickname" th:name="nickname" class="form-control border-1"
placeholder="사용하실 닉네임을 입력해주세요." required="">
<p th:if="${#fields.hasErrors('nickname')}" th:errors="*{nickname}" class="fieldError"> 닉네임은 영문으로 시작해야하며 영문, 숫자, 언더스코어(_)만을 허용합니다. </p>
</div>
<div class="col-3 d-flex justify-content-around">
<button id="checkNicknameBth" type="button" class="btn btn-light checkNicknameBth" onclick="checkNickname()">중복 확인</button>
</div>
</div>
</div>
<div class="mb-3 mb-4">
<label for="gender" class="form-label text-dark">Gender </label>
<div class="row g-2">
<div class="col-9">
<select th:name="gender" class="form-select" id="gender" required>
<option value="">성별</option>
<option value="MALE">남성</option>
<option value="FEMALE">여성</option>
</select>
<p th:if="${#fields.hasErrors('gender')}" th:errors="*{gender}" class="fieldError"> 성별을 입력해주세요. </p>
</div>
</div>
</div>
<div class="mb-3 mb-4">
<label for="email" class="form-label text-dark">Email </label>
<div class="row">
<div class="col-9">
<input type="email" id="email" th:name="emailAddress" class="form-control border-1" placeholder="이메일 주소를 입력해주세요."
required="">
<p th:if="${#fields.hasErrors('emailAddress')}" th:errors="*{emailAddress}" class="fieldError">이메일 형식에 맞게 작성해주세요.</p>
</div>
<div class="col-3 d-flex justify-content-around">
<button class="btn btn-light" type="button" id="checkEmailBth" onclick="checkEmail()">중복 확인</button>
</div>
</div>
</div>
<div class="mb-3 mb-4">
<label for="birthday" class="form-label text-dark">Birthday </label>
<div class="row">
<div class="col-9">
<input type="text" id="birthday" class="form-control border-1"
th:name="birthday"
placeholder="공백과 기호 없이 입력해주세요. ex)19960320" required="">
<p th:if="${#fields.hasErrors('birthday')}" th:errors="*{birthday}" class="fieldError">공백과 하이픈(-)은 허용하지 않습니다. 다시 입력해주세요 </p>
</div>
<div class="col-3">
</div>
</div>
</div>
<div class="mb-3 mb-4">
<label for="phoneNumber" class="form-label text-dark">PhoneNumber </label>
<div class="row">
<div class="col-9">
<input th:name="phoneNumber" type="text" id="phoneNumber" class="form-control border-1"
oninput="autoHyphen(this)" maxlength="13" required="">
<p th:if="${#fields.hasErrors('phoneNumber')}" th:errors="*{phoneNumber}" class="fieldError"> 휴대전화 번호는 010, 011 형식만을 지원합니다. 확인하여 다시 작성해주세요 </p>
</div>
<div class="col-3 d-flex justify-content-around">
<button id="checkPhoneNumberBtn" class="btn btn-light" type="button" onclick="checkPhoneNumber()">중복 확인</button>
</div>
</div>
</div>
<div class="d-grid">
<button class="btn btn-primary" type="submit">
Sign up
</button>
</div>
</form>
<div class="d-flex justify-content-center">
<p class="mt-3 mb-3 text-muted font-14 text-dark">
Already have an account? <a href="/members/login"> Sign in.</a>
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<p class="text-center mt-5 mb-3 text-muted pb-5"><span><a href="https://github.com/yeomyaloo"> yaloo</a><span>© 2023</p>
<!-- Optional JavaScript -->
<!-- Libs JS -->
<script src="../assets/libs/jquery/dist/jquery.min.js"></script>
<script src="../assets/libs/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="../assets/libs/jquery-slimscroll/jquery.slimscroll.min.js"></script>
<!-- Bootstrap JS CDN -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj" crossorigin="anonymous"></script>
<!-- Theme JS -->
<script src="../assets/js/theme.min.js"></script>
</body>
</html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="keywords" content="">
<meta name="author" content="Codescandy">
<title>yalooStore</title>
<!-- Favicon icon-->
<link rel="shortcut icon" type="image/x-icon" href="../assets/images/favicon/favicon.ico">
<!-- Libs CSS -->
<link rel="stylesheet" href="../assets/libs/bootstrap-icons/font/bootstrap-icons.css">
<!-- Theme CSS -->
<link rel="stylesheet" href="../assets/css/theme.min.css">
<!-- Bootstrap CSS CDN -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-KyZXEAg3QhqLMpG8r+8fhAXLRk2vvoC2f3B09zVXn8CA5QIVfZOJ3BCsw2P0p/We"
crossorigin="anonymous">
<!-- Bootstrap icons-->
<!-- Core theme CSS (includes Bootstrap)-->
<link th:href="@{../../static/css/styles.css}" rel="stylesheet"/>
<link href="../assets/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js"
integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js"
integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF"
crossorigin="anonymous"></script>
</head>
<body class="bg-light">
<script type="text/javascript" th:src="@{/js/member/signUp.js}"></script>
<div th:replace="~{common/fragments/header-nonbtn::fragment-header-nonbtn}"></div>
<!-- sign up -->
<div class="min-vh-100 d-flex align-items-center ">
<!--style="background:url(../assets/images/register-img.jpg)no-repeat; background-size: cover;">-->
<div class="container ">
<div class="row">
<div class="offset-lg-3 col-lg-6 col-12">
<a href="../index.html" class="mb-4 d-flex justify-content-center"><img
src="../assets/images/logo.svg" alt=""></a>
<div class="bg-white p-4 p-lg-8 rounded-3 shadow-lg p-3 mb-5 bg-white rounded">
<form class="signup-form" id="signup-form" action="/members/signup" method="post" th:object="${member}">
<h1 class="mb-2 text-dark text-center pb-2 h3">Sign up</h1>
<p class="mb-4 text-center">Please fill in this form to create account!</p>
<!--이름-->
<div class="mb-3">
<label for="name" class="form-label text-dark">Name </label>
<div class="row">
<div class="col-12">
<input type="text" id="name" th:name="name" class="form-control border-1 col-9" disabled
th:text="${response.getName()}">
</div>
</div>
</div>
<div class="mb-3">
<label for="id" class="form-label text-dark">Id </label>
<div class="row">
<div class="col-12">
<input type="text" id="id" th:name="id" class="form-control border-1"
disabled th:text="${response.getId()}">
</div>
</div>
</div>
<div class="mb-3 mb-4">
<label for="nickname" class="form-label text-dark">Nickname </label>
<div class="row">
<div class="col-12">
<input type="text" id="nickname" th:name="nickname" class="form-control border-1"
disabled th:text="${response.getNickname()}">
</div>
<div class=" d-flex justify-content-around">
</div>
</div>
</div>
<div class="mb-3 mb-4">
<label class="form-label text-dark">Grade </label>
<div class="row">
<div class="col-12">
<input th:name="phoneNumber" type="text" id="phoneNumber" class="form-control border-1"
disabled th:text="${response.getGrade()}">
</div>
<div class=" d-flex justify-content-around">
</div>
</div>
</div>
</form>
<div class="d-flex justify-content-center">
<button onclick="location.href='/members/login'"
class="btn mt-3 mb-3 text-muted font-14 text-dark border-0" type="button">
로그인하러 가기
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<p class="text-center mt-5 mb-3 text-muted pb-5"><span><a href="https://github.com/yeomyaloo"> yaloo</a><span>© 2023</p>
<!-- Optional JavaScript -->
<!-- Libs JS -->
<script src="../assets/libs/jquery/dist/jquery.min.js"></script>
<script src="../assets/libs/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="../assets/libs/jquery-slimscroll/jquery.slimscroll.min.js"></script>
<!-- Bootstrap JS CDN -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-U1DAWAznBHeqEIlVSCgzq+c9gqGAJn5c/t99JyeKa9xxaYpSvHU5awsuZVVFIhvj" crossorigin="anonymous"></script>
<!-- Theme JS -->
<script src="../assets/js/theme.min.js"></script>
</body>
</html>
@Service
@Slf4j
@RequiredArgsConstructor
public class MemberServiceImpl implements MemberService {
private final PasswordEncoder passwordEncoder;
private final RestTemplate restTemplate;
private final GatewayConfig gatewayConfig;
/**
* {@inheritDoc}
* */
@Override
public SignUpResponse signUp(SignUpRequest request) {
request.setPassword(passwordEncoder.encode(request.getPassword()));
HttpEntity<SignUpRequest> entity = getHttpEntity(request);
ResponseEntity<ResponseDto<SignUpResponse>> response = restTemplate.exchange(
gatewayConfig.getShopUrl() + "/api/service/members/sign-up",
HttpMethod.POST,
entity,
new ParameterizedTypeReference<>() {
}
);
log.info("response = {}", response.getBody().getData());
return response.getBody().getData();
}
private static HttpEntity<SignUpRequest> getHttpEntity(SignUpRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<SignUpRequest> entity = new HttpEntity<>(request, headers);
return entity;
}
}
RestController
가 아닌 Contoller
를 사용한다.
@Slf4j
@Controller
@RequiredArgsConstructor
@RequestMapping("/members")
public class MemberAuthWebController {
private final MemberService memberService;
/**
* 회원가입 화면으로 이동합니다.
* */
@GetMapping("/signup")
public String signupForm(@ModelAttribute(name = "member") SignUpRequest request){
return "auth/signup-form";
}
@PostMapping("/signup")
public String signup(@Valid @ModelAttribute(name ="member") SignUpRequest request,
BindingResult bindingResult,
Model model){
if(bindingResult.hasErrors()){
log.info("binding result : {}",bindingResult);
log.info(bindingResult.getObjectName());
return "redirect:/members/signup";
}
SignUpResponse response = memberService.signUp(request);
model.addAttribute("response", response);
return "auth/signup-success";
}
-@Valid @ModelAttribute(name ="member") SignUpRequest request
: 해당 객체를 데이터 바인딩하고 바인딩한 데이터를 검증한다.
Model model
: 응답 객체 뷰페이지로 넘겨주기 위한 작업BindingResult bindingResult
: 검증 결과@Getter
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Setter
public class SignUpRequest {
@NotBlank(message = "공백과 빈값은 허용하지 않습니다. 올바르게 입력해주세요.")
@Size(min = 2, max = 20)
@Pattern(regexp = "^[a-zA-Z0-9]{8,15}$", message ="아이디는 영문과 숫자로만 가능하며 숫자로 시작할 수 없습니다. 올바르게 입력해주세요.")
private String id;
@NotBlank(message = "공백과 빈값은 허용하지 않습니다. 올바르게 입력해주세요.")
@Size(min = 2, max = 30)
@Pattern(regexp = "^[가-힣ㄱ-ㅎa-zA-Z0-9._-]{2,15}$", message = "닉네임은 한글과 영문만 입력해주세요.")
private String nickname;
@Size(min = 2, max = 50)
@NotBlank(message = "공백과 빈값은 허용하지 않습니다. 올바르게 입력해주세요.")
@Pattern(regexp = "^[가-힣a-zA-Z]{2,50}$")
private String name;
@NotBlank(message = "공백과 빈값은 허용하지 않습니다. 올바르게 입력해주세요.")
private String gender;
@NotBlank(message = "공백과 빈값은 허용하지 않습니다. 올바르게 입력해주세요.")
@Pattern(regexp = "^[0-9]{8}", message = "ex) 1999090 숫자로만 8글자 작성해주세요")
private String birthday;
@NotBlank
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]{8,20}$",
message = "비밀번호는 최소 8자 최대 20자, 하나 이상의 문자와 하나의 숫자 및 하나의 특수 문자가 들어가야 합니다.")
private String password;
@NotBlank(message = "공백과 빈값은 허용하지 않습니다. 올바르게 입력해주세요.")
@Size(min = 11, max = 30)
private String phoneNumber;
@Email
@NotBlank(message = "공백과 빈값은 허용하지 않습니다. 올바르게 입력해주세요.")
@Size(min = 2, max = 100)
private String emailAddress;
}