22
33import DGU_AI_LAB .admin_be .domain .requests .entity .Request ;
44import 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 ;
75import lombok .RequiredArgsConstructor ;
86import lombok .extern .log4j .Log4j2 ;
97import org .springframework .beans .factory .annotation .Value ;
1311import org .springframework .stereotype .Service ;
1412import org .springframework .web .client .RestTemplate ;
1513
16- import java .util .List ;
1714import java .util .Map ;
18- import java .util .stream .Collectors ;
1915
16+ /**
17+ * 사용자(이메일, DM) 및 관리자(Slack 채널)에게 알림을 전송하는 서비스
18+ */
2019@ Service
2120@ RequiredArgsConstructor
2221@ Log4j2
2322public 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