Skip to content

Commit 019a26c

Browse files
authored
Merge pull request #133 from kookmin-sw/fix/121
[Backend] fix: SSE 통신 에러 해결 및 API 수정
2 parents 55e6e9c + 6468ac2 commit 019a26c

File tree

6 files changed

+47
-24
lines changed

6 files changed

+47
-24
lines changed

backend/src/main/java/com/example/backend/config/SecurityConfig.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import org.springframework.web.cors.CorsConfigurationSource;
1313
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
1414

15-
import java.util.Arrays;
1615
import java.util.List;
1716

1817
@Configuration
@@ -36,9 +35,10 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Excepti
3635
public CorsConfigurationSource corsConfigurationSource() {
3736
CorsConfiguration configuration = new CorsConfiguration();
3837
configuration.setAllowedOrigins(List.of(allowedOrigins)); // 프론트엔드 도메인
39-
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD")); // 허용할 HTTP 메서드
40-
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "Last-Event-ID")); // 허용할 헤더
41-
configuration.setExposedHeaders(Arrays.asList("Authorization", "Content-Type", "Cache-Control", "Last-Event-ID")); // 응답에서 노출할 헤더
38+
39+
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"));
40+
configuration.setAllowedHeaders(List.of("Authorization", "Content-Type", "Last-Event-ID"));
41+
configuration.setExposedHeaders(List.of("Authorization", "Content-Type", "Cache-Control", "Last-Event-ID"));
4242
configuration.setAllowCredentials(true); // 자격 증명 포함 요청 허용
4343

4444
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

