From 4ed055a6aebdc11115605f47a21394aeacdba461 Mon Sep 17 00:00:00 2001 From: junekyu02 Date: Thu, 14 Aug 2025 15:49:26 +0900 Subject: [PATCH 1/7] =?UTF-8?q?=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EB=B2=88=ED=98=B8=20=EB=B0=9C=EC=86=A1=20=EB=B0=8F=20?= =?UTF-8?q?=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=9D=B8=EC=A6=9D=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 10 ++ .../apiPayload/code/error/EmailErrorCode.java | 28 +++++ .../apiPayload/handler/ExceptionAdvice.java | 38 +++++- .../email/Controller/EmailController.java | 57 +++++++++ .../sumte/email/EmailVerificationCleaner.java | 24 ++++ .../email/EmailVerificationProperties.java | 45 +++++++ .../Service/EmailVerificationService.java | 115 ++++++++++++++++++ .../email/dto/request/EmailSendRequest.java | 9 ++ .../email/dto/request/EmailVerifyRequest.java | 11 ++ .../email/dto/response/EmailSendResponse.java | 8 ++ .../dto/response/EmailVerifyResponse.java | 7 ++ .../com/sumte/email/sender/EmailSender.java | 5 + .../sumte/email/sender/SmtpEmailSender.java | 35 ++++++ .../sender/VerificationCodeGenerator.java | 17 +++ .../email/store/EmailVerificationStore.java | 34 ++++++ .../sumte/email/store/VerificationEntry.java | 13 ++ 16 files changed, 451 insertions(+), 5 deletions(-) create mode 100644 src/main/java/com/sumte/apiPayload/code/error/EmailErrorCode.java create mode 100644 src/main/java/com/sumte/email/Controller/EmailController.java create mode 100644 src/main/java/com/sumte/email/EmailVerificationCleaner.java create mode 100644 src/main/java/com/sumte/email/EmailVerificationProperties.java create mode 100644 src/main/java/com/sumte/email/Service/EmailVerificationService.java create mode 100644 src/main/java/com/sumte/email/dto/request/EmailSendRequest.java create mode 100644 src/main/java/com/sumte/email/dto/request/EmailVerifyRequest.java create mode 100644 src/main/java/com/sumte/email/dto/response/EmailSendResponse.java create mode 100644 src/main/java/com/sumte/email/dto/response/EmailVerifyResponse.java create mode 100644 src/main/java/com/sumte/email/sender/EmailSender.java create mode 100644 src/main/java/com/sumte/email/sender/SmtpEmailSender.java create mode 100644 src/main/java/com/sumte/email/sender/VerificationCodeGenerator.java create mode 100644 src/main/java/com/sumte/email/store/EmailVerificationStore.java create mode 100644 src/main/java/com/sumte/email/store/VerificationEntry.java diff --git a/build.gradle b/build.gradle index ba5ce78..65a4ef9 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,16 @@ dependencies { // AWS S3 의존성 implementation 'software.amazon.awssdk:s3:2.17.70' implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' + + // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + + // 메일 전송 + implementation 'org.springframework.boot:spring-boot-starter-mail' + + // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' + } sourceSets { diff --git a/src/main/java/com/sumte/apiPayload/code/error/EmailErrorCode.java b/src/main/java/com/sumte/apiPayload/code/error/EmailErrorCode.java new file mode 100644 index 0000000..d5225c2 --- /dev/null +++ b/src/main/java/com/sumte/apiPayload/code/error/EmailErrorCode.java @@ -0,0 +1,28 @@ +package com.sumte.apiPayload.code.error; + +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum EmailErrorCode implements ErrorCode { + + // 400 Bad Request + VERIFICATION_CODE_NOT_FOUND("Email404", "인증코드가 만료되었거나 요청되지 않았습니다.", HttpStatus.BAD_REQUEST), + VERIFICATION_CODE_MISMATCH("Email404", "인증코드가 일치하지 않습니다.", HttpStatus.BAD_REQUEST), + MAIL_SENDING_FAILED("Email404", "메일 전송에 실패했습니다.", HttpStatus.BAD_REQUEST), + EMAIL_ALREADY_VERIFIED("Email400", "이미 인증이 완료된 이메일입니다.", HttpStatus.BAD_REQUEST), + RESEND_COOLDOWN("Email429", "재전송 쿨다운 중입니다.", HttpStatus.TOO_MANY_REQUESTS), + INVALID_EMAIL_FORMAT("Email400", "올바르지 않은 이메일 형식입니다.", HttpStatus.BAD_REQUEST); + + private final String code; + private final String message; + private final HttpStatus httpStatus; + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } +} \ No newline at end of file diff --git a/src/main/java/com/sumte/apiPayload/handler/ExceptionAdvice.java b/src/main/java/com/sumte/apiPayload/handler/ExceptionAdvice.java index 4a0e976..30bb1f3 100644 --- a/src/main/java/com/sumte/apiPayload/handler/ExceptionAdvice.java +++ b/src/main/java/com/sumte/apiPayload/handler/ExceptionAdvice.java @@ -1,10 +1,10 @@ package com.sumte.apiPayload.handler; -import jakarta.validation.ConstraintViolationException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatusCode; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.MethodArgumentNotValidException; @@ -17,9 +17,11 @@ import com.fasterxml.jackson.databind.exc.InvalidFormatException; import com.sumte.apiPayload.ApiResponse; import com.sumte.apiPayload.code.error.CommonErrorCode; +import com.sumte.apiPayload.code.error.EmailErrorCode; import com.sumte.apiPayload.code.error.ErrorCode; import com.sumte.apiPayload.exception.SumteException; +import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; /** @@ -49,11 +51,26 @@ public ResponseEntity handleIllegalArgument(IllegalArgumentException ex) public ResponseEntity handleConstraintViolationException(ConstraintViolationException ex) { log.warn("handleConstraintViolationException"); + //Email 전용 코드로 + var first = ex.getConstraintViolations().stream().findFirst(); + if (first.isPresent()) { + var v = first.get(); + String path = v.getPropertyPath() != null ? v.getPropertyPath().toString() : ""; + String msg = v.getMessage(); + + if (path.toLowerCase().contains("email")) { + return handleExceptionInternal( + EmailErrorCode.INVALID_EMAIL_FORMAT, + msg != null ? msg : EmailErrorCode.INVALID_EMAIL_FORMAT.getMessage() + ); + } + } + String message = ex.getConstraintViolations() - .stream() - .findFirst() - .map(violation -> violation.getMessage()) - .orElse(CommonErrorCode.INVALID_PARAMETER.getMessage()); + .stream() + .findFirst() + .map(violation -> violation.getMessage()) + .orElse(CommonErrorCode.INVALID_PARAMETER.getMessage()); return handleExceptionInternal(CommonErrorCode.INVALID_PARAMETER, message); } @@ -71,6 +88,17 @@ public ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotVali HttpStatusCode status, WebRequest request) { log.warn("MethodArgumentNotValidException "); + + //email 필드 검증 실패시 + FieldError fe = e.getBindingResult().getFieldErrors().stream() + .findFirst() + .orElse(null); + if (fe != null && "email".equals(fe.getField())) { + String msg = fe.getDefaultMessage() != null ? fe.getDefaultMessage() + : EmailErrorCode.INVALID_EMAIL_FORMAT.getMessage(); + return handleExceptionInternal(EmailErrorCode.INVALID_EMAIL_FORMAT, msg); + } + ErrorCode errorCode = CommonErrorCode.INVALID_PARAMETER; return handleExceptionInternal(errorCode, getDefaultMessage(e)); } diff --git a/src/main/java/com/sumte/email/Controller/EmailController.java b/src/main/java/com/sumte/email/Controller/EmailController.java new file mode 100644 index 0000000..88f2681 --- /dev/null +++ b/src/main/java/com/sumte/email/Controller/EmailController.java @@ -0,0 +1,57 @@ +package com.sumte.email.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.sumte.email.Service.EmailVerificationService; +import com.sumte.email.dto.request.EmailSendRequest; +import com.sumte.email.dto.request.EmailVerifyRequest; +import com.sumte.email.dto.response.EmailSendResponse; +import com.sumte.email.dto.response.EmailVerifyResponse; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; + +@Tag(name = "이메일 인증", description = "이메일 인증번호 발송 및 검증 API") +@RestController +@RequestMapping("/email") +public class EmailController { + + private final EmailVerificationService service; + + public EmailController(EmailVerificationService service) { + this.service = service; + } + + @Operation( + summary = "이메일 인증번호 발송", + description = """ + 사용자가 입력한 이메일 주소로 인증번호를 발송합니다. + - 이메일 형식 검증이 이루어지고, + - 아래 쿨타임 필드가 보이실텐데 이는 재전송까지 남은 시간을 볼 수 있는 필드로 넣었습니다. + - 쿨다임의 경우 10초로 지정하였고, 쿨타임에 재전송을 하는 경우 에러가 나니 확인해주시면 될 것 같습니다. + """ + ) + @PostMapping("/send") + public ResponseEntity send(@Valid @RequestBody EmailSendRequest req) { + return ResponseEntity.ok(service.sendCode(req.email())); + } + + @Operation( + summary = "이메일 인증번호 검증", + description = """ + 사용자가 입력한 이메일과 인증번호가 일치하는지 확인합니다. + - 요청 시 바디에 이메일도 꼭 포함되도록 했습니다! + - 인증번호가 만료되었거나 요청되지 않은 경우 에러가 발생하고 + - 검증 성공 시 바로 인증완료 처리되니 확인해주시면 될 것 같습니다! + """ + ) + @PostMapping("/verify") + public ResponseEntity verify(@Valid @RequestBody EmailVerifyRequest req) { + return ResponseEntity.ok(service.verifyCode(req.email(), req.code())); + } +} \ No newline at end of file diff --git a/src/main/java/com/sumte/email/EmailVerificationCleaner.java b/src/main/java/com/sumte/email/EmailVerificationCleaner.java new file mode 100644 index 0000000..327930d --- /dev/null +++ b/src/main/java/com/sumte/email/EmailVerificationCleaner.java @@ -0,0 +1,24 @@ +package com.sumte.email; + +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import com.sumte.email.store.EmailVerificationStore; + +@Component +@EnableScheduling +public class EmailVerificationCleaner { + + private final EmailVerificationStore store; + + public EmailVerificationCleaner(EmailVerificationStore store) { + this.store = store; + } + + // 1분마다 만료된 인증 항목 정리 + @Scheduled(fixedDelay = 60_000L) + public void purge() { + store.purgeExpired(); + } +} diff --git a/src/main/java/com/sumte/email/EmailVerificationProperties.java b/src/main/java/com/sumte/email/EmailVerificationProperties.java new file mode 100644 index 0000000..d31d0b3 --- /dev/null +++ b/src/main/java/com/sumte/email/EmailVerificationProperties.java @@ -0,0 +1,45 @@ +package com.sumte.email; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +@Component +@ConfigurationProperties(prefix = "app.email-verification") +public class EmailVerificationProperties { + private int codeLength = 6; // 인증코드 자리수 + private int ttlSeconds = 600; // 유효기간(초) + private int resendCooldownSeconds = 60; // 재전송 쿨다운(초) + private String from = "no-reply@sumte.com"; + + public int getCodeLength() { + return codeLength; + } + + public void setCodeLength(int codeLength) { + this.codeLength = codeLength; + } + + public int getTtlSeconds() { + return ttlSeconds; + } + + public void setTtlSeconds(int ttlSeconds) { + this.ttlSeconds = ttlSeconds; + } + + public int getResendCooldownSeconds() { + return resendCooldownSeconds; + } + + public void setResendCooldownSeconds(int resendCooldownSeconds) { + this.resendCooldownSeconds = resendCooldownSeconds; + } + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } +} \ No newline at end of file diff --git a/src/main/java/com/sumte/email/Service/EmailVerificationService.java b/src/main/java/com/sumte/email/Service/EmailVerificationService.java new file mode 100644 index 0000000..61dbdfa --- /dev/null +++ b/src/main/java/com/sumte/email/Service/EmailVerificationService.java @@ -0,0 +1,115 @@ +package com.sumte.email.Service; + +import java.time.Duration; +import java.time.Instant; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.mail.MailException; +import org.springframework.stereotype.Service; + +import com.sumte.apiPayload.code.error.EmailErrorCode; +import com.sumte.apiPayload.exception.SumteException; +import com.sumte.email.EmailVerificationProperties; +import com.sumte.email.dto.response.EmailSendResponse; +import com.sumte.email.dto.response.EmailVerifyResponse; +import com.sumte.email.sender.EmailSender; +import com.sumte.email.sender.VerificationCodeGenerator; +import com.sumte.email.store.EmailVerificationStore; +import com.sumte.email.store.VerificationEntry; + +/** + * 📌 이메일 인증 서비스 + * + * - /email/send : 인증번호 생성 & 저장 & 발송 + * - /email/verify : 인증번호 검증 (성공 시 일회성으로 폐기) + * + * ⚠️ 실패 시에는 ApiException(EmailErrorCode.*)을 던져서 + * GlobalExceptionHandler가 Email4xx 형식의 본문을 내려주게 한다. + * (이렇게 해야 Swagger/프론트에서 COMMON400이 아니라 Email4xx가 보임) + */ +@Service +public class EmailVerificationService { + + private static final Logger log = LoggerFactory.getLogger(EmailVerificationService.class); + + private final EmailVerificationStore store; + private final VerificationCodeGenerator generator; + private final EmailSender sender; + private final EmailVerificationProperties props; + + public EmailVerificationService(EmailVerificationStore store, + VerificationCodeGenerator generator, + EmailSender sender, + EmailVerificationProperties props) { + this.store = store; + this.generator = generator; + this.sender = sender; + this.props = props; + } + + public EmailSendResponse sendCode(String email) { + Instant now = Instant.now(); + var existing = store.get(email); + + // 재전송 쿨다운 적용 (프론트에서 처리안할경우 굳이..) + if (existing != null) { + long elapsed = Duration.between(existing.lastSentAt(), now).getSeconds(); + long cooldown = props.getResendCooldownSeconds(); + if (elapsed < cooldown) { + long remaining = cooldown - elapsed; + log.debug("Resend cooldown: email={}, remainingSeconds={}", email, remaining); + return new EmailSendResponse(false, "재전송 쿨다운 중입니다.", remaining); + } + } + + // 코드 생성 & 저장 + String code = generator.numeric(props.getCodeLength()); + Instant expiresAt = now.plusSeconds(props.getTtlSeconds()); + store.put(email, new VerificationEntry(code, expiresAt, now)); + + // 메일 양식 + String subject = "[숨터] 이메일 인증번호"; + String body = """ + 숨터 이메일 인증번호는 [%s] 입니다. + 유효시간: %d분 + (잘못 수신한 경우 이 메일을 무시하세요.) + """.formatted(code, props.getTtlSeconds() / 60); + + try { + sender.send(email, subject, body); + } catch (MailException e) { + // 메일 전송 실패는 EmailErrorCode.MAIL_SENDING_FAILED 로 통일 + log.warn("Mail sending failed: email={}, err={}", email, e.getMessage()); + throw new SumteException(EmailErrorCode.MAIL_SENDING_FAILED); + } + + log.debug("Email verification code generated: email={}, code={}, expiresAt={}", email, code, expiresAt); + return new EmailSendResponse(true, "인증번호가 발송되었습니다.", 0); + } + + public EmailVerifyResponse verifyCode(String email, String code) { + Instant now = Instant.now(); + var entry = store.get(email); + + // 요청되지 않았거나, 성공을 이미 한 경우 + if (entry == null) { + throw new SumteException(EmailErrorCode.VERIFICATION_CODE_NOT_FOUND); + } + + // 만료되는경우 + if (entry.isExpired(now)) { + store.remove(email); + throw new SumteException(EmailErrorCode.VERIFICATION_CODE_NOT_FOUND); + } + + // 일치하지 않는경우 + if (!entry.code().equals(code)) { + throw new SumteException(EmailErrorCode.VERIFICATION_CODE_MISMATCH); + } + + // 성공 후 바로 삭제 + store.remove(email); + return new EmailVerifyResponse(true, "인증이 완료되었습니다."); + } +} \ No newline at end of file diff --git a/src/main/java/com/sumte/email/dto/request/EmailSendRequest.java b/src/main/java/com/sumte/email/dto/request/EmailSendRequest.java new file mode 100644 index 0000000..ad1033d --- /dev/null +++ b/src/main/java/com/sumte/email/dto/request/EmailSendRequest.java @@ -0,0 +1,9 @@ +package com.sumte.email.dto.request; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +public record EmailSendRequest( + @NotBlank @Email String email +) { +} \ No newline at end of file diff --git a/src/main/java/com/sumte/email/dto/request/EmailVerifyRequest.java b/src/main/java/com/sumte/email/dto/request/EmailVerifyRequest.java new file mode 100644 index 0000000..1942ede --- /dev/null +++ b/src/main/java/com/sumte/email/dto/request/EmailVerifyRequest.java @@ -0,0 +1,11 @@ +package com.sumte.email.dto.request; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; + +public record EmailVerifyRequest( + @NotBlank @Email String email, + @NotBlank @Size(min = 4, max = 10) String code +) { +} \ No newline at end of file diff --git a/src/main/java/com/sumte/email/dto/response/EmailSendResponse.java b/src/main/java/com/sumte/email/dto/response/EmailSendResponse.java new file mode 100644 index 0000000..18b98f9 --- /dev/null +++ b/src/main/java/com/sumte/email/dto/response/EmailSendResponse.java @@ -0,0 +1,8 @@ +package com.sumte.email.dto.response; + +public record EmailSendResponse( + boolean success, + String message, + long cooldownRemainingSeconds +) { +} \ No newline at end of file diff --git a/src/main/java/com/sumte/email/dto/response/EmailVerifyResponse.java b/src/main/java/com/sumte/email/dto/response/EmailVerifyResponse.java new file mode 100644 index 0000000..583561f --- /dev/null +++ b/src/main/java/com/sumte/email/dto/response/EmailVerifyResponse.java @@ -0,0 +1,7 @@ +package com.sumte.email.dto.response; + +public record EmailVerifyResponse( + boolean success, + String message +) { +} \ No newline at end of file diff --git a/src/main/java/com/sumte/email/sender/EmailSender.java b/src/main/java/com/sumte/email/sender/EmailSender.java new file mode 100644 index 0000000..8488c7f --- /dev/null +++ b/src/main/java/com/sumte/email/sender/EmailSender.java @@ -0,0 +1,5 @@ +package com.sumte.email.sender; + +public interface EmailSender { + void send(String to, String subject, String plainTextBody); +} diff --git a/src/main/java/com/sumte/email/sender/SmtpEmailSender.java b/src/main/java/com/sumte/email/sender/SmtpEmailSender.java new file mode 100644 index 0000000..0fa8c83 --- /dev/null +++ b/src/main/java/com/sumte/email/sender/SmtpEmailSender.java @@ -0,0 +1,35 @@ +package com.sumte.email.sender; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.lang.NonNull; +import org.springframework.mail.SimpleMailMessage; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.stereotype.Component; + +/** + * spring.mail.host 가 설정돼 있으면 자동 사용되는 SMTP 발송기 + */ +@Component +@ConditionalOnProperty(prefix = "spring.mail", name = "host") +public class SmtpEmailSender implements EmailSender { + + private final JavaMailSender mailSender; + + @Value("${app.email-verification.from:no-reply@sumte.com}") + private String from; + + public SmtpEmailSender(JavaMailSender mailSender) { + this.mailSender = mailSender; + } + + @Override + public void send(@NonNull String to, @NonNull String subject, @NonNull String plainTextBody) { + SimpleMailMessage msg = new SimpleMailMessage(); + msg.setFrom(from); + msg.setTo(to); + msg.setSubject(subject); + msg.setText(plainTextBody); + mailSender.send(msg); + } +} \ No newline at end of file diff --git a/src/main/java/com/sumte/email/sender/VerificationCodeGenerator.java b/src/main/java/com/sumte/email/sender/VerificationCodeGenerator.java new file mode 100644 index 0000000..db43791 --- /dev/null +++ b/src/main/java/com/sumte/email/sender/VerificationCodeGenerator.java @@ -0,0 +1,17 @@ +package com.sumte.email.sender; + +import java.security.SecureRandom; + +import org.springframework.stereotype.Component; + +@Component +public class VerificationCodeGenerator { + private final SecureRandom random = new SecureRandom(); + + public String numeric(int length) { + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) + sb.append(random.nextInt(10)); + return sb.toString(); + } +} diff --git a/src/main/java/com/sumte/email/store/EmailVerificationStore.java b/src/main/java/com/sumte/email/store/EmailVerificationStore.java new file mode 100644 index 0000000..b92c17b --- /dev/null +++ b/src/main/java/com/sumte/email/store/EmailVerificationStore.java @@ -0,0 +1,34 @@ +package com.sumte.email.store; + +import java.time.Instant; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.springframework.stereotype.Component; + +@Component +public class EmailVerificationStore { + + private final Map store = new ConcurrentHashMap<>(); + + public VerificationEntry get(String email) { + return store.get(norm(email)); + } + + public void put(String email, VerificationEntry entry) { + store.put(norm(email), entry); + } + + public void remove(String email) { + store.remove(norm(email)); + } + + public void purgeExpired() { + Instant now = Instant.now(); + store.entrySet().removeIf(e -> e.getValue().isExpired(now)); + } + + private String norm(String email) { + return email.trim().toLowerCase(); + } +} \ No newline at end of file diff --git a/src/main/java/com/sumte/email/store/VerificationEntry.java b/src/main/java/com/sumte/email/store/VerificationEntry.java new file mode 100644 index 0000000..6dda419 --- /dev/null +++ b/src/main/java/com/sumte/email/store/VerificationEntry.java @@ -0,0 +1,13 @@ +package com.sumte.email.store; + +import java.time.Instant; + +public record VerificationEntry( + String code, + Instant expiresAt, + Instant lastSentAt +) { + public boolean isExpired(Instant now) { + return expiresAt.isBefore(now); + } +} From 12a9be63623cea63c6287d7e050fbb424462baa6 Mon Sep 17 00:00:00 2001 From: junekyu02 Date: Thu, 14 Aug 2025 16:05:27 +0900 Subject: [PATCH 2/7] =?UTF-8?q?=EB=A0=88=EB=94=94=EC=8A=A4=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=A0=9C=EA=B1=B0=20(=EC=82=AC=EC=9A=A9x)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 65a4ef9..09efac0 100644 --- a/build.gradle +++ b/build.gradle @@ -59,9 +59,7 @@ dependencies { implementation 'software.amazon.awssdk:s3:2.17.70' implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' - // Redis - implementation 'org.springframework.boot:spring-boot-starter-data-redis' - + // 메일 전송 implementation 'org.springframework.boot:spring-boot-starter-mail' From cf0bc2000b1c222197529b2fba307b2cc5713880 Mon Sep 17 00:00:00 2001 From: junekyu02 Date: Thu, 14 Aug 2025 18:02:08 +0900 Subject: [PATCH 3/7] gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index dab71af..88af6ec 100644 --- a/.gitignore +++ b/.gitignore @@ -38,5 +38,9 @@ out/ ### application.properties ### src/main/resources/application-local.yml +src/main/resources/application-prod.yml +/application-prod.yml +/application-local.yml + src/main/generated/ \ No newline at end of file From 162eb0e0fe04bbb265ca3f6a1bc24c5c78e97cf8 Mon Sep 17 00:00:00 2001 From: junekyu02 Date: Thu, 14 Aug 2025 18:14:06 +0900 Subject: [PATCH 4/7] gitignore --- src/main/resources/application-prod.yml | 49 ------------------------- 1 file changed, 49 deletions(-) delete mode 100644 src/main/resources/application-prod.yml diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml deleted file mode 100644 index 60201bf..0000000 --- a/src/main/resources/application-prod.yml +++ /dev/null @@ -1,49 +0,0 @@ -spring: - config: - activate: - on-profile: prod - application: - name: sumte - datasource: - url: jdbc:mysql://${RDS_HOST}:${RDS_PORT}/${RDS_DB} - username: ${RDS_USERNAME} - password: ${RDS_PASSWORD} - driver-class-name: com.mysql.cj.jdbc.Driver - jpa: - hibernate: - ddl-auto: update - show-sql: true - properties: - hibernate: - format_sql: true - -jwt: - secret: ${JWT_SECRET} - -cloud: - aws: - region: ap-northeast-2 - credentials: - access-key: ${S3_ACCESS_KEY} - secret-key: ${S3_SECRET_KEY} - s3: - bucket: sumte-file - url-prefix: https://sumte-file.s3.ap-northeast-2.amazonaws.com - stack: - auto: false - -# spring actuator 활용한 도커 헬스체크용 -management: - endpoints: - web: - exposure: - include: health - -app: - host: ${EC2_HOST} - -kakao: - pay: - secret-key: ${KAKAOPAY_SECRET_KEY} - redirect: - domain: ${DOMAIN} \ No newline at end of file From 9135190231a1613229948b2b9cc51b5995b5de26 Mon Sep 17 00:00:00 2001 From: junekyu02 Date: Thu, 14 Aug 2025 18:25:07 +0900 Subject: [PATCH 5/7] . --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 88af6ec..23aec00 100644 --- a/.gitignore +++ b/.gitignore @@ -39,8 +39,8 @@ out/ ### application.properties ### src/main/resources/application-local.yml src/main/resources/application-prod.yml -/application-prod.yml -/application-local.yml +application-local.yml +application-prod.yml src/main/generated/ \ No newline at end of file From ae2503439ef25bee71525defae3b59db3687885e Mon Sep 17 00:00:00 2001 From: junekyu02 Date: Thu, 14 Aug 2025 18:52:57 +0900 Subject: [PATCH 6/7] =?UTF-8?q?gitignore=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- src/main/resources/application-prod.yml | 67 +++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/main/resources/application-prod.yml diff --git a/.gitignore b/.gitignore index 23aec00..360bc3d 100644 --- a/.gitignore +++ b/.gitignore @@ -38,9 +38,8 @@ out/ ### application.properties ### src/main/resources/application-local.yml -src/main/resources/application-prod.yml application-local.yml -application-prod.yml + src/main/generated/ \ No newline at end of file diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..4115d4f --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,67 @@ +spring: + config: + activate: + on-profile: prod + application: + name: sumte + datasource: + url: jdbc:mysql://${RDS_HOST}:${RDS_PORT}/${RDS_DB} + username: ${RDS_USERNAME} + password: ${RDS_PASSWORD} + driver-class-name: com.mysql.cj.jdbc.Driver + jpa: + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + format_sql: true + + mail: + host: smtp.gmail.com + port: 587 + username: ${MAIL_APP_USERMAIL} + password: ${MAIL_APP_KEY} + properties: + mail: + smtp: + auth: true + starttls: + enable: true + default-encoding: UTF-8 + +jwt: + secret: ${JWT_SECRET} + +cloud: + aws: + region: ap-northeast-2 + credentials: + access-key: ${S3_ACCESS_KEY} + secret-key: ${S3_SECRET_KEY} + s3: + bucket: sumte-file + url-prefix: https://sumte-file.s3.ap-northeast-2.amazonaws.com + stack: + auto: false + +# spring actuator 활용한 도커 헬스체크용 +management: + endpoints: + web: + exposure: + include: health + +app: + host: ${EC2_HOST} + email-verification: + code-length: 6 # 인증코드 자리수 + ttl-seconds: 600 # 10분(600초) 내 인증 + resend-cooldown-seconds: 10 # 재전송 쿨다운(60초) + from: "no-reply@sumte.com" # 발신자 표기 (SMTP/콘솔 공통) + +kakao: + pay: + secret-key: ${KAKAOPAY_SECRET_KEY} + redirect: + domain: ${DOMAIN} \ No newline at end of file From 780299326d73b5e3178e6e70dd11258064f1d1c3 Mon Sep 17 00:00:00 2001 From: junekyu02 Date: Thu, 14 Aug 2025 22:07:03 +0900 Subject: [PATCH 7/7] =?UTF-8?q?redis=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.gradle b/build.gradle index 09efac0..a18e64b 100644 --- a/build.gradle +++ b/build.gradle @@ -63,8 +63,6 @@ dependencies { // 메일 전송 implementation 'org.springframework.boot:spring-boot-starter-mail' - // Redis - implementation 'org.springframework.boot:spring-boot-starter-data-redis' }