-
Notifications
You must be signed in to change notification settings - Fork 0
[FEATURE] 메일 발송 로직 및 템플릿 이관 #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| package ditda.notification.mail; | ||
|
|
||
| import java.io.UnsupportedEncodingException; | ||
| import java.util.Map; | ||
|
|
||
| import org.springframework.boot.mail.autoconfigure.MailProperties; | ||
| import org.springframework.core.io.ClassPathResource; | ||
| import org.springframework.mail.javamail.JavaMailSender; | ||
| import org.springframework.mail.javamail.MimeMessageHelper; | ||
| import org.springframework.stereotype.Component; | ||
| import org.thymeleaf.context.Context; | ||
| import org.thymeleaf.spring6.SpringTemplateEngine; | ||
|
|
||
| import jakarta.mail.MessagingException; | ||
| import jakarta.mail.internet.InternetAddress; | ||
| import jakarta.mail.internet.MimeMessage; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.extern.slf4j.Slf4j; | ||
|
|
||
| @Slf4j | ||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class EmailSender { | ||
|
|
||
| private static final String LOGO_CID = "logoImage"; | ||
| private static final String LOGO_PATH = "email-images/logo.png"; | ||
|
|
||
| private static final String FROM_NAME = "DITDA"; | ||
|
|
||
| private final JavaMailSender mailSender; | ||
| private final SpringTemplateEngine templateEngine; | ||
| private final MailProperties mailProperties; | ||
|
|
||
| public void send(String to, NotificationType type, Map<String, Object> variables) | ||
| throws MessagingException { | ||
|
|
||
| MimeMessage mimeMessage = mailSender.createMimeMessage(); | ||
| MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8"); | ||
|
|
||
| try { | ||
| helper.setFrom(new InternetAddress(mailProperties.getUsername(), FROM_NAME, "UTF-8")); | ||
| } catch (UnsupportedEncodingException e) { | ||
| throw new IllegalStateException("UTF-8 is not supported", e); | ||
| } | ||
|
|
||
| helper.setTo(to); | ||
| helper.setSubject(type.getSubject()); | ||
| helper.setText(renderTemplate(type.getTemplate(), variables), true); | ||
|
|
||
| helper.addInline(LOGO_CID, new ClassPathResource(LOGO_PATH)); | ||
|
|
||
| mailSender.send(mimeMessage); | ||
|
|
||
| log.info("[Email] Email sent. to={}, type={}", MailMasker.mask(to), type); | ||
| } | ||
|
|
||
| private String renderTemplate(String templateName, Map<String, Object> variables) { | ||
| Context context = new Context(); | ||
| if (variables != null) { | ||
| variables.forEach(context::setVariable); | ||
| } | ||
|
|
||
| return templateEngine.process(templateName, context); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package ditda.notification.mail; | ||
|
|
||
| import org.springframework.util.StringUtils; | ||
|
|
||
| import lombok.AccessLevel; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||
| public final class MailMasker { | ||
|
|
||
| public static String mask(String email) { | ||
| if (!StringUtils.hasText(email) || !email.contains("@")) { | ||
| return "***"; | ||
| } | ||
|
|
||
| int at = email.indexOf("@"); | ||
| String local = email.substring(0, at); | ||
| String domain = email.substring(at); | ||
|
|
||
| if (local.length() <= 2) { | ||
| return "****" + domain; | ||
| } | ||
|
|
||
| return local.substring(0, 2) + "****" + domain; | ||
| } | ||
| } |
81 changes: 81 additions & 0 deletions
81
src/main/java/ditda/notification/mail/NotificationType.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| package ditda.notification.mail; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public enum NotificationType { | ||
|
|
||
| // 지원 마감 (ApplicationDeadlineClosed) | ||
| APPLICATION_REFUND_REQUEST_ADMIN( | ||
| "[DITDA] 외주 마감에 따른 환불 처리 요망", "email/admin-refund-request"), | ||
| APPLICATION_CANCELLED_INSTRUCTOR( | ||
| "[DITDA] 신청하신 외주가 지원자 부족으로 취소되었습니다.", "email/commission-cancelled"), | ||
| APPLICATION_SHORTFALL_INSTRUCTOR( | ||
| "[DITDA] 신청하신 외주의 모집 인원이 미달되었습니다.", "email/commission-shortfall-instructor"), | ||
| APPLICATION_MATCHED_INSTRUCTOR( | ||
| "[DITDA] 신청하신 외주의 디자이너 매칭이 완료되었습니다.", "email/commission-matched-instructor"), | ||
| APPLICATION_MATCHED_DESIGNER( | ||
| "[DITDA] 지원하신 외주의 1차 시안 대상자로 선정되었습니다.", "email/commission-matched-designer"), | ||
|
|
||
| // 1차 시안 마감 (FirstDraftDeadlineClosed) | ||
| FIRST_DRAFT_REFUND_REQUEST_ADMIN( | ||
| "[DITDA] 1차 시안 미제출에 따른 환불 처리 요망", "email/admin-refund-request"), | ||
| FIRST_DRAFT_ZERO_INSTRUCTOR( | ||
| "[DITDA] 1차 시안 제출이 없어 외주가 취소되었습니다.", "email/first-draft-zero-instructor"), | ||
| FIRST_DRAFT_SHORTFALL_INSTRUCTOR( | ||
| "[DITDA] 일부 디자이너의 1차 시안이 미제출되었습니다.", "email/first-draft-shortfall-instructor"), | ||
| FIRST_DRAFT_MISSED_DESIGNER( | ||
| "[DITDA] 1차 시안 기한 초과로 외주 진행이 종료되었습니다.", "email/first-draft-missed-designer"), | ||
|
|
||
| // 최종 마감 (FinalDeadlineClosed) | ||
| FINAL_CANCELLED_INSTRUCTOR( | ||
| "[DITDA] 시안 선택 기한 초과로 외주가 취소되었습니다.", "email/final-cancelled-instructor"), | ||
| FINAL_CANCELLED_DESIGNER( | ||
| "[DITDA] 강사 미선택으로 외주가 취소되었습니다.", "email/final-cancelled-designer"), | ||
|
|
||
| // 전원 1차 시안 제출 (AllFirstDraftsSubmitted) | ||
| ALL_FIRST_DRAFTS_SUBMITTED_INSTRUCTOR( | ||
| "[DITDA] 모든 1차 시안이 제출되었습니다. 시안을 선택해 주세요.", "email/first-draft-all-submitted-instructor"), | ||
|
|
||
| // 시안 선택 (DraftSelected) | ||
| DRAFT_SELECTED_DESIGNER( | ||
| "[DITDA] 제출하신 1차 시안이 최종 선택되었습니다.", "email/first-draft-selected-designer"), | ||
| DRAFT_REJECTED_DESIGNER( | ||
| "[DITDA] 1차 시안 선정 결과를 안내해 드립니다.", "email/first-draft-rejected-designer"), | ||
|
|
||
| // 외주 최종 확정 (CommissionCompleted) | ||
| COMMISSION_FINALIZED_INSTRUCTOR( | ||
| "[DITDA] 신청하신 외주가 최종 확정되었습니다.", "email/commission-finalized-instructor"), | ||
| COMMISSION_FINALIZED_DESIGNER( | ||
| "[DITDA] 작업하신 외주가 최종 확정되었습니다.", "email/commission-finalized-designer"), | ||
|
|
||
| // 수정 요청/제출 (RevisionRequested / RevisionSubmitted) | ||
| REVISION_REQUESTED_DESIGNER( | ||
| "[DITDA] 시안 수정 요청이 도착했습니다. 확인해주세요.", "email/revision-requested-designer"), | ||
| REVISION_SUBMITTED_INSTRUCTOR( | ||
| "[DITDA] 수정본이 제출되었습니다. 확인해주세요.", "email/revision-submitted-instructor"), | ||
|
|
||
| // 정산 요청 (PayoutRequested) | ||
| PAYOUT_REQUEST_COMPLETED_ADMIN( | ||
| "[DITDA] 외주 최종 확정 - 디자이너 정산 요청", "email/admin-payout-request"), | ||
| PAYOUT_REQUEST_CANCELLED_ADMIN( | ||
| "[DITDA] 외주 취소 - 제출 디자이너 정산 요청", "email/admin-payout-request"), | ||
| PAYOUT_REQUEST_REJECTED_ADMIN( | ||
| "[DITDA] 시안 선택 완료 - 미선택 디자이너 정산 요청", "email/admin-payout-request"), | ||
|
|
||
| // auth | ||
| EMAIL_VERIFICATION( | ||
| "[DITDA] 이메일 인증 코드", "email/verification-code"), | ||
| DESIGNER_SIGNUP_REVIEW_ADMIN( | ||
| "[DITDA] 새 디자이너 가입 검토 요청", "email/designer-signup-notification"), | ||
|
|
||
| // payment | ||
| DEPOSIT_CONFIRM_REQUEST_ADMIN( | ||
| "[DITDA] 외주 입금 확인 요청", "email/deposit-notification"); | ||
|
|
||
| private final String subject; | ||
| private final String template; | ||
| } | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 100 additions & 0 deletions
100
src/main/resources/templates/email/admin-payout-request.html
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| <!DOCTYPE html> | ||
| <html xmlns:th="http://www.thymeleaf.org"> | ||
| <head> | ||
| <meta charset="UTF-8"> | ||
| </head> | ||
| <body> | ||
| <div style="font-family: 'Apple SD Gothic Neo', sans-serif; max-width: 560px; margin: 0 auto; padding: 24px;"> | ||
|
|
||
| <div style="text-align: center; margin-bottom: 24px;"> | ||
| <img alt="ditda" src="cid:logoImage" style="height: 48px;"> | ||
| </div> | ||
|
|
||
| <div style="text-align: center;"> | ||
| <h2 style="color: #874fff;">디자이너 정산 요청</h2> | ||
| <p style="color: #666; line-height: 1.6;"> | ||
| <span th:switch="${reason}"> | ||
| <span th:case="'FINAL_COMPLETED_AUTO'">외주가 <strong>자동 최종 확정</strong>되어 디자이너 정산이 필요합니다.</span> | ||
| <span th:case="'FINAL_COMPLETED_MANUAL'">외주가 <strong>강사 수동 확정</strong>되어 디자이너 정산이 필요합니다.</span> | ||
| <span th:case="'FINAL_CANCELLED_BY_DEADLINE'">외주가 <strong>강사 미선택으로 취소</strong>되어 제출 디자이너에 대한 기본금 정산이 필요합니다.</span> | ||
| <span th:case="'DRAFT_SELECTION_REJECTED'">강사가 시안을 선택하여 <strong>미선택 디자이너에 대한 기본금 정산</strong>이 필요합니다.</span> | ||
| </span><br> | ||
| <strong>어드민 Swagger에서 디자이너 계좌 정보를 조회 후 송금</strong>해 주세요. | ||
| </p> | ||
| </div> | ||
|
|
||
| <!-- 정산 안내 --> | ||
| <div style="text-align: center;"> | ||
| <p style="color: #666; line-height: 1.6;"> | ||
| <strong>📌 정산 안내</strong><br> | ||
| 계좌 정보는 보안상 메일에 포함되지 않습니다.<br> | ||
| 어드민 Swagger에서 디자이너 ID로 계좌 정보를 조회하세요. | ||
| </p> | ||
| </div> | ||
|
|
||
| <!-- 외주 정보 --> | ||
| <div style="margin-top: 24px; padding: 16px 20px; background: #f5f5f7; border-radius: 8px;"> | ||
| <p style="margin: 0 0 12px 0; font-weight: 700; color: #222;">📋 외주 정보</p> | ||
| <div style="margin: 6px 0;"> | ||
| <span style="display: inline-block; width: 110px; color: #888; font-size: 14px;">외주 ID</span> | ||
| <span style="font-weight: 600;" th:text="${commissionId}"></span> | ||
| </div> | ||
| <div style="margin: 6px 0;"> | ||
| <span style="display: inline-block; width: 110px; color: #888; font-size: 14px;">외주명</span> | ||
| <span style="font-weight: 600;" th:text="${commissionTitle}"></span> | ||
| </div> | ||
| <div style="margin: 6px 0;"> | ||
| <span style="display: inline-block; width: 110px; color: #888; font-size: 14px;">강사</span> | ||
| <span style="font-weight: 600;" | ||
| th:text="${instructorName} + ' (' + ${instructorEmail} + ')'"></span> | ||
| </div> | ||
| </div> | ||
|
|
||
| <!-- 정산 대상 --> | ||
| <div style="margin-top: 16px; padding: 16px 20px; background: #fdf4ff; border-radius: 8px; border-left: 4px solid #874fff;"> | ||
| <p style="margin: 0 0 12px 0; font-weight: 700; color: #222;"> | ||
| 💰 정산 대상 | ||
| <span style="color: #888; font-size: 14px; font-weight: 400;" | ||
| th:text="'(' + ${payoutCount} + '명)'"></span> | ||
| </p> | ||
|
|
||
| <div th:each="payout, iter : ${payouts}" | ||
| th:style="${iter.index > 0} ? 'margin-top: 12px; padding-top: 12px; border-top: 1px dashed #ddd;' : ''"> | ||
| <div style="margin: 4px 0;"> | ||
| <span style="display: inline-block; width: 110px; color: #888; font-size: 14px;">디자이너 ID</span> | ||
| <span style="font-weight: 600;" th:text="${payout.designerId}"></span> | ||
| </div> | ||
| <div style="margin: 4px 0;"> | ||
| <span style="display: inline-block; width: 110px; color: #888; font-size: 14px;">디자이너</span> | ||
| <span style="font-weight: 600;" | ||
| th:text="${payout.designerName} + ' (' + ${payout.level} + ')'"></span> | ||
| </div> | ||
| <div style="margin: 4px 0;"> | ||
| <span style="display: inline-block; width: 110px; color: #888; font-size: 14px;">정산 금액</span> | ||
| <span style="font-weight: 700; color: #874fff;" | ||
| th:text="${#numbers.formatInteger(payout.amount, 1, 'COMMA')} + '원'"></span> | ||
| </div> | ||
|
fervovita marked this conversation as resolved.
|
||
| </div> | ||
|
|
||
| <div style="margin-top: 16px; padding-top: 12px; border-top: 2px solid #874fff;"> | ||
| <div style="margin: 4px 0;"> | ||
| <span style="display: inline-block; width: 110px; color: #222; font-size: 14px; font-weight: 700;">총 정산 금액</span> | ||
| <span style="font-weight: 800; color: #874fff; font-size: 18px;" | ||
| th:text="${#numbers.formatInteger(totalAmount, 1, 'COMMA')} + '원'"></span> | ||
| </div> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div style="margin-top: 24px; text-align: center;"> | ||
| <a href="https://api.ditda.kr/swagger-ui/index.html" | ||
| style="display: inline-block; padding: 12px 24px; background: #874fff; color: #fff; text-decoration: none; border-radius: 8px; font-weight: bold; font-size: 14px;"> | ||
| 어드민 Swagger 바로가기 | ||
| </a> | ||
| </div> | ||
|
|
||
| <p style="color: #888; margin-top: 24px; font-size: 13px; text-align: center;"> | ||
| 본 메일은 시스템에 의해 자동 발송된 관리자 전용 알림입니다. | ||
| </p> | ||
| </div> | ||
| </body> | ||
| </html> | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.