-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[임시 비밀번호 발급 및 전송] 임시 비밀번호 발급 및 전송 비즈니스 로직/API 구성(#35) #40
Conversation
보낼 화살 수를 담은 요청 DTO 정의
- 화살 송수신 내역 repository 정의 - 보낸 사용자, 받은 사용자를 통해 내역 존재 여부를 확인하는 로직 구현
보낸 사용자, 받은 사용자를 통해 송수신 내역 존재 여부를 확인하는 로직 구현
다음 상황들에 대한 예외 enum을 정의하였다. - 자기 자신에게 화살을 보내는 경우 - 이미 화살을 보낸 사용자인 경우 - 가진 화살 수가 부족해 화살을 보낼 수 없는 경우
감소하려는 화살 수가 사용자가 보유한 화살 수보다 클 경우 예외 발생
화살 보내기 비즈니스 로직은 다음 과정을 거친다. 1. 자기 자신에게 화살을 보내는 상황 검증 2. 화살을 보낸 사용자에게 또 보내는 상황 검증 3. 화살 내역 저장 4. 보내는 사용자 화살 감소, 화살이 부족할 경우 예외 발생(과정 3 롤백) 5. 받는 사용자 화살 증가
- query, command를 service, repository를 중개하는 별도의 계층을 통해 구분 - 이에 따라 기존 command repository 삭제 - 기존 command repository 사용 위치, 화살 command로 대체
# Conflicts: # be/src/main/java/yeonba/be/arrow/repository/ArrowCommand.java # be/src/main/java/yeonba/be/arrow/service/ArrowService.java # be/src/main/java/yeonba/be/exception/CommonException.java # be/src/main/java/yeonba/be/user/entity/User.java
서로 대치되는 작업을 수행하는 상황을 잘 표현할 수 있도록 메서드 이름 수정
- 이미 화살을 보낸 사용자 예외 - 화살이 부족하여 화살을 보낼 수 없는 예외
가독성 향상을 위해 간단한 이름을 수정
dev 브랜치 작업 내역 병합에 따른 코드 배치 수정
좀 더 명확하게 대치되는 의미를 나타낼 수 있도록 화살을 받는 사용자 ID 파라미터 이름을 receiverId로 수정
- 이름 칼럼 추가 - 테스트시 활용 가능한 생성자 추가
- 래퍼 타입 필드들 기본 타입으로 변경 - 프로필 사진 URL 리스트 필드 추가 - 사용자가 가진 총 화살 수 필드 추가 - 음주 성향, 흡연 성향 필드 추가 - 코드 정렬
- 레퍼런스 타입 not null 필드들, Column 어노테이션 사용 명시 - salt 필드 추가 - 생성 일시, 최종 수정 일시 필드 추가 - JpaAuditing 활용, 생성/최종 수정 일시 필드들 엔티티 저장시 자동 설정 - 새로운 생성자 구성
프로필을 조회하는 사용자 ID의 경우 상대방이 화살을 보넀던 사용자인지 확인하기 위해 파라미터로 받는다.
- 요구사항 변경에 따라 음주 성향, 흡연 성향 필드 삭제 - 성별 정보 제공 메서드 추가
- 음주 성향, 흡연 성향 필드 삭제 - 성별 필드 추가
- 음주 성향, 흡연 성향 필드 삭제 - 생성자 성별 필드 설정 로직 수정 - 코드 정렬
- 음주 성향, 흡연 성향 삭제 - 성별 정보 응답에 포함토록 수정 - 코드 정렬
# Conflicts: # be/src/main/java/yeonba/be/arrow/service/ArrowService.java # be/src/main/java/yeonba/be/user/controller/UserController.java # be/src/main/java/yeonba/be/user/entity/User.java
# Conflicts: # be/src/main/java/yeonba/be/user/entity/User.java
dev 브랜치 작업 내역 병합에 따른 코드 재배치
dev 브랜치 작업 내역 병합에 따른 코드 재배치
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코드, 팀 컨벤션 생각해주세요. 테스트 코드가 없기 때문에 급하게 구현만 하는 것보다 postman으로 테스트도 성공/실패 케이스 모두 해보시고 PR 올려주시기 바랍니다.
mail: | ||
host: smtp.gmail.com | ||
port: 587 | ||
username: ${GOOGLE_SMTP_USERNAME} | ||
password: ${GOOGLE_SMTP_PASSWORD} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
secret의 경우 properties에서 관리하기로 했는데 yml에 작성하신 이유는 무엇인가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
secret이라는 게 노출되었을 경우 보안적으로 위험할 수 있는 대상들을 지칭한다고 생각했습니다. smtp 서비스 제공 호스트와 그에 따른 포트의 경우 지극히 공개적인 값이라 굳이 application.properties
로 관리하지 않고 YAML 파일에 바로 명시하였습니다. 하지만 말씀해주신 부분을 생각해보면 application.properties
에서 일관성 있게 관리하는 것이 맞다고 생각됩니다. 수정하겠습니다.
if(result.matches(pattern)){ | ||
|
||
return result; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return 문이 없습니다.
public static String generatePassword(){ | ||
String pattern = ServiceRegex.PASSWORD.getPattern(); | ||
SecureRandom random = new SecureRandom(); | ||
StringBuilder sb = new StringBuilder(); | ||
|
||
int length = MIN_LENGTH + random.nextInt(MAX_LENGTH - MIN_LENGTH+1); | ||
|
||
while(true){ | ||
sb.setLength(0); | ||
for(int i=0; i<length; i++){ | ||
int choice = random.nextInt(4); | ||
|
||
if(choice == 0){ | ||
sb.append(getRandomCharacterFromSrc(LOWER, random)); | ||
continue; | ||
} | ||
|
||
if(choice==1){ | ||
sb.append(getRandomCharacterFromSrc(UPPER, random)); | ||
continue; | ||
} | ||
|
||
if(choice==2){ | ||
sb.append(getRandomCharacterFromSrc(DIGITS, random)); | ||
continue; | ||
} | ||
|
||
sb.append(getRandomCharacterFromSrc(SPECIAL, random)); | ||
} | ||
|
||
String result = sb.toString(); | ||
if(result.matches(pattern)){ | ||
|
||
return result; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
depth가 너무 깊어 어디부터 어디까지 하나의 블록인지 구분하기가 어려운데 아래와 같은 방식은 어떤가요?
public static String generatePassword(){ | |
String pattern = ServiceRegex.PASSWORD.getPattern(); | |
SecureRandom random = new SecureRandom(); | |
StringBuilder sb = new StringBuilder(); | |
int length = MIN_LENGTH + random.nextInt(MAX_LENGTH - MIN_LENGTH+1); | |
while(true){ | |
sb.setLength(0); | |
for(int i=0; i<length; i++){ | |
int choice = random.nextInt(4); | |
if(choice == 0){ | |
sb.append(getRandomCharacterFromSrc(LOWER, random)); | |
continue; | |
} | |
if(choice==1){ | |
sb.append(getRandomCharacterFromSrc(UPPER, random)); | |
continue; | |
} | |
if(choice==2){ | |
sb.append(getRandomCharacterFromSrc(DIGITS, random)); | |
continue; | |
} | |
sb.append(getRandomCharacterFromSrc(SPECIAL, random)); | |
} | |
String result = sb.toString(); | |
if(result.matches(pattern)){ | |
return result; | |
} | |
} | |
} | |
public static String generatePassword() { | |
StringBuilder password = new StringBuilder(MIN_LENGTH); | |
Random random = new Random(); | |
// 각 카테고리에서 최소 하나의 문자를 선택 | |
password.append(LOWER_CASES.charAt(random.nextInt(LOWER_CASES.length()))); | |
password.append(UPPER_CASES.charAt(random.nextInt(UPPER_CASES.length()))); | |
password.append(DIGITS.charAt(random.nextInt(DIGITS.length()))); | |
password.append(SPECIAL_CHARS.charAt(random.nextInt(SPECIAL_CHARS.length()))); | |
// 모든 가능한 문자를 포함하는 문자열 | |
String allPossibleChars = LOWER_CASES + UPPER_CASES + DIGITS + SPECIAL_CHARS; | |
// 나머지 길이를 채우기 | |
for (int i = 4; i < MIN_LENGTH; i++) { | |
password.append(allPossibleChars.charAt(random.nextInt(allPossibleChars.length()))); | |
} | |
// 문자열 섞기 | |
ArrayList<Character> pwdChars = new ArrayList<>(); | |
for (char c : password.toString().toCharArray()) { | |
pwdChars.add(c); | |
} | |
Collections.shuffle(pwdChars); | |
StringBuilder finalPassword = new StringBuilder(); | |
for (char c : pwdChars) { | |
finalPassword.append(c); | |
} | |
return finalPassword.toString(); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
위 구성 형태가 더 깔끔하고 가독성도 좋은 것 같습니다. 앞서 언급했던 return
문 관련 코멘트도 고려하여 수정하겠습니다.
while(true){ | ||
sb.setLength(0); | ||
for(int i=0; i<length; i++){ | ||
int choice = random.nextInt(4); | ||
|
||
if(choice == 0){ | ||
sb.append(getRandomCharacterFromSrc(LOWER, random)); | ||
continue; | ||
} | ||
|
||
if(choice==1){ | ||
sb.append(getRandomCharacterFromSrc(UPPER, random)); | ||
continue; | ||
} | ||
|
||
if(choice==2){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
코드 컨벤션 생각해주세요.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
수정하겠습니다! 앞으로 유의하겠습니다.
임시 비밀번호는 다음 과정을 거쳐 생성된다. 1. 각 카테고리(영어 대소문자, 숫자, 특수문자)에서 한 글자씩 랜덤 선택 2. 모든 가능한 문자를 포함하는 문자열 형성 3. 앞서 생성한 문자열에서 나머지 길이를 채울 문자 랜덤 선택 4. 선택된 문자들 섞기 5. 최종 임시 비밀번호 생성 - 랜덤한 문자를 선택하는 부분은 spring boot에서 기본 제공되는 RandomStringUtils를 활용 - 문자열을 섞는 부분에서 shuffle을 위해 가변 컬렉션을 생성하려 새로운 List 할당
메일 서비스 제공 호스트와 포트 번호도 application.properties에서 관리하는 형태로 수정
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제가 잘 몰라서 그러는데 spring mail을 사용하려면 application.yml 파일에
spring:
mail:
host:
...
과 같은 형식이 들어가야 하는 건가요?
String subject, | ||
String text){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
String subject, | |
String text){ | |
String subject, | |
String text) { |
public enum ServiceRegex { | ||
|
||
EMAIL("[a-zA-Z0-9_!#$%&’*+/=?`{|}~^.-]+@[a-zA-Z0-9.-]+$"), | ||
PASSWORD("^(?=.*[a-zA-Z])(?=.*\\d)(?=.*[~#@!]).{8,20}$"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
조금 더 직관적인 이름을 사용하는 것은 어떤가요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
클래스명 수정하고 반영하겠습니다!
mail: | ||
host: ${GOOGLE_SMTP_HOST} | ||
port: ${GOOGLE_SMTP_PORT} | ||
username: ${GOOGLE_SMTP_USERNAME} | ||
password: ${GOOGLE_SMTP_PASSWORD} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mail: | |
host: ${GOOGLE_SMTP_HOST} | |
port: ${GOOGLE_SMTP_PORT} | |
username: ${GOOGLE_SMTP_USERNAME} | |
password: ${GOOGLE_SMTP_PASSWORD} |
@Value("${spring.mail.host}") | ||
private String serverHost; | ||
|
||
@Value("${spring.mail.port}") | ||
private int serverPort; | ||
|
||
@Value("${spring.mail.username}") | ||
private String username; | ||
|
||
@Value("${spring.mail.password}") | ||
private String password; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Value("${spring.mail.host}") | |
private String serverHost; | |
@Value("${spring.mail.port}") | |
private int serverPort; | |
@Value("${spring.mail.username}") | |
private String username; | |
@Value("${spring.mail.password}") | |
private String password; | |
@Value("${GOOGLE_SMTP_HOST}") | |
private String serverHost; | |
@Value("${GOOGLE_SMTP_PORT}") | |
private int serverPort; | |
@Value("${GOOGLE_SMTP_USERNAME}") | |
private String username; | |
@Value("${GOOGLE_SMTP_PASSWORD}") | |
private String password; |
@RequestBody @Valid UserPasswordInquiryRequest request) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
제가 생각할 때는 @RequestBody UserPasswordInquiryRequest
를 하나로 보고 이것을 @Valid
로 검증한다라는 의미로 생각하는게 자연스러운데 어떻게 생각하시나요?
@RequestBody @Valid UserPasswordInquiryRequest request) { | |
@Valid @RequestBody UserPasswordInquiryRequest request) { | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
로직 흐름상 더 자연스러운 것 같습니다. 반영하겠습니다!
# Conflicts: # be/src/main/java/yeonba/be/mypage/dto/response/UserProfileDetailResponse.java # be/src/main/java/yeonba/be/user/dto/response/UserProfileResponse.java # be/src/main/java/yeonba/be/user/entity/User.java
|
어플리케이션 전역에서 검증을 위해 사용되는 정규식들을 모아놓는 용도를 잘 표현하기 위해 클래스명 수정
사용자 지정 속성을 사용한다는 맥락을 잘 나타낼 수 있도록 수정
요청 바디에서 읽어온 데이터를 검증한다는 흐름에서 @Valid 어노테이션이 @RequestBody보다 먼저 위치하도록 코드 수정
|
|
/* | ||
임시 비밀번호는 다음 과정을 거친다. | ||
1. 요청 이메일 기반 사용자 조회 | ||
2. 임시 비밀번호 생성 | ||
3. 사용자 비밀번호, 임시 비밀번호로 변경 | ||
4. 임시 비밀번호 발급 메일 전송 | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
작업 대상
ServiceRegex
)TemporaryPasswordGenerator
)MailConfig
)EmailService
)LoginService
)LoginController
)📄 작업 내용
JavaMailSender
) 사용을 위한 설정 정의임시 비밀번호를 암호화하는 로직, 비동기 처리 로직은 추후에 구현 예정
🙋🏻 주의 사항
build.gradle
에 spring mail 모듈에 대한 의존성을 추가하였습니다.application.properties
와application.yml
에 해당 서버를 이용하는데 필요한 속성들을 정의했으니 참고해주세요.📎 관련 이슈
springframwork.util
의 Javadoc에서 해당 패키지에서 제공하는StringUtils
보다 복합적인 유틸 기능을 필요로 할 시apache.commons.lang
에서 제공하는 유틸 클래스를 사용할 것을 언급하고 있음이에 임시 비밀번호 생성 로직에 spring boot에서 기본적으로 제공하는
apache.commons.lang3.RandomStringUtils
를 사용이메일을 발송하는 로직이 동기 방식으로 동작할 경우 SMTP 서버에서 서비스 서버가 응답을 받은 후 클라이언트에 응답
이로 인해 응답 시간이 17~20초로 매우 길게 측정됨
비동기 처리를 하면 시간을 훨씬 단축되나 해당 로직이 별도의 쓰레드에 실행됨(컨텍스트가 다르다)
이에 따라 기존
ExceptionAdvice
에서 비동기 로직에서 발생한 예외를 커스텀 처리할 수 없음추후에 현재의 일관성 있는 예외 응답 형식을 비동기 예외에 대해서도 적용할 방법을 찾는다면 비동기 처리 예정
레퍼런스