feat: 회의예약 시, 이메일발송 기능 보강

This commit is contained in:
djeon
2025-10-26 10:34:16 +09:00
parent 3f20f19f44
commit d708f0b501
9 changed files with 16395 additions and 18 deletions
@@ -0,0 +1,43 @@
package com.unicorn.hgzero.notification.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* 비동기 처리 설정
*
* 이메일 발송 등 시간이 오래 걸리는 작업을 비동기로 처리
*
* @author 준호 (Backend Developer)
* @version 1.0
* @since 2025-10-26
*/
@Configuration
@EnableAsync
public class AsyncConfig {
/**
* 이메일 발송용 비동기 Executor
*
* - 코어 스레드: 5개
* - 최대 스레드: 10개
* - 큐 용량: 100
* - 스레드 이름: email-async-
*/
@Bean(name = "emailTaskExecutor")
public Executor emailTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("email-async-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}
@@ -62,9 +62,11 @@ public class EventHandler implements Consumer<EventContext> {
// 토픽 및 이벤트 유형에 따라 처리
if ("meeting".equals(topic)) {
handleMeetingEvent(eventType, eventBody);
log.info("이벤트 처리 제외");
// handleMeetingEvent(eventType, eventBody);
} else if ("todo".equals(topic)) {
handleTodoEvent(eventType, eventBody);
log.info("이벤트 처리 제외");
// handleTodoEvent(eventType, eventBody);
} else if ("notification".equals(topic)) {
handleNotificationEvent(eventType, eventBody);
} else {
@@ -8,6 +8,7 @@ import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
/**
@@ -28,22 +29,23 @@ public class EmailClient {
private final JavaMailSender mailSender;
/**
* HTML 이메일 발송 (재시도 지원)
* HTML 이메일 발송 (비동기 + 재시도 지원)
*
* 발송 실패 시 최대 3번까지 재시도
* Exponential Backoff: 초기 5분, 최대 30분, 배수 2.0
* Exponential Backoff: 초기 1초, 최대 10초, 배수 2.0
*
* @param to 수신자 이메일 주소
* @param subject 이메일 제목
* @param htmlContent HTML 이메일 본문
* @throws MessagingException 이메일 발송 실패 시
*/
@Async("emailTaskExecutor")
@Retryable(
retryFor = {MessagingException.class},
maxAttempts = 3,
backoff = @Backoff(
delay = 300000, // 5분
maxDelay = 1800000, // 30분
delay = 1000, // 1초
maxDelay = 10000, // 10초
multiplier = 2.0
)
)
@@ -57,6 +59,7 @@ public class EmailClient {
helper.setTo(to);
helper.setSubject(subject);
helper.setText(htmlContent, true); // true = HTML 모드
// helper.setFrom("du0928@gmail.com");
mailSender.send(message);
@@ -69,19 +72,20 @@ public class EmailClient {
}
/**
* 텍스트 이메일 발송 (재시도 지원)
* 텍스트 이메일 발송 (비동기 + 재시도 지원)
*
* @param to 수신자 이메일 주소
* @param subject 이메일 제목
* @param textContent 텍스트 이메일 본문
* @throws MessagingException 이메일 발송 실패 시
*/
@Async("emailTaskExecutor")
@Retryable(
retryFor = {MessagingException.class},
maxAttempts = 3,
backoff = @Backoff(
delay = 300000,
maxDelay = 1800000,
delay = 1000, // 1초
maxDelay = 10000, // 10초
multiplier = 2.0
)
)
@@ -49,14 +49,15 @@ spring:
password: ${MAIL_PASSWORD:}
properties:
mail:
debug: true
smtp:
auth: true
starttls:
enable: true
required: true
connectiontimeout: 5000
timeout: 5000
writetimeout: 5000
connectiontimeout: 3000
timeout: 3000
writetimeout: 3000
# Thymeleaf Configuration
thymeleaf:
@@ -97,10 +98,11 @@ azure:
notification:
from-email: ${NOTIFICATION_FROM_EMAIL:noreply@hgzero.com}
from-name: ${NOTIFICATION_FROM_NAME:HGZero}
retry:
max-attempts: ${NOTIFICATION_RETRY_MAX_ATTEMPTS:3}
initial-interval: ${NOTIFICATION_RETRY_INITIAL_INTERVAL:1000}
multiplier: ${NOTIFICATION_RETRY_MULTIPLIER:2.0}
# retry 설정은 EmailClient.java의 @Retryable 어노테이션에서 직접 관리됨
# retry:
# max-attempts: 3
# initial-interval: 1000 # 1초
# multiplier: 2.0
# Actuator Configuration
management: