Skip to content

Commit d47ee2e

Browse files
committed
Feat: 건의 추가 시 메일 알림 추가
1 parent 5ef3fdc commit d47ee2e

6 files changed

Lines changed: 158 additions & 3 deletions

File tree

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ dependencies {
105105

106106
// prometheus
107107
implementation 'io.micrometer:micrometer-registry-prometheus'
108+
109+
// email
110+
implementation 'org.springframework.boot:spring-boot-starter-mail'
111+
108112
}
109113
dependencyManagement {
110114
imports {

src/main/java/devkor/com/teamcback/TeamCBackApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import org.springframework.boot.SpringApplication;
66
import org.springframework.boot.autoconfigure.SpringBootApplication;
77
import org.springframework.cloud.openfeign.EnableFeignClients;
8+
import org.springframework.scheduling.annotation.EnableAsync;
89
import org.springframework.scheduling.annotation.EnableScheduling;
910

1011
@OpenAPIDefinition(servers = {@Server(url = "/")})
1112
@EnableScheduling
1213
@EnableFeignClients
14+
@EnableAsync
1315
@SpringBootApplication
1416
public class TeamCBackApplication {
1517

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
package devkor.com.teamcback.domain.common;
2+
3+
import devkor.com.teamcback.domain.suggestion.entity.SuggestionImage;
4+
import jakarta.mail.MessagingException;
5+
import jakarta.mail.internet.MimeMessage;
6+
import org.springframework.beans.factory.annotation.Value;
7+
import org.springframework.mail.SimpleMailMessage;
8+
import org.springframework.mail.javamail.JavaMailSender;
9+
import org.springframework.mail.javamail.MimeMessageHelper;
10+
import org.springframework.scheduling.annotation.Async;
11+
import org.springframework.stereotype.Service;
12+
13+
import java.util.HashMap;
14+
import java.util.List;
15+
import java.util.Map;
16+
17+
@Service
18+
public class EmailService {
19+
20+
private static final String SUGGESTION_TITLE = "[고대로] 건의사항 추가됨 - 제목: ";
21+
private static final String SENDER = "고대로팀 <leeyejin113@gmail.com>";
22+
23+
@Value("${staff.emails}")
24+
private String notifyEmails;
25+
26+
@Value("${metrics.environment}")
27+
private String env;
28+
29+
private final JavaMailSender mailSender;
30+
31+
public EmailService(JavaMailSender mailSender) {
32+
this.mailSender = mailSender;
33+
}
34+
35+
/**
36+
* 건의사항 이메일 전송
37+
*/
38+
@Async
39+
public void sendNotificationMessage(String title, String writer, String type, String content, List<SuggestionImage> images) throws MessagingException {
40+
41+
// 내용 설정
42+
Map<String, String> contents = new HashMap<>();
43+
contents.put("건의제목", title);
44+
contents.put("건의종류", type);
45+
contents.put("건의내용", content);
46+
47+
// 전송
48+
sendHtmlEmail(SUGGESTION_TITLE + title, "건의", writer, contents, images);
49+
}
50+
51+
/**
52+
* 일반 이메일 전송
53+
*/
54+
private void sendSimpleEmail(String title, String type, String writer, Map<String, String> contents, List<SuggestionImage> images) {
55+
56+
for(String notifyEmail : notifyEmails.split(",")) {
57+
SimpleMailMessage message = new SimpleMailMessage();
58+
message.setFrom(SENDER); // 보내는 사람
59+
message.setTo(notifyEmail); // 받는 사람
60+
message.setSubject(title); // 제목
61+
62+
// 내용
63+
String content = writer +"(으)로부터" + type + "가 추가되었습니다!";
64+
for(String key : contents.keySet()) {
65+
content += "\n - " + key + ": " + contents.get(key);
66+
}
67+
68+
// 첨부파일
69+
if(images != null) {
70+
content += "\n - 첨부 파일:";
71+
for (int i = 0; i < images.size(); i++) {
72+
content += "\n[" + (i+1) + "] link: " + images.get(i).getImageUrl();
73+
}
74+
}
75+
76+
content += "\n* 서버 환경: " + env;
77+
78+
message.setText(content);
79+
80+
mailSender.send(message);
81+
}
82+
83+
}
84+
85+
/**
86+
* HTML 이메일 전송
87+
*/
88+
private void sendHtmlEmail(String title, String type, String writer, Map<String, String> contents, List<SuggestionImage> images) throws MessagingException {
89+
90+
for(String notifyEmail : notifyEmails.split(",")) {
91+
92+
MimeMessage message = mailSender.createMimeMessage();
93+
94+
// true: multipart 메시지 (첨부파일 가능), "UTF-8": 문자 인코딩
95+
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
96+
97+
helper.setFrom(SENDER);
98+
helper.setTo(notifyEmail);
99+
helper.setSubject(title);
100+
101+
// 내용
102+
String html = "<h2 style='color:green;'>" + writer +"(으)로부터" + type + "가 추가되었습니다!</h2>";
103+
for(String key : contents.keySet()) {
104+
html += "<p><strong>" + key + "</strong><br>" + contents.get(key) + "</p>";
105+
}
106+
107+
// 첨부파일
108+
if(images != null) {
109+
html += "<p><strong>첨부 파일:</strong><br>";
110+
for (int i = 0; i < images.size(); i++) {
111+
html += "<a href='" + images.get(i).getImageUrl() + "'>[" + (i+1) + "]</a> ";
112+
}
113+
html += "</p>";
114+
}
115+
116+
html += "<p>* 서버 환경: " + env + "</p>";
117+
118+
helper.setText(html, true); // HTML 모드
119+
120+
mailSender.send(message);
121+
}
122+
}
123+
}

src/main/java/devkor/com/teamcback/domain/suggestion/controller/SuggestionController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.swagger.v3.oas.annotations.media.Schema;
1212
import io.swagger.v3.oas.annotations.responses.ApiResponse;
1313
import io.swagger.v3.oas.annotations.responses.ApiResponses;
14+
import jakarta.mail.MessagingException;
1415
import lombok.RequiredArgsConstructor;
1516
import org.springframework.http.MediaType;
1617
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@@ -44,7 +45,7 @@ public CommonResponse<CreateSuggestionRes> createSuggestion(
4445
@Parameter(description = "건의 제목, 분류, 내용, 이메일", required = true)
4546
@RequestPart(value = "req") CreateSuggestionReq req,
4647
@Parameter(description = "건의 사진") @RequestPart(value = "images", required = false) List<MultipartFile> images
47-
) {
48+
) throws MessagingException {
4849
Long userId = userDetail == null ? null : userDetail.getUser().getUserId();
4950
return CommonResponse.success(suggestionService.createSuggestion(userId, req, images));
5051
}

src/main/java/devkor/com/teamcback/domain/suggestion/service/SuggestionService.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import static devkor.com.teamcback.global.response.ResultCode.NOT_FOUND_SUGGESTION;
44
import static devkor.com.teamcback.global.response.ResultCode.NOT_FOUND_USER;
55

6+
import devkor.com.teamcback.domain.common.EmailService;
67
import devkor.com.teamcback.domain.suggestion.dto.request.CreateSuggestionReq;
78
import devkor.com.teamcback.domain.suggestion.dto.response.CreateSuggestionRes;
89
import devkor.com.teamcback.domain.suggestion.dto.response.GetSuggestionRes;
@@ -17,12 +18,14 @@
1718
import devkor.com.teamcback.global.exception.exception.GlobalException;
1819
import devkor.com.teamcback.infra.s3.FilePath;
1920
import devkor.com.teamcback.infra.s3.S3Util;
21+
import jakarta.mail.MessagingException;
2022
import lombok.RequiredArgsConstructor;
2123
import org.springframework.data.domain.Page;
2224
import org.springframework.data.domain.PageRequest;
2325
import org.springframework.data.domain.Pageable;
2426
import org.springframework.data.domain.Sort;
2527
import org.springframework.data.domain.Sort.Direction;
28+
import org.springframework.mail.MailSender;
2629
import org.springframework.stereotype.Service;
2730
import org.springframework.transaction.annotation.Transactional;
2831
import org.springframework.web.multipart.MultipartFile;
@@ -36,13 +39,14 @@ public class SuggestionService {
3639
private final SuggestionRepository suggestionRepository;
3740
private final UserRepository userRepository;
3841
private final S3Util s3Util;
42+
private final EmailService emailService;
3943

4044
/**
4145
* 건의 생성
4246
*/
4347
@Transactional
4448
@UpdateScore(addScore = 3)
45-
public CreateSuggestionRes createSuggestion(Long userId, CreateSuggestionReq req, List<MultipartFile> images) {
49+
public CreateSuggestionRes createSuggestion(Long userId, CreateSuggestionReq req, List<MultipartFile> images) throws MessagingException {
4650
User user = null;
4751
if(userId != null) user = findUser(userId);
4852
Suggestion suggestion = new Suggestion(req, user);
@@ -59,6 +63,13 @@ public CreateSuggestionRes createSuggestion(Long userId, CreateSuggestionReq req
5963

6064
Suggestion savedSuggestion = suggestionRepository.save(suggestion);
6165

66+
// 이메일 전송
67+
emailService.sendNotificationMessage(savedSuggestion.getTitle(),
68+
user == null ? "익명" : user.getUsername(),
69+
savedSuggestion.getSuggestionType().getType(),
70+
savedSuggestion.getContent(),
71+
savedSuggestion.getImages());
72+
6273
return new CreateSuggestionRes(savedSuggestion);
6374
}
6475

src/main/resources/application.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@ spring:
3030
port: 6379
3131
password: ${REDIS_PASSWORD}
3232

33+
mail:
34+
host: smtp.gmail.com
35+
port: 587
36+
username: leeyejin113@gmail.com
37+
password: ${EMAIL_PASSWORD}
38+
protocol: smtp
39+
tls: true
40+
properties:
41+
mail.smtp.auth: true
42+
mail.smtp.starttls.enable: true
43+
3344

3445
logging:
3546
level:
@@ -114,4 +125,7 @@ management:
114125
include: prometheus, health, info
115126
metrics:
116127
tags:
117-
application: kodaero
128+
application: kodaero
129+
130+
staff:
131+
emails: leeyejin113@gmail.com

0 commit comments

Comments
 (0)