Skip to content

Commit e57f54a

Browse files
authored
Merge pull request #163 from CSID-DGU/develop
[deploy] develop -> main
2 parents c3a1d48 + 9237a78 commit e57f54a

14 files changed

Lines changed: 802 additions & 159 deletions

File tree

Dockerfile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
FROM openjdk:17-jdk-slim
1+
FROM eclipse-temurin:17-jre-jammy
22

33
WORKDIR /app
44

55
COPY build/libs/admin_be-0.0.1-SNAPSHOT.jar app.jar
66

77
EXPOSE 8080
88

9+
ENV TZ=Asia/Seoul
10+
911
ENTRYPOINT ["java", "-jar", "app.jar"]

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ dependencies {
3232
implementation 'org.springframework.boot:spring-boot-starter-validation'
3333
testImplementation 'io.projectreactor:reactor-test'
3434
implementation 'org.springframework.boot:spring-boot-starter-webflux'
35+
testRuntimeOnly 'com.h2database:h2'
3536

3637
//spring security
3738
implementation 'org.springframework.boot:spring-boot-starter-security'

src/main/java/DGU_AI_LAB/admin_be/domain/alarm/service/AlarmService.java

Lines changed: 74 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import DGU_AI_LAB.admin_be.domain.requests.entity.Request;
44
import DGU_AI_LAB.admin_be.domain.users.entity.User;
5-
import DGU_AI_LAB.admin_be.error.ErrorCode;
6-
import DGU_AI_LAB.admin_be.error.exception.BusinessException;
75
import lombok.RequiredArgsConstructor;
86
import lombok.extern.log4j.Log4j2;
97
import org.springframework.beans.factory.annotation.Value;
@@ -13,36 +11,35 @@
1311
import org.springframework.stereotype.Service;
1412
import org.springframework.web.client.RestTemplate;
1513

16-
import java.util.List;
1714
import java.util.Map;
18-
import java.util.stream.Collectors;
1915

16+
/**
17+
* 사용자(이메일, DM) 및 관리자(Slack 채널)에게 알림을 전송하는 서비스
18+
*/
2019
@Service
2120
@RequiredArgsConstructor
2221
@Log4j2
2322
public class AlarmService {
2423

24+
// --- Slack Webhook (관리자 채널) ---
2525
@Value("${slack-webhook-url.monitoring}")
2626
private String defaultWebhookUrl;
2727
@Value("${slack-webhook-url.farm-admin}")
2828
private String farmAdminWebhookUrl;
2929
@Value("${slack-webhook-url.lab-admin}")
3030
private String labAdminWebhookUrl;
3131

32-
33-
@Value("${slack.bot-token}")
34-
private String botToken;
35-
private final RestTemplate restTemplate = new RestTemplate();
36-
32+
// --- 외부 서비스 의존성 ---
3733
private final JavaMailSender mailSender;
34+
private final SlackApiService slackApiService;
35+
private final RestTemplate restTemplate = new RestTemplate();
3836

3937
@Value("${spring.mail.username}")
4038
private String from;
4139

42-
public void sendSlackAlert(String message) {
43-
sendSlackAlert(message, null);
44-
}
45-
40+
/**
41+
* Slack Webhook을 사용하여 특정 채널에 메시지를 전송합니다.
42+
*/
4643
public void sendSlackAlert(String message, String webhookUrl) {
4744
HttpHeaders headers = new HttpHeaders();
4845
headers.setContentType(MediaType.APPLICATION_JSON);
@@ -52,144 +49,80 @@ public void sendSlackAlert(String message, String webhookUrl) {
5249

5350
String urlToUse = (webhookUrl != null && !webhookUrl.isEmpty()) ? webhookUrl : defaultWebhookUrl;
5451

55-
ResponseEntity<String> response = restTemplate.postForEntity(urlToUse, request, String.class);
56-
57-
if (!response.getStatusCode().is2xxSuccessful()) {
58-
log.debug("Slack 알림 전송 실패: {}", response.getStatusCode());
59-
} else {
60-
log.debug("Slack 알림 전송 성공");
52+
try {
53+
ResponseEntity<String> response = restTemplate.postForEntity(urlToUse, request, String.class);
54+
if (!response.getStatusCode().is2xxSuccessful()) {
55+
log.warn("Slack 알림 전송 실패: {}", response.getStatusCode());
56+
} else {
57+
log.debug("Slack 알림 전송 성공");
58+
}
59+
} catch (Exception e) {
60+
log.error("Slack 알림 전송 중 예외 발생: (URL: {})", urlToUse, e);
6161
}
6262
}
63-
public void sendMailAlert(String to, String subject, String body) {
64-
SimpleMailMessage message = new SimpleMailMessage();
65-
message.setFrom(from);
66-
message.setTo(to);
67-
message.setSubject(subject);
68-
message.setText(body);
69-
mailSender.send(message);
70-
System.out.printf("메일 전송 성공: 수신자=%s, 제목=%s%n", to, subject);
71-
}
72-
73-
// slack dm 전송
74-
public void sendDMAlert(String username, String email, String message) {
75-
String userId = getSlackUser(username, email, botToken);
76-
if (userId == null) {
77-
throw new BusinessException(ErrorCode.SLACK_USER_NOT_FOUND);
78-
}
79-
80-
String channelId = openDMChannel(userId, botToken);
81-
if (channelId == null) {
82-
throw new BusinessException(ErrorCode.SLACK_DM_CHANNEL_FAILED);
83-
}
8463

64+
/**
65+
* 사용자에게 이메일을 전송합니다.
66+
*/
67+
public void sendMailAlert(String to, String subject, String body) {
8568
try {
86-
sendMessageToSlackChannel(channelId, message, botToken);
69+
SimpleMailMessage message = new SimpleMailMessage();
70+
message.setFrom(from);
71+
message.setTo(to);
72+
message.setSubject(subject);
73+
message.setText(body);
74+
mailSender.send(message);
75+
log.info("메일 전송 성공: 수신자={}, 제목={}", to, subject);
8776
} catch (Exception e) {
88-
throw new BusinessException(ErrorCode.SLACK_SEND_FAILED);
77+
log.error("메일 전송 실패: 수신자={}", to, e);
8978
}
9079
}
9180

92-
// 이름이 일치하는 사용자에게 dm 전송
93-
// 이름이 같은 사용자가 있는 경우 email이 일치하는 사용자에게 dm 전송
94-
private String getSlackUser(String username, String email, String token) {
95-
String url = "https://slack.com/api/users.list";
96-
HttpHeaders headers = new HttpHeaders();
97-
headers.setBearerAuth(token);
98-
HttpEntity<Void> request = new HttpEntity<>(headers);
99-
100-
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, request, Map.class);
101-
if (!Boolean.TRUE.equals(response.getBody().get("ok"))) {
102-
throw new BusinessException(ErrorCode.SLACK_USER_NOT_FOUND);
103-
}
104-
105-
List<Map<String, Object>> members = (List<Map<String, Object>>) response.getBody().get("members");
106-
107-
// 이름이 일치하는 사용자 목록 필터링
108-
List<Map<String, Object>> matchedUsers = members.stream()
109-
.filter(user -> {
110-
Map<String, Object> profile = (Map<String, Object>) user.get("profile");
111-
String displayName = (String) profile.get("display_name");
112-
String realName = (String) profile.get("real_name");
113-
String name = (String) user.get("name");
114-
115-
return username.equals(name) || username.equals(displayName) || username.equals(realName);
116-
})
117-
.collect(Collectors.toList());
118-
119-
if (matchedUsers.isEmpty()) {
120-
throw new BusinessException(ErrorCode.SLACK_USER_NOT_FOUND);
121-
}
122-
123-
if (matchedUsers.size() == 1) {
124-
return (String) matchedUsers.get(0).get("id");
125-
}
126-
127-
Map<String, Object> selectedUser = matchedUsers.stream()
128-
.filter(user -> {
129-
Map<String, Object> profile = (Map<String, Object>) user.get("profile");
130-
String userEmail = (String) profile.get("email");
131-
return userEmail != null && userEmail.equalsIgnoreCase(email);
132-
})
133-
.findFirst()
134-
.orElseThrow(() -> new BusinessException(ErrorCode.SLACK_USER_EMAIL_NOT_MATCH));
135-
136-
return (String) selectedUser.get("id");
81+
/**
82+
* 사용자에게 Slack DM을 전송합니다.
83+
*/
84+
public void sendDMAlert(String username, String email, String message) {
85+
slackApiService.sendDM(username, email, message);
13786
}
13887

139-
private String openDMChannel(String userId, String token) {
140-
String url = "https://slack.com/api/conversations.open";
141-
HttpHeaders headers = new HttpHeaders();
142-
headers.setBearerAuth(token);
143-
headers.setContentType(MediaType.APPLICATION_JSON);
144-
145-
Map<String, Object> body = Map.of("users", userId);
146-
HttpEntity<Map<String, Object>> request = new HttpEntity<>(body, headers);
147-
148-
ResponseEntity<Map> response = restTemplate.postForEntity(url, request, Map.class);
149-
if (Boolean.TRUE.equals(response.getBody().get("ok"))) {
150-
Map channel = (Map) response.getBody().get("channel");
151-
return (String) channel.get("id");
88+
/**
89+
* 사용자에게 DM과 메일을 모두 전송합니다. (주로 사용자 대상 알림)
90+
*/
91+
public void sendAllAlerts(String username, String email, String subject, String message) {
92+
try {
93+
sendMailAlert(email, subject, message);
94+
} catch (Exception e) {
95+
log.error("sendAllAlerts 중 메일 전송 실패: {}", email, e);
15296
}
153-
return null;
154-
}
155-
156-
private void sendMessageToSlackChannel(String channelId, String message, String token) {
157-
String url = "https://slack.com/api/chat.postMessage";
158-
HttpHeaders headers = new HttpHeaders();
159-
headers.setBearerAuth(token);
160-
headers.setContentType(MediaType.APPLICATION_JSON);
16197

162-
Map<String, Object> body = Map.of(
163-
"channel", channelId,
164-
"text", message
165-
);
166-
HttpEntity<Map<String, Object>> request = new HttpEntity<>(body, headers);
167-
168-
ResponseEntity<Map> response = restTemplate.postForEntity(url, request, Map.class);
169-
if (!Boolean.TRUE.equals(response.getBody().get("ok"))) {
170-
throw new BusinessException(ErrorCode.SLACK_SEND_FAILED);
98+
try {
99+
sendDMAlert(username, email, message);
100+
} catch (Exception e) {
101+
log.error("sendAllAlerts 중 DM 전송 실패: {}", username, e);
171102
}
172103
}
173104

174-
public void sendAllAlerts(String username, String email, String subject, String message) {
175-
sendDMAlert(username, email, message);
176-
sendMailAlert(email, subject, message);
177-
}
178-
179-
public void sendNewRequestNotification(Request request) {
180-
String serverName = request.getResourceGroup().getServerName();
181-
String targetWebhookUrl;
182-
183-
// serverName에 따라 사용할 다른 채널로 전송
105+
/**
106+
* serverName에 따라 적절한 Webhook URL을 반환합니다.
107+
*/
108+
private String getAdminWebhookUrl(String serverName) {
184109
if ("FARM".equalsIgnoreCase(serverName)) {
185-
targetWebhookUrl = farmAdminWebhookUrl;
110+
return farmAdminWebhookUrl;
186111
} else if ("LAB".equalsIgnoreCase(serverName)) {
187-
targetWebhookUrl = labAdminWebhookUrl;
112+
return labAdminWebhookUrl;
188113
} else {
189-
// FARM이나 LAB이 아닌 잘못된 입력값이 있을 경우, 기본 모니터링 채널로 전송
114+
// FARM이나 LAB이 아닌 잘못된 입력값이 있을 경우, 기본 모니토링 채널로 전송
190115
log.warn("알 수 없는 serverName '{}'에 대한 요청 알림입니다. 기본 채널로 전송합니다.", serverName);
191-
targetWebhookUrl = defaultWebhookUrl;
116+
return defaultWebhookUrl;
192117
}
118+
}
119+
120+
/**
121+
* 관리자 채널(FARM/LAB)로 신규 신청 알림을 보냅니다.
122+
*/
123+
public void sendNewRequestNotification(Request request) {
124+
String serverName = request.getResourceGroup().getServerName();
125+
String targetWebhookUrl = getAdminWebhookUrl(serverName); // 중복 로직 제거
193126

194127
// 슬랙 메시지 내용을 생성합니다.
195128
String message = String.format(
@@ -215,8 +148,7 @@ public void sendNewRequestNotification(Request request) {
215148
}
216149

217150
/**
218-
* 사용자에게 서버 사용 신청 승인 알림을 보냅니다.
219-
* @param request 승인된 Request 엔티티
151+
* 사용자에게 서버 사용 신청 승인 알림을 보냅니다. (DM + Email)
220152
*/
221153
public void sendApprovalNotification(Request request) {
222154
User user = request.getUser();
@@ -248,4 +180,13 @@ public void sendApprovalNotification(Request request) {
248180
sendAllAlerts(user.getName(), user.getEmail(), subject, message);
249181
}
250182

183+
/**
184+
* 서버 이름에 따라 적절한 관리자 채널로 메시지를 보냅니다.
185+
* @param serverName "FARM", "LAB" 등
186+
* @param message 보낼 메시지
187+
*/
188+
public void sendAdminSlackNotification(String serverName, String message) {
189+
String targetWebhookUrl = getAdminWebhookUrl(serverName);
190+
sendSlackAlert(message, targetWebhookUrl);
191+
}
251192
}

0 commit comments

Comments
 (0)