Skip to content

Commit

Permalink
이메일 전송 로직 개선 완료 (#369)
Browse files Browse the repository at this point in the history
* refactor: EmailTask 클래스 domain 패키지로 위치 이동 #368

* refactor: Async 관련 메소드 다른 컴포넌트로 분리 #368
  • Loading branch information
SongJaeHoonn authored Jun 9, 2024
1 parent 913e310 commit 2bdcd64
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 116 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package page.clab.api.global.common.email.application;

import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeUtility;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import page.clab.api.global.common.email.domain.EmailTask;
import page.clab.api.global.common.email.domain.EmailTemplateType;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import static page.clab.api.global.common.email.application.EmailService.emailQueue;

@Service
@Slf4j
@RequiredArgsConstructor
public class EmailAsyncService {

private final JavaMailSender javaMailSender;

@Value("${spring.mail.username}")
private String sender;

private static final int MAX_BATCH_SIZE = 10;

@Async
public void sendEmailAsync(String to, String subject, String content, List<File> files, EmailTemplateType emailTemplateType) throws MessagingException {
log.debug("Sending email to: {}", to);
emailQueue.add(new EmailTask(to, subject, content, files, emailTemplateType));
}

@Async
public void processEmailQueue() {
try {
List<EmailTask> batch = new ArrayList<>();
EmailTask task;
while ((task = emailQueue.poll()) != null) {
batch.add(task);
if (batch.size() >= MAX_BATCH_SIZE) {
sendBatchEmail(batch);
batch.clear();
}
}
if (!batch.isEmpty()) {
sendBatchEmail(batch);
}
} catch (Exception e) {
log.debug("Error processing email queue: {}", e.getMessage(), e);
}
}

public void sendBatchEmail(List<EmailTask> emailTasks) throws MessagingException, IOException {
MimeMessage[] mimeMessages = new MimeMessage[emailTasks.size()];
for (int i = 0; i < emailTasks.size(); i++) {
EmailTask task = emailTasks.get(i);
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(message, true, "UTF-8");
messageHelper.setFrom(sender);
messageHelper.setTo(task.getTo());
messageHelper.setSubject(task.getSubject());
messageHelper.setText(task.getContent(), true);
setImageInTemplate(messageHelper, task.getTemplateType());
if (task.getFiles() != null) {
for (File file : task.getFiles()) {
messageHelper.addAttachment(MimeUtility.encodeText(file.getName(), "UTF-8", "B"), file);
}
}
mimeMessages[i] = message;
}

try {
javaMailSender.send(mimeMessages);
} catch (Exception e) {
log.error("Error sending batch email: " + e.getMessage(), e);
}
log.debug("Batch email sent successfully.");
}

private void setImageInTemplate(MimeMessageHelper messageHelper, EmailTemplateType templateType) throws MessagingException {
switch(templateType) {
case NORMAL -> {
messageHelper.addInline("image-1", new ClassPathResource("images/image-1.png"));
break;
}
}
}

}
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
package page.clab.api.global.common.email.application;

import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeUtility;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.thymeleaf.context.Context;
import org.thymeleaf.spring6.SpringTemplateEngine;
import page.clab.api.domain.member.application.MemberService;
import page.clab.api.domain.member.domain.Member;
import page.clab.api.domain.member.dto.response.MemberResponseDto;
import page.clab.api.global.common.email.domain.EmailTask;
import page.clab.api.global.common.email.domain.EmailTemplateType;
import page.clab.api.global.common.email.dto.request.EmailDto;
import page.clab.api.global.common.email.exception.MessageSendingFailedException;
Expand All @@ -37,21 +31,16 @@
@Slf4j
public class EmailService {

private final JavaMailSender javaMailSender;

private final MemberService memberService;

private final SpringTemplateEngine springTemplateEngine;

@Value("${spring.mail.username}")
private String sender;
private final EmailAsyncService emailAsyncService;

@Value("${resource.file.path}")
private String filePath;

private static final int MAX_BATCH_SIZE = 10;

private static final BlockingQueue<EmailTask> emailQueue = new LinkedBlockingQueue<>();
protected static final BlockingQueue<EmailTask> emailQueue = new LinkedBlockingQueue<>();

public List<String> broadcastEmail(EmailDto emailDto, List<MultipartFile> multipartFiles) {
List<File> convertedFiles = multipartFiles != null && !multipartFiles.isEmpty()
Expand All @@ -64,12 +53,13 @@ public List<String> broadcastEmail(EmailDto emailDto, List<MultipartFile> multip
try {
Member recipient = memberService.getMemberByEmail(address);
String emailContent = generateEmailContent(emailDto, recipient.getName());
sendEmailAsync(address, emailDto.getSubject(), emailContent, convertedFiles, emailDto.getEmailTemplateType());
emailAsyncService.sendEmailAsync(address, emailDto.getSubject(), emailContent, convertedFiles, emailDto.getEmailTemplateType());
successfulAddresses.add(address);
} catch (MessagingException e) {
throw new MessageSendingFailedException(address + "에게 이메일을 보내는데 실패했습니다.");
}
});
emailAsyncService.processEmailQueue();
return successfulAddresses;
}

Expand All @@ -84,12 +74,13 @@ public List<String> broadcastEmailToAllMember(EmailDto emailDto, List<MultipartF
memberList.parallelStream().forEach(member -> {
try {
String emailContent = generateEmailContent(emailDto, member.getName());
sendEmailAsync(member.getEmail(), emailDto.getSubject(), emailContent, convertedFiles, emailDto.getEmailTemplateType());
emailAsyncService.sendEmailAsync(member.getEmail(), emailDto.getSubject(), emailContent, convertedFiles, emailDto.getEmailTemplateType());
successfulEmails.add(member.getEmail());
} catch (MessagingException e) {
throw new MessageSendingFailedException(member.getEmail() + "에게 이메일을 보내는데 실패했습니다.");
}
});
emailAsyncService.processEmailQueue();
return successfulEmails;
}

Expand All @@ -109,10 +100,11 @@ public void broadcastEmailToApprovedMember(Member member, String password) {
EmailDto emailDto = EmailDto.create(List.of(member.getEmail()), subject, content, EmailTemplateType.NORMAL);
try {
String emailContent = generateEmailContent(emailDto, member.getName());
sendEmailAsync(member.getEmail(), emailDto.getSubject(), emailContent, null, emailDto.getEmailTemplateType());
emailAsyncService.sendEmailAsync(member.getEmail(), emailDto.getSubject(), emailContent, null, emailDto.getEmailTemplateType());
} catch (MessagingException e) {
throw new MessageSendingFailedException(member.getEmail() + " 계정 발급 안내 메일 전송에 실패했습니다.");
}
emailAsyncService.processEmailQueue();
}

public void sendPasswordResetEmail(Member member, String code) {
Expand Down Expand Up @@ -167,105 +159,6 @@ private String generateEmailContent(EmailDto emailDto, String name) {
return springTemplateEngine.process(emailTemplate, context);
}

@Async
public void sendEmailAsync(String to, String subject, String content, List<File> files, EmailTemplateType emailTemplateType) throws MessagingException {
log.debug("Sending email to: {}", to);
emailQueue.add(new EmailTask(to, subject, content, files, emailTemplateType));
}

@Async
@Scheduled(fixedRate = 1000)
public void processEmailQueue() {
try {
List<EmailTask> batch = new ArrayList<>();
EmailTask task;
while ((task = emailQueue.poll()) != null) {
batch.add(task);
if (batch.size() >= MAX_BATCH_SIZE) {
sendBatchEmail(batch);
batch.clear();
}
}
if (!batch.isEmpty()) {
sendBatchEmail(batch);
}
} catch (Exception e) {
log.debug("Error processing email queue: {}", e.getMessage(), e);
}
}

public void sendBatchEmail(List<EmailTask> emailTasks) throws MessagingException, IOException {
MimeMessage[] mimeMessages = new MimeMessage[emailTasks.size()];
for (int i = 0; i < emailTasks.size(); i++) {
EmailTask task = emailTasks.get(i);
MimeMessage message = javaMailSender.createMimeMessage();
MimeMessageHelper messageHelper = new MimeMessageHelper(message, true, "UTF-8");
messageHelper.setFrom(sender);
messageHelper.setTo(task.getTo());
messageHelper.setSubject(task.getSubject());
messageHelper.setText(task.getContent(), true);
setImageInTemplate(messageHelper, task.getTemplateType());
if (task.getFiles() != null) {
for (File file : task.getFiles()) {
messageHelper.addAttachment(MimeUtility.encodeText(file.getName(), "UTF-8", "B"), file);
}
}
mimeMessages[i] = message;
}

try {
javaMailSender.send(mimeMessages);
} catch (Exception e) {
log.error("Error sending batch email: " + e.getMessage(), e);
}
log.debug("Batch email sent successfully.");
}

protected static class EmailTask {
private final String to;
private final String subject;
private final String content;
private final List<File> files;
private final EmailTemplateType templateType;

public EmailTask(String to, String subject, String content, List<File> files, EmailTemplateType templateType) {
this.to = to;
this.subject = subject;
this.content = content;
this.files = files;
this.templateType = templateType;
}

public String getTo() {
return to;
}

public String getSubject() {
return subject;
}

public String getContent() {
return content;
}

public List<File> getFiles() {
return files;
}

public EmailTemplateType getTemplateType() {
return templateType;
}
}

private void setImageInTemplate(MimeMessageHelper messageHelper, EmailTemplateType templateType) throws MessagingException {
switch(templateType) {
case NORMAL -> {
messageHelper.addInline("image-1", new ClassPathResource("images/image-1.png"));
break;
}
}
}

private void checkDir(File file) {
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package page.clab.api.global.common.email.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.io.File;
import java.util.List;

@Getter
@AllArgsConstructor
public class EmailTask {

private final String to;

private final String subject;

private final String content;

private final List<File> files;

private final EmailTemplateType templateType;
}

0 comments on commit 2bdcd64

Please sign in to comment.