-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* [feat] : MailConfig 작성 * [fix] : AuthError code 오탈자 수정 * [feat] : 인증코드 메일 발송 구현 - 난수 6자리 인증번호 생성 구현 - 인증코드 메일 발송 구현 * [feat] : 이메일 발송 후 반환타입 변경 * [feat] : 이메일 인증코드 발송 컨트롤러 추가 * [feat] : 이메일 발송 request/response DTO 추가(+ Mapper) * [feat] : 인증코드 검증 request DTO 이메일 필드 추가 * [feat] : 공무원 이메일 인증 요청 DTO Email validate 추가 * [feat] : 공무원 이메일 인증코드 검증 API 추가 * [feat] : 공무원 이메일 인증코드 검증 응답 DTO 추가(+Mapper) * [feat] : 공무원 이메일 비즈니스 로직 - 인증코드 생성 후 redis 저장 로직 추가 - 이메일 인증코드 검증 로직 추가 * [feat] : Redis 에러 코드 추가 * [feat] : Redis Util 메서드 추가 * [feat] : officalEmail를 통한 회원 찾기 추가 * [feat] : 메일 발송 전 중복 회원 검증 추가 * [fix] : 오탈자 수정 * [feat] : 만료일 검증 메서드 추가 * [test] : MailService 단위 테스트 추가 * [rename] : 인증코드 요청 API 메서드명 변경 * [fix] : 인증코드 요청 API HTTP Method 누락 추가 * [fix] : redis key PREFIX 변경 * [test] : 인증번호 검증 단위테스트 수정 * [test] : MailController 통합테스트 추가 * [feat] : 만료 키 관련 에러코드 추가 * [chore] : mail 의존추가 * [fix] : 인증 코드 검증 API 수정 - GetMapping -> PostMapping 변경 * [rename] : PR Review에 따른 변수명, 메서드명 수정 * [test] : PR Review에 따른 클래스명, 변수명 수정 * [test] : API 오류 수정 * [feat] : MailController 재추가 * [test] : MailContorllerTest 네이밍 변경 * [test] : 컨트롤러 통합테스트 인증을 위한 setUp 설정 * [fix] : redis 트랜잭션 제거 * [feat] : 인증코드 검증 로직 수정 * [feat] : redis data 검증 로직 변경 * [test] : MailService 단위 테스트 수정 - Mock 주입 문제로 인한 테스트 오류 해결 - Mockito -> BDDMockito 변경
- Loading branch information
Showing
19 changed files
with
611 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
src/main/java/com/dnd/gongmuin/common/config/MailConfig.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package com.dnd.gongmuin.common.config; | ||
|
||
import java.util.Properties; | ||
|
||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.mail.javamail.JavaMailSender; | ||
import org.springframework.mail.javamail.JavaMailSenderImpl; | ||
|
||
@Configuration | ||
public class MailConfig { | ||
|
||
@Value("${spring.mail.host}") | ||
private String host; | ||
|
||
@Value("${spring.mail.port}") | ||
private int port; | ||
|
||
@Value("${spring.mail.username}") | ||
private String username; | ||
|
||
@Value("${spring.mail.password}") | ||
private String password; | ||
|
||
@Value("${spring.mail.properties.mail.smtp.auth}") | ||
private boolean auth; | ||
|
||
@Value("${spring.mail.properties.mail.smtp.starttls.enable}") | ||
private boolean starttlsEnable; | ||
|
||
@Value("${spring.mail.properties.mail.smtp.starttls.required}") | ||
private boolean starttlsRequired; | ||
|
||
@Value("${spring.mail.properties.mail.smtp.connectiontimeout}") | ||
private int connectionTimeout; | ||
|
||
@Value("${spring.mail.properties.mail.smtp.timeout}") | ||
private int timeout; | ||
|
||
@Value("${spring.mail.properties.mail.smtp.writetimeout}") | ||
private int writeTimeout; | ||
|
||
@Bean | ||
public JavaMailSender javaMailSender() { | ||
JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); | ||
mailSender.setHost(host); | ||
mailSender.setPort(port); | ||
mailSender.setUsername(username); | ||
mailSender.setPassword(password); | ||
mailSender.setDefaultEncoding("UTF-8"); | ||
mailSender.setJavaMailProperties(getMailProperties()); | ||
|
||
return mailSender; | ||
} | ||
|
||
private Properties getMailProperties() { | ||
Properties properties = new Properties(); | ||
properties.put("mail.smtp.auth", auth); | ||
properties.put("mail.smtp.starttls.enable", starttlsEnable); | ||
properties.put("mail.smtp.starttls.required", starttlsRequired); | ||
properties.put("mail.smtp.connectiontimeout", connectionTimeout); | ||
properties.put("mail.smtp.timeout", timeout); | ||
properties.put("mail.smtp.writetimeout", writeTimeout); | ||
|
||
return properties; | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
src/main/java/com/dnd/gongmuin/mail/controller/MailController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package com.dnd.gongmuin.mail.controller; | ||
|
||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
import com.dnd.gongmuin.mail.dto.request.AuthCodeRequest; | ||
import com.dnd.gongmuin.mail.dto.request.SendMailRequest; | ||
import com.dnd.gongmuin.mail.dto.response.AuthCodeResponse; | ||
import com.dnd.gongmuin.mail.dto.response.SendMailResponse; | ||
import com.dnd.gongmuin.mail.service.MailService; | ||
|
||
import jakarta.validation.Valid; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/api/auth/check-email") | ||
public class MailController { | ||
|
||
private final MailService mailService; | ||
|
||
@PostMapping | ||
public ResponseEntity<SendMailResponse> sendAuthCodeToMail( | ||
@RequestBody @Valid SendMailRequest sendMailRequest) { | ||
SendMailResponse targetEmail = mailService.sendEmail(sendMailRequest); | ||
return ResponseEntity.ok(targetEmail); | ||
} | ||
|
||
@PostMapping("/authCode") | ||
public ResponseEntity<AuthCodeResponse> verifyMailAuthCode( | ||
@RequestBody @Valid AuthCodeRequest authCodeRequest) { | ||
AuthCodeResponse authCodeResponse = mailService.verifyMailAuthCode(authCodeRequest); | ||
return ResponseEntity.ok(authCodeResponse); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.dnd.gongmuin.mail.dto; | ||
|
||
import com.dnd.gongmuin.mail.dto.response.AuthCodeResponse; | ||
import com.dnd.gongmuin.mail.dto.response.SendMailResponse; | ||
|
||
import lombok.AccessLevel; | ||
import lombok.NoArgsConstructor; | ||
|
||
@NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
public class MailMapper { | ||
|
||
public static SendMailResponse toSendMailResponse(String targetEmail) { | ||
return new SendMailResponse(targetEmail); | ||
} | ||
|
||
public static AuthCodeResponse toAuthCodeResponse(boolean result) { | ||
return new AuthCodeResponse(result); | ||
} | ||
} |
16 changes: 16 additions & 0 deletions
16
src/main/java/com/dnd/gongmuin/mail/dto/request/AuthCodeRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package com.dnd.gongmuin.mail.dto.request; | ||
|
||
import jakarta.validation.constraints.Email; | ||
import jakarta.validation.constraints.NotBlank; | ||
import jakarta.validation.constraints.Pattern; | ||
|
||
public record AuthCodeRequest( | ||
@NotBlank(message = "인증 코드를 입력해주세요.") | ||
@Pattern(regexp = "\\d{6}", message = "인증 코드는 6자리 숫자여야 합니다.") | ||
String authCode, | ||
|
||
@NotBlank(message = "공무원 이메일을 입력해주세요.") | ||
String targetEmail | ||
) { | ||
} |
11 changes: 11 additions & 0 deletions
11
src/main/java/com/dnd/gongmuin/mail/dto/request/SendMailRequest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
package com.dnd.gongmuin.mail.dto.request; | ||
|
||
import jakarta.validation.constraints.Email; | ||
import jakarta.validation.constraints.NotBlank; | ||
|
||
public record SendMailRequest( | ||
@NotBlank(message = "공무원 이메일을 입력해주세요.") | ||
String targetEmail | ||
) { | ||
} |
6 changes: 6 additions & 0 deletions
6
src/main/java/com/dnd/gongmuin/mail/dto/response/AuthCodeResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.dnd.gongmuin.mail.dto.response; | ||
|
||
public record AuthCodeResponse( | ||
boolean result | ||
) { | ||
} |
6 changes: 6 additions & 0 deletions
6
src/main/java/com/dnd/gongmuin/mail/dto/response/SendMailResponse.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.dnd.gongmuin.mail.dto.response; | ||
|
||
public record SendMailResponse( | ||
String targetEmail | ||
) { | ||
} |
18 changes: 18 additions & 0 deletions
18
src/main/java/com/dnd/gongmuin/mail/exception/MailErrorCode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.dnd.gongmuin.mail.exception; | ||
|
||
import com.dnd.gongmuin.common.exception.ErrorCode; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Getter | ||
@RequiredArgsConstructor | ||
public enum MailErrorCode implements ErrorCode { | ||
|
||
CONFIGURATION_ERROR("메일 설정 오류입니다.", "MAIL_001"), | ||
CONTENT_ERROR("인증 코드 생성에 실패했습니다.", "MAIL_002"), | ||
DUPLICATED_ERROR("이미 존재하는 공무원 이메일입니다.", "MAIL_003"); | ||
|
||
private final String message; | ||
private final String code; | ||
} |
94 changes: 94 additions & 0 deletions
94
src/main/java/com/dnd/gongmuin/mail/service/MailService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package com.dnd.gongmuin.mail.service; | ||
|
||
import java.time.Duration; | ||
import java.util.Objects; | ||
|
||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.mail.javamail.JavaMailSender; | ||
import org.springframework.mail.javamail.MimeMessageHelper; | ||
import org.springframework.stereotype.Service; | ||
|
||
import com.dnd.gongmuin.common.exception.runtime.NotFoundException; | ||
import com.dnd.gongmuin.mail.dto.MailMapper; | ||
import com.dnd.gongmuin.mail.dto.request.AuthCodeRequest; | ||
import com.dnd.gongmuin.mail.dto.request.SendMailRequest; | ||
import com.dnd.gongmuin.mail.dto.response.AuthCodeResponse; | ||
import com.dnd.gongmuin.mail.dto.response.SendMailResponse; | ||
import com.dnd.gongmuin.mail.exception.MailErrorCode; | ||
import com.dnd.gongmuin.mail.util.AuthCodeGenerator; | ||
import com.dnd.gongmuin.member.service.MemberService; | ||
import com.dnd.gongmuin.redis.util.RedisUtil; | ||
|
||
import jakarta.mail.internet.MimeMessage; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class MailService { | ||
|
||
@Value("${spring.mail.auth-code-expiration-millis}") | ||
private long authCodeExpirationMillis; | ||
private final String SUBJECT = "[공무인] 공무원 인증 메일입니다."; | ||
private static final String AUTH_CODE_PREFIX = "AuthCode "; | ||
|
||
private final JavaMailSender mailSender; | ||
private final AuthCodeGenerator authCodeGenerator; | ||
private final RedisUtil redisUtil; | ||
private final MemberService memberService; | ||
|
||
public SendMailResponse sendEmail(SendMailRequest request) { | ||
String targetEmail = request.targetEmail(); | ||
|
||
checkDuplicatedOfficialEmail(targetEmail); | ||
MimeMessage email = createMail(targetEmail); | ||
mailSender.send(email); | ||
|
||
return MailMapper.toSendMailResponse(targetEmail); | ||
} | ||
|
||
public AuthCodeResponse verifyMailAuthCode(AuthCodeRequest authCodeRequest) { | ||
String targetEmail = AUTH_CODE_PREFIX + authCodeRequest.targetEmail(); | ||
String authCode = authCodeRequest.authCode(); | ||
|
||
redisUtil.validateExpiredFromKey(targetEmail); | ||
|
||
boolean result = redisUtil.validateData(targetEmail, authCode); | ||
|
||
return MailMapper.toAuthCodeResponse(result); | ||
} | ||
|
||
private MimeMessage createMail(String targetEmail) { | ||
try { | ||
String authCode = authCodeGenerator.createAuthCode(); | ||
|
||
if (Objects.isNull(authCode) || authCode.isBlank()) { | ||
throw new IllegalArgumentException(); | ||
} | ||
|
||
saveAuthCodeToRedis(targetEmail, authCode, authCodeExpirationMillis); | ||
|
||
MimeMessage mimeMessage = mailSender.createMimeMessage(); | ||
MimeMessageHelper messageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8"); | ||
messageHelper.setTo(targetEmail); | ||
messageHelper.setSubject(SUBJECT); | ||
messageHelper.setText("인증 코드는 다음과 같습니다.\n" + authCode); | ||
|
||
return mimeMessage; | ||
} catch (IllegalArgumentException e) { | ||
throw new NotFoundException(MailErrorCode.CONTENT_ERROR); | ||
} catch (Exception e) { | ||
throw new NotFoundException(MailErrorCode.CONFIGURATION_ERROR); | ||
} | ||
} | ||
|
||
private void saveAuthCodeToRedis(String targetEmail, String authCode, long authCodeExpirationMillis) { | ||
String key = AUTH_CODE_PREFIX + targetEmail; | ||
redisUtil.setValues(key, authCode, Duration.ofMillis(authCodeExpirationMillis)); | ||
} | ||
|
||
private void checkDuplicatedOfficialEmail(String officialEmail) { | ||
if (memberService.isOfficialEmailExists(officialEmail)) { | ||
throw new NotFoundException(MailErrorCode.DUPLICATED_ERROR); | ||
} | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
src/main/java/com/dnd/gongmuin/mail/util/AuthCodeGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package com.dnd.gongmuin.mail.util; | ||
|
||
import java.security.SecureRandom; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.Stream; | ||
|
||
import org.springframework.stereotype.Component; | ||
|
||
@Component | ||
public class AuthCodeGenerator { | ||
|
||
private static final SecureRandom RANDOM = new SecureRandom(); | ||
private static final int CODE_LENGTH = 6; | ||
|
||
public String createAuthCode() { | ||
return Stream.generate(() -> RANDOM.nextInt(10)) | ||
.limit(CODE_LENGTH) | ||
.map(String::valueOf) | ||
.collect(Collectors.joining()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
20 changes: 20 additions & 0 deletions
20
src/main/java/com/dnd/gongmuin/redis/exception/RedisErrorCode.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package com.dnd.gongmuin.redis.exception; | ||
|
||
import com.dnd.gongmuin.common.exception.ErrorCode; | ||
|
||
import lombok.Getter; | ||
import lombok.RequiredArgsConstructor; | ||
|
||
@Getter | ||
@RequiredArgsConstructor | ||
public enum RedisErrorCode implements ErrorCode { | ||
|
||
REDIS_SAVE_ERROR("저장하지 못했습니다.", "REDIS_001"), | ||
REDIS_FIND_ERROR("값을 찾는 도중 오류가 발생했습니다.", "REDIS_002"), | ||
REDIS_DELETE_ERROR("삭제하지 못했습니다.", "REDIS_003"), | ||
REDIS_EXPIRE_ERROR("만료시키지하지 못했습니다.", "REDIS_004"), | ||
REDIS_EXPIRED_ERROR("만료된 키 입니다.", "REDIS_005"); | ||
|
||
private final String message; | ||
private final String code; | ||
} |
Oops, something went wrong.