Skip to content

Commit

Permalink
refactor: 이메일 템플릿 하드코딩 개선 및 외부 관리로 전환 완료 (#580)
Browse files Browse the repository at this point in the history
  • Loading branch information
limehee authored Oct 1, 2024
1 parent 71db146 commit a5eaf49
Show file tree
Hide file tree
Showing 9 changed files with 99 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void processApplicationMemberCreated(ApplicationMemberCreationDto dto) {
String finalPassword = manageMemberPasswordUseCase.generateOrRetrievePassword(member.getPassword());
member.updatePassword(finalPassword, passwordEncoder);
registerMemberPort.save(member);
emailService.broadcastEmailToApprovedMember(member, finalPassword);
emailService.sendAccountCreationEmail(member, finalPassword);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public String resendMemberPassword(String memberId) {
member.updatePassword(newPassword, passwordEncoder);
registerMemberPort.save(member);

emailService.broadcastEmailToApprovedMember(member, newPassword);
emailService.sendNewPasswordEmail(member, newPassword);
return member.getId();
}

Expand All @@ -45,7 +45,7 @@ public String requestMemberPasswordReset(MemberResetPasswordRequestDto requestDt
Member member = validateResetPasswordRequest(requestDto);
String code = verificationService.generateVerificationCode();
verificationService.saveVerificationCode(member.getId(), code);
emailService.sendPasswordResetEmail(member, code);
emailService.sendPasswordResetCodeEmail(member, code);
return member.getId();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public String registerMember(MemberRequestDto requestDto) {
member.updatePassword(finalPassword, passwordEncoder);
registerMemberPort.save(member);
createPositionByMember(member);
emailService.broadcastEmailToApprovedMember(member, finalPassword);
emailService.sendAccountCreationEmail(member, finalPassword);
return member.getId();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,9 @@ public void sendBatchEmail(List<EmailTask> emailTasks) throws MessagingException
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);
Expand All @@ -91,7 +93,7 @@ public void sendBatchEmail(List<EmailTask> emailTasks) throws MessagingException
}

private void setImageInTemplate(MimeMessageHelper messageHelper, EmailTemplateType templateType) throws MessagingException {
if (Objects.requireNonNull(templateType) == EmailTemplateType.NORMAL) {
if (Objects.equals(templateType.getTemplateName(), "clabEmail.html")) {
messageHelper.addInline("image-1", new ClassPathResource("images/image-1.png"));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,117 +3,86 @@
import jakarta.mail.MessagingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Value;
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.memberManagement.member.domain.Member;
import page.clab.api.external.memberManagement.member.application.port.ExternalRetrieveMemberUseCase;
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;
import page.clab.api.global.util.FileUtil;
import page.clab.api.global.config.EmailTemplateProperties;

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

@Service
@RequiredArgsConstructor
@Slf4j
public class EmailService {

private final ExternalRetrieveMemberUseCase externalRetrieveMemberUseCase;
private final SpringTemplateEngine springTemplateEngine;
private final EmailAsyncService emailAsyncService;
private final EmailTemplateProperties emailTemplateProperties;
private final SpringTemplateEngine springTemplateEngine;

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

public List<String> broadcastEmail(EmailDto emailDto, List<MultipartFile> multipartFiles) {
List<File> convertedFiles = multipartFiles != null && !multipartFiles.isEmpty()
? convertMultipartFiles(multipartFiles)
: new ArrayList<>();

List<String> successfulAddresses = Collections.synchronizedList(new ArrayList<>());

emailDto.getTo().parallelStream().forEach(address -> {
try {
Member recipient = externalRetrieveMemberUseCase.findByEmail(address);
String emailContent = generateEmailContent(emailDto, recipient.getName());
emailAsyncService.sendEmailAsync(address, emailDto.getSubject(), emailContent, convertedFiles, emailDto.getEmailTemplateType());
successfulAddresses.add(address);
} catch (MessagingException e) {
throw new MessageSendingFailedException(address + "에게 이메일을 보내는데 실패했습니다.");
}
});
return successfulAddresses;
}
public void sendAccountCreationEmail(Member member, String password) {
EmailTemplateProperties.Template template = emailTemplateProperties.getTemplate(EmailTemplateType.ACCOUNT_CREATION);

public void broadcastEmailToApprovedMember(Member member, String password) {
String subject = "C-Lab 계정 발급 안내";
String content = """
정식으로 C-Lab의 일원이 된 것을 축하드립니다.
C-Lab과 함께하는 동안 불타는 열정으로 모든 원하는 목표를 이루어 내시기를 바라고,
훗날, 당신의 합류가 C-Lab에겐 최고의 행운이었다고 기억되기를 희망합니다.
로그인을 위해 아래의 계정 정보를 확인해주세요.
ID: %s
Password: %s
로그인 후 비밀번호를 변경해주세요.
""".formatted(member.getId(), password);
EmailDto emailDto = EmailDto.create(List.of(member.getEmail()), subject, content, EmailTemplateType.NORMAL);
try {
String emailContent = generateEmailContent(emailDto, member.getName());
emailAsyncService.sendEmailAsync(member.getEmail(), emailDto.getSubject(), emailContent, null, emailDto.getEmailTemplateType());
} catch (MessagingException e) {
throw new MessageSendingFailedException(member.getEmail() + " 계정 발급 안내 메일 전송에 실패했습니다.");
}
}
String subject = template.getSubject();
String content = template.getContent()
.replace("{{id}}", member.getId())
.replace("{{password}}", password);

public void sendPasswordResetEmail(Member member, String code) {
String subject = "C-Lab 비밀번호 재발급 인증 안내";
String content = """
C-Lab 비밀번호 재발급 인증 안내 메일입니다.
인증번호는 %s입니다.
해당 인증번호를 비밀번호 재설정 페이지에 입력해주세요.
재설정시 비밀번호는 인증번호로 대체됩니다.
""".formatted(code);
EmailDto emailDto = EmailDto.create(List.of(member.getEmail()), subject, content, EmailTemplateType.NORMAL);
try {
broadcastEmail(emailDto, null);
} catch (Exception e) {
throw new MessageSendingFailedException(member.getEmail() + " 비밀번호 재발급 인증 메일 전송에 실패했습니다.");
}
EmailDto emailDto = EmailDto.create(
List.of(member.getEmail()),
subject,
content,
EmailTemplateType.ACCOUNT_CREATION
);

sendEmail(emailDto, member, " 계정 발급 안내 메일 전송에 실패했습니다.");
}

private List<File> convertMultipartFiles(List<MultipartFile> multipartFiles) {
List<File> convertedFiles = new ArrayList<>();
for (MultipartFile multipartFile : multipartFiles) {
File file = convertMultipartFileToFile(multipartFile);
convertedFiles.add(file);
}
return convertedFiles;
public void sendPasswordResetCodeEmail(Member member, String code) {
EmailTemplateProperties.Template template = emailTemplateProperties.getTemplate(EmailTemplateType.PASSWORD_RESET_CODE);

String subject = template.getSubject();
String content = template.getContent()
.replace("{{code}}", code);

EmailDto emailDto = EmailDto.create(
List.of(member.getEmail()),
subject,
content,
EmailTemplateType.PASSWORD_RESET_CODE
);

sendEmail(emailDto, member, " 비밀번호 재발급 인증 메일 전송에 실패했습니다.");
}

private File convertMultipartFileToFile(MultipartFile multipartFile) {
String originalFilename = multipartFile.getOriginalFilename();
String extension = FilenameUtils.getExtension(originalFilename);
String path = filePath + File.separator + "temp" + File.separator + FileUtil.makeFileName(extension);
path = path.replace("/", File.separator).replace("\\", File.separator);
File file = new File(path);
FileUtil.ensureParentDirectoryExists(file, filePath);
public void sendNewPasswordEmail(Member member, String newPassword) {
EmailTemplateProperties.Template template = emailTemplateProperties.getTemplate(EmailTemplateType.NEW_PASSWORD);

String subject = template.getSubject();
String content = template.getContent()
.replace("{{id}}", member.getId())
.replace("{{password}}", newPassword);

EmailDto emailDto = EmailDto.create(
List.of(member.getEmail()),
subject,
content,
EmailTemplateType.NEW_PASSWORD
);

sendEmail(emailDto, member, " 비밀번호 재설정 안내 메일 전송에 실패했습니다.");
}

private void sendEmail(EmailDto emailDto, Member member, String message) {
try {
multipartFile.transferTo(file);
} catch (IOException e) {
throw new RuntimeException("Failed to convert MultipartFile to File", e);
String emailContent = generateEmailContent(emailDto, member.getName());
emailAsyncService.sendEmailAsync(member.getEmail(), emailDto.getSubject(), emailContent, null, emailDto.getEmailTemplateType());
} catch (MessagingException e) {
throw new MessageSendingFailedException(member.getEmail() + message);
}
return file;
}

private String generateEmailContent(EmailDto emailDto, String name) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
package page.clab.api.global.common.email.domain;

import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum EmailTemplateType {

NORMAL("NORMAL", "기본", "clabEmail.html");
ACCOUNT_CREATION("account-creation", "계정 생성", "clabEmail.html"),
PASSWORD_RESET_CODE("password-reset-code", "비밀번호 재설정", "clabEmail.html"),
NEW_PASSWORD("new-password", "새 비밀번호", "clabEmail.html");

@Enumerated(EnumType.STRING)
private final String key;
private final String description;
private final String templateName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class EmailDto {
private String content;

@NotNull(message = "{notNull.email.templateType}")
@Schema(description = "이메일 템플릿", example = "NORMAL")
@Schema(description = "이메일 템플릿", example = "ACCOUNT_CREATION")
private EmailTemplateType emailTemplateType;

public static EmailDto create(List<String> to, String subject, String content, EmailTemplateType emailTemplateType) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package page.clab.api.global.config;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import page.clab.api.global.common.email.domain.EmailTemplateType;

import java.util.Map;

@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "email")
public class EmailTemplateProperties {

private Map<String, Template> templates;

public Template getTemplate(EmailTemplateType templateType) {
Template template = templates.get(templateType.getKey());
if (template == null) {
throw new IllegalArgumentException("템플릿을 찾을 수 없습니다: " + templateType);
}
return template;
}

@Getter
@Setter
public static class Template {
private String subject;
private String content;
}
}
2 changes: 1 addition & 1 deletion src/main/resources/templates/clabEmail.html
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ <h1 class="v-font-size" style="margin: 0px; color: #2a2c32; line-height: 140%; t
<td class="v-container-padding-padding" style="overflow-wrap:break-word;word-break:break-word;padding:10px;font-family:'Montserrat',sans-serif;" align="center">

<div class="v-font-size" style="font-size: 14px; color: #908f8f; line-height: 200%; text-align: center; word-wrap: break-word;">
<p style="line-height: 200%;">C-Lab  |  경기대학교 컴퓨터공학부 개발보안동아리</p>
<p style="line-height: 200%;">C-Lab  |  경기대학교 AI컴퓨터공학부 개발동아리</p>
</div>

</td>
Expand Down

0 comments on commit a5eaf49

Please sign in to comment.