Skip to content

Commit

Permalink
메일 비동기 전송 처리 (#40)
Browse files Browse the repository at this point in the history
* refactor: 필드 주입 대신 생성자 주입으로 변경

* refactor: 개행 및 EOL 추가

* refactor: 서류, 최종 메일 분리 및 비동기 처리

* refactor: 빌더 생성자로 변경

* test: displayName 추가

* fix: 스레드 작업 대기 시간 연장

* feat: 메일 예외 추가

* feat: 메일 병렬처리 추가

* fix: 대기시간 연장

* refactor: 메일 예약, 메일 전송 로직 분리

* fix: 메일 전송 테스트 비활성화

* fix: setter 추가

* feat: 서류 합격 메일 전송

* fix: 템플릿 정구 수

* feat: 메일 전송 비동기, 40개 배치 전송
  • Loading branch information
ay-eonii authored Nov 11, 2024
1 parent afaa003 commit b29837c
Show file tree
Hide file tree
Showing 9 changed files with 152 additions and 149 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import land.leets.domain.application.domain.Application;
import land.leets.domain.application.domain.repository.ApplicationRepository;
import land.leets.domain.application.type.ApplicationStatus;
import land.leets.global.mail.MailProvider;
import land.leets.global.mail.MailManager;
import land.leets.global.mail.dto.MailDto;
import lombok.RequiredArgsConstructor;

Expand All @@ -30,7 +30,7 @@ public class SendFinalMailImpl implements SendMail {

private final Random RANDOM = new Random();
private final ApplicationRepository applicationRepository;
private final MailProvider mailProvider;
private final MailManager mailManager;
private final TemplateEngine templateEngine;

@Override
Expand All @@ -44,7 +44,7 @@ public void execute(ApplicationStatus status) {
MailDto mailDto = new MailDto(MAIL_TITLE, new String[] {application.getUser().getEmail()}, message);
mailDtos.add(mailDto);
}
mailProvider.sendEmails(mailDtos);
mailManager.sendEmails(mailDtos);
}

private Context makeContext(String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import land.leets.domain.interview.domain.repository.InterviewRepository;
import land.leets.domain.interview.exception.InterviewNotFoundException;
import land.leets.domain.interview.type.HasInterview;
import land.leets.global.mail.MailProvider;
import land.leets.global.mail.MailManager;
import land.leets.global.mail.dto.MailDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -48,7 +48,7 @@ public class SendPaperMailImpl implements SendMail {
private final InterviewRepository interviewRepository;
private final ApplicationRepository applicationRepository;
private final TemplateEngine templateEngine;
private final MailProvider mailProvider;
private final MailManager mailManager;

@Value("${target.url.dev}")
private String LOCAL_TARGET_URL;
Expand All @@ -70,7 +70,7 @@ public void execute(ApplicationStatus status) {
MailDto mailDto = new MailDto(MAIL_TITLE, new String[] {application.getUser().getEmail()}, message);
mailDtos.add(mailDto);
}
mailProvider.sendEmails(mailDtos);
mailManager.sendEmails(mailDtos);
}

private Context makeContext(String name) {
Expand Down
29 changes: 29 additions & 0 deletions src/main/java/land/leets/global/config/AsyncConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package land.leets.global.config;

import java.util.concurrent.Executor;

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 lombok.extern.slf4j.Slf4j;

@Slf4j
@EnableAsync
@Configuration
public class AsyncConfig {

@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(4);
executor.setQueueCapacity(200);
executor.setThreadNamePrefix("Mailing-");
executor.setKeepAliveSeconds(60);
executor.setRejectedExecutionHandler(((r, asyncExecutor) -> log.warn("더 이상 요청을 처리할 수 없습니다.")));
executor.initialize();
return executor;
}
}
7 changes: 3 additions & 4 deletions src/main/java/land/leets/global/cron/SendMailCron.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package land.leets.global.cron;

import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import land.leets.domain.application.type.ApplicationStatus;
Expand All @@ -17,7 +16,7 @@ public class SendMailCron {
private final SendFinalMailImpl sendFinalMailImpl;
private final SendPaperMailImpl sendPaperMailImpl;

@Scheduled(cron = "0 0 23 8 9 ?")
// @Scheduled(cron = "0 0 23 8 9 ?")
public void sendPaperMail() {
for (ApplicationStatus status : ApplicationStatus.papers()) {
sendPaperMailImpl.execute(status);
Expand All @@ -26,14 +25,14 @@ public void sendPaperMail() {
log.info("Send paper result mail successfully.");
}

@Scheduled(cron = "0 30 23 8 9 ?")
// @Scheduled(cron = "0 30 23 8 9 ?")
public void sendPassPaperMail() {
sendPaperMailImpl.execute(ApplicationStatus.PASS_PAPER);

log.info("Send paper result mail successfully.");
}

@Scheduled(cron = "0 0 20 11 9 ?")
// @Scheduled(cron = "0 0 20 11 9 ?")
public void sendFinalMail() {
for (ApplicationStatus status : ApplicationStatus.finals()) {
sendFinalMailImpl.execute(status);
Expand Down
75 changes: 75 additions & 0 deletions src/main/java/land/leets/global/mail/MailFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package land.leets.global.mail;

import java.util.List;
import java.util.Objects;

import org.springframework.mail.MailSendException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.function.ThrowingSupplier;

import jakarta.mail.Address;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import land.leets.global.mail.dto.MailDto;
import land.leets.global.mail.exception.MailException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
@RequiredArgsConstructor
public class MailFactory {

private final JavaMailSender javaMailSender;

public MimeMessage createMail(MailDto mailDto) {
try {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8");
mimeMessageHelper.setTo(mailDto.to());
mimeMessageHelper.setSubject(mailDto.title());
mimeMessageHelper.setText(mailDto.body(), true);
return mimeMessage;
} catch (MessagingException e) {
throw new MailException();
}
}

@Async
public void sendMails(List<MimeMessage> batch) {
try {
javaMailSender.send(batch.toArray(new MimeMessage[0]));
} catch (MailSendException e) {
MimeMessage[] failedMessages = e.getFailedMessages().keySet()
.stream()
.map(object -> (MimeMessage)object)
.toArray(MimeMessage[]::new);
retry(failedMessages);
}
}

private void retry(MimeMessage[] failedMessages) {
try {
javaMailSender.send(failedMessages);
} catch (MailSendException e) {
e.getFailedMessages().keySet()
.stream()
.map(failure -> (MimeMessage)failure)
.map(mimeMessage -> apply(mimeMessage::getAllRecipients))
.filter(Objects::nonNull)
.forEach(address -> log.error("fail to send mail. address = {}", address));
}
}

private Address apply(ThrowingSupplier<Address[]> function) {
try {
return function.get()[0];
} catch (Exception e) {
log.error("fail to load failed address");
return null;
}
}
}
39 changes: 39 additions & 0 deletions src/main/java/land/leets/global/mail/MailManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package land.leets.global.mail;

import java.util.List;
import java.util.stream.IntStream;

import org.springframework.stereotype.Component;

import jakarta.mail.internet.MimeMessage;
import land.leets.global.mail.dto.MailDto;
import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class MailManager {

private static final int BATCH_SIZE = 40;

private final MailFactory mailFactory;

public void sendEmails(List<MailDto> mailDtos) {
List<List<MimeMessage>> batchedMails = createChunkedMails(mailDtos);

batchedMails.parallelStream()
.forEach(mailFactory::sendMails);
}

private List<List<MimeMessage>> createChunkedMails(List<MailDto> mailDtos) {
List<MimeMessage> messages = mailDtos.parallelStream()
.map(mailFactory::createMail)
.toList();
return partitionMessages(messages);
}

private List<List<MimeMessage>> partitionMessages(List<MimeMessage> messages) {
return IntStream.range(0, (messages.size() + BATCH_SIZE - 1) / BATCH_SIZE)
.mapToObj(i -> messages.subList(i * BATCH_SIZE, Math.min((i + 1) * BATCH_SIZE, messages.size())))
.toList();
}
}
56 changes: 0 additions & 56 deletions src/main/java/land/leets/global/mail/MailProvider.java

This file was deleted.

This file was deleted.

36 changes: 0 additions & 36 deletions src/test/java/land/leets/global/mail/MailProviderTest.java

This file was deleted.

0 comments on commit b29837c

Please sign in to comment.