-
Notifications
You must be signed in to change notification settings - Fork 0
๐ Email Send #27
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
๐ Email Send #27
Changes from 10 commits
c5b7666
8a35e5b
b4a5657
92944df
3915305
d6f20ad
5fca75a
b40a9e5
0bae0de
0bb17dc
13cd90f
1da4aef
579d964
ed0d689
8aa701d
9991a3d
89a7f63
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,131 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.mycom.socket.auth.service; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.mycom.socket.auth.service.data.VerificationData; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.mycom.socket.global.exception.BaseException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.mail.MessagingException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import jakarta.mail.internet.MimeMessage; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.beans.factory.annotation.Value; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.http.HttpStatus; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.mail.javamail.JavaMailSender; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Service; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.util.StringUtils; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.security.SecureRandom; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.concurrent.ConcurrentHashMap; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.regex.Pattern; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Service | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class MailService { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final JavaMailSender javaMailSender; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final RateLimiter rateLimiter; // ์ธ์ฆ ๋ฒํธ ์์ฒญ ์ ํ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final Map<String, VerificationData> verificationDataMap = new ConcurrentHashMap<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static final String EMAIL_REGEX = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Value("${spring.mail.username}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String senderEmail; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+22
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ ๏ธ Refactor suggestion ํ๋ ๊ตฌ์กฐ ๊ฐ์ ํ์ ๋ ๊ฐ์ Map์ ์ฌ์ฉํ๋ ๋์ ๋จ์ผ Map๊ณผ ๋ณตํฉ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ํจ์จ์ ์ ๋๋ค. - private final Map<String, Integer> verificationCodes = new ConcurrentHashMap<>();
- private final Map<String, LocalDateTime> expiryTimes = new ConcurrentHashMap<>();
+ private final Map<String, VerificationData> verificationData = new ConcurrentHashMap<>();
+
+ @Value("${mail.verification.expiry-minutes:5}")
+ private int expiryMinutes;
+
+ private record VerificationData(
+ int code,
+ LocalDateTime expiryTime
+ ) {}๐ Committable suggestion
Suggested change
Comment on lines
+23
to
+28
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ ๏ธ Refactor suggestion ์ด๋ฉ์ผ ์ ํจ์ฑ ๊ฒ์ฆ ๋ก์ง ์ถ๊ฐ ํ์ ์ด๋ฉ์ผ ์ฃผ์์ ์ ํจ์ฑ์ ๊ฒ์ฆํ๋ ๋ก์ง์ด ์์ต๋๋ค. ์๋ชป๋ ์ด๋ฉ์ผ ์ฃผ์๋ก ์ธํ ์ค๋ฅ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด ์ ํจ์ฑ ๊ฒ์ฆ์ด ํ์ํฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ ์ ํธ๋ฆฌํฐ ๋ฉ์๋๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค: private boolean isValidEmail(String email) {
String emailRegex = "^[A-Za-z0-9+_.-]+@(.+)$";
return email != null && email.matches(emailRegex);
} |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * 6์๋ฆฌ ๋์ ์ธ์ฆ๋ฒํธ ์์ฑ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * SecureRandom ์ฌ์ฉํ์ฌ ๋ณด์์ฑ ํฅ์ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return 100000~999999 ๋ฒ์์ ์ธ์ฆ๋ฒํธ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String createVerificationCode() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Math.random()์ ์์ธก ๊ฐ๋ฅํ ๋์๋ฅผ ์์ฑํ ์ ์์ด ๋ณด์์ ์ทจ์ฝ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // SecureRandom์ ์ํธํ์ ์ผ๋ก ์์ ํ ๋์๋ฅผ ์์ฑํ๋ฏ๋ก ์ธ์ฆ๋ฒํธ ์์ฑ์ ๋ ์ ํฉ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| SecureRandom secureRandom = new SecureRandom(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return String.format("%06d", secureRandom.nextInt(1000000)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * ์ธ์ฆ๋ฉ์ผ ์์ฑ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param mail ์์ ์ ์ด๋ฉ์ผ ์ฃผ์ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return ์์ฑ๋ ์ธ์ฆ๋ฉ์ผ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public MimeMessage createMail(String mail, String verificationCode) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MimeMessage message = javaMailSender.createMimeMessage(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message.setFrom(senderEmail); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message.setRecipients(MimeMessage.RecipientType.TO, mail); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message.setSubject("์ด๋ฉ์ผ ์ธ์ฆ"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String body = String.format(""" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h3>์์ฒญํ์ ์ธ์ฆ ๋ฒํธ์ ๋๋ค.</h3> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h1>%s</h1> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <h3>๊ฐ์ฌํฉ๋๋ค.</h3> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """, verificationCode); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| message.setText(body, "UTF-8", "html"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (MessagingException e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new BaseException("์ด๋ฉ์ผ ์์ฑ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: " + e.getMessage(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HttpStatus.BAD_REQUEST); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+64
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ ๏ธ Refactor suggestion ์์ธ ์ฒ๋ฆฌ ๊ฐ์ ํ์ ์์ธ ์์ธ๋ฅผ ํฌํจํ์ฌ ๋ ์์ธํ ๋๋ฒ๊น ์ด ๊ฐ๋ฅํ๋๋ก ํด์ผ ํฉ๋๋ค. - throw new BaseException("์ด๋ฉ์ผ ์์ฑ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.", HttpStatus.BAD_REQUEST);
+ throw new BaseException("์ด๋ฉ์ผ ์์ฑ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: " + e.getMessage(),
+ HttpStatus.BAD_REQUEST, e);๐ Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return message; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * ์ด๋ฉ์ผ ์ ํจ์ฑ ๊ฒ์ฌ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param email ๊ฒ์ฌํ ์ด๋ฉ์ผ ์ฃผ์ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return ์ ํจํ ์ด๋ฉ์ผ ์ฃผ์์ธ์ง ์ฌ๋ถ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private boolean isValidEmail(String email) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return StringUtils.hasText(email) && Pattern.matches(EMAIL_REGEX, email); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * ์ธ์ฆ๋ฉ์ผ ๋ฐ์ก ๋ฐ ์ธ์ฆ๋ฒํธ ๋ฐํ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param mail ์์ ์ ์ด๋ฉ์ผ ์ฃผ์ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return ์์ฑ๋ ์ธ์ฆ๋ฒํธ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public boolean sendMail(String mail) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isValidEmail(mail)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new BaseException("์ ํจํ์ง ์์ ์ด๋ฉ์ผ ํ์์ ๋๋ค.", HttpStatus.BAD_REQUEST); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rateLimiter.checkRateLimit(mail); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String verificationCode = createVerificationCode(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| verificationDataMap.put(mail, new VerificationData(verificationCode)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MimeMessage message = createMail(mail, verificationCode); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| javaMailSender.send(message); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new BaseException("์ด๋ฉ์ผ ๋ฐ์ก ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค: " + e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * ์ธ์ฆ๋ฒํธ ๊ฒ์ฆ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param email ์์ ์ ์ด๋ฉ์ผ ์ฃผ์ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @param code ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ์ธ์ฆ๋ฒํธ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * @return ์ธ์ฆ๋ฒํธ ์ผ์น ์ฌ๋ถ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public boolean verifyCode(String email, String code) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!isValidEmail(email)){ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new BaseException("์ ํจํ์ง ์์ ์ด๋ฉ์ผ ํ์์ ๋๋ค.", HttpStatus.BAD_REQUEST); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!StringUtils.hasText(code) || !code.matches("\\d{6}")) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VerificationData data = verificationDataMap.get(email); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (data == null || data.isExpired()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| boolean isVerified = data.code().equals(code); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (isVerified){ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| verificationDataMap.remove(email); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+93
to
+107
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ ๏ธ Refactor suggestion ์ ๋ ฅ๊ฐ ๊ฒ์ฆ ๊ฐํ ํ์ code ํ๋ผ๋ฏธํฐ์ ๋ํ null ์ฒดํฌ๊ฐ ๋๋ฝ๋์์ต๋๋ค. ๋ํ ์ฝ๋ ํ์(์ซ์ 6์๋ฆฌ)์ ๋ํ ๊ฒ์ฆ๋ ํ์ํฉ๋๋ค. ๋ค์๊ณผ ๊ฐ์ด ์์ ํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค: public boolean verifyCode(String email, String code) {
+ if (code == null || !code.matches("\\d{6}")) {
+ return false;
+ }
VerificationData data = verificationDataMap.get(email);๐ Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return isVerified; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+62
to
+109
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ธ์ฆ ์ฝ๋ ๊ฒ์ฆ ๋ก์ง ๊ฐ์ ํ์ ํ์ฌ ๊ตฌํ์์๋ ์ธ์ฆ ์ฝ๋ ๋ง๋ฃ ์ฌ๋ถ๋ฅผ ํ์ธํ์ง ์์ต๋๋ค. ๋ํ, ๊ฒ์ฆ ํ์๋ ์ฝ๋๊ฐ ๊ณ์ ์ ํจํ ์ํ๋ก ๋จ์์์ต๋๋ค. ๋ค์๊ณผ ๊ฐ์ ๊ฐ์ ์ ์ ์ํฉ๋๋ค: public boolean verifyCode(String email, String code) {
- Integer savedCode = verificationCodes.get(email);
- return savedCode != null && String.valueOf(savedCode).equals(code);
+ VerificationData data = verificationCodes.get(email);
+ if (data == null || LocalDateTime.now().isAfter(data.expiryTime)) {
+ return false;
+ }
+ boolean isValid = String.valueOf(data.code).equals(code);
+ if (isValid) {
+ verificationCodes.remove(email); // ์ฌ์ฉ๋ ์ฝ๋ ์ ๊ฑฐ
+ }
+ return isValid;
}
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,47 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.mycom.socket.auth.service; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import com.mycom.socket.global.exception.BaseException; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.RequiredArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.http.HttpStatus; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.scheduling.annotation.Scheduled; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.Duration; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.time.LocalDateTime; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.ArrayList; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.List; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.Map; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.concurrent.ConcurrentHashMap; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @RequiredArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class RateLimiter { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final Map<String, List<LocalDateTime>> requestMap = new ConcurrentHashMap<>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static final int MAX_REQUESTS = 3; // 1๋ถ๋น ์ต๋ 3๋ฒ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static final Duration WINDOW_SIZE = Duration.ofMinutes(1); // 1๋ถ์ ์๊ฐ ๊ฐ๊ฒฉ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+19
to
+21
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ ๏ธ Refactor suggestion ๋ฉ๋ชจ๋ฆฌ ๋์ ๊ฐ๋ฅ์ฑ ๊ฐ์ ํ์
๋ค์๊ณผ ๊ฐ์ ๊ฐ์ ์ ์ ์ํฉ๋๋ค: private final Map<String, List<LocalDateTime>> requestMap = new ConcurrentHashMap<>();
private static final int MAX_REQUESTS = 3; // 1๋ถ๋น ์ต๋ 3๋ฒ
private static final Duration WINDOW_SIZE = Duration.ofMinutes(1); // 1๋ถ์ ์๊ฐ ๊ฐ๊ฒฉ
+private static final Duration CLEANUP_INTERVAL = Duration.ofHours(1);
+
+@Scheduled(fixedRate = 3600000) // 1์๊ฐ๋ง๋ค ์คํ
+public void cleanup() {
+ LocalDateTime threshold = LocalDateTime.now().minus(WINDOW_SIZE);
+ requestMap.entrySet().removeIf(entry ->
+ entry.getValue().stream().allMatch(time -> time.isBefore(threshold)));
+}
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Scheduled(fixedRate = 3600000) // 1์๊ฐ๋ง๋ค ์คํ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void cleanup() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime threshold = LocalDateTime.now().minus(WINDOW_SIZE); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requestMap.entrySet().removeIf(entry -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| entry.getValue().stream().allMatch(time -> time.isBefore(threshold))); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void checkRateLimit(String email) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| List<LocalDateTime> requests = requestMap.computeIfAbsent(email, k -> new ArrayList<>()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime now = LocalDateTime.now(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requests.removeIf(requestTime -> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requestTime.plus(WINDOW_SIZE).isBefore(now)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (requests.size() >= MAX_REQUESTS) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| LocalDateTime oldestRequest = requests.get(0); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Duration waitTime = WINDOW_SIZE.minus(Duration.between(oldestRequest, now)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw new BaseException( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String.format("๋๋ฌด ๋ง์ ์์ฒญ์ ๋๋ค. %d์ด ํ์ ๋ค์ ์๋ํด์ฃผ์ธ์.",waitTime.getSeconds()), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| HttpStatus.TOO_MANY_REQUESTS); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| requests.add(now); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๐ ๏ธ Refactor suggestion ์์ฒญ ์ ํ ๋ก์ง ๋ณด์ ํ์ ๋ค์ ์ฌํญ๋ค์ ๊ณ ๋ คํด์ผ ํฉ๋๋ค:
if (requests.size() >= MAX_REQUESTS) {
- throw new BaseException("๋๋ฌด ๋ง์ ์์ฒญ์
๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์.", HttpStatus.TOO_MANY_REQUESTS);
+ LocalDateTime oldestRequest = requests.get(0);
+ Duration waitTime = WINDOW_SIZE.minus(Duration.between(oldestRequest, now));
+ throw new BaseException(
+ String.format("๋๋ฌด ๋ง์ ์์ฒญ์
๋๋ค. %d์ด ํ์ ๋ค์ ์๋ํด์ฃผ์ธ์.",
+ waitTime.getSeconds()),
+ HttpStatus.TOO_MANY_REQUESTS);
}๐ Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package com.mycom.socket.auth.service.data; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| import java.time.Duration; | ||
|
|
||
| public record VerificationData(String code, LocalDateTime expiryTime) { | ||
|
|
||
| private static final Duration CODE_VALID_DURATION = Duration.ofMinutes(5); | ||
|
|
||
| public VerificationData(String code) { | ||
| this(code, LocalDateTime.now().plus(CODE_VALID_DURATION)); | ||
| } | ||
|
|
||
| public boolean isExpired() { | ||
| return LocalDateTime.now().isAfter(expiryTime); | ||
| } | ||
| } |
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.
๐ ๏ธ Refactor suggestion
์ ๋ ฅ๊ฐ ๊ฒ์ฆ ๋ฐ ์๋ต ํ์ ํ์คํ ํ์
์ด๋ฉ์ผ ๊ฒ์ฆ ์๋ํฌ์ธํธ์ ๋ค์ ๊ฐ์ ์ฌํญ์ด ํ์ํฉ๋๋ค:
@Email์ด๋ ธํ ์ด์ ์ผ๋ก ์ด๋ฉ์ผ ํ์ ๊ฒ์ฆ@PostMapping("/email/verification") - public Boolean mailSend(@RequestParam(name = "mail") String mail) { + public ApiResponse<EmailVerificationResponse> mailSend( + @RequestParam(name = "mail") @Email(message = "์ ํจํ ์ด๋ฉ์ผ ์ฃผ์๋ฅผ ์ ๋ ฅํด์ฃผ์ธ์") String mail) { try { rateLimiter.checkRateLimit(mail); // ์์ฒญ ์ ํ ์ฒดํฌ - return mailService.sendMail(mail); + boolean sent = mailService.sendMail(mail); + return ApiResponse.success(new EmailVerificationResponse(sent)); } catch (Exception e) { throw new BaseException("์ด๋ฉ์ผ ์ ์ก์ ์คํจํ์ต๋๋ค.", HttpStatus.BAD_REQUEST); } }