From 7a8193256b889520a1c03052dae6070540e802aa Mon Sep 17 00:00:00 2001 From: kyeong-hyeok Date: Sat, 28 Oct 2023 13:12:43 +0900 Subject: [PATCH 1/7] =?UTF-8?q?chore:=20mail,=20thymeleaf=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build.gradle b/build.gradle index 800d4099..d9f41b13 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,11 @@ dependencies { testImplementation 'org.springframework.security:spring-security-test' // Swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + // mail + implementation 'org.springframework.boot:spring-boot-starter-mail' + // thymeleaf + implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect' } tasks.named('test') { From db9727c11b9a50babe870b0e697875c9289d31cb Mon Sep 17 00:00:00 2001 From: kyeong-hyeok Date: Sat, 28 Oct 2023 13:13:25 +0900 Subject: [PATCH 2/7] =?UTF-8?q?chore:=20SecurityConfig=20url=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/pawwithu/connectdog/config/SecurityConfig.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/pawwithu/connectdog/config/SecurityConfig.java b/src/main/java/com/pawwithu/connectdog/config/SecurityConfig.java index b6bcd90f..5780e47d 100644 --- a/src/main/java/com/pawwithu/connectdog/config/SecurityConfig.java +++ b/src/main/java/com/pawwithu/connectdog/config/SecurityConfig.java @@ -35,7 +35,7 @@ public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospe .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(request -> request.requestMatchers(mvcMatcherBuilder.pattern("/login")).permitAll() - .requestMatchers(mvcMatcherBuilder.pattern("/api/sign-up")).permitAll() + .requestMatchers(mvcMatcherBuilder.pattern("/sign-up/**")).permitAll() .requestMatchers(mvcMatcherBuilder.pattern("/h2-console/**")).permitAll() .requestMatchers(mvcMatcherBuilder.pattern("/css/**")).permitAll() .requestMatchers(mvcMatcherBuilder.pattern("/js/**")).permitAll() @@ -45,8 +45,7 @@ public SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospe .requestMatchers(mvcMatcherBuilder.pattern("/swagger-ui/**")).permitAll() .requestMatchers(mvcMatcherBuilder.pattern("/swagger-resources/**")).permitAll() .requestMatchers(mvcMatcherBuilder.pattern("/v3/api-docs/**")).permitAll() - .requestMatchers(mvcMatcherBuilder.pattern("/api/sign-up/email")).permitAll() - .requestMatchers(mvcMatcherBuilder.pattern("/api/members/userName/isDuplicated")).permitAll() + .requestMatchers(mvcMatcherBuilder.pattern("/members/userName/isDuplicated")).permitAll() .anyRequest().authenticated()); return http.build(); From fc2a978f5b3c688e018e9e6d2407d7053ac735f8 Mon Sep 17 00:00:00 2001 From: kyeong-hyeok Date: Sat, 28 Oct 2023 13:14:31 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20EmailService=20=EB=B0=8F=20Controll?= =?UTF-8?q?er,=20Dto=20=EC=B6=94=EA=B0=80=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/SignUpController.java | 13 ++- .../domain/auth/dto/request/EmailRequest.java | 8 ++ .../auth/dto/response/EmailResponse.java | 4 + .../domain/auth/service/EmailService.java | 94 +++++++++++++++++++ .../domain/volunteer/entity/Volunteer.java | 2 +- .../pawwithu/connectdog/error/ErrorCode.java | 2 + 6 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/pawwithu/connectdog/domain/auth/dto/request/EmailRequest.java create mode 100644 src/main/java/com/pawwithu/connectdog/domain/auth/dto/response/EmailResponse.java create mode 100644 src/main/java/com/pawwithu/connectdog/domain/auth/service/EmailService.java diff --git a/src/main/java/com/pawwithu/connectdog/domain/auth/controller/SignUpController.java b/src/main/java/com/pawwithu/connectdog/domain/auth/controller/SignUpController.java index 53ed4ee9..0689cf99 100644 --- a/src/main/java/com/pawwithu/connectdog/domain/auth/controller/SignUpController.java +++ b/src/main/java/com/pawwithu/connectdog/domain/auth/controller/SignUpController.java @@ -1,7 +1,10 @@ package com.pawwithu.connectdog.domain.auth.controller; +import com.pawwithu.connectdog.domain.auth.dto.request.EmailRequest; import com.pawwithu.connectdog.domain.auth.dto.request.VolunteerSignUpRequest; +import com.pawwithu.connectdog.domain.auth.dto.response.EmailResponse; import com.pawwithu.connectdog.domain.auth.service.AuthService; +import com.pawwithu.connectdog.domain.auth.service.EmailService; import com.pawwithu.connectdog.error.dto.ErrorResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; @@ -18,10 +21,11 @@ @Tag(name = "Sign-Up", description = "Sign-Up API") @RestController @RequiredArgsConstructor -@RequestMapping("/api/sign-up") +@RequestMapping("/sign-up") public class SignUpController { private final AuthService authService; + private final EmailService emailService; @Operation(summary = "자체 회원가입", description = "이메일을 사용해 회원가입을 합니다.", responses = {@ApiResponse(responseCode = "204", description = "자체 회원가입 성공") @@ -35,4 +39,11 @@ public ResponseEntity signUp(@RequestBody VolunteerSignUpRequest volunteer return ResponseEntity.noContent().build(); } + @Operation(summary = "이메일 인증번호 전송", description = "입력한 이메일로 인증번호를 전송합니다.") + @PostMapping("/email") + public ResponseEntity mailConfirm(@RequestBody EmailRequest emailRequest){ + EmailResponse emailResponse = emailService.sendEmail(emailRequest); + return ResponseEntity.ok(emailResponse); + } + } diff --git a/src/main/java/com/pawwithu/connectdog/domain/auth/dto/request/EmailRequest.java b/src/main/java/com/pawwithu/connectdog/domain/auth/dto/request/EmailRequest.java new file mode 100644 index 00000000..6856696e --- /dev/null +++ b/src/main/java/com/pawwithu/connectdog/domain/auth/dto/request/EmailRequest.java @@ -0,0 +1,8 @@ +package com.pawwithu.connectdog.domain.auth.dto.request; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +public record EmailRequest(@Email(message="이메일 형식에 맞지 않습니다.") + @NotBlank(message = "이메일은 필수 입력 값입니다.")String email) { +} \ No newline at end of file diff --git a/src/main/java/com/pawwithu/connectdog/domain/auth/dto/response/EmailResponse.java b/src/main/java/com/pawwithu/connectdog/domain/auth/dto/response/EmailResponse.java new file mode 100644 index 00000000..36467e1d --- /dev/null +++ b/src/main/java/com/pawwithu/connectdog/domain/auth/dto/response/EmailResponse.java @@ -0,0 +1,4 @@ +package com.pawwithu.connectdog.domain.auth.dto.response; + +public record EmailResponse(String authCode) { +} \ No newline at end of file diff --git a/src/main/java/com/pawwithu/connectdog/domain/auth/service/EmailService.java b/src/main/java/com/pawwithu/connectdog/domain/auth/service/EmailService.java new file mode 100644 index 00000000..b0eccc16 --- /dev/null +++ b/src/main/java/com/pawwithu/connectdog/domain/auth/service/EmailService.java @@ -0,0 +1,94 @@ +package com.pawwithu.connectdog.domain.auth.service; + +import com.pawwithu.connectdog.domain.auth.dto.request.EmailRequest; +import com.pawwithu.connectdog.domain.auth.dto.response.EmailResponse; +import com.pawwithu.connectdog.error.exception.custom.BadRequestException; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Service; +import org.thymeleaf.context.Context; +import org.thymeleaf.spring6.SpringTemplateEngine; + +import java.io.UnsupportedEncodingException; +import java.util.Random; + +import static com.pawwithu.connectdog.error.ErrorCode.EMAIL_SEND_ERROR; + +@Service +@RequiredArgsConstructor +public class EmailService { + + private final JavaMailSender emailSender; + private final SpringTemplateEngine templateEngine; + private String authNum; //랜덤 인증 코드 + + /** + * 랜덤 인증 코드 생성 + */ + private void createCode() { + Random random = new Random(); + StringBuffer key = new StringBuffer(); + + for(int i = 0; i < 8; i++) { // 8자리 + int index = random.nextInt(3); + + switch (index) { + case 0 : // 소문자 + key.append((char) ((int)random.nextInt(26) + 97)); + break; + case 1: // 대문자 + key.append((char) ((int)random.nextInt(26) + 65)); + break; + case 2: // 0-9 숫자 + key.append(random.nextInt(9)); + break; + } + } + authNum = key.toString(); + } + + /** + * 메일 양식 작성 + */ + private MimeMessage createEmailForm(String email) throws MessagingException, UnsupportedEncodingException { + + createCode(); //인증 코드 생성 + String setFrom = "iconnectdog@gmail.com"; // email-config에 설정한 자신의 이메일 주소 + String toEmail = email; // 받는 사람 + String title = "ConnectDog\uD83D\uDC3E 회원가입 인증 번호입니다."; // 제목 + + MimeMessage message = emailSender.createMimeMessage(); + message.addRecipients(MimeMessage.RecipientType.TO, toEmail); // 보낼 이메일 설정 + message.setSubject(title); + message.setFrom(setFrom); + message.setText(setContext(authNum), "utf-8", "html"); + + return message; + } + + /** + * 메일 전송 + */ + public EmailResponse sendEmail(EmailRequest emailRequest) throws BadRequestException { + try{ + // 메일전송에 필요한 정보 설정 + MimeMessage emailForm = createEmailForm(emailRequest.email()); + emailSender.send(emailForm); + return new EmailResponse(authNum); + }catch (UnsupportedEncodingException | MessagingException e){ + throw new BadRequestException(EMAIL_SEND_ERROR); + } + + } + + /** + * context 설정 + */ + private String setContext(String code) { + Context context = new Context(); + context.setVariable("code", code); + return templateEngine.process("mail", context); + } +} \ No newline at end of file diff --git a/src/main/java/com/pawwithu/connectdog/domain/volunteer/entity/Volunteer.java b/src/main/java/com/pawwithu/connectdog/domain/volunteer/entity/Volunteer.java index 1c09413f..33278949 100644 --- a/src/main/java/com/pawwithu/connectdog/domain/volunteer/entity/Volunteer.java +++ b/src/main/java/com/pawwithu/connectdog/domain/volunteer/entity/Volunteer.java @@ -17,7 +17,7 @@ public class Volunteer { private String password; // 비밀번호 @Column(length = 15, nullable = false) private String nickname; // 닉네임 - @Column(length = 10, nullable = false) + @Column(length = 10) private String name; // 이름 private String phone; // 이동봉사자 휴대폰 번호 @Enumerated(EnumType.STRING) diff --git a/src/main/java/com/pawwithu/connectdog/error/ErrorCode.java b/src/main/java/com/pawwithu/connectdog/error/ErrorCode.java index 1217d09b..e314dc1f 100644 --- a/src/main/java/com/pawwithu/connectdog/error/ErrorCode.java +++ b/src/main/java/com/pawwithu/connectdog/error/ErrorCode.java @@ -7,6 +7,8 @@ @RequiredArgsConstructor public enum ErrorCode { + EMAIL_SEND_ERROR("S1", "이메일 인증 코드 전송을 실패했습니다."), + ALREADY_EXIST_EMAIL("A1", "이미 존재하는 이메일입니다."), ALREADY_EXIST_NICKNAME("A2", "이미 존재하는 사용자 닉네임입니다."), ALREADY_LOGOUT_MEMBER("A3", "이미 로그아웃한 회원입니다"), From 83e2504bdb04c076abfd98458babb16890a520a6 Mon Sep 17 00:00:00 2001 From: kyeong-hyeok Date: Sat, 28 Oct 2023 13:15:18 +0900 Subject: [PATCH 4/7] =?UTF-8?q?docs:=20mail.html=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EC=83=9D=EC=84=B1=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/templates/mail.html | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/resources/templates/mail.html diff --git a/src/main/resources/templates/mail.html b/src/main/resources/templates/mail.html new file mode 100644 index 00000000..70f9cba0 --- /dev/null +++ b/src/main/resources/templates/mail.html @@ -0,0 +1,19 @@ + + + + +
+

안녕하세요. ConnectDog🐾입니다.

+
+

아래 코드를 회원가입 창으로 돌아가 입력해주세요.

+
+ +
+

회원가입 인증 코드 입니다.

+
+
+
+
+ + + \ No newline at end of file From df87af1250723979c4595b8ca8f81971e1a4a8d3 Mon Sep 17 00:00:00 2001 From: kyeong-hyeok Date: Sat, 28 Oct 2023 14:13:03 +0900 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20Valid=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20Handler=20=EC=B6=94=EA=B0=80=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalControllerAdvice.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/com/pawwithu/connectdog/error/exception/GlobalControllerAdvice.java b/src/main/java/com/pawwithu/connectdog/error/exception/GlobalControllerAdvice.java index ede0cd37..0a9f204e 100644 --- a/src/main/java/com/pawwithu/connectdog/error/exception/GlobalControllerAdvice.java +++ b/src/main/java/com/pawwithu/connectdog/error/exception/GlobalControllerAdvice.java @@ -6,9 +6,16 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.util.List; +import java.util.stream.Collectors; + +import static org.springframework.http.HttpStatus.BAD_REQUEST; + @Slf4j @RestControllerAdvice public class GlobalControllerAdvice { @@ -31,5 +38,17 @@ public ResponseEntity handle(final TokenException e) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ErrorResponse.of(e.getCode(), e.getMessage())); } + @ExceptionHandler + public ResponseEntity handle(MethodArgumentNotValidException e) { + BindingResult bindingResult = e.getBindingResult(); + // 에러 반환이 여러 개일 경우 첫 번째 에러를 반환 + String firstErrorMessage = bindingResult.getFieldErrors().get(0).getDefaultMessage(); + // 모든 에러 메시지는 로그로 남김 + List errorList = bindingResult.getFieldErrors().stream().map(err -> err.getDefaultMessage()).collect(Collectors.toList()); + log.warn("MethodArgumentNotValidExceptionException = {}", errorList); + + return ResponseEntity.status(BAD_REQUEST).body(ErrorResponse.of("V1", firstErrorMessage)); + } + } From 876fbe3a0edaa665047991a00985d2747919054c Mon Sep 17 00:00:00 2001 From: kyeong-hyeok Date: Sat, 28 Oct 2023 14:56:04 +0900 Subject: [PATCH 6/7] =?UTF-8?q?chore:=20OpenAPIDefinition=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/pawwithu/connectdog/ConnectdogApplication.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/pawwithu/connectdog/ConnectdogApplication.java b/src/main/java/com/pawwithu/connectdog/ConnectdogApplication.java index b8c6cd43..8130a41b 100644 --- a/src/main/java/com/pawwithu/connectdog/ConnectdogApplication.java +++ b/src/main/java/com/pawwithu/connectdog/ConnectdogApplication.java @@ -1,5 +1,7 @@ package com.pawwithu.connectdog; +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.servers.Server; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @@ -8,6 +10,7 @@ @EnableJpaAuditing @EnableCaching @SpringBootApplication +@OpenAPIDefinition(servers = {@Server(url = "/", description = "https://dev-api.connectdog.site")}) public class ConnectdogApplication { public static void main(String[] args) { From dd36619250d8f93391ec9dc42158dfdc75b00f63 Mon Sep 17 00:00:00 2001 From: kyeong-hyeok Date: Sat, 28 Oct 2023 14:56:33 +0900 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EA=B2=80=EC=82=AC=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/auth/controller/SignUpController.java | 10 ++++++++-- .../connectdog/domain/auth/service/EmailService.java | 12 ++++++++++++ .../com/pawwithu/connectdog/error/ErrorCode.java | 3 +-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/pawwithu/connectdog/domain/auth/controller/SignUpController.java b/src/main/java/com/pawwithu/connectdog/domain/auth/controller/SignUpController.java index 0689cf99..bb8cdbf2 100644 --- a/src/main/java/com/pawwithu/connectdog/domain/auth/controller/SignUpController.java +++ b/src/main/java/com/pawwithu/connectdog/domain/auth/controller/SignUpController.java @@ -11,6 +11,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; @@ -39,9 +40,14 @@ public ResponseEntity signUp(@RequestBody VolunteerSignUpRequest volunteer return ResponseEntity.noContent().build(); } - @Operation(summary = "이메일 인증번호 전송", description = "입력한 이메일로 인증번호를 전송합니다.") + @Operation(summary = "이메일 인증번호 전송", description = "입력한 이메일로 인증번호를 전송합니다.", + responses = {@ApiResponse(responseCode = "200", description = "이메일 인증번호 전송 성공") + , @ApiResponse(responseCode = "400" + , description = "V1, 이메일 형식에 맞지 않습니다. \t\n V1, 이메일은 필수 입력 값입니다. \t\n A1, 이미 존재하는 이메일입니다. \t\n A4, 이메일 인증 코드 전송을 실패했습니다." + , content = @Content(schema = @Schema(implementation = ErrorResponse.class))) + }) @PostMapping("/email") - public ResponseEntity mailConfirm(@RequestBody EmailRequest emailRequest){ + public ResponseEntity mailConfirm(@RequestBody @Valid EmailRequest emailRequest){ EmailResponse emailResponse = emailService.sendEmail(emailRequest); return ResponseEntity.ok(emailResponse); } diff --git a/src/main/java/com/pawwithu/connectdog/domain/auth/service/EmailService.java b/src/main/java/com/pawwithu/connectdog/domain/auth/service/EmailService.java index b0eccc16..96b50736 100644 --- a/src/main/java/com/pawwithu/connectdog/domain/auth/service/EmailService.java +++ b/src/main/java/com/pawwithu/connectdog/domain/auth/service/EmailService.java @@ -2,6 +2,8 @@ import com.pawwithu.connectdog.domain.auth.dto.request.EmailRequest; import com.pawwithu.connectdog.domain.auth.dto.response.EmailResponse; +import com.pawwithu.connectdog.domain.intermediary.repository.IntermediaryRepository; +import com.pawwithu.connectdog.domain.volunteer.repository.VolunteerRepository; import com.pawwithu.connectdog.error.exception.custom.BadRequestException; import jakarta.mail.MessagingException; import jakarta.mail.internet.MimeMessage; @@ -14,6 +16,7 @@ import java.io.UnsupportedEncodingException; import java.util.Random; +import static com.pawwithu.connectdog.error.ErrorCode.ALREADY_EXIST_EMAIL; import static com.pawwithu.connectdog.error.ErrorCode.EMAIL_SEND_ERROR; @Service @@ -23,6 +26,8 @@ public class EmailService { private final JavaMailSender emailSender; private final SpringTemplateEngine templateEngine; private String authNum; //랜덤 인증 코드 + private final VolunteerRepository volunteerRepository; + private final IntermediaryRepository intermediaryRepository; /** * 랜덤 인증 코드 생성 @@ -72,6 +77,13 @@ private MimeMessage createEmailForm(String email) throws MessagingException, Uns * 메일 전송 */ public EmailResponse sendEmail(EmailRequest emailRequest) throws BadRequestException { + // 이메일 중복 검사 + if (volunteerRepository.existsByEmail(emailRequest.email())) { + throw new BadRequestException(ALREADY_EXIST_EMAIL); + } + if (intermediaryRepository.existsByEmail(emailRequest.email())) { + throw new BadRequestException(ALREADY_EXIST_EMAIL); + } try{ // 메일전송에 필요한 정보 설정 MimeMessage emailForm = createEmailForm(emailRequest.email()); diff --git a/src/main/java/com/pawwithu/connectdog/error/ErrorCode.java b/src/main/java/com/pawwithu/connectdog/error/ErrorCode.java index e314dc1f..434838de 100644 --- a/src/main/java/com/pawwithu/connectdog/error/ErrorCode.java +++ b/src/main/java/com/pawwithu/connectdog/error/ErrorCode.java @@ -7,11 +7,10 @@ @RequiredArgsConstructor public enum ErrorCode { - EMAIL_SEND_ERROR("S1", "이메일 인증 코드 전송을 실패했습니다."), - ALREADY_EXIST_EMAIL("A1", "이미 존재하는 이메일입니다."), ALREADY_EXIST_NICKNAME("A2", "이미 존재하는 사용자 닉네임입니다."), ALREADY_LOGOUT_MEMBER("A3", "이미 로그아웃한 회원입니다"), + EMAIL_SEND_ERROR("A4", "이메일 인증 코드 전송을 실패했습니다."), TOKEN_NOT_EXIST("T1", "토큰이 존재하지 않습니다."), TOKEN_IS_EXPIRED("T2", "만료된 토큰입니다."),