Skip to content

Commit 00a9f7f

Browse files
authored
Merge pull request #166 from kookmin-sw/feat/165
[Backend] feat: 카테고리 추가 및 API 수정
2 parents 3a238c1 + dd7bb39 commit 00a9f7f

15 files changed

+87
-94
lines changed

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

Lines changed: 18 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -22,33 +22,17 @@ public class CaseStatsController {
2222
// 개요 조회
2323
@GetMapping("/overview")
2424
public ResponseEntity<?> getOverview(HttpSession session) {
25-
try {
26-
CaseStatsOverviewResponse response = caseStatsService.getOverview(session);
27-
return ResponseEntity.ok(response);
28-
} catch (IllegalStateException e) {
29-
return ResponseEntity.status(401).body(Collections.singletonMap("message", e.getMessage()));
30-
} catch (NoSuchElementException e) {
31-
return ResponseEntity.status(404).body(Collections.singletonMap("message", e.getMessage()));
32-
} catch (Exception e) {
33-
return ResponseEntity.status(500).body(Collections.singletonMap("message", "내부 서버 오류 발생"));
34-
}
25+
CaseStatsOverviewResponse response = caseStatsService.getOverview(session);
26+
return ResponseEntity.ok(response);
3527
}
3628

3729
// 시간대별 사건 수 조회 (0시~23시 모두 반환)
3830
@GetMapping("/hour")
3931
public ResponseEntity<?> getHourlyCaseStats(@RequestParam("date") String date,
4032
@RequestParam(value = "category", required = false) String category,
4133
HttpSession session) {
42-
try {
43-
List<HourlyCaseStatsResponse> stats = caseStatsService.getHourlyCaseStats(date, category, session);
44-
return ResponseEntity.ok(stats);
45-
} catch (IllegalStateException e) {
46-
return ResponseEntity.status(401).body(Collections.singletonMap("message", e.getMessage()));
47-
} catch (NoSuchElementException e) {
48-
return ResponseEntity.status(404).body(Collections.singletonMap("message", e.getMessage()));
49-
} catch (Exception e) {
50-
return ResponseEntity.status(500).body(Collections.singletonMap("message", "내부 서버 오류가 발생했습니다."));
51-
}
34+
List<HourlyCaseStatsResponse> stats = caseStatsService.getHourlyCaseStats(date, category, session);
35+
return ResponseEntity.ok(stats);
5236
}
5337

5438
// 월별/일별 사건 수 조회 (month 파라미터 존재 여부에 따라 분기; 전체 범위를 0으로 채워 반환)
@@ -57,37 +41,19 @@ public ResponseEntity<?> getCaseStats(@RequestParam("year") int year,
5741
@RequestParam(value = "month", required = false) Integer month,
5842
@RequestParam(value = "category", required = false) String category,
5943
HttpSession session) {
60-
try {
61-
if (month == null) {
62-
List<MonthlyCaseStatsResponse> monthlyStats = caseStatsService.getMonthlyCaseStats(year, category, session);
63-
return ResponseEntity.ok(monthlyStats);
64-
}
65-
List<DailyCaseStatsResponse> dailyStats = caseStatsService.getDailyCaseStats(year, month, category, session);
66-
return ResponseEntity.ok(dailyStats);
67-
} catch (IllegalStateException e) {
68-
return ResponseEntity.status(401).body(Collections.singletonMap("message", e.getMessage()));
69-
} catch (NoSuchElementException e) {
70-
return ResponseEntity.status(404).body(Collections.singletonMap("message", e.getMessage()));
71-
} catch (Exception e) {
72-
return ResponseEntity.status(500).body(Collections.singletonMap("message", "내부 서버 오류가 발생했습니다."));
44+
if (month == null) {
45+
List<MonthlyCaseStatsResponse> monthlyStats = caseStatsService.getMonthlyCaseStats(year, category, session);
46+
return ResponseEntity.ok(monthlyStats);
7347
}
48+
List<DailyCaseStatsResponse> dailyStats = caseStatsService.getDailyCaseStats(year, month, category, session);
49+
return ResponseEntity.ok(dailyStats);
7450
}
7551

7652
// 유형별 사건 수 조회 (기본 카테고리 0 포함)
7753
@GetMapping("/category")
7854
public ResponseEntity<?> getCategoryCaseStats(@RequestParam("period") String period, HttpSession session) {
79-
try {
80-
Map<String, Integer> stats = caseStatsService.getCategoryCaseStats(period, session);
81-
return ResponseEntity.ok(stats);
82-
} catch (IllegalStateException e) {
83-
return ResponseEntity.status(401).body(Collections.singletonMap("message", e.getMessage()));
84-
} catch (IllegalArgumentException e) {
85-
return ResponseEntity.status(400).body(Collections.singletonMap("message", e.getMessage()));
86-
} catch (NoSuchElementException e) {
87-
return ResponseEntity.status(404).body(Collections.singletonMap("message", e.getMessage()));
88-
} catch (Exception e) {
89-
return ResponseEntity.status(500).body(Collections.singletonMap("message", "내부 서버 오류가 발생했습니다."));
90-
}
55+
Map<String, Integer> stats = caseStatsService.getCategoryCaseStats(period, session);
56+
return ResponseEntity.ok(stats);
9157
}
9258

9359
// 장소별 사건 수 조회
@@ -96,21 +62,19 @@ public ResponseEntity<?> getLocationCaseStats(@RequestParam("period") String per
9662
try {
9763
List<LocationCaseStatsResponse> stats = caseStatsService.getLocationCaseStats(period, session);
9864
return ResponseEntity.ok(stats);
99-
} catch (IllegalStateException e) {
100-
return ResponseEntity.status(401).body(Collections.singletonMap("message", e.getMessage()));
101-
} catch (IllegalArgumentException e) {
102-
return ResponseEntity.status(400).body(Collections.singletonMap("message", e.getMessage()));
10365
} catch (NoSuchElementException e) {
104-
return ResponseEntity.status(404).body(Collections.singletonMap("message", e.getMessage()));
105-
} catch (Exception e) {
106-
return ResponseEntity.status(500).body(Collections.singletonMap("message", "내부 서버 오류가 발생했습니다."));
66+
return ResponseEntity.ok(Collections.singletonMap("message", e.getMessage()));
10767
}
10868
}
10969

11070
// 지도용 장소별 사건 수 조회
11171
@GetMapping("/map")
11272
public ResponseEntity<?> getMapCaseStats(HttpSession session) {
113-
List<MapCaseStatsResponse> stats = caseStatsService.getMapCaseStats(session);
114-
return ResponseEntity.ok(stats);
73+
try{
74+
List<MapCaseStatsResponse> stats = caseStatsService.getMapCaseStats(session);
75+
return ResponseEntity.ok(stats);
76+
} catch (NoSuchElementException e) {
77+
return ResponseEntity.ok(Collections.singletonMap("message", e.getMessage()));
78+
}
11579
}
11680
}

backend/src/main/java/com/example/backend/analysis/dto/DailyCaseStatsResponse.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ public class DailyCaseStatsResponse {
1313
private int crowdCongestionCount;
1414
private int weaponCount;
1515
private int swoonCount;
16+
private int smokeCount;
1617
}

backend/src/main/java/com/example/backend/analysis/dto/HourlyCaseStatsResponse.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,5 @@ public class HourlyCaseStatsResponse {
1212
private int crowdCongestionCount;
1313
private int weaponCount;
1414
private int swoonCount;
15+
private int smokeCount;
1516
}

backend/src/main/java/com/example/backend/analysis/dto/MapCaseStatsResponse.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,10 @@ public class MapCaseStatsResponse {
99
String address;
1010
double latitude;
1111
double longitude;
12-
int fire_count;
13-
int assault_count;
14-
int crowd_congestion_count;
15-
int weapon_count;
16-
int swoon_count;
12+
int fireCount;
13+
int assaultCount;
14+
int crowdCongestionCount;
15+
int weaponCount;
16+
int swoonCount;
17+
int smokeCount;
1718
}

backend/src/main/java/com/example/backend/analysis/dto/MonthlyCaseStatsResponse.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ public class MonthlyCaseStatsResponse {
1313
private int crowdCongestionCount;
1414
private int weaponCount;
1515
private int swoonCount;
16+
private int smokeCount;
1617
}

backend/src/main/java/com/example/backend/analysis/repository/CaseStatsCategoryRepository.java

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public interface CaseStatsCategoryRepository extends JpaRepository<CaseStatsCate
1919
SUM(CASE WHEN :category = 'assault' OR :category = 'all' THEN c.assault_count ELSE 0 END) AS assault,
2020
SUM(CASE WHEN :category = 'crowd' OR :category = 'all' THEN c.crowd_congestion_count ELSE 0 END) AS crowdCongestion,
2121
SUM(CASE WHEN :category = 'weapon' OR :category = 'all' THEN c.weapon_count ELSE 0 END) AS weapon,
22-
SUM(CASE WHEN :category = 'swoon' OR :category = 'all' THEN c.swoon_count ELSE 0 END) AS swoon
22+
SUM(CASE WHEN :category = 'swoon' OR :category = 'all' THEN c.swoon_count ELSE 0 END) AS swoon,
23+
SUM(CASE WHEN :category = 'smoke' OR :category = 'all' THEN c.smoke_count ELSE 0 END) AS smoke
2324
FROM case_stats_category c
2425
WHERE DATE(c.date) = DATE(:date)
2526
AND c.office_id = :officeId
@@ -35,7 +36,8 @@ WHERE DATE(c.date) = DATE(:date)
3536
SUM(CASE WHEN :category = 'assault' OR :category = 'all' THEN c.assault_count ELSE 0 END) AS assault,
3637
SUM(CASE WHEN :category = 'crowd' OR :category = 'all' THEN c.crowd_congestion_count ELSE 0 END) AS crowdCongestion,
3738
SUM(CASE WHEN :category = 'weapon' OR :category = 'all' THEN c.weapon_count ELSE 0 END) AS weapon,
38-
SUM(CASE WHEN :category = 'swoon' OR :category = 'all' THEN c.swoon_count ELSE 0 END) AS swoon
39+
SUM(CASE WHEN :category = 'swoon' OR :category = 'all' THEN c.swoon_count ELSE 0 END) AS swoon,
40+
SUM(CASE WHEN :category = 'smoke' OR :category = 'all' THEN c.smoke_count ELSE 0 END) AS smoke
3941
FROM case_stats_category c
4042
WHERE EXTRACT(YEAR FROM c.date) = :year
4143
AND c.office_id = :officeId
@@ -51,7 +53,8 @@ WHERE EXTRACT(YEAR FROM c.date) = :year
5153
SUM(CASE WHEN :category = 'assault' OR :category = 'all' THEN c.assault_count ELSE 0 END) AS assault,
5254
SUM(CASE WHEN :category = 'crowd' OR :category = 'all' THEN c.crowd_congestion_count ELSE 0 END) AS crowdCongestion,
5355
SUM(CASE WHEN :category = 'weapon' OR :category = 'all' THEN c.weapon_count ELSE 0 END) AS weapon,
54-
SUM(CASE WHEN :category = 'swoon' OR :category = 'all' THEN c.swoon_count ELSE 0 END) AS swoon
56+
SUM(CASE WHEN :category = 'swoon' OR :category = 'all' THEN c.swoon_count ELSE 0 END) AS swoon,
57+
SUM(CASE WHEN :category = 'smoke' OR :category = 'all' THEN c.smoke_count ELSE 0 END) AS smoke
5558
FROM case_stats_category c
5659
WHERE EXTRACT(YEAR FROM c.date) = :year
5760
AND EXTRACT(MONTH FROM c.date) = :month
@@ -88,6 +91,11 @@ SELECT category, SUM(count) AS total
8891
FROM case_stats_category c
8992
WHERE c.office_id = :officeId
9093
AND c.date >= :startDate
94+
UNION ALL
95+
SELECT 'smoke', SUM(c.smoke_count)
96+
FROM case_stats_category c
97+
WHERE c.office_id = :officeId
98+
AND c.date >= :startDate
9199
) AS stats
92100
GROUP BY category
93101
""", nativeQuery = true)
@@ -96,13 +104,13 @@ SELECT category, SUM(count) AS total
96104
@Query(value = """
97105
SELECT cv.address, cv.latitude, cv.longitude, SUM(total) AS total
98106
FROM (
99-
SELECT c.cctv_id, SUM(c.fire_count + c.assault_count + c.swoon_count + c.weapon_count + c.crowd_congestion_count) AS total
107+
SELECT c.cctv_id, SUM(c.fire_count + c.assault_count + c.swoon_count + c.weapon_count + c.crowd_congestion_count + c.smoke_count) AS total
100108
FROM case_stats_category c
101-
WHERE c.office_id = :officeId
102-
AND c.date >= :startDate
109+
WHERE c.date >= :startDate
103110
GROUP BY c.cctv_id
104111
) AS sub
105112
JOIN cctv_info cv ON sub.cctv_id = cv.id
113+
WHERE cv.office_id = :officeId
106114
GROUP BY cv.address, cv.latitude, cv.longitude
107115
ORDER BY total DESC
108116
""", nativeQuery = true)
@@ -114,12 +122,13 @@ SELECT c.cctv_id, SUM(c.fire_count + c.assault_count + c.swoon_count + c.weapon_
114122
SUM(c.assault_count) AS assault,
115123
SUM(c.swoon_count) AS swoon,
116124
SUM(c.weapon_count) AS weapon,
117-
SUM(c.crowd_congestion_count) AS crowd_congestion
125+
SUM(c.crowd_congestion_count) AS crowd_congestion,
126+
SUM(c.smoke_count) AS smoke
118127
FROM case_stats_category c
119128
JOIN cctv_info cv ON c.cctv_id = cv.id
120129
WHERE c.date >= :startDate
121130
GROUP BY cv.address, cv.latitude, cv.longitude
122-
ORDER BY (SUM(c.fire_count) + SUM(c.assault_count) + SUM(c.swoon_count) + SUM(c.weapon_count) + SUM(c.crowd_congestion_count)) DESC
131+
ORDER BY (SUM(c.fire_count) + SUM(c.assault_count) + SUM(c.swoon_count) + SUM(c.weapon_count) + SUM(c.crowd_congestion_count) + SUM(c.smoke_count)) DESC
123132
""", nativeQuery = true)
124133
List<Object[]> findMapCaseStats(@Param("startDate") LocalDateTime startDate);
125134