backend/src/main/java/com/example/backend/dashboard/controller/CaseDetectController.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import com.example.backend.dashboard.service.SseEmitterService;
77
import jakarta.servlet.http.HttpServletResponse;
88
import lombok.RequiredArgsConstructor;
9-
import org.springframework.beans.factory.annotation.Value;
9+
import org.springframework.http.MediaType;
1010
import org.springframework.http.ResponseEntity;
1111
import org.springframework.web.bind.annotation.*;
1212
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@@ -19,22 +19,19 @@ public class CaseDetectController {
1919
private final CaseDetectService caseDetectService;
2020
private final SseEmitterService sseEmitterService;
2121

22-
@Value("${cors.allowed-origins}")
23-
private String allowedOrigin;
24-
2522
@PostMapping("/detect")
2623
public ResponseEntity<?> detectAlarm(@RequestBody CaseDetectRequest request) {
2724
CaseDetectResponse response = caseDetectService.saveCase(request);
2825
sseEmitterService.broadcast(response);
2926
return ResponseEntity.ok(response);
3027
}
3128

32-
@GetMapping("/subscribe")
29+
@GetMapping(value = "/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
3330
public SseEmitter subscribe(HttpServletResponse response) {
34-
response.setHeader("Access-Control-Allow-Origin", allowedOrigin);
35-
response.setHeader("Access-Control-Allow-Credentials", "true");
31+
3632
response.setHeader("Cache-Control", "no-cache");
37-
response.setHeader("Connection", "keep-alive");
33+
response.setHeader("X-Accel-Buffering", "no"); // Nginx 버퍼링 해제
34+
response.setHeader("Connection", "keep-alive"); // 명시적 Keep-Alive
3835

3936
return sseEmitterService.createEmitter();
4037
}

backend/src/main/java/com/example/backend/dashboard/dto/CaseDetectResponse.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,20 @@
1212
@NoArgsConstructor
1313
@AllArgsConstructor
1414
public class CaseDetectResponse {
15-
private Integer caseId; // 사건 ID
15+
private Integer id; // 사건 ID
1616
private String category; // 사건 카테고리 (예: fire, assault 등)
17-
private String cctvAddress; // CCTV 주소
18-
private String dateTime; // 사건 감지 시각 (문자열 형태, yyyy-MM-dd HH:mm:ss)
17+
private String address; // CCTV 주소
18+
private String date; // 사건 감지 시각 (문자열 형태, yyyy-MM-dd HH:mm:ss)
1919
private String video; // 영상 URL
2020

2121
public static CaseDetectResponse fromEntity(CaseEntity entity) {
2222
String formattedDateTime = entity.getDate()
2323
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
2424
return CaseDetectResponse.builder()
25-
.caseId(entity.getId())
25+
.id(entity.getId())
2626
.category(entity.getCategory().name())
27-
.cctvAddress(entity.getCctv().getAddress())
28-
.dateTime(formattedDateTime)
27+
.address(entity.getCctv().getAddress())
28+
.date(formattedDateTime)
2929
.video(entity.getVideo())
3030
.build();
3131
}

backend/src/main/java/com/example/backend/dashboard/repository/DashboardRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@
66
import java.util.List;
77

88
public interface DashboardRepository extends JpaRepository<CaseEntity, Integer> {
9-
List<CaseEntity> findAllByOfficeIdAndStateInOrderById(int officeId, List<CaseEntity.CaseState> states);
9+
List<CaseEntity> findAllByOfficeIdAndStateInOrderByIdDesc(int officeId, List<CaseEntity.CaseState> states);
1010

1111
}

backend/src/main/java/com/example/backend/dashboard/service/DashboardService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public List<DashboardResponse> getCases(HttpSession session) {
6464
CaseEntity.CaseState.확인,
6565
CaseEntity.CaseState.출동
6666
);
67-
List<CaseEntity> cases = dashboardRepository.findAllByOfficeIdAndStateInOrderById(officeId, targetStates);
67+
List<CaseEntity> cases = dashboardRepository.findAllByOfficeIdAndStateInOrderByIdDesc(officeId, targetStates);
6868

6969
if (cases.isEmpty()) {
7070
throw new NoSuchElementException("미확인, 확인 또는 출동 중인 사건이 없습니다.");

backend/src/main/java/com/example/backend/dashboard/service/SseEmitterService.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
package com.example.backend.dashboard.service;
2+
23
import com.example.backend.dashboard.dto.CaseDetectResponse;
34
import org.springframework.http.MediaType;
45
import org.springframework.stereotype.Service;
@@ -7,7 +8,7 @@
78
import java.io.IOException;
89
import java.util.ArrayList;
910
import java.util.List;
10-
import java.util.concurrent.CopyOnWriteArrayList;
11+
import java.util.concurrent.*;
1112

1213
@Service
1314
public class SseEmitterService {
@@ -18,20 +19,45 @@ public SseEmitter createEmitter() {
1819
SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
1920
emitters.add(emitter);
2021

21-
emitter.onCompletion(() -> emitters.remove(emitter));
22-
emitter.onTimeout(() -> emitters.remove(emitter));
22+
emitter.onCompletion(() -> {
23+
emitters.remove(emitter);
24+
System.out.println("[SseEmitterService] Emitter completed. Current count = " + emitters.size());
25+
});
26+
27+
emitter.onTimeout(() -> {
28+
emitters.remove(emitter);
29+
System.out.println("[SseEmitterService] Emitter timed out. Current count = " + emitters.size());
30+
});
31+
32+
emitter.onError((e) -> {
33+
emitters.remove(emitter);
34+
System.out.println("[SseEmitterService] Emitter error: " + e.getMessage());
35+
});
36+
37+
try {
38+
emitter.send(SseEmitter.event().name("connect").data("SSE connection established!"));
39+
System.out.println("[SseEmitterService] Sent initial 'connect' event.");
40+
} catch (IOException e) {
41+
emitters.remove(emitter);
42+
System.out.println("[SseEmitterService] Failed to send initial event: " + e.getMessage());
43+
}
44+
2345
return emitter;
2446
}
2547

2648
public void broadcast(CaseDetectResponse response) {
2749
List<SseEmitter> deadEmitters = new ArrayList<>();
50+
2851
for (SseEmitter emitter : emitters) {
2952
try {
3053
emitter.send(SseEmitter.event()
3154
.name("alarm-detected")
32-
.data(response, MediaType.APPLICATION_JSON));
55+
.data(response, MediaType.APPLICATION_JSON)
56+
);
57+
System.out.println(" -> [alarm-detected] sent to " + emitter);
3358
} catch (IOException e) {
3459
deadEmitters.add(emitter);
60+
System.out.println(" -> Failed to send to Emitter: " + emitter);
3561
}
3662
}
3763
emitters.removeAll(deadEmitters);

0 commit comments

Comments
 (0)