mirror of
https://github.com/hwanny1128/HGZero.git
synced 2025-12-06 13:46:24 +00:00
feat: 회의예약 시, 이메일발송 기능 보강
This commit is contained in:
parent
3f20f19f44
commit
d708f0b501
@ -35,8 +35,8 @@
|
|||||||
<!-- Mail Configuration -->
|
<!-- Mail Configuration -->
|
||||||
<entry key="MAIL_HOST" value="smtp.gmail.com" />
|
<entry key="MAIL_HOST" value="smtp.gmail.com" />
|
||||||
<entry key="MAIL_PORT" value="587" />
|
<entry key="MAIL_PORT" value="587" />
|
||||||
<entry key="MAIL_USERNAME" value="" />
|
<entry key="MAIL_USERNAME" value="du0928@gmail.com" />
|
||||||
<entry key="MAIL_PASSWORD" value="" />
|
<entry key="MAIL_PASSWORD" value="dwga zzqo ugnp iskv" />
|
||||||
|
|
||||||
<!-- Azure EventHub Configuration -->
|
<!-- Azure EventHub Configuration -->
|
||||||
<entry key="AZURE_EVENTHUB_ENABLED" value="true" />
|
<entry key="AZURE_EVENTHUB_ENABLED" value="true" />
|
||||||
|
|||||||
173
notification/DB_FIX_GUIDE.md
Normal file
173
notification/DB_FIX_GUIDE.md
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
# Notification DB 체크 제약 조건 수정 가이드
|
||||||
|
|
||||||
|
## 🚨 문제 상황
|
||||||
|
```
|
||||||
|
ERROR: new row for relation "notifications" violates check constraint "notifications_notification_type_check"
|
||||||
|
```
|
||||||
|
|
||||||
|
Event Hub에서 메시지는 정상적으로 수신되지만, 데이터베이스 저장 시 체크 제약 조건 위반으로 실패
|
||||||
|
|
||||||
|
## ✅ 해결 방법
|
||||||
|
|
||||||
|
### 방법 1: Azure Portal 사용 (권장)
|
||||||
|
|
||||||
|
1. **Azure Portal 접속**
|
||||||
|
- https://portal.azure.com 접속
|
||||||
|
|
||||||
|
2. **PostgreSQL 서버 찾기**
|
||||||
|
- 리소스 검색에서 "4.230.159.143" 또는 "notificationdb" 검색
|
||||||
|
|
||||||
|
3. **Query Editor 열기**
|
||||||
|
- 좌측 메뉴에서 "Query editor (preview)" 선택
|
||||||
|
- 사용자명: `hgzerouser`
|
||||||
|
- 비밀번호: `Hi5Jessica!`
|
||||||
|
- 데이터베이스: `notificationdb`
|
||||||
|
|
||||||
|
4. **SQL 실행**
|
||||||
|
```sql
|
||||||
|
-- 기존 제약 조건 삭제
|
||||||
|
ALTER TABLE notifications DROP CONSTRAINT IF EXISTS notifications_notification_type_check;
|
||||||
|
|
||||||
|
-- 새로운 제약 조건 추가
|
||||||
|
ALTER TABLE notifications
|
||||||
|
ADD CONSTRAINT notifications_notification_type_check
|
||||||
|
CHECK (notification_type IN (
|
||||||
|
'MEETING_INVITATION',
|
||||||
|
'TODO_ASSIGNED',
|
||||||
|
'TODO_REMINDER',
|
||||||
|
'MEETING_REMINDER',
|
||||||
|
'MINUTES_UPDATED',
|
||||||
|
'TODO_COMPLETED'
|
||||||
|
));
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **확인**
|
||||||
|
```sql
|
||||||
|
SELECT constraint_name, check_clause
|
||||||
|
FROM information_schema.check_constraints
|
||||||
|
WHERE constraint_name = 'notifications_notification_type_check';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 방법 2: DB 관리 도구 사용
|
||||||
|
|
||||||
|
#### DBeaver 사용
|
||||||
|
1. DBeaver 다운로드: https://dbeaver.io/download/
|
||||||
|
2. 새 연결 생성:
|
||||||
|
- Host: `4.230.159.143`
|
||||||
|
- Port: `5432`
|
||||||
|
- Database: `notificationdb`
|
||||||
|
- Username: `hgzerouser`
|
||||||
|
- Password: `Hi5Jessica!`
|
||||||
|
3. SQL Editor에서 위의 SQL 실행
|
||||||
|
|
||||||
|
#### pgAdmin 사용
|
||||||
|
1. pgAdmin 다운로드: https://www.pgadmin.org/download/
|
||||||
|
2. 서버 등록 후 Query Tool에서 SQL 실행
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 방법 3: psql 직접 설치 (Mac/Linux)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Mac (Homebrew)
|
||||||
|
brew install postgresql@15
|
||||||
|
|
||||||
|
# 실행
|
||||||
|
PGPASSWORD='Hi5Jessica!' psql -h 4.230.159.143 -p 5432 -U hgzerouser -d notificationdb -f notification/fix_constraint.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 방법 4: Python 스크립트 사용
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# psycopg2 설치
|
||||||
|
pip install psycopg2-binary
|
||||||
|
|
||||||
|
# 스크립트 실행
|
||||||
|
python3 notification/fix_db_constraint.py
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 실행할 SQL
|
||||||
|
|
||||||
|
생성된 파일: `notification/fix_constraint.sql`
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 기존 제약 조건 삭제
|
||||||
|
ALTER TABLE notifications DROP CONSTRAINT IF EXISTS notifications_notification_type_check;
|
||||||
|
|
||||||
|
-- 새로운 제약 조건 추가 (Java Enum의 모든 값 포함)
|
||||||
|
ALTER TABLE notifications
|
||||||
|
ADD CONSTRAINT notifications_notification_type_check
|
||||||
|
CHECK (notification_type IN (
|
||||||
|
'MEETING_INVITATION', -- 회의 초대
|
||||||
|
'TODO_ASSIGNED', -- Todo 할당
|
||||||
|
'TODO_REMINDER', -- Todo 리마인더
|
||||||
|
'MEETING_REMINDER', -- 회의 리마인더
|
||||||
|
'MINUTES_UPDATED', -- 회의록 수정
|
||||||
|
'TODO_COMPLETED' -- Todo 완료
|
||||||
|
));
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 검증 방법
|
||||||
|
|
||||||
|
### 1. 제약 조건 확인
|
||||||
|
```sql
|
||||||
|
SELECT constraint_name, check_clause
|
||||||
|
FROM information_schema.check_constraints
|
||||||
|
WHERE constraint_name = 'notifications_notification_type_check';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 서비스 재시작 후 로그 확인
|
||||||
|
```bash
|
||||||
|
# notification 서비스 재시작
|
||||||
|
cd notification
|
||||||
|
./gradlew bootRun
|
||||||
|
|
||||||
|
# 로그 모니터링
|
||||||
|
tail -f logs/notification-service.log | grep -E "ERROR|이벤트 수신|알림 발송"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 에러가 사라졌는지 확인
|
||||||
|
이 에러가 더 이상 나타나지 않아야 합니다:
|
||||||
|
```
|
||||||
|
ERROR: new row for relation "notifications" violates check constraint "notifications_notification_type_check"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📌 참고
|
||||||
|
|
||||||
|
### Java Enum 정의 위치
|
||||||
|
`notification/src/main/java/com/unicorn/hgzero/notification/domain/Notification.java:220-227`
|
||||||
|
|
||||||
|
```java
|
||||||
|
public enum NotificationType {
|
||||||
|
MEETING_INVITATION, // 회의 초대
|
||||||
|
TODO_ASSIGNED, // Todo 할당
|
||||||
|
TODO_REMINDER, // Todo 리마인더
|
||||||
|
MEETING_REMINDER, // 회의 리마인더
|
||||||
|
MINUTES_UPDATED, // 회의록 수정
|
||||||
|
TODO_COMPLETED // Todo 완료
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 현재 상태
|
||||||
|
- ✅ Event Hub 연결: 정상
|
||||||
|
- ✅ 메시지 수신: 정상
|
||||||
|
- ✅ 이메일 발송: 정상
|
||||||
|
- ❌ DB 저장: 제약 조건 위반으로 실패 ← **해결 필요**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ 주의사항
|
||||||
|
|
||||||
|
- 프로덕션 환경이라면 백업 먼저 수행
|
||||||
|
- 테스트 환경에서 먼저 검증 후 프로덕션 적용
|
||||||
|
- SQL 실행 권한이 필요합니다
|
||||||
39
notification/fix_constraint.sql
Normal file
39
notification/fix_constraint.sql
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
-- ====================================================================
|
||||||
|
-- Notification DB 체크 제약 조건 수정 스크립트
|
||||||
|
-- ====================================================================
|
||||||
|
-- 목적: notification_type의 체크 제약 조건을 Java Enum과 일치시킴
|
||||||
|
-- 날짜: 2025-10-26
|
||||||
|
-- ====================================================================
|
||||||
|
|
||||||
|
-- 1. 현재 체크 제약 조건 확인
|
||||||
|
SELECT constraint_name, check_clause
|
||||||
|
FROM information_schema.check_constraints
|
||||||
|
WHERE constraint_name = 'notifications_notification_type_check';
|
||||||
|
|
||||||
|
-- 2. 기존 체크 제약 조건 삭제
|
||||||
|
ALTER TABLE notifications DROP CONSTRAINT IF EXISTS notifications_notification_type_check;
|
||||||
|
|
||||||
|
-- 3. 새로운 체크 제약 조건 추가 (Java Enum의 모든 값 포함)
|
||||||
|
ALTER TABLE notifications
|
||||||
|
ADD CONSTRAINT notifications_notification_type_check
|
||||||
|
CHECK (notification_type IN (
|
||||||
|
'MEETING_INVITATION', -- 회의 초대
|
||||||
|
'TODO_ASSIGNED', -- Todo 할당
|
||||||
|
'TODO_REMINDER', -- Todo 리마인더
|
||||||
|
'MEETING_REMINDER', -- 회의 리마인더
|
||||||
|
'MINUTES_UPDATED', -- 회의록 수정
|
||||||
|
'TODO_COMPLETED' -- Todo 완료
|
||||||
|
));
|
||||||
|
|
||||||
|
-- 4. 업데이트 결과 확인
|
||||||
|
SELECT constraint_name, check_clause
|
||||||
|
FROM information_schema.check_constraints
|
||||||
|
WHERE constraint_name = 'notifications_notification_type_check';
|
||||||
|
|
||||||
|
-- 5. 테이블 정보 확인
|
||||||
|
\d+ notifications
|
||||||
|
|
||||||
|
-- ====================================================================
|
||||||
|
-- 참고: Java Enum 위치
|
||||||
|
-- notification/src/main/java/com/unicorn/hgzero/notification/domain/Notification.java:220-227
|
||||||
|
-- ====================================================================
|
||||||
129
notification/fix_db_constraint.py
Executable file
129
notification/fix_db_constraint.py
Executable file
@ -0,0 +1,129 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Notification DB 체크 제약 조건 수정 스크립트
|
||||||
|
실행 전 psycopg2 설치 필요: pip install psycopg2-binary
|
||||||
|
"""
|
||||||
|
|
||||||
|
import psycopg2
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# 데이터베이스 연결 정보
|
||||||
|
DB_CONFIG = {
|
||||||
|
'host': '4.230.159.143',
|
||||||
|
'port': 5432,
|
||||||
|
'database': 'notificationdb',
|
||||||
|
'user': 'hgzerouser',
|
||||||
|
'password': 'Hi5Jessica!'
|
||||||
|
}
|
||||||
|
|
||||||
|
# 실행할 SQL
|
||||||
|
SQL_DROP_CONSTRAINT = """
|
||||||
|
ALTER TABLE notifications
|
||||||
|
DROP CONSTRAINT IF EXISTS notifications_notification_type_check;
|
||||||
|
"""
|
||||||
|
|
||||||
|
SQL_ADD_CONSTRAINT = """
|
||||||
|
ALTER TABLE notifications
|
||||||
|
ADD CONSTRAINT notifications_notification_type_check
|
||||||
|
CHECK (notification_type IN (
|
||||||
|
'MEETING_INVITATION',
|
||||||
|
'TODO_ASSIGNED',
|
||||||
|
'TODO_REMINDER',
|
||||||
|
'MEETING_REMINDER',
|
||||||
|
'MINUTES_UPDATED',
|
||||||
|
'TODO_COMPLETED'
|
||||||
|
));
|
||||||
|
"""
|
||||||
|
|
||||||
|
SQL_VERIFY = """
|
||||||
|
SELECT constraint_name, check_clause
|
||||||
|
FROM information_schema.check_constraints
|
||||||
|
WHERE constraint_name = 'notifications_notification_type_check';
|
||||||
|
"""
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=" * 70)
|
||||||
|
print("Notification DB 체크 제약 조건 수정 시작")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
conn = None
|
||||||
|
cursor = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 데이터베이스 연결
|
||||||
|
print(f"\n1. 데이터베이스 연결 중...")
|
||||||
|
print(f" Host: {DB_CONFIG['host']}")
|
||||||
|
print(f" Database: {DB_CONFIG['database']}")
|
||||||
|
|
||||||
|
conn = psycopg2.connect(**DB_CONFIG)
|
||||||
|
cursor = conn.cursor()
|
||||||
|
print(" ✅ 연결 성공")
|
||||||
|
|
||||||
|
# 기존 제약 조건 삭제
|
||||||
|
print("\n2. 기존 체크 제약 조건 삭제 중...")
|
||||||
|
cursor.execute(SQL_DROP_CONSTRAINT)
|
||||||
|
print(" ✅ 삭제 완료")
|
||||||
|
|
||||||
|
# 새로운 제약 조건 추가
|
||||||
|
print("\n3. 새로운 체크 제약 조건 추가 중...")
|
||||||
|
cursor.execute(SQL_ADD_CONSTRAINT)
|
||||||
|
print(" ✅ 추가 완료")
|
||||||
|
|
||||||
|
# 변경 사항 커밋
|
||||||
|
conn.commit()
|
||||||
|
print("\n4. 변경 사항 커밋 완료")
|
||||||
|
|
||||||
|
# 결과 확인
|
||||||
|
print("\n5. 제약 조건 확인 중...")
|
||||||
|
cursor.execute(SQL_VERIFY)
|
||||||
|
result = cursor.fetchone()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
print(" ✅ 제약 조건이 정상적으로 생성되었습니다")
|
||||||
|
print(f" - Constraint Name: {result[0]}")
|
||||||
|
print(f" - Check Clause: {result[1]}")
|
||||||
|
else:
|
||||||
|
print(" ⚠️ 제약 조건을 찾을 수 없습니다")
|
||||||
|
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
print("✅ 모든 작업이 성공적으로 완료되었습니다!")
|
||||||
|
print("=" * 70)
|
||||||
|
print("\n다음 단계:")
|
||||||
|
print("1. notification 서비스를 재시작하세요")
|
||||||
|
print("2. 로그에서 에러가 사라졌는지 확인하세요")
|
||||||
|
print(" tail -f notification/logs/notification-service.log")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
except psycopg2.Error as e:
|
||||||
|
print(f"\n❌ 데이터베이스 오류 발생: {e}")
|
||||||
|
if conn:
|
||||||
|
conn.rollback()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\n❌ 예기치 않은 오류 발생: {e}")
|
||||||
|
if conn:
|
||||||
|
conn.rollback()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# 연결 종료
|
||||||
|
if cursor:
|
||||||
|
cursor.close()
|
||||||
|
if conn:
|
||||||
|
conn.close()
|
||||||
|
print("\n6. 데이터베이스 연결 종료")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
import psycopg2
|
||||||
|
except ImportError:
|
||||||
|
print("❌ psycopg2 모듈이 설치되지 않았습니다.")
|
||||||
|
print("\n다음 명령어로 설치하세요:")
|
||||||
|
print(" pip install psycopg2-binary")
|
||||||
|
print("\n또는 conda를 사용하는 경우:")
|
||||||
|
print(" conda install -c conda-forge psycopg2")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
sys.exit(main())
|
||||||
File diff suppressed because it is too large
Load Diff
@ -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)) {
|
if ("meeting".equals(topic)) {
|
||||||
handleMeetingEvent(eventType, eventBody);
|
log.info("이벤트 처리 제외");
|
||||||
|
// handleMeetingEvent(eventType, eventBody);
|
||||||
} else if ("todo".equals(topic)) {
|
} else if ("todo".equals(topic)) {
|
||||||
handleTodoEvent(eventType, eventBody);
|
log.info("이벤트 처리 제외");
|
||||||
|
// handleTodoEvent(eventType, eventBody);
|
||||||
} else if ("notification".equals(topic)) {
|
} else if ("notification".equals(topic)) {
|
||||||
handleNotificationEvent(eventType, eventBody);
|
handleNotificationEvent(eventType, eventBody);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import org.springframework.mail.javamail.JavaMailSender;
|
|||||||
import org.springframework.mail.javamail.MimeMessageHelper;
|
import org.springframework.mail.javamail.MimeMessageHelper;
|
||||||
import org.springframework.retry.annotation.Backoff;
|
import org.springframework.retry.annotation.Backoff;
|
||||||
import org.springframework.retry.annotation.Retryable;
|
import org.springframework.retry.annotation.Retryable;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -28,22 +29,23 @@ public class EmailClient {
|
|||||||
private final JavaMailSender mailSender;
|
private final JavaMailSender mailSender;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* HTML 이메일 발송 (재시도 지원)
|
* HTML 이메일 발송 (비동기 + 재시도 지원)
|
||||||
*
|
*
|
||||||
* 발송 실패 시 최대 3번까지 재시도
|
* 발송 실패 시 최대 3번까지 재시도
|
||||||
* Exponential Backoff: 초기 5분, 최대 30분, 배수 2.0
|
* Exponential Backoff: 초기 1초, 최대 10초, 배수 2.0
|
||||||
*
|
*
|
||||||
* @param to 수신자 이메일 주소
|
* @param to 수신자 이메일 주소
|
||||||
* @param subject 이메일 제목
|
* @param subject 이메일 제목
|
||||||
* @param htmlContent HTML 이메일 본문
|
* @param htmlContent HTML 이메일 본문
|
||||||
* @throws MessagingException 이메일 발송 실패 시
|
* @throws MessagingException 이메일 발송 실패 시
|
||||||
*/
|
*/
|
||||||
|
@Async("emailTaskExecutor")
|
||||||
@Retryable(
|
@Retryable(
|
||||||
retryFor = {MessagingException.class},
|
retryFor = {MessagingException.class},
|
||||||
maxAttempts = 3,
|
maxAttempts = 3,
|
||||||
backoff = @Backoff(
|
backoff = @Backoff(
|
||||||
delay = 300000, // 5분
|
delay = 1000, // 1초
|
||||||
maxDelay = 1800000, // 30분
|
maxDelay = 10000, // 10초
|
||||||
multiplier = 2.0
|
multiplier = 2.0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -57,6 +59,7 @@ public class EmailClient {
|
|||||||
helper.setTo(to);
|
helper.setTo(to);
|
||||||
helper.setSubject(subject);
|
helper.setSubject(subject);
|
||||||
helper.setText(htmlContent, true); // true = HTML 모드
|
helper.setText(htmlContent, true); // true = HTML 모드
|
||||||
|
// helper.setFrom("du0928@gmail.com");
|
||||||
|
|
||||||
mailSender.send(message);
|
mailSender.send(message);
|
||||||
|
|
||||||
@ -69,19 +72,20 @@ public class EmailClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 텍스트 이메일 발송 (재시도 지원)
|
* 텍스트 이메일 발송 (비동기 + 재시도 지원)
|
||||||
*
|
*
|
||||||
* @param to 수신자 이메일 주소
|
* @param to 수신자 이메일 주소
|
||||||
* @param subject 이메일 제목
|
* @param subject 이메일 제목
|
||||||
* @param textContent 텍스트 이메일 본문
|
* @param textContent 텍스트 이메일 본문
|
||||||
* @throws MessagingException 이메일 발송 실패 시
|
* @throws MessagingException 이메일 발송 실패 시
|
||||||
*/
|
*/
|
||||||
|
@Async("emailTaskExecutor")
|
||||||
@Retryable(
|
@Retryable(
|
||||||
retryFor = {MessagingException.class},
|
retryFor = {MessagingException.class},
|
||||||
maxAttempts = 3,
|
maxAttempts = 3,
|
||||||
backoff = @Backoff(
|
backoff = @Backoff(
|
||||||
delay = 300000,
|
delay = 1000, // 1초
|
||||||
maxDelay = 1800000,
|
maxDelay = 10000, // 10초
|
||||||
multiplier = 2.0
|
multiplier = 2.0
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@ -49,14 +49,15 @@ spring:
|
|||||||
password: ${MAIL_PASSWORD:}
|
password: ${MAIL_PASSWORD:}
|
||||||
properties:
|
properties:
|
||||||
mail:
|
mail:
|
||||||
|
debug: true
|
||||||
smtp:
|
smtp:
|
||||||
auth: true
|
auth: true
|
||||||
starttls:
|
starttls:
|
||||||
enable: true
|
enable: true
|
||||||
required: true
|
required: true
|
||||||
connectiontimeout: 5000
|
connectiontimeout: 3000
|
||||||
timeout: 5000
|
timeout: 3000
|
||||||
writetimeout: 5000
|
writetimeout: 3000
|
||||||
|
|
||||||
# Thymeleaf Configuration
|
# Thymeleaf Configuration
|
||||||
thymeleaf:
|
thymeleaf:
|
||||||
@ -97,10 +98,11 @@ azure:
|
|||||||
notification:
|
notification:
|
||||||
from-email: ${NOTIFICATION_FROM_EMAIL:noreply@hgzero.com}
|
from-email: ${NOTIFICATION_FROM_EMAIL:noreply@hgzero.com}
|
||||||
from-name: ${NOTIFICATION_FROM_NAME:HGZero}
|
from-name: ${NOTIFICATION_FROM_NAME:HGZero}
|
||||||
retry:
|
# retry 설정은 EmailClient.java의 @Retryable 어노테이션에서 직접 관리됨
|
||||||
max-attempts: ${NOTIFICATION_RETRY_MAX_ATTEMPTS:3}
|
# retry:
|
||||||
initial-interval: ${NOTIFICATION_RETRY_INITIAL_INTERVAL:1000}
|
# max-attempts: 3
|
||||||
multiplier: ${NOTIFICATION_RETRY_MULTIPLIER:2.0}
|
# initial-interval: 1000 # 1초
|
||||||
|
# multiplier: 2.0
|
||||||
|
|
||||||
# Actuator Configuration
|
# Actuator Configuration
|
||||||
management:
|
management:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user