Skip to content

Commit 974bd32

Browse files
committed
refactor: 이메일 전송 비동기 처리 (DASOMBE-13)
1 parent 98de3f8 commit 974bd32

File tree

4 files changed

+126
-93
lines changed

4 files changed

+126
-93
lines changed

src/main/java/dmu/dasom/api/ApiApplication.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
import org.springframework.boot.SpringApplication;
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
55
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
6+
import org.springframework.scheduling.annotation.EnableAsync;
67

78
@EnableJpaAuditing
89
@SpringBootApplication
10+
@EnableAsync
911
public class ApiApplication {
10-
1112
public static void main(String[] args) {
1213
SpringApplication.run(ApiApplication.class, args);
1314
}

src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,7 @@ public void sendEmailsToApplicants(MailType mailType) {
132132
}
133133

134134
for (Applicant applicant : applicants) {
135-
try {
136-
emailService.sendEmail(applicant.getEmail(), applicant.getName(), mailType);
137-
} catch (MessagingException e) {
138-
System.err.println("Failed to send email to: " + applicant.getEmail());
139-
}
135+
emailService.sendEmail(applicant.getEmail(), applicant.getName(), mailType);
140136
}
141137
}
142138

src/main/java/dmu/dasom/api/domain/google/service/EmailService.java

