Quellcode durchsuchen

feat(data-engine): #71 历史数据回溯 + 报表生成(水量/水质/报警)

- 新增历史数据查询功能,支持按类型、时间、区域过滤
- 新增水量汇总报表,包含总量、分区域统计、每日趋势
- 新增水质合格率报表,包含总检测数、合格数、分区域合格率
- 新增报警统计报表,包含总量、级别统计、区域分布、每日趋势
- 新增综合数据报表,整合水量、水质、报警数据并提供评估建议
- 新增HistoryDataController,提供完整的RESTful API接口
- 扩展ReportService支持各种类型报表生成和保存
- 支持报表发布和状态管理功能

API端点:
- GET /api/data/historical - 历史数据查询
- GET /api/data/report/water-volume - 水量汇总报表
- GET /api/data/report/water-quality - 水质合格率报表
- GET /api/data/report/alarm-statistics - 报警统计报表
- POST /api/data/report/*/generate - 生成各类报表并保存
- GET /api/data/reports - 报表列表
- GET /api/data/reports/{id} - 报表详情
- POST /api/data/reports/{id}/publish - 发布报表

提交ID: f6a7b11c
Issue: #71
分支: feature/issue-71
bot_dev1 vor 3 Tagen
Ursprung
Commit
30c4b0e7b5

+ 316
- 0
wm-data-engine/src/main/java/com/water/data_engine/controller/HistoryDataController.java Datei anzeigen

@@ -0,0 +1,316 @@
1
+package com.water.data_engine.controller;
2
+
3
+import com.water.common.core.result.R;
4
+import com.water.data_engine.service.DataStatisticsService;
5
+import com.water.data_engine.service.ReportService;
6
+import io.swagger.v3.oas.annotations.Operation;
7
+import io.swagger.v3.oas.annotations.Parameter;
8
+import io.swagger.v3.oas.annotations.tags.Tag;
9
+import lombok.RequiredArgsConstructor;
10
+import lombok.extern.slf4j.Slf4j;
11
+import org.springframework.format.annotation.DateTimeFormat;
12
+import org.springframework.web.bind.annotation.*;
13
+
14
+import java.time.LocalDateTime;
15
+import java.util.List;
16
+import java.util.Map;
17
+
18
+/**
19
+ * 历史数据查询和报表生成控制器
20
+ * 对应 Issue #71:历史数据回溯 + 报表生成(水量/水质/报警)
21
+ */
22
+@Tag(name = "历史数据查询与报表")
23
+@RestController
24
+@RequestMapping("/api/data")
25
+@Slf4j
26
+@RequiredArgsConstructor
27
+public class HistoryDataController {
28
+
29
+    private final DataStatisticsService dataStatisticsService;
30
+    private final ReportService reportService;
31
+
32
+    @Operation(summary = "历史数据回溯查询")
33
+    @GetMapping("/historical")
34
+    public R<Map<String, Object>> queryHistoricalData(
35
+            @Parameter(description = "数据类型:water_volume/pressure/water_quality/alarm") 
36
+            @RequestParam(required = false) String dataType,
37
+            @Parameter(description = "区域名称") 
38
+            @RequestParam(required = false) String area,
39
+            @Parameter(description = "开始时间,格式:yyyy-MM-dd HH:mm:ss") 
40
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String startTime,
41
+            @Parameter(description = "结束时间,格式:yyyy-MM-dd HH:mm:ss") 
42
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String endTime) {
43
+        
44
+        log.info("开始查询历史数据: dataType={}, area={}, startTime={}, endTime={}", dataType, area, startTime, endTime);
45
+        
46
+        try {
47
+            Map<String, Object> result = dataStatisticsService.queryHistoricalData(dataType, area, startTime, endTime);
48
+            log.info("历史数据查询成功,返回 {} 条记录", result.get("totalRecords"));
49
+            return R.ok(result);
50
+        } catch (Exception e) {
51
+            log.error("历史数据查询失败: {}", e.getMessage());
52
+            return R.error("历史数据查询失败: " + e.getMessage());
53
+        }
54
+    }
55
+
56
+    @Operation(summary = "水量汇总报表")
57
+    @GetMapping("/report/water-volume")
58
+    public R<Map<String, Object>> getWaterVolumeSummary(
59
+            @Parameter(description = "查询开始时间,格式:yyyy-MM-dd HH:mm:ss") 
60
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String startTime,
61
+            @Parameter(description = "查询结束时间,格式:yyyy-MM-dd HH:mm:ss") 
62
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String endTime) {
63
+        
64
+        log.info("开始生成水量汇总报表: startTime={}, endTime={}", startTime, endTime);
65
+        
66
+        try {
67
+            Map<String, Object> summary = dataStatisticsService.getWaterVolumeSummary(startTime, endTime);
68
+            log.info("水量汇总生成成功");
69
+            return R.ok(summary);
70
+        } catch (Exception e) {
71
+            log.error("水量汇总生成失败: {}", e.getMessage());
72
+            return R.error("水量汇总生成失败: " + e.getMessage());
73
+        }
74
+    }
75
+
76
+    @Operation(summary = "水质合格率报表")
77
+    @GetMapping("/report/water-quality")
78
+    public R<Map<String, Object>> getWaterQualityRate(
79
+            @Parameter(description = "查询开始时间,格式:yyyy-MM-dd HH:mm:ss") 
80
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String startTime,
81
+            @Parameter(description = "查询结束时间,格式:yyyy-MM-dd HH:mm:ss") 
82
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String endTime) {
83
+        
84
+        log.info("开始生成水质合格率报表: startTime={}, endTime={}", startTime, endTime);
85
+        
86
+        try {
87
+            Map<String, Object> qualityStats = dataStatisticsService.getWaterQualityRate(startTime, endTime);
88
+            log.info("水质合格率统计成功");
89
+            return R.ok(qualityStats);
90
+        } catch (Exception e) {
91
+            log.error("水质合格率统计失败: {}", e.getMessage());
92
+            return R.error("水质合格率统计失败: " + e.getMessage());
93
+        }
94
+    }
95
+
96
+    @Operation(summary = "报警统计报表")
97
+    @GetMapping("/report/alarm-statistics")
98
+    public R<Map<String, Object>> getAlarmStatistics(
99
+            @Parameter(description = "查询开始时间,格式:yyyy-MM-dd HH:mm:ss") 
100
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String startTime,
101
+            @Parameter(description = "查询结束时间,格式:yyyy-MM-dd HH:mm:ss") 
102
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String endTime) {
103
+        
104
+        log.info("开始生成报警统计报表: startTime={}, endTime={}", startTime, endTime);
105
+        
106
+        try {
107
+            Map<String, Object> alarmStats = dataStatisticsService.getAlarmStatistics(startTime, endTime);
108
+            log.info("报警统计生成成功");
109
+            return R.ok(alarmStats);
110
+        } catch (Exception e) {
111
+            log.error("报警统计生成失败: {}", e.getMessage());
112
+            return R.error("报警统计生成失败: " + e.getMessage());
113
+        }
114
+    }
115
+
116
+    @Operation(summary = "生成水量汇总报表并保存")
117
+    @PostMapping("/report/water-volume/generate")
118
+    public R<Map<String, Object>> generateWaterVolumeReport(
119
+            @Parameter(description = "报表周期描述") 
120
+            @RequestParam String period,
121
+            @Parameter(description = "查询开始时间,格式:yyyy-MM-dd HH:mm:ss") 
122
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String startTime,
123
+            @Parameter(description = "查询结束时间,格式:yyyy-MM-dd HH:mm:ss") 
124
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String endTime) {
125
+        
126
+        log.info("开始生成并保存水量汇总报表: period={}, startTime={}, endTime={}", period, startTime, endTime);
127
+        
128
+        try {
129
+            var report = reportService.generateWaterVolumeReport(period, startTime, endTime);
130
+            
131
+            Map<String, Object> result = Map.of(
132
+                "reportNo", report.getReportNo(),
133
+                "reportType", report.getReportType(),
134
+                "period", report.getPeriod(),
135
+                "title", report.getTitle(),
136
+                "status", report.getStatus(),
137
+                "createdTime", report.getCreatedTime()
138
+            );
139
+            
140
+            log.info("水量汇总报表生成并保存成功: reportNo={}", report.getReportNo());
141
+            return R.ok(result);
142
+        } catch (Exception e) {
143
+            log.error("水量汇总报表生成失败: {}", e.getMessage());
144
+            return R.error("水量汇总报表生成失败: " + e.getMessage());
145
+        }
146
+    }
147
+
148
+    @Operation(summary = "生成水质合格率报表并保存")
149
+    @PostMapping("/report/water-quality/generate")
150
+    public R<Map<String, Object>> generateWaterQualityReport(
151
+            @Parameter(description = "报表周期描述") 
152
+            @RequestParam String period,
153
+            @Parameter(description = "查询开始时间,格式:yyyy-MM-dd HH:mm:ss") 
154
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String startTime,
155
+            @Parameter(description = "查询结束时间,格式:yyyy-MM-dd HH:mm:ss") 
156
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String endTime) {
157
+        
158
+        log.info("开始生成并保存水质合格率报表: period={}, startTime={}, endTime={}", period, startTime, endTime);
159
+        
160
+        try {
161
+            var report = reportService.generateWaterQualityReport(period, startTime, endTime);
162
+            
163
+            Map<String, Object> result = Map.of(
164
+                "reportNo", report.getReportNo(),
165
+                "reportType", report.getReportType(),
166
+                "period", report.getPeriod(),
167
+                "title", report.getTitle(),
168
+                "status", report.getStatus(),
169
+                "createdTime", report.getCreatedTime()
170
+            );
171
+            
172
+            log.info("水质合格率报表生成并保存成功: reportNo={}", report.getReportNo());
173
+            return R.ok(result);
174
+        } catch (Exception e) {
175
+            log.error("水质合格率报表生成失败: {}", e.getMessage());
176
+            return R.error("水质合格率报表生成失败: " + e.getMessage());
177
+        }
178
+    }
179
+
180
+    @Operation(summary = "生成报警统计报表并保存")
181
+    @PostMapping("/report/alarm-statistics/generate")
182
+    public R<Map<String, Object>> generateAlarmStatisticsReport(
183
+            @Parameter(description = "报表周期描述") 
184
+            @RequestParam String period,
185
+            @Parameter(description = "查询开始时间,格式:yyyy-MM-dd HH:mm:ss") 
186
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String startTime,
187
+            @Parameter(description = "查询结束时间,格式:yyyy-MM-dd HH:mm:ss") 
188
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String endTime) {
189
+        
190
+        log.info("开始生成并保存报警统计报表: period={}, startTime={}, endTime={}", period, startTime, endTime);
191
+        
192
+        try {
193
+            var report = reportService.generateAlarmStatisticsReport(period, startTime, endTime);
194
+            
195
+            Map<String, Object> result = Map.of(
196
+                "reportNo", report.getReportNo(),
197
+                "reportType", report.getReportType(),
198
+                "period", report.getPeriod(),
199
+                "title", report.getTitle(),
200
+                "status", report.getStatus(),
201
+                "createdTime", report.getCreatedTime()
202
+            );
203
+            
204
+            log.info("报警统计报表生成并保存成功: reportNo={}", report.getReportNo());
205
+            return R.ok(result);
206
+        } catch (Exception e) {
207
+            log.error("报警统计报表生成失败: {}", e.getMessage());
208
+            return R.error("报警统计报表生成失败: " + e.getMessage());
209
+        }
210
+    }
211
+
212
+    @Operation(summary = "生成综合数据报表")
213
+    @PostMapping("/report/comprehensive/generate")
214
+    public R<Map<String, Object>> generateComprehensiveReport(
215
+            @Parameter(description = "报表周期描述") 
216
+            @RequestParam String period,
217
+            @Parameter(description = "查询开始时间,格式:yyyy-MM-dd HH:mm:ss") 
218
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String startTime,
219
+            @Parameter(description = "查询结束时间,格式:yyyy-MM-dd HH:mm:ss") 
220
+            @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") String endTime) {
221
+        
222
+        log.info("开始生成综合数据报表: period={}, startTime={}, endTime={}", period, startTime, endTime);
223
+        
224
+        try {
225
+            var report = reportService.generateComprehensiveReport(period, startTime, endTime);
226
+            
227
+            Map<String, Object> result = Map.of(
228
+                "reportNo", report.getReportNo(),
229
+                "reportType", report.getReportType(),
230
+                "period", report.getPeriod(),
231
+                "title", report.getTitle(),
232
+                "status", report.getStatus(),
233
+                "createdTime", report.getCreatedTime()
234
+            );
235
+            
236
+            log.info("综合数据报表生成并保存成功: reportNo={}", report.getReportNo());
237
+            return R.ok(result);
238
+        } catch (Exception e) {
239
+            log.error("综合数据报表生成失败: {}", e.getMessage());
240
+            return R.error("综合数据报表生成失败: " + e.getMessage());
241
+        }
242
+    }
243
+
244
+    @Operation(summary = "获取报表列表")
245
+    @GetMapping("/reports")
246
+    public R<List<Map<String, Object>>> getReports(
247
+            @Parameter(description = "报表类型:water_volume/water_quality/alarm_statistics/comprehensive") 
248
+            @RequestParam(required = false) String reportType,
249
+            @Parameter(description = "报表状态:GENERATED/PUBLISHED") 
250
+            @RequestParam(required = false) String status) {
251
+        
252
+        try {
253
+            var reports = reportService.listReports(reportType, status);
254
+            List<Map<String, Object>> result = reports.stream()
255
+                .map(report -> Map.of(
256
+                    "id", report.getId(),
257
+                    "reportNo", report.getReportNo(),
258
+                    "reportType", report.getReportType(),
259
+                    "period", report.getPeriod(),
260
+                    "title", report.getTitle(),
261
+                    "status", report.getStatus(),
262
+                    "createdTime", report.getCreatedTime(),
263
+                    "publishedTime", report.getPublishedTime()
264
+                ))
265
+                .toList();
266
+            
267
+            return R.ok(result);
268
+        } catch (Exception e) {
269
+            log.error("获取报表列表失败: {}", e.getMessage());
270
+            return R.error("获取报表列表失败: " + e.getMessage());
271
+        }
272
+    }
273
+
274
+    @Operation(summary = "获取报表详情")
275
+    @GetMapping("/reports/{id}")
276
+    public R<Map<String, Object>> getReportDetail(@PathVariable Long id) {
277
+        
278
+        try {
279
+            var report = reportService.getReport(id);
280
+            if (report == null) {
281
+                return R.error("报表不存在");
282
+            }
283
+            
284
+            Map<String, Object> result = Map.of(
285
+                "id", report.getId(),
286
+                "reportNo", report.getReportNo(),
287
+                "reportType", report.getReportType(),
288
+                "period", report.getPeriod(),
289
+                "title", report.getTitle(),
290
+                "status", report.getStatus(),
291
+                "content", report.getContent(),
292
+                "createdTime", report.getCreatedTime(),
293
+                "publishedTime", report.getPublishedTime()
294
+            );
295
+            
296
+            return R.ok(result);
297
+        } catch (Exception e) {
298
+            log.error("获取报表详情失败: {}", e.getMessage());
299
+            return R.error("获取报表详情失败: " + e.getMessage());
300
+        }
301
+    }
302
+
303
+    @Operation(summary = "发布报表")
304
+    @PostMapping("/reports/{id}/publish")
305
+    public R<String> publishReport(@PathVariable Long id) {
306
+        
307
+        try {
308
+            reportService.publishReport(id);
309
+            log.info("报表发布成功: id={}", id);
310
+            return R.ok("报表发布成功");
311
+        } catch (Exception e) {
312
+            log.error("报表发布失败: {}", e.getMessage());
313
+            return R.error("报表发布失败: " + e.getMessage());
314
+        }
315
+    }
316
+}