backend/src/main/java/com/example/backend/analysis/repository/CaseStatsOverviewRepository.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public interface CaseStatsOverviewRepository extends JpaRepository<CaseStatsOver
1919
WHERE csc.office_id = :officeId
2020
AND csc.date >= NOW() - INTERVAL '1 month'
2121
GROUP BY ci.address
22-
ORDER BY SUM(csc.fire_count + csc.assault_count + csc.crowd_congestion_count + csc.weapon_count + csc.swoon_count) DESC
22+
ORDER BY SUM(csc.fire_count + csc.assault_count + csc.crowd_congestion_count + csc.weapon_count + csc.swoon_count + csc.smoke_count) DESC
2323
LIMIT 1
2424
""", nativeQuery = true)
2525
Optional<String> findAddressWithMostIncidentsLastMonth(@Param("officeId") int officeId);

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

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import lombok.RequiredArgsConstructor;
1010
import org.springframework.stereotype.Service;
1111

12+
import java.time.LocalDate;
1213
import java.time.LocalDateTime;
1314
import java.time.YearMonth;
1415
import java.util.ArrayList;
@@ -34,14 +35,15 @@ private int getOfficeId(HttpSession session) {
3435
return user.getOfficeId();
3536
}
3637

37-
// period에 따른 시작일 계산 (weekly: 최근 7일, monthly: 최근 1개월, yearly: 최근 1년)
38+
// period에 따른 시작일 계산 (weekly: 이번 주, monthly: 이번 달, yearly: 이번 년도)
3839
private LocalDateTime getStartDateFromPeriod(String period) {
3940
String periodParam = (period != null) ? period.toLowerCase() : "weekly";
41+
LocalDate today = LocalDate.now();
4042

4143
return switch (periodParam) {
42-
case "weekly" -> LocalDateTime.now().minusDays(7);
43-
case "monthly" -> LocalDateTime.now().minusMonths(1);
44-
case "yearly" -> LocalDateTime.now().minusYears(1);
44+
case "weekly" -> today.with(java.time.DayOfWeek.MONDAY).atStartOfDay();
45+
case "monthly" -> today.withDayOfMonth(1).atStartOfDay();
46+
case "yearly" -> today.withDayOfYear(1).atStartOfDay();
4547
default -> throw new IllegalArgumentException("잘못된 period 값입니다. 'weekly', 'monthly', 'yearly' 중 하나여야 합니다.");
4648
};
4749
}
@@ -88,7 +90,8 @@ public List<HourlyCaseStatsResponse> getHourlyCaseStats(String date, String cate
8890
((Number) row[2]).intValue(), // assault
8991
((Number) row[3]).intValue(), // crowdCongestion
9092
((Number) row[4]).intValue(), // weapon
91-
((Number) row[5]).intValue() // swoon
93+
((Number) row[5]).intValue(), // swoon
94+
((Number) row[6]).intValue() // smoke
9295
);
9396
hourMap.put(hour, response);
9497
}
@@ -99,7 +102,7 @@ public List<HourlyCaseStatsResponse> getHourlyCaseStats(String date, String cate
99102
if (hourMap.containsKey(h)) {
100103
fullHourlyList.add(hourMap.get(h));
101104
} else {
102-
fullHourlyList.add(new HourlyCaseStatsResponse(h, 0, 0, 0, 0, 0));
105+
fullHourlyList.add(new HourlyCaseStatsResponse(h, 0, 0, 0, 0, 0, 0));
103106
}
104107
}
105108

@@ -123,7 +126,8 @@ public List<DailyCaseStatsResponse> getDailyCaseStats(int year, int month, Strin
123126
((Number) row[2]).intValue(), // assault
124127
((Number) row[3]).intValue(), // crowdCongestion
125128
((Number) row[4]).intValue(), // weapon
126-
((Number) row[5]).intValue() // swoon
129+
((Number) row[5]).intValue(), // swoon
130+
((Number) row[6]).intValue() // smoke
127131
);
128132
dayMap.put(day, response);
129133
}
@@ -137,7 +141,7 @@ public List<DailyCaseStatsResponse> getDailyCaseStats(int year, int month, Strin
137141
if (dayMap.containsKey(d)) {
138142
fullDailyList.add(dayMap.get(d));
139143
} else {
140-
fullDailyList.add(new DailyCaseStatsResponse(d, 0, 0, 0, 0, 0));
144+
fullDailyList.add(new DailyCaseStatsResponse(d, 0, 0, 0, 0, 0, 0));
141145
}
142146
}
143147

@@ -161,7 +165,8 @@ public List<MonthlyCaseStatsResponse> getMonthlyCaseStats(int year, String categ
161165
((Number) row[2]).intValue(), // assault
162166
((Number) row[3]).intValue(), // crowdCongestion
163167
((Number) row[4]).intValue(), // weapon
164-
((Number) row[5]).intValue() // swoon
168+
((Number) row[5]).intValue(), // swoon
169+
((Number) row[6]).intValue() // smoke
165170
);
166171
monthMap.put(month, response);
167172
}
@@ -171,7 +176,7 @@ public List<MonthlyCaseStatsResponse> getMonthlyCaseStats(int year, String categ
171176
if (monthMap.containsKey(m)) {
172177
fullMonthlyList.add(monthMap.get(m));
173178
} else {
174-
fullMonthlyList.add(new MonthlyCaseStatsResponse(m, 0, 0, 0, 0, 0));
179+
fullMonthlyList.add(new MonthlyCaseStatsResponse(m, 0, 0, 0, 0, 0, 0));
175180
}
176181
}
177182

@@ -239,11 +244,13 @@ public List<MapCaseStatsResponse> getMapCaseStats(HttpSession session) {
239244
(String) row[0], // address
240245
(Double) row[1], // latitude
241246
(Double) row[2], // longitude
242-
((Number) row[3]).intValue(), // fire_count
243-
((Number) row[4]).intValue(), // assault_count
244-
((Number) row[5]).intValue(), // crowd_congestion_count
245-
((Number) row[6]).intValue(), // weapon_count
246-
((Number) row[7]).intValue())) // swoon_count
247+
((Number) row[3]).intValue(), // fire
248+
((Number) row[4]).intValue(), // assault
249+
((Number) row[5]).intValue(), // crowd_congestion
250+
((Number) row[6]).intValue(), // weapon
251+
((Number) row[7]).intValue(), // swoon
252+
((Number) row[6]).intValue() // smoke
253+
))
247254
.collect(Collectors.toList());
248255
}
249256

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ public ResponseEntity<?> handleIllegalStateException(IllegalStateException e) {
3737
.body(Collections.singletonMap("message", e.getMessage()));
3838
}
3939

40+
@ExceptionHandler(IllegalArgumentException.class)
41+
public ResponseEntity<?> handleIllegalArgumentException(IllegalArgumentException e) {
42+
log.warn("IllegalArgumentException: {}", e.getMessage());
43+
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
44+
.body(Collections.singletonMap("message", e.getMessage()));
45+
}
46+
4047
@ExceptionHandler(NoSuchElementException.class)
4148
public ResponseEntity<?> handleNoSuchElementException(NoSuchElementException e) {
4249
return ResponseEntity.status(HttpStatus.NOT_FOUND)

backend/src/main/java/com/example/backend/common/domain/CaseEntity.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public class CaseEntity {
5252
private LocalDateTime progressDate;
5353

5454
public enum CaseCategory {
55-
fire, assault, crowd_congestion, weapon, swoon
55+
fire, assault, crowd_congestion, weapon, swoon, smoke
5656
}
5757

5858
public enum CaseState {

0 commit comments

Comments
 (0)