Lines changed: 76 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
import jakarta.mail.MessagingException;
77
import jakarta.mail.internet.MimeMessage;
88
import lombok.RequiredArgsConstructor;
9+
import org.slf4j.Logger;
10+
import org.slf4j.LoggerFactory;
911
import org.springframework.beans.factory.annotation.Value;
1012
import org.springframework.mail.javamail.JavaMailSender;
1113
import org.springframework.mail.javamail.MimeMessageHelper;
14+
import org.springframework.scheduling.annotation.Async;
1215
import org.springframework.stereotype.Service;
1316
import org.thymeleaf.TemplateEngine;
1417
import org.thymeleaf.context.Context;
@@ -19,62 +22,92 @@ public class EmailService {
1922

2023
private final TemplateEngine templateEngine;
2124
private final JavaMailSender javaMailSender;
25+
private static final Logger log = LoggerFactory.getLogger(EmailService.class);
2226

2327
@Value("${spring.mail.username}")
2428
private String from;
2529

26-
public void sendEmail(String to, String name, MailType mailType) throws MessagingException {
27-
if (mailType == null){
28-
throw new CustomException(ErrorCode.MAIL_TYPE_NOT_VALID);
30+
@Async
31+
public void sendEmail(String to, String name, MailType mailType) {
32+
try {
33+
if (mailType == null) {
34+
throw new CustomException(ErrorCode.MAIL_TYPE_NOT_VALID);
35+
}
36+
37+
// 메일 제목 및 템플릿 설정
38+
String subject = getSubject(mailType);
39+
String emailContent = getEmailContent(mailType);
40+
String buttonText = getButtonText(mailType);
41+
String buttonUrl = "https://dmu-dasom.or.kr/recruit/result";
42+
43+
// HTML 템플릿에 전달할 데이터 설정
44+
Context context = new Context();
45+
context.setVariable("name", name); // 지원자 이름 전달
46+
context.setVariable("emailContent", emailContent); // 이메일 내용 전달
47+
context.setVariable("buttonUrl", buttonUrl); // 버튼 링크 전달
48+
context.setVariable("buttonText", buttonText);
49+
50+
// HTML 템플릿 처리
51+
String htmlBody = templateEngine.process("email-template", context);
52+
53+
// 이메일 생성 및 전송
54+
MimeMessage message = javaMailSender.createMimeMessage();
55+
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
56+
57+
helper.setTo(to);
58+
helper.setSubject(subject);
59+
helper.setText(htmlBody, true);
60+
helper.setFrom((from != null && !from.isEmpty()) ? from : "[email protected]");
61+
62+
// Content-Type을 명시적으로 설정
63+
message.setContent(htmlBody, "text/html; charset=utf-8");
64+
65+
javaMailSender.send(message);
66+
log.info("Email sent successfull {}", to);
67+
} catch (MessagingException e) {
68+
log.error("Failed to send email to {}: {}", to, e.getMessage());
69+
} catch (CustomException e) {
70+
log.error("Email sending error for {}: {}", to, e.getMessage());
2971
}
72+
}
3073

31-
// 메일 제목 및 템플릿 설정
32-
String subject;
33-
String emailContent;
34-
String buttonUrl = "https://dmu-dasom.or.kr/recruit/result";
35-
String buttonText;
74+
// 메일 유형에 맞는 제목 반환
75+
private String getSubject(MailType mailType){
76+
switch (mailType){
77+
case DOCUMENT_RESULT:
78+
return "동양미래대학교 컴퓨터소프트웨어공학과 전공 동아리 DASOM 서류 결과 안내";
79+
case FINAL_RESULT:
80+
return "동양미래대학교 컴퓨터소프트웨어공학과 전공 동아리 DASOM 최종 면접 결과 안내";
81+
default:
82+
throw new CustomException(ErrorCode.MAIL_TYPE_NOT_VALID);
83+
}
84+
}
3685

86+
// 메일 유형에 맞는 본문 반환
87+
private String getEmailContent(MailType mailType){
3788
switch (mailType) {
38-
case DOCUMENT_RESULT -> {
39-
subject = "동양미래대학교 컴퓨터소프트웨어공학과 전공 동아리 DASOM 서류 결과 안내";
40-
emailContent = "먼저 다솜 34기에 많은 관심을 두고 지원해 주셔서 감사드리며,<br>" +
89+
case DOCUMENT_RESULT:
90+
return "먼저 다솜 34기에 많은 관심을 두고 지원해 주셔서 감사드리며,<br>" +
4191
"내부 서류 평가 결과 및 추후 일정에 관해 안내해드리고자 이메일을 발송하게 되었습니다.<br>" +
4292
"서류 전형 결과는 아래 버튼 혹은 홈페이지를 통해 확인이 가능합니다.";
43-
buttonText = "서류 결과 확인하기";
44-
}
45-
case FINAL_RESULT -> {
46-
subject = "동양미래대학교 컴퓨터소프트웨어공학과 전공 동아리 DASOM 최종 면접 결과 안내";
47-
emailContent = "먼저 다솜 34기에 많은 관심을 두고 지원해 주셔서 감사드리며,<br>" +
93+
case FINAL_RESULT:
94+
return "먼저 다솜 34기에 많은 관심을 두고 지원해 주셔서 감사드리며,<br>" +
4895
"최종 면접 결과 및 추후 일정에 관해 안내해드리고자 이메일을 발송하게 되었습니다.<br>" +
4996
"최종 면접 결과는 아래 버튼 혹은 홈페이지를 통해 확인이 가능합니다.";
50-
buttonText = "최종 결과 확인하기";
51-
}
52-
default -> throw new IllegalStateException("Unexpected value: " + mailType);
97+
default:
98+
throw new CustomException(ErrorCode.MAIL_TYPE_NOT_VALID);
5399
}
54-
55-
// HTML 템플릿에 전달할 데이터 설정
56-
Context context = new Context();
57-
context.setVariable("name", name); // 지원자 이름 전달
58-
context.setVariable("emailContent", emailContent); // 이메일 내용 전달
59-
context.setVariable("buttonUrl", buttonUrl); // 버튼 링크 전달
60-
context.setVariable("buttonText", buttonText);
61-
62-
// HTML 템플릿 처리
63-
String htmlBody = templateEngine.process("email-template", context);
64-
65-
// 이메일 생성 및 전송
66-
MimeMessage message = javaMailSender.createMimeMessage();
67-
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
68-
69-
helper.setTo(to);
70-
helper.setSubject(subject);
71-
helper.setText(htmlBody, true);
72-
helper.setFrom((from != null && !from.isEmpty()) ? from : "[email protected]");
73-
74-
// Content-Type을 명시적으로 설정
75-
message.setContent(htmlBody, "text/html; charset=utf-8");
76-
77-
javaMailSender.send(message);
78100
}
79101

102+
// 메일 유형에 맞는 버튼 텍스트 반환
103+
private String getButtonText(MailType mailType) {
104+
switch (mailType) {
105+
case DOCUMENT_RESULT:
106+
return "서류 결과 확인하기";
107+
case FINAL_RESULT:
108+
return "최종 결과 확인하기";
109+
default:
110+
throw new CustomException(ErrorCode.MAIL_TYPE_NOT_VALID);
111+
}
112+
}
80113
}
Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package dmu.dasom.api.domain.email;
22

3-
import dmu.dasom.api.domain.common.exception.CustomException;
4-
import dmu.dasom.api.domain.common.exception.ErrorCode;
3+
54
import dmu.dasom.api.domain.google.enums.MailType;
65
import dmu.dasom.api.domain.google.service.EmailService;
76
import jakarta.mail.MessagingException;
@@ -18,92 +17,96 @@
1817
import org.thymeleaf.TemplateEngine;
1918
import org.thymeleaf.context.Context;
2019

21-
import static org.junit.jupiter.api.Assertions.*;
20+
import static org.junit.jupiter.api.Assertions.assertEquals;
21+
import static org.junit.jupiter.api.Assertions.assertNotNull;
22+
import static org.mockito.ArgumentMatchers.eq;
2223
import static org.mockito.Mockito.*;
2324

2425
class EmailServiceTest {
2526

2627
@Mock
2728
private JavaMailSender javaMailSender;
28-
2929
@Mock
3030
private TemplateEngine templateEngine;
31-
3231
@InjectMocks
3332
private EmailService emailService;
3433

35-
private MimeMessage mimeMessage;
36-
3734
@BeforeEach
38-
void setUp() {
35+
void setUp(){
3936
MockitoAnnotations.openMocks(this);
40-
mimeMessage = mock(MimeMessage.class);
41-
when(javaMailSender.createMimeMessage()).thenReturn(mimeMessage);
42-
43-
// 테스트 환경에서 from 값을 설정
37+
when(javaMailSender.createMimeMessage()).thenReturn(mock(MimeMessage.class));
4438
ReflectionTestUtils.setField(emailService, "from", "[email protected]");
4539
}
4640

4741
@Test
48-
@DisplayName("서류 합격 메일 발송 테스트")
49-
void sendEmail_documentResult() throws MessagingException {
42+
@DisplayName("성공 - 서류 결과 메일 발송 테스트")
43+
void sendDocumentResultMessage_Success() throws MessagingException {
5044
// given
5145
String to = "[email protected]";
5246
String name = "지원자";
5347
MailType mailType = MailType.DOCUMENT_RESULT;
5448

55-
String expectedTemplate = "email-template";
56-
String expectedHtmlBody = "<html><body>Document Pass</body></html>";
57-
when(templateEngine.process(eq(expectedTemplate), any(Context.class))).thenReturn(expectedHtmlBody);
49+
String expectedHtmlBody = "<html><body>Test HTML</body></html>";
50+
when(templateEngine.process(eq("email-template"), any(Context.class))).thenReturn(expectedHtmlBody);
5851

5952
// when
6053
emailService.sendEmail(to, name, mailType);
6154

62-
// then
63-
ArgumentCaptor<MimeMessage> messageCaptor = ArgumentCaptor.forClass(MimeMessage.class);
64-
verify(javaMailSender).send(messageCaptor.capture());
65-
66-
MimeMessage sentMessage = messageCaptor.getValue();
67-
assertNotNull(sentMessage);
68-
verify(templateEngine).process(eq(expectedTemplate), any(Context.class));
55+
//then
56+
// 비동기 처리 확인
57+
verify(javaMailSender, timeout(1000)).send(any(MimeMessage.class));
58+
// 메일 전송 확인
59+
ArgumentCaptor<Context> contextCaptor = ArgumentCaptor.forClass(Context.class);
60+
verify(templateEngine).process(eq("email-template"), contextCaptor.capture());
61+
Context capturedContext = contextCaptor.getValue();
62+
63+
// context 변수 확인
64+
assertEquals(name, capturedContext.getVariable("name"));
65+
assertEquals("서류 결과 확인하기", capturedContext.getVariable("buttonText"));
66+
assertNotNull(capturedContext.getVariable("emailContent"));
6967
}
7068

7169
@Test
72-
@DisplayName("최종 합격 메일 발송 테스트")
73-
void sendEmail_finalResult() throws MessagingException {
70+
@DisplayName("성공 - 최종 결과 메일 발송 테스트")
71+
void sendFinalResultMessage_Success() throws MessagingException {
7472
// given
7573
String to = "[email protected]";
7674
String name = "지원자";
7775
MailType mailType = MailType.FINAL_RESULT;
7876

79-
String expectedTemplate = "email-template";
80-
String expectedHtmlBody = "<html><body>Final Pass</body></html>";
81-
when(templateEngine.process(eq(expectedTemplate), any(Context.class))).thenReturn(expectedHtmlBody);
77+
String expectedHtmlBody = "<html><body>Test HTML</body></html>";
78+
when(templateEngine.process(eq("email-template"), any(Context.class))).thenReturn(expectedHtmlBody);
8279

8380
// when
8481
emailService.sendEmail(to, name, mailType);
8582

86-
// then
87-
ArgumentCaptor<MimeMessage> messageCaptor = ArgumentCaptor.forClass(MimeMessage.class);
88-
verify(javaMailSender).send(messageCaptor.capture());
89-
90-
MimeMessage sentMessage = messageCaptor.getValue();
91-
assertNotNull(sentMessage);
92-
verify(templateEngine).process(eq(expectedTemplate), any(Context.class));
83+
//then
84+
// 비동기 처리 확인
85+
verify(javaMailSender, timeout(1000)).send(any(MimeMessage.class));
86+
// 메일 전송 확인
87+
ArgumentCaptor<Context> contextCaptor = ArgumentCaptor.forClass(Context.class);
88+
verify(templateEngine).process(eq("email-template"), contextCaptor.capture());
89+
Context capturedContext = contextCaptor.getValue();
90+
91+
// context 변수 확인
92+
assertEquals(name, capturedContext.getVariable("name"));
93+
assertEquals("최종 결과 확인하기", capturedContext.getVariable("buttonText"));
94+
assertNotNull(capturedContext.getVariable("emailContent"));
9395
}
9496

9597
@Test
96-
@DisplayName("잘못된 MailType 처리 테스트")
97-
void sendEmail_invalidMailType() {
98-
// given
98+
@DisplayName("실패 - MailType이 null일 경우, 예외 발생 테스트")
99+
void sendEmail_nullMailType_shouldNotSend() {
100+
//given
99101
String to = "[email protected]";
100102
String name = "지원자";
101103

102-
// when & then
103-
CustomException exception = assertThrows(CustomException.class, () -> {
104-
emailService.sendEmail(to, name, null);
105-
});
104+
// when
105+
emailService.sendEmail(to, name, null);
106106

107-
assertEquals(ErrorCode.MAIL_TYPE_NOT_VALID, exception.getErrorCode());
107+
// then
108+
verify(javaMailSender, never()).send(any(MimeMessage.class));
108109
}
110+
111+
109112
}

0 commit comments

Comments
 (0)