+ 240
- 4
wm-data-engine/src/main/java/com/water/data_engine/service/DataStatisticsService.java Datei anzeigen

@@ -7,13 +7,12 @@ import org.springframework.stereotype.Service;
7 7
 
8 8
 import java.time.LocalDateTime;
9 9
 import java.time.format.DateTimeFormatter;
10
-import java.util.HashMap;
11
-import java.util.List;
12
-import java.util.Map;
10
+import java.util.*;
11
+import java.util.stream.Collectors;
13 12
 
14 13
 /**
15 14
  * 数据统计服务
16
- * 提供数据采集统计、质量分析等功能
15
+ * 提供数据采集统计、质量分析、历史数据查询等功能
17 16
  */
18 17
 @Slf4j
19 18
 @Service
@@ -83,6 +82,243 @@ public class DataStatisticsService {
83 82
         return stats;
84 83
     }
85 84
 
85
+    /**
86
+     * 历史数据回溯查询
87
+     * 支持按数据类型、时间范围、区域查询历史数据
88
+     */
89
+    public Map<String, Object> queryHistoricalData(String dataType, String area, String startTime, String endTime) {
90
+        Map<String, Object> result = new HashMap<>();
91
+        
92
+        // 默认查询最近7天
93
+        if (startTime == null) {
94
+            startTime = LocalDateTime.now().minusDays(7).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
95
+        }
96
+        if (endTime == null) {
97
+            endTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
98
+        }
99
+        
100
+        try {
101
+            List<Map<String, Object>> dataRecords = new ArrayList<>();
102
+            String baseSql = "SELECT * FROM collect_record WHERE collect_time BETWEEN ? AND ?";
103
+            List<Object> params = new ArrayList<>();
104
+            params.add(startTime);
105
+            params.add(endTime);
106
+            
107
+            // 按数据类型过滤
108
+            if (dataType != null && !dataType.trim().isEmpty()) {
109
+                baseSql += " AND data_type = ?";
110
+                params.add(dataType);
111
+            }
112
+            
113
+            // 按区域过滤
114
+            if (area != null && !area.trim().isEmpty()) {
115
+                baseSql += " AND area = ?";
116
+                params.add(area);
117
+            }
118
+            
119
+            baseSql += " ORDER BY collect_time DESC";
120
+            
121
+            // 执行查询
122
+            dataRecords = jdbcTemplate.queryForList(baseSql, params.toArray());
123
+            
124
+            // 数据预处理
125
+            List<Map<String, Object>> processedData = dataRecords.stream()
126
+                .map(record -> {
127
+                    Map<String, Object> processed = new HashMap<>();
128
+                    processed.put("id", record.get("id"));
129
+                    processed.put("sourceKey", record.get("source_key"));
130
+                    processed.put("dataType", record.get("data_type"));
131
+                    processed.put("area", record.get("area"));
132
+                    processed.put("collectTime", record.get("collect_time"));
133
+                    processed.put("value", record.get("data_value"));
134
+                    processed.put("status", record.get("status"));
135
+                    processed.put("errorMsg", record.get("error_msg"));
136
+                    return processed;
137
+                })
138
+                .collect(Collectors.toList());
139
+            
140
+            result.put("dataRecords", processedData);
141
+            result.put("totalRecords", processedData.size());
142
+            result.put("queryPeriod", startTime + " - " + endTime);
143
+            
144
+            if (dataType != null && !dataType.trim().isEmpty()) {
145
+                result.put("filterByType", dataType);
146
+            }
147
+            
148
+            if (area != null && !area.trim().isEmpty()) {
149
+                result.put("filterByArea", area);
150
+            }
151
+            
152
+            log.info("查询历史数据成功: {}条记录, 类型={}, 区域={}", processedData.size(), dataType, area);
153
+            
154
+        } catch (Exception e) {
155
+            log.error("查询历史数据失败: {}", e.getMessage());
156
+            throw new RuntimeException("历史数据查询失败: " + e.getMessage());
157
+        }
158
+        
159
+        return result;
160
+    }
161
+
162
+    /**
163
+     * 获取水量汇总报表数据
164
+     */
165
+    public Map<String, Object> getWaterVolumeSummary(String startTime, String endTime) {
166
+        Map<String, Object> summary = new HashMap<>();
167
+        
168
+        if (startTime == null) {
169
+            startTime = LocalDateTime.now().minusDays(7).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
170
+        }
171
+        if (endTime == null) {
172
+            endTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
173
+        }
174
+        
175
+        try {
176
+            // 总供水量
177
+            String totalSupplySql = "SELECT SUM(data_value) as totalSupply FROM collect_record " +
178
+                                 "WHERE data_type = 'water_volume' AND collect_time BETWEEN ? AND ?";
179
+            Double totalSupply = jdbcTemplate.queryForObject(totalSupplySql, Double.class, startTime, endTime);
180
+            
181
+            // 分区域供水量统计
182
+            String areaSupplySql = "SELECT area, SUM(data_value) as supply FROM collect_record " +
183
+                                "WHERE data_type = 'water_volume' AND collect_time BETWEEN ? AND ? " +
184
+                                "GROUP BY area ORDER BY supply DESC";
185
+            List<Map<String, Object>> areaSupply = jdbcTemplate.queryForList(areaSupplySql, startTime, endTime);
186
+            
187
+            // 每日供水量趋势
188
+            String dailyTrendSql = "SELECT DATE(collect_time) as date, SUM(data_value) as dailySupply " +
189
+                                "FROM collect_record WHERE data_type = 'water_volume' AND collect_time BETWEEN ? AND ? " +
190
+                                "GROUP BY DATE(collect_time) ORDER BY date";
191
+            List<Map<String, Object>> dailyTrend = jdbcTemplate.queryForList(dailyTrendSql, startTime, endTime);
192
+            
193
+            summary.put("totalSupply", totalSupply != null ? totalSupply : 0.0);
194
+            summary.put("areaSupplyStats", areaSupply);
195
+            summary.put("dailyTrend", dailyTrend);
196
+            
197
+            log.info("获取水量汇总成功: 总供水量={}, 分区域数={}", totalSupply, areaSupply.size());
198
+            
199
+        } catch (Exception e) {
200
+            log.error("获取水量汇总失败: {}", e.getMessage());
201
+            throw new RuntimeException("水量汇总查询失败: " + e.getMessage());
202
+        }
203
+        
204
+        return summary;
205
+    }
206
+
207
+    /**
208
+     * 获取水质合格率统计
209
+     */
210
+    public Map<String, Object> getWaterQualityRate(String startTime, String endTime) {
211
+        Map<String, Object> qualityStats = new HashMap<>();
212
+        
213
+        if (startTime == null) {
214
+            startTime = LocalDateTime.now().minusDays(7).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
215
+        }
216
+        if (endTime == null) {
217
+            endTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
218
+        }
219
+        
220
+        try {
221
+            // 水质检测总量
222
+            String totalTestsSql = "SELECT COUNT(*) as totalTests FROM collect_record " +
223
+                                "WHERE data_type = 'water_quality' AND collect_time BETWEEN ? AND ?";
224
+            Integer totalTests = jdbcTemplate.queryForObject(totalTestsSql, Integer.class, startTime, endTime);
225
+            
226
+            // 合格检测数量(假设数据值 > 80 为合格)
227
+            String passedTestsSql = "SELECT COUNT(*) as passedTests FROM collect_record " +
228
+                                 "WHERE data_type = 'water_quality' AND data_value >= 80 AND collect_time BETWEEN ? AND ?";
229
+            Integer passedTests = jdbcTemplate.queryForObject(passedTestsSql, Integer.class, startTime, endTime);
230
+            
231
+            // 合格率计算
232
+            double passRate = totalTests > 0 ? (double) passedTests / totalTests * 100 : 0;
233
+            
234
+            // 分区域合格率
235
+            String areaQualitySql = "SELECT area, COUNT(*) as total, SUM(CASE WHEN data_value >= 80 THEN 1 ELSE 0 END) as passed " +
236
+                                  "FROM collect_record WHERE data_type = 'water_quality' AND collect_time BETWEEN ? AND ? " +
237
+                                  "GROUP BY area";
238
+            List<Map<String, Object>> areaQuality = jdbcTemplate.queryForList(areaQualitySql, startTime, endTime);
239
+            
240
+            // 处理分区域合格率数据
241
+            List<Map<String, Object>> processedAreaQuality = areaQuality.stream()
242
+                .map(area -> {
243
+                    Map<String, Object> processed = new HashMap<>();
244
+                    processed.put("area", area.get("area"));
245
+                    processed.put("totalTests", area.get("total"));
246
+                    processed.put("passedTests", area.get("passed"));
247
+                    double areaPassRate = (int)area.get("total") > 0 ? 
248
+                        (double)(int)area.get("passed") / (int)area.get("total") * 100 : 0;
249
+                    processed.put("passRate", String.format("%.2f%%", areaPassRate));
250
+                    return processed;
251
+                })
252
+                .collect(Collectors.toList());
253
+            
254
+            qualityStats.put("totalTests", totalTests != null ? totalTests : 0);
255
+            qualityStats.put("passedTests", passedTests != null ? passedTests : 0);
256
+            qualityStats.put("passRate", String.format("%.2f%%", passRate));
257
+            qualityStats.put("areaQualityStats", processedAreaQuality);
258
+            
259
+            log.info("获取水质合格率成功: 总检测={}, 合格={}, 合格率={}", totalTests, passedTests, String.format("%.2f%%", passRate));
260
+            
261
+        } catch (Exception e) {
262
+            log.error("获取水质合格率失败: {}", e.getMessage());
263
+            throw new RuntimeException("水质合格率查询失败: " + e.getMessage());
264
+        }
265
+        
266
+        return qualityStats;
267
+    }
268
+
269
+    /**
270
+     * 获取报警统计
271
+     */
272
+    public Map<String, Object> getAlarmStatistics(String startTime, String endTime) {
273
+        Map<String, Object> alarmStats = new HashMap<>();
274
+        
275
+        if (startTime == null) {
276
+            startTime = LocalDateTime.now().minusDays(7).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
277
+        }
278
+        if (endTime == null) {
279
+            endTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
280
+        }
281
+        
282
+        try {
283
+            // 报警总量
284
+            String totalAlarmsSql = "SELECT COUNT(*) as totalAlarms FROM collect_record " +
285
+                                 "WHERE data_type = 'alarm' AND collect_time BETWEEN ? AND ?";
286
+            Integer totalAlarms = jdbcTemplate.queryForObject(totalAlarmsSql, Integer.class, startTime, endTime);
287
+            
288
+            // 按报警级别统计
289
+            String levelStatsSql = "SELECT source_key as alarmLevel, COUNT(*) as count FROM collect_record " +
290
+                                  "WHERE data_type = 'alarm' AND collect_time BETWEEN ? AND ? " +
291
+                                  "GROUP BY source_key ORDER BY count DESC";
292
+            List<Map<String, Object>> levelStats = jdbcTemplate.queryForList(levelStatsSql, startTime, endTime);
293
+            
294
+            // 按区域报警统计
295
+            String areaAlarmSql = "SELECT area, COUNT(*) as alarmCount FROM collect_record " +
296
+                                "WHERE data_type = 'alarm' AND collect_time BETWEEN ? AND ? " +
297
+                                "GROUP BY area ORDER BY alarmCount DESC";
298
+            List<Map<String, Object>> areaAlarmStats = jdbcTemplate.queryForList(areaAlarmSql, startTime, endTime);
299
+            
300
+            // 每日报警趋势
301
+            String dailyAlarmSql = "SELECT DATE(collect_time) as date, COUNT(*) as alarmCount " +
302
+                                "FROM collect_record WHERE data_type = 'alarm' AND collect_time BETWEEN ? AND ? " +
303
+                                "GROUP BY DATE(collect_time) ORDER BY date";
304
+            List<Map<String, Object>> dailyAlarmTrend = jdbcTemplate.queryForList(dailyAlarmSql, startTime, endTime);
305
+            
306
+            alarmStats.put("totalAlarms", totalAlarms != null ? totalAlarms : 0);
307
+            alarmStats.put("levelStatistics", levelStats);
308
+            alarmStats.put("areaAlarmStats", areaAlarmStats);
309
+            alarmStats.put("dailyAlarmTrend", dailyAlarmTrend);
310
+            
311
+            log.info("获取报警统计成功: 总报警={}, 级别统计={}, 区域统计={}", 
312
+                totalAlarms, levelStats.size(), areaAlarmStats.size());
313
+            
314
+        } catch (Exception e) {
315
+            log.error("获取报警统计失败: {}", e.getMessage());
316
+            throw new RuntimeException("报警统计查询失败: " + e.getMessage());
317
+        }
318
+        
319
+        return alarmStats;
320
+    }
321
+
86 322
     /**
87 323
      * 获取设备数据统计
88 324
      */

