Skip to content

Commit 5832c81

Browse files
authored
Merge pull request #113 from kookmin-sw/fix/110
[Backend] fix: 지도용 API 기간 수정
2 parents 1ac28b1 + a5e06ec commit 5832c81

File tree

8 files changed

+62
-36
lines changed

8 files changed

+62
-36
lines changed

backend/src/main/java/com/example/backend/analysis/controller/CaseStatsController.java

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import org.springframework.http.ResponseEntity;
88
import org.springframework.web.bind.annotation.*;
99

10-
import java.util.Collections;
1110
import java.util.List;
1211
import java.util.Map;
1312

@@ -23,10 +22,6 @@ public class CaseStatsController {
2322
public ResponseEntity<?> getOverview(HttpSession session) {
2423
// 결과가 없으면 404, 있으면 200
2524
CaseStatsOverviewResponse response = caseStatsService.getOverview(session);
26-
if (response == null) {
27-
return ResponseEntity.status(404)
28-
.body(Collections.singletonMap("message", "개요 정보가 없습니다."));
29-
}
3025
return ResponseEntity.ok(response);
3126
}
3227

@@ -36,10 +31,6 @@ public ResponseEntity<?> getHourlyCaseStats(@RequestParam("date") String date,
3631
@RequestParam(value = "category", required = false) String category,
3732
HttpSession session) {
3833
List<HourlyCaseStatsResponse> stats = caseStatsService.getHourlyCaseStats(date, category, session);
39-
if (stats.isEmpty()) {
40-
return ResponseEntity.status(404)
41-
.body(Collections.singletonMap("message", "시간대별 사건 정보가 없습니다."));
42-
}
4334
return ResponseEntity.ok(stats);
4435
}
4536

@@ -76,8 +67,8 @@ public ResponseEntity<?> getLocationCaseStats(@RequestParam("period") String per
7667

7768
// 지도용 장소별 사건 수 조회
7869
@GetMapping("/map")
79-
public ResponseEntity<?> getMapCaseStats(@RequestParam("period") String period, HttpSession session) {
80-
List<MapCaseStatsResponse> stats = caseStatsService.getMapCaseStats(period, session);
70+
public ResponseEntity<?> getMapCaseStats(HttpSession session) {
71+
List<MapCaseStatsResponse> stats = caseStatsService.getMapCaseStats(session);
8172
return ResponseEntity.ok(stats);
8273
}
8374

backend/src/main/java/com/example/backend/analysis/service/CaseStatsService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public CaseStatsOverviewResponse getOverview(HttpSession session) {
5656

5757
// 최근 한 달간 데이터를 기준으로 사건 건수가 가장 많은 CCTV 주소 조회
5858
String patrolRegionAddress = statsOverviewRepository.findAddressWithMostIncidentsLastMonth(officeId)
59-
.orElse("정보 없음");
59+
.orElse("해당 지역에 순찰 강화 필요 구역이 없습니다.");
6060

6161
return CaseStatsOverviewResponse.fromEntity(stats, patrolRegionAddress);
6262
}
@@ -188,10 +188,10 @@ public List<LocationCaseStatsResponse> getLocationCaseStats(String period, HttpS
188188
}
189189

190190
// 지도용 장소별 사건 수 조회 (startDate를 계산해 전달)
191-
public List<MapCaseStatsResponse> getMapCaseStats(String period, HttpSession session) {
191+
public List<MapCaseStatsResponse> getMapCaseStats(HttpSession session) {
192192
int officeId = getOfficeId(session);
193193

194-
LocalDateTime startDate = getStartDateFromPeriod(period);
194+
LocalDateTime startDate = getStartDateFromPeriod("monthly");
195195
List<Object[]> results = statsCategoryRepository.findMapCaseStats(startDate);
196196

197197
if (results.isEmpty()) {

backend/src/main/java/com/example/backend/common/GlobalExceptionHandler.java

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
11
package com.example.backend.common;
22

3-
import org.springframework.web.bind.annotation.ControllerAdvice;
4-
53
import jakarta.persistence.EntityNotFoundException;
4+
import jakarta.servlet.http.HttpServletResponse;
5+
import lombok.extern.slf4j.Slf4j;
66
import org.springframework.http.HttpStatus;
7+
import org.springframework.http.MediaType;
78
import org.springframework.http.ResponseEntity;
9+
import org.springframework.web.bind.annotation.ControllerAdvice;
810
import org.springframework.web.bind.annotation.ExceptionHandler;
911

12+
import java.io.IOException;
1013
import java.util.Collections;
1114
import java.util.NoSuchElementException;
1215

16+
@Slf4j
1317
@ControllerAdvice
1418
public class GlobalExceptionHandler {
1519

20+
// SSE 연결 종료 (브라우저에서 탭 닫거나, 연결 끊긴 경우)
21+
@ExceptionHandler(IOException.class)
22+
public void handleIOException(IOException e) {
23+
log.warn("SSE 연결 종료: {}", e.getMessage());
24+
}
25+
1626
@ExceptionHandler(IllegalStateException.class)
1727
public ResponseEntity<?> handleIllegalStateException(IllegalStateException e) {
18-
if (e.getMessage().contains("로그인이 필요합니다.")) {
28+
log.warn("IllegalStateException: {}", e.getMessage());
29+
if (e.getMessage().contains("로그인이 필요")) {
1930
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
2031
.body(Collections.singletonMap("message", e.getMessage()));
2132
}
@@ -36,8 +47,19 @@ public ResponseEntity<?> handleEntityNotFoundException(EntityNotFoundException e
3647
}
3748

3849
@ExceptionHandler(Exception.class)
39-
public ResponseEntity<?> handleGenericException(Exception e) {
40-
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
41-
.body(Collections.singletonMap("message", e.getMessage()));
50+
public void handleGenericException(Exception e, HttpServletResponse response) throws IOException {
51+
log.error("Unexpected error: ", e);
52+
if ("text/event-stream".equals(response.getContentType())) {
53+
// SSE 통신 도중 에러 → event-stream 포맷으로 전달
54+
response.getWriter().write("event: error\n");
55+
response.getWriter().write("data: " + e.getMessage() + "\n\n");
56+
response.getWriter().flush();
57+
} else {
58+
// 일반 요청 → JSON 에러 응답
59+
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
60+
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
61+
response.getWriter().write("{\"message\": \"" + e.getMessage() + "\"}");
62+
response.getWriter().flush();
63+
}
4264
}
4365
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ public void addCorsMappings(CorsRegistry corsRegistry) {
1616
corsRegistry.addMapping("/**")
1717
.allowedOrigins(allowedOrigins)
1818
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD")
19-
.allowedHeaders("Authorization", "Content-Type")
20-
.exposedHeaders("Authorization")
19+
.allowedHeaders("Authorization", "Content-Type", "Last-Event-ID")
20+
.exposedHeaders("Authorization", "Content-Type", "Cache-Control", "Last-Event-ID")
2121
.allowCredentials(true);
2222
}
2323
}

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
package com.example.backend.config;
22

3-
import java.util.Arrays;
4-
import java.util.List;
5-
63
import lombok.RequiredArgsConstructor;
74
import org.springframework.beans.factory.annotation.Value;
85
import org.springframework.context.annotation.Bean;
@@ -15,6 +12,9 @@
1512
import org.springframework.web.cors.CorsConfigurationSource;
1613
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
1714

15+
import java.util.Arrays;
16+
import java.util.List;
17+
1818
@Configuration
1919
@EnableWebSecurity
2020
@RequiredArgsConstructor
@@ -28,22 +28,21 @@ public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Excepti
2828
return httpSecurity
2929
.csrf(AbstractHttpConfigurer::disable)
3030
.httpBasic(AbstractHttpConfigurer::disable)
31-
// CORS 설정을 수동으로 추가
3231
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
3332
.build();
3433
}
3534

36-
// CORS 설정을 위한 Bean 정의
3735
@Bean
3836
public CorsConfigurationSource corsConfigurationSource() {
3937
CorsConfiguration configuration = new CorsConfiguration();
4038
configuration.setAllowedOrigins(List.of(allowedOrigins)); // 프론트엔드 도메인
4139
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD")); // 허용할 HTTP 메서드
42-
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type")); // 허용할 헤더
43-
configuration.setExposedHeaders(List.of("Authorization")); // 응답에서 노출할 헤더
40+
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "Last-Event-ID")); // 허용할 헤더
41+
configuration.setExposedHeaders(Arrays.asList("Authorization", "Content-Type", "Cache-Control", "Last-Event-ID")); // 응답에서 노출할 헤더
4442
configuration.setAllowCredentials(true); // 자격 증명 포함 요청 허용
43+
4544
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
4645
source.registerCorsConfiguration("/**", configuration); // 모든 경로에 대해 CORS 설정 적용
4746
return source;
4847
}
49-
}
48+
}

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

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import com.example.backend.dashboard.dto.CaseDetectResponse;
55
import com.example.backend.dashboard.service.CaseDetectService;
66
import com.example.backend.dashboard.service.SseEmitterService;
7+
import jakarta.servlet.http.HttpServletResponse;
78
import lombok.RequiredArgsConstructor;
9+
import org.springframework.beans.factory.annotation.Value;
810
import org.springframework.http.ResponseEntity;
911
import org.springframework.web.bind.annotation.*;
1012
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@@ -17,6 +19,9 @@ public class CaseDetectController {
1719
private final CaseDetectService caseDetectService;
1820
private final SseEmitterService sseEmitterService;
1921

22+
@Value("${cors.allowed-origins}")
23+
private String allowedOrigin;
24+
2025
@PostMapping("/detect")
2126
public ResponseEntity<?> detectAlarm(@RequestBody CaseDetectRequest request) {
2227
CaseDetectResponse response = caseDetectService.saveCase(request);
@@ -25,7 +30,12 @@ public ResponseEntity<?> detectAlarm(@RequestBody CaseDetectRequest request) {
2530
}
2631

2732
@GetMapping("/subscribe")
28-
public SseEmitter subscribe() {
33+
public SseEmitter subscribe(HttpServletResponse response) {
34+
response.setHeader("Access-Control-Allow-Origin", allowedOrigin);
35+
response.setHeader("Access-Control-Allow-Credentials", "true");
36+
response.setHeader("Cache-Control", "no-cache");
37+
response.setHeader("Connection", "keep-alive");
38+
2939
return sseEmitterService.createEmitter();
3040
}
3141

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import org.springframework.http.ResponseEntity;
88
import org.springframework.web.bind.annotation.*;
99

10-
import java.util.Collections;
1110
import java.util.List;
1211
import java.util.Map;
1312

@@ -22,10 +21,6 @@ public class DashboardController {
2221
@GetMapping("")
2322
public ResponseEntity<?> getCases(HttpSession session) {
2423
List<DashboardResponse> cases = dashboardService.getCases(session);
25-
if (cases.isEmpty()) {
26-
return ResponseEntity.status(404)
27-
.body(Collections.singletonMap("message", "사건이 없습니다."));
28-
}
2924
return ResponseEntity.ok(cases);
3025
}
3126

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ public List<DashboardResponse> getCases(HttpSession session) {
6666
);
6767
List<CaseEntity> cases = dashboardRepository.findAllByOfficeIdAndStateInOrderById(officeId, targetStates);
6868

69+
if (cases.isEmpty()) {
70+
throw new NoSuchElementException("미확인, 확인 또는 출동 중인 사건이 없습니다.");
71+
}
72+
6973
return cases.stream()
7074
.map(DashboardResponse::fromEntity)
7175
.collect(Collectors.toList());
@@ -80,6 +84,11 @@ public Map<String, String> getCaseVideo(int id, HttpSession session) {
8084
throw new EntityNotFoundException("해당 사건에 대한 영상이 없습니다.");
8185
}
8286

87+
if (caseEntity.getState() == CaseEntity.CaseState.미확인) {
88+
caseEntity.setState(CaseEntity.CaseState.확인);
89+
dashboardRepository.save(caseEntity);
90+
}
91+
8392
return Collections.singletonMap("video", videoUrl);
8493
}
8594

0 commit comments

Comments
 (0)