Skip to content

Commit 2e3436b

Browse files
authored
Merge pull request #74 from Sysone-Final/feature/SYSONE-73-monitoring
Feature/sysone 73 monitoring
2 parents 490983d + e227acb commit 2e3436b

34 files changed

Lines changed: 3695 additions & 1294 deletions
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
package org.example.finalbe.domains.alert.controller;
2+
3+
import jakarta.validation.Valid;
4+
import lombok.RequiredArgsConstructor;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.example.finalbe.domains.alert.domain.AlertHistory;
7+
import org.example.finalbe.domains.alert.dto.AlertHistoryDto;
8+
import org.example.finalbe.domains.alert.dto.AlertStatisticsDto;
9+
import org.example.finalbe.domains.alert.dto.AcknowledgeMultipleRequest;
10+
import org.example.finalbe.domains.alert.dto.ResolveMultipleRequest;
11+
import org.example.finalbe.domains.alert.repository.AlertHistoryRepository;
12+
import org.example.finalbe.domains.alert.service.AlertNotificationService;
13+
import org.example.finalbe.domains.common.enumdir.AlertLevel;
14+
import org.example.finalbe.domains.common.enumdir.AlertStatus;
15+
import org.example.finalbe.domains.common.enumdir.TargetType;
16+
import org.example.finalbe.domains.common.exception.AlertNotFoundException;
17+
import org.springframework.http.MediaType;
18+
import org.springframework.http.ResponseEntity;
19+
import org.springframework.security.access.prepost.PreAuthorize;
20+
import org.springframework.security.core.Authentication;
21+
import org.springframework.security.core.context.SecurityContextHolder;
22+
import org.springframework.web.bind.annotation.*;
23+
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
24+
25+
import java.util.List;
26+
import java.util.stream.Collectors;
27+
28+
@Slf4j
29+
@RestController
30+
@RequestMapping("/api/alerts")
31+
@RequiredArgsConstructor
32+
@PreAuthorize("isAuthenticated()")
33+
public class AlertController {
34+
35+
private final AlertNotificationService notificationService;
36+
private final AlertHistoryRepository alertHistoryRepository;
37+
38+
// ========== SSE 구독 ==========
39+
40+
/**
41+
* 전체 알림 구독
42+
*/
43+
@GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
44+
public SseEmitter subscribeAll() {
45+
log.info("전체 알림 구독 요청");
46+
return notificationService.subscribeAll();
47+
}
48+
49+
/**
50+
* Equipment 알림 구독
51+
*/
52+
@GetMapping(value = "/subscribe/equipment/{id}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
53+
public SseEmitter subscribeEquipment(@PathVariable Long id) {
54+
log.info("Equipment 알림 구독 요청: equipmentId={}", id);
55+
return notificationService.subscribeEquipment(id);
56+
}
57+
58+
/**
59+
* Rack 알림 구독
60+
*/
61+
@GetMapping(value = "/subscribe/rack/{id}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
62+
public SseEmitter subscribeRack(@PathVariable Long id) {
63+
log.info("Rack 알림 구독 요청: rackId={}", id);
64+
return notificationService.subscribeRack(id);
65+
}
66+
67+
/**
68+
* ServerRoom 알림 구독
69+
*/
70+
@GetMapping(value = "/subscribe/serverroom/{id}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
71+
public SseEmitter subscribeServerRoom(@PathVariable Long id) {
72+
log.info("ServerRoom 알림 구독 요청: serverRoomId={}", id);
73+
return notificationService.subscribeServerRoom(id);
74+
}
75+
76+
/**
77+
* DataCenter 알림 구독
78+
*/
79+
@GetMapping(value = "/subscribe/datacenter/{id}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
80+
public SseEmitter subscribeDataCenter(@PathVariable Long id) {
81+
log.info("DataCenter 알림 구독 요청: dataCenterId={}", id);
82+
return notificationService.subscribeDataCenter(id);
83+
}
84+
85+
// ========== 알림 조회 ==========
86+
87+
/**
88+
* 전체 활성 알림 조회
89+
*/
90+
@GetMapping
91+
public ResponseEntity<List<AlertHistoryDto>> getAllActiveAlerts() {
92+
List<AlertHistory> alerts = alertHistoryRepository
93+
.findByStatusOrderByTriggeredAtDesc(AlertStatus.TRIGGERED);
94+
95+
List<AlertHistoryDto> dtos = alerts.stream()
96+
.map(AlertHistoryDto::from)
97+
.collect(Collectors.toList());
98+
99+
return ResponseEntity.ok(dtos);
100+
}
101+
102+
/**
103+
* Equipment 알림 조회
104+
*/
105+
@GetMapping("/equipment/{id}")
106+
public ResponseEntity<List<AlertHistoryDto>> getEquipmentAlerts(
107+
@PathVariable Long id,
108+
@RequestParam(defaultValue = "TRIGGERED") AlertStatus status) {
109+
110+
List<AlertHistory> alerts = alertHistoryRepository
111+
.findByEquipmentIdAndStatusOrderByTriggeredAtDesc(id, status);
112+
113+
List<AlertHistoryDto> dtos = alerts.stream()
114+
.map(AlertHistoryDto::from)
115+
.collect(Collectors.toList());
116+
117+
return ResponseEntity.ok(dtos);
118+
}
119+
120+
/**
121+
* Rack 알림 조회
122+
*/
123+
@GetMapping("/rack/{id}")
124+
public ResponseEntity<List<AlertHistoryDto>> getRackAlerts(
125+
@PathVariable Long id,
126+
@RequestParam(defaultValue = "TRIGGERED") AlertStatus status) {
127+
128+
List<AlertHistory> alerts = alertHistoryRepository
129+
.findByRackIdAndStatusOrderByTriggeredAtDesc(id, status);
130+
131+
List<AlertHistoryDto> dtos = alerts.stream()
132+
.map(AlertHistoryDto::from)
133+
.collect(Collectors.toList());
134+
135+
return ResponseEntity.ok(dtos);
136+
}
137+
138+
/**
139+
* 알림 상세 조회
140+
*/
141+
@GetMapping("/{id}")
142+
public ResponseEntity<AlertHistoryDto> getAlertDetail(@PathVariable Long id) {
143+
AlertHistory alert = alertHistoryRepository.findById(id)
144+
.orElseThrow(() -> new AlertNotFoundException(id));
145+
146+
return ResponseEntity.ok(AlertHistoryDto.from(alert));
147+
}
148+
/**
149+
* 알림 통계 조회
150+
*/
151+
@GetMapping("/statistics")
152+
public ResponseEntity<AlertStatisticsDto> getStatistics() {
153+
long totalAlerts = alertHistoryRepository.count();
154+
long triggeredAlerts = alertHistoryRepository.countByStatus(AlertStatus.TRIGGERED);
155+
long acknowledgedAlerts = alertHistoryRepository.countByStatus(AlertStatus.ACKNOWLEDGED);
156+
long resolvedAlerts = alertHistoryRepository.countByStatus(AlertStatus.RESOLVED);
157+
158+
long criticalAlerts = alertHistoryRepository.countByLevel(AlertLevel.CRITICAL);
159+
long warningAlerts = alertHistoryRepository.countByLevel(AlertLevel.WARNING);
160+
161+
long equipmentAlerts = alertHistoryRepository.countByTargetType(TargetType.EQUIPMENT);
162+
long rackAlerts = alertHistoryRepository.countByTargetType(TargetType.RACK);
163+
long serverRoomAlerts = alertHistoryRepository.countByTargetType(TargetType.SERVER_ROOM);
164+
long dataCenterAlerts = alertHistoryRepository.countByTargetType(TargetType.DATA_CENTER);
165+
166+
AlertStatisticsDto stats = new AlertStatisticsDto(
167+
totalAlerts,
168+
triggeredAlerts,
169+
acknowledgedAlerts,
170+
resolvedAlerts,
171+
criticalAlerts,
172+
warningAlerts,
173+
equipmentAlerts,
174+
rackAlerts,
175+
serverRoomAlerts,
176+
dataCenterAlerts
177+
);
178+
179+
return ResponseEntity.ok(stats);
180+
}
181+
182+
// ========== 알림 액션 ==========
183+
184+
/**
185+
* 알림 확인
186+
*/
187+
@PostMapping("/{id}/acknowledge")
188+
public ResponseEntity<AlertHistoryDto> acknowledgeAlert(@PathVariable Long id) {
189+
Long userId = extractUserId();
190+
191+
AlertHistory alert = alertHistoryRepository.findById(id)
192+
.orElseThrow(() -> new AlertNotFoundException(id));
193+
194+
alert.acknowledge(userId);
195+
alertHistoryRepository.save(alert);
196+
197+
notificationService.sendAlertAcknowledged(alert);
198+
199+
log.info("알림 확인됨: alertId={}, userId={}", id, userId);
200+
201+
return ResponseEntity.ok(AlertHistoryDto.from(alert));
202+
}
203+
204+
/**
205+
* 알림 해결
206+
*/
207+
@PostMapping("/{id}/resolve")
208+
public ResponseEntity<AlertHistoryDto> resolveAlert(@PathVariable Long id) {
209+
Long userId = extractUserId();
210+
211+
AlertHistory alert = alertHistoryRepository.findById(id)
212+
.orElseThrow(() -> new AlertNotFoundException(id));
213+
214+
alert.resolve(userId);
215+
alertHistoryRepository.save(alert);
216+
217+
notificationService.sendAlertResolved(alert);
218+
219+
log.info("알림 해결됨: alertId={}, userId={}", id, userId);
220+
221+
return ResponseEntity.ok(AlertHistoryDto.from(alert));
222+
}
223+
224+
/**
225+
* 여러 알림 일괄 확인
226+
*/
227+
@PostMapping("/acknowledge-multiple")
228+
public ResponseEntity<List<AlertHistoryDto>> acknowledgeMultipleAlerts(
229+
@Valid @RequestBody AcknowledgeMultipleRequest request) {
230+
231+
Long userId = extractUserId();
232+
233+
List<AlertHistory> alerts = alertHistoryRepository.findAllById(request.alertIds());
234+
235+
if (alerts.isEmpty()) {
236+
throw new AlertNotFoundException("요청한 알림을 찾을 수 없습니다.");
237+
}
238+
239+
alerts.forEach(alert -> {
240+
alert.acknowledge(userId);
241+
notificationService.sendAlertAcknowledged(alert);
242+
});
243+
244+
alertHistoryRepository.saveAll(alerts);
245+
246+
List<AlertHistoryDto> dtos = alerts.stream()
247+
.map(AlertHistoryDto::from)
248+
.collect(Collectors.toList());
249+
250+
log.info("여러 알림 확인됨: count={}, userId={}", alerts.size(), userId);
251+
252+
return ResponseEntity.ok(dtos);
253+
}
254+
255+
/**
256+
* 여러 알림 일괄 해결
257+
*/
258+
@PostMapping("/resolve-multiple")
259+
public ResponseEntity<List<AlertHistoryDto>> resolveMultipleAlerts(
260+
@Valid @RequestBody ResolveMultipleRequest request) {
261+
262+
Long userId = extractUserId();
263+
264+
List<AlertHistory> alerts = alertHistoryRepository.findAllById(request.alertIds());
265+
266+
if (alerts.isEmpty()) {
267+
throw new AlertNotFoundException("요청한 알림을 찾을 수 없습니다.");
268+
}
269+
270+
alerts.forEach(alert -> {
271+
alert.resolve(userId);
272+
notificationService.sendAlertResolved(alert);
273+
});
274+
275+
alertHistoryRepository.saveAll(alerts);
276+
277+
List<AlertHistoryDto> dtos = alerts.stream()
278+
.map(AlertHistoryDto::from)
279+
.collect(Collectors.toList());
280+
281+
log.info("여러 알림 해결됨: count={}, userId={}", alerts.size(), userId);
282+
283+
return ResponseEntity.ok(dtos);
284+
}
285+
286+
// ========== Private Methods ==========
287+
288+
/**
289+
* JWT 토큰에서 userId 추출
290+
*/
291+
private Long extractUserId() {
292+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
293+
294+
if (authentication == null || !authentication.isAuthenticated()) {
295+
throw new IllegalStateException("인증되지 않은 사용자입니다.");
296+
}
297+
298+
String userId = authentication.getName();
299+
300+
try {
301+
return Long.parseLong(userId);
302+
} catch (NumberFormatException e) {
303+
throw new IllegalStateException("유효하지 않은 사용자 ID입니다.", e);
304+
}
305+
}
306+
}

0 commit comments

Comments
 (0)