+ 363
- 1
wm-data-engine/src/main/java/com/water/data_engine/service/ReportService.java Datei anzeigen

@@ -10,6 +10,7 @@ import org.springframework.stereotype.Service;
10 10
 
11 11
 import java.time.LocalDate;
12 12
 import java.time.LocalDateTime;
13
+import java.time.format.DateTimeFormatter;
13 14
 import java.util.*;
14 15
 
15 16
 @Service
@@ -18,9 +19,293 @@ public class ReportService {
18 19
 
19 20
     private final DataReportMapper reportMapper;
20 21
     private final ReportTemplateMapper templateMapper;
22
+    private final DataStatisticsService dataStatisticsService;
21 23
 
22 24
     /**
23
-     * 自动生成报表
25
+     * 生成水量汇总报表
26
+     */
27
+    public DataReport generateWaterVolumeReport(String period, String startTime, String endTime) {
28
+        DataReport report = new DataReport();
29
+        report.setReportName("水量汇总报表");
30
+        report.setReportCode("WATER-VOL-" + System.currentTimeMillis());
31
+        report.setReportType("water_volume");
32
+        report.setDataType("water_supply");
33
+        report.setArea("ALL");
34
+        
35
+        try {
36
+            // 设置时间范围
37
+            LocalDate start = startTime != null ? LocalDate.parse(startTime.substring(0, 10)) : LocalDate.now().minusDays(7);
38
+            LocalDate end = endTime != null ? LocalDate.parse(endTime.substring(0, 10)) : LocalDate.now();
39
+            report.setPeriodStart(start);
40
+            report.setPeriodEnd(end);
41
+            
42
+            // 获取水量汇总数据
43
+            Map<String, Object> waterVolumeData = dataStatisticsService.getWaterVolumeSummary(startTime, endTime);
44
+            
45
+            Map<String, Object> content = new LinkedHashMap<>();
46
+            content.put("generatedAt", LocalDateTime.now());
47
+            content.put("period", period);
48
+            content.put("startTime", startTime);
49
+            content.put("endTime", endTime);
50
+            
51
+            // 基础统计
52
+            content.put("totalWaterSupply", waterVolumeData.get("totalSupply"));
53
+            content.put("totalAreaCount", ((List<?>) waterVolumeData.get("areaSupplyStats")).size());
54
+            
55
+            // 分区域供水量统计
56
+            content.put("areaWaterSupply", waterVolumeData.get("areaSupplyStats"));
57
+            
58
+            // 每日趋势数据
59
+            content.put("dailyTrend", waterVolumeData.get("dailyTrend"));
60
+            
61
+            // 计算平均日供水量
62
+            List<Map<String, Object>> dailyTrend = (List<Map<String, Object>>) waterVolumeData.get("dailyTrend");
63
+            if (dailyTrend != null && !dailyTrend.isEmpty()) {
64
+                double totalDailySupply = dailyTrend.stream()
65
+                    .mapToDouble(day -> (double) day.getOrDefault("dailySupply", 0.0))
66
+                    .sum();
67
+                double avgDailySupply = totalDailySupply / dailyTrend.size();
68
+                content.put("avgDailySupply", avgDailySupply);
69
+            }
70
+            
71
+            // 生成摘要
72
+            Map<String, Object> summary = new LinkedHashMap<>();
73
+            summary.put("totalSupply", waterVolumeData.get("totalSupply"));
74
+            summary.put("avgDailySupply", content.get("avgDailySupply"));
75
+            summary.put("areaCount", ((List<?>) waterVolumeData.get("areaSupplyStats")).size());
76
+            
77
+            report.setContent(content);
78
+            report.setSummary(summary);
79
+            report.setStatus("GENERATED");
80
+            report.setGeneratedBy("bot_dev1");
81
+            
82
+            reportMapper.insert(report);
83
+            
84
+            log.info("生成水量汇总报表成功: reportCode={}, period={}", report.getReportCode(), period);
85
+            
86
+            return report;
87
+            
88
+        } catch (Exception e) {
89
+            log.error("生成水量汇总报表失败: {}", e.getMessage());
90
+            throw new RuntimeException("水量汇总报表生成失败: " + e.getMessage());
91
+        }
92
+    }
93
+
94
+    /**
95
+     * 生成水质合格率报表
96
+     */
97
+    public DataReport generateWaterQualityReport(String period, String startTime, String endTime) {
98
+        DataReport report = new DataReport();
99
+        report.setReportName("水质合格率报表");
100
+        report.setReportCode("WATER-QUAL-" + System.currentTimeMillis());
101
+        report.setReportType("water_quality");
102
+        report.setDataType("water_quality");
103
+        report.setArea("ALL");
104
+        
105
+        try {
106
+            // 设置时间范围
107
+            LocalDate start = startTime != null ? LocalDate.parse(startTime.substring(0, 10)) : LocalDate.now().minusDays(7);
108
+            LocalDate end = endTime != null ? LocalDate.parse(endTime.substring(0, 10)) : LocalDate.now();
109
+            report.setPeriodStart(start);
110
+            report.setPeriodEnd(end);
111
+            
112
+            // 获取水质合格率数据
113
+            Map<String, Object> qualityData = dataStatisticsService.getWaterQualityRate(startTime, endTime);
114
+            
115
+            Map<String, Object> content = new LinkedHashMap<>();
116
+            content.put("generatedAt", LocalDateTime.now());
117
+            content.put("period", period);
118
+            content.put("startTime", startTime);
119
+            content.put("endTime", endTime);
120
+            
121
+            // 基础统计
122
+            content.put("totalTests", qualityData.get("totalTests"));
123
+            content.put("passedTests", qualityData.get("passedTests"));
124
+            content.put("passRate", qualityData.get("passRate"));
125
+            
126
+            // 分区域合格率
127
+            content.put("areaQualityStats", qualityData.get("areaQualityStats"));
128
+            
129
+            // 生成摘要
130
+            Map<String, Object> summary = new LinkedHashMap<>();
131
+            summary.put("totalTests", qualityData.get("totalTests"));
132
+            summary.put("passedTests", qualityData.get("passedTests"));
133
+            summary.put("passRate", qualityData.get("passRate"));
134
+            summary.put("areaCount", ((List<?>) qualityData.get("areaQualityStats")).size());
135
+            
136
+            report.setContent(content);
137
+            report.setSummary(summary);
138
+            report.setStatus("GENERATED");
139
+            report.setGeneratedBy("bot_dev1");
140
+            
141
+            reportMapper.insert(report);
142
+            
143
+            log.info("生成水质合格率报表成功: reportCode={}, period={}", report.getReportCode(), period);
144
+            
145
+            return report;
146
+            
147
+        } catch (Exception e) {
148
+            log.error("生成水质合格率报表失败: {}", e.getMessage());
149
+            throw new RuntimeException("水质合格率报表生成失败: " + e.getMessage());
150
+        }
151
+    }
152
+
153
+    /**
154
+     * 生成报警统计报表
155
+     */
156
+    public DataReport generateAlarmStatisticsReport(String period, String startTime, String endTime) {
157
+        DataReport report = new DataReport();
158
+        report.setReportName("报警统计报表");
159
+        report.setReportCode("ALARM-STAT-" + System.currentTimeMillis());
160
+        report.setReportType("alarm_statistics");
161
+        report.setDataType("alarm");
162
+        report.setArea("ALL");
163
+        
164
+        try {
165
+            // 设置时间范围
166
+            LocalDate start = startTime != null ? LocalDate.parse(startTime.substring(0, 10)) : LocalDate.now().minusDays(7);
167
+            LocalDate end = endTime != null ? LocalDate.parse(endTime.substring(0, 10)) : LocalDate.now();
168
+            report.setPeriodStart(start);
169
+            report.setPeriodEnd(end);
170
+            
171
+            // 获取报警统计数据
172
+            Map<String, Object> alarmData = dataStatisticsService.getAlarmStatistics(startTime, endTime);
173
+            
174
+            Map<String, Object> content = new LinkedHashMap<>();
175
+            content.put("generatedAt", LocalDateTime.now());
176
+            content.put("period", period);
177
+            content.put("startTime", startTime);
178
+            content.put("endTime", endTime);
179
+            
180
+            // 基础统计
181
+            content.put("totalAlarms", alarmData.get("totalAlarms"));
182
+            
183
+            // 按级别统计
184
+            content.put("levelStatistics", alarmData.get("levelStatistics"));
185
+            
186
+            // 按区域统计
187
+            content.put("areaAlarmStats", alarmData.get("areaAlarmStats"));
188
+            
189
+            // 每日趋势
190
+            content.put("dailyAlarmTrend", alarmData.get("dailyAlarmTrend"));
191
+            
192
+            // 计算最严重区域
193
+            List<Map<String, Object>> areaStats = (List<Map<String, Object>>) alarmData.get("areaAlarmStats");
194
+            if (areaStats != null && !areaStats.isEmpty()) {
195
+                String worstArea = areaStats.stream()
196
+                    .max(Comparator.comparingInt(area -> (int) area.get("alarmCount")))
197
+                    .map(area -> (String) area.get("area"))
198
+                    .orElse("N/A");
199
+                content.put("worstArea", worstArea);
200
+            }
201
+            
202
+            // 生成摘要
203
+            Map<String, Object> summary = new LinkedHashMap<>();
204
+            summary.put("totalAlarms", alarmData.get("totalAlarms"));
205
+            summary.put("areaCount", areaStats != null ? areaStats.size() : 0);
206
+            summary.put("worstArea", content.get("worstArea"));
207
+            
208
+            report.setContent(content);
209
+            report.setSummary(summary);
210
+            report.setStatus("GENERATED");
211
+            report.setGeneratedBy("bot_dev1");
212
+            
213
+            reportMapper.insert(report);
214
+            
215
+            log.info("生成报警统计报表成功: reportCode={}, period={}", report.getReportCode(), period);
216
+            
217
+            return report;
218
+            
219
+        } catch (Exception e) {
220
+            log.error("生成报警统计报表失败: {}", e.getMessage());
221
+            throw new RuntimeException("报警统计报表生成失败: " + e.getMessage());
222
+        }
223
+    }
224
+
225
+    /**
226
+     * 生成综合报表(包含所有类型)
227
+     */
228
+    public DataReport generateComprehensiveReport(String period, String startTime, String endTime) {
229
+        DataReport report = new DataReport();
230
+        report.setReportName("综合数据报表");
231
+        report.setReportCode("COMP-" + System.currentTimeMillis());
232
+        report.setReportType("comprehensive");
233
+        report.setDataType("all");
234
+        report.setArea("ALL");
235
+        
236
+        try {
237
+            // 设置时间范围
238
+            LocalDate start = startTime != null ? LocalDate.parse(startTime.substring(0, 10)) : LocalDate.now().minusDays(7);
239
+            LocalDate end = endTime != null ? LocalDate.parse(endTime.substring(0, 10)) : LocalDate.now();
240
+            report.setPeriodStart(start);
241
+            report.setPeriodEnd(end);
242
+            
243
+            // 获取各类数据
244
+            Map<String, Object> waterVolumeData = dataStatisticsService.getWaterVolumeSummary(startTime, endTime);
245
+            Map<String, Object> qualityData = dataStatisticsService.getWaterQualityRate(startTime, endTime);
246
+            Map<String, Object> alarmData = dataStatisticsService.getAlarmStatistics(startTime, endTime);
247
+            
248
+            Map<String, Object> content = new LinkedHashMap<>();
249
+            content.put("generatedAt", LocalDateTime.now());
250
+            content.put("period", period);
251
+            content.put("startTime", startTime);
252
+            content.put("endTime", endTime);
253
+            
254
+            // 水量汇总
255
+            Map<String, Object> waterSection = new LinkedHashMap<>();
256
+            waterSection.put("totalWaterSupply", waterVolumeData.get("totalSupply"));
257
+            waterSection.put("avgDailySupply", waterVolumeData.get("avgDailySupply"));
258
+            waterSection.put("areaWaterSupply", waterVolumeData.get("areaSupplyStats"));
259
+            content.put("waterVolumeSummary", waterSection);
260
+            
261
+            // 水质分析
262
+            Map<String, Object> qualitySection = new LinkedHashMap<>();
263
+            qualitySection.put("totalTests", qualityData.get("totalTests"));
264
+            qualitySection.put("passedTests", qualityData.get("passedTests"));
265
+            qualitySection.put("passRate", qualityData.get("passRate"));
266
+            qualitySection.put("areaQualityStats", qualityData.get("areaQualityStats"));
267
+            content.put("waterQualityAnalysis", qualitySection);
268
+            
269
+            // 报警统计
270
+            Map<String, Object> alarmSection = new LinkedHashMap<>();
271
+            alarmSection.put("totalAlarms", alarmData.get("totalAlarms"));
272
+            alarmSection.put("levelStatistics", alarmData.get("levelStatistics"));
273
+            alarmSection.put("areaAlarmStats", alarmData.get("areaAlarmStats"));
274
+            alarmSection.put("dailyAlarmTrend", alarmData.get("dailyAlarmTrend"));
275
+            content.put("alarmStatistics", alarmSection);
276
+            
277
+            // 综合评估
278
+            Map<String, Object> assessment = new LinkedHashMap<>();
279
+            assessment.put("overallRating", calculateOverallRating(waterVolumeData, qualityData, alarmData));
280
+            assessment.put("recommendations", generateRecommendations(waterVolumeData, qualityData, alarmData));
281
+            content.put("comprehensiveAssessment", assessment);
282
+            
283
+            // 生成摘要
284
+            Map<String, Object> summary = new LinkedHashMap<>();
285
+            summary.put("totalWaterSupply", waterVolumeData.get("totalSupply"));
286
+            summary.put("avgWaterQuality", qualityData.get("passRate"));
287
+            summary.put("totalAlarms", alarmData.get("totalAlarms"));
288
+            summary.put("overallRating", assessment.get("overallRating"));
289
+            
290
+            report.setContent(content);
291
+            report.setSummary(summary);
292
+            report.setStatus("GENERATED");
293
+            report.setGeneratedBy("bot_dev1");
294
+            
295
+            reportMapper.insert(report);
296
+            
297
+            log.info("生成综合报表成功: reportCode={}, period={}", report.getReportCode(), period);
298
+            
299
+            return report;
300
+            
301
+        } catch (Exception e) {
302
+            log.error("生成综合报表失败: {}", e.getMessage());
303
+            throw new RuntimeException("综合报表生成失败: " + e.getMessage());
304
+        }
305
+    }
306
+
307
+    /**
308
+     * 自动生成报表(原有功能保留)
24 309
      */
25 310
     public DataReport generateReport(String reportType, String period) {
26 311
         DataReport report = new DataReport();
@@ -67,6 +352,73 @@ public class ReportService {
67 352
         reportMapper.insert(report);
68 353
         return report;
69 354
     }
355
+    
356
+    /**
357
+     * 计算综合评分
358
+     */
359
+    private String calculateOverallRating(Map<String, Object> waterData, Map<String, Object> qualityData, Map<String, Object> alarmData) {
360
+        double waterScore = calculateWaterScore(waterData);
361
+        double qualityScore = calculateQualityScore(qualityData);
362
+        double alarmScore = calculateAlarmScore(alarmData);
363
+        
364
+        double totalScore = (waterScore + qualityScore + alarmScore) / 3;
365
+        
366
+        if (totalScore >= 90) return "优秀";
367
+        if (totalScore >= 80) return "良好";
368
+        if (totalScore >= 70) return "一般";
369
+        if (totalScore >= 60) return "较差";
370
+        return "差";
371
+    }
372
+    
373
+    private double calculateWaterScore(Map<String, Object> waterData) {
374
+        Double totalSupply = (Double) waterData.getOrDefault("totalSupply", 0.0);
375
+        // 这里可以根据实际业务逻辑计算水量分数
376
+        return Math.min(100, totalSupply / 50000 * 100); // 假设5万为满分
377
+    }
378
+    
379
+    private double calculateQualityScore(Map<String, Object> qualityData) {
380
+        String passRateStr = (String) qualityData.getOrDefault("passRate", "0%");
381
+        double passRate = Double.parseDouble(passRateStr.replace("%", ""));
382
+        return passRate; // 水质合格率直接作为分数
383
+    }
384
+    
385
+    private double calculateAlarmScore(Map<String, Object> alarmData) {
386
+        Integer totalAlarms = (Integer) alarmData.getOrDefault("totalAlarms", 0);
387
+        // 报警越少分数越高
388
+        return Math.max(0, 100 - totalAlarms * 2);
389
+    }
390
+    
391
+    /**
392
+     * 生成改进建议
393
+     */
394
+    private List<String> generateRecommendations(Map<String, Object> waterData, Map<String, Object> qualityData, Map<String, Object> alarmData) {
395
+        List<String> recommendations = new ArrayList<>();
396
+        
397
+        // 基于水量数据的建议
398
+        Double totalSupply = (Double) waterData.getOrDefault("totalSupply", 0.0);
399
+        if (totalSupply < 30000) {
400
+            recommendations.add("建议增加供水能力,当前供水量偏低");
401
+        }
402
+        
403
+        // 基于水质数据的建议
404
+        String passRateStr = (String) qualityData.getOrDefault("passRate", "0%");
405
+        double passRate = Double.parseDouble(passRateStr.replace("%", ""));
406
+        if (passRate < 95) {
407
+            recommendations.add("建议加强水质监测和处理,当前合格率偏低");
408
+        }
409
+        
410
+        // 基于报警数据的建议
411
+        Integer totalAlarms = (Integer) alarmData.getOrDefault("totalAlarms", 0);
412
+        if (totalAlarms > 20) {
413
+            recommendations.add("建议检查设备状态,报警频率过高");
414
+        }
415
+        
416
+        if (recommendations.isEmpty()) {
417
+            recommendations.add("运行状态良好,继续保持");
418
+        }
419
+        
420
+        return recommendations;
421
+    }
70 422
 
71 423
     /**
72 424
      * 获取报表列表
@@ -95,6 +447,16 @@ public class ReportService {
95 447
         report.setPublishedTime(LocalDateTime.now());
96 448
         reportMapper.updateById(report);
97 449
     }
450
+    
451
+    /**
452
+     * 获取报表ID(从报表代码)
453
+     */
454
+    public Long getReportIdByCode(String reportCode) {
455
+        LambdaQueryWrapper<DataReport> wrapper = new LambdaQueryWrapper<>();
456
+        wrapper.eq(DataReport::getReportCode, reportCode);
457
+        DataReport report = reportMapper.selectOne(wrapper);
458
+        return report != null ? report.getId() : null;
459
+    }
98 460
 
99 461
     /**
100 462
      * 模板管理