ソースを参照

feat: 实现 Issue 13 - 数据中心与配置中心功能

- DATA-01 数据查看:自动监测数据+人工填报数据统一查看,只读不可修改;图表/报表形式展示,支持历史回溯
- DATA-02 设备管理:供水工程设备列表,按设备名称/类型/运行时间/位置查询
- DATA-03 阈值管理:水压、流量等各类实时监测数据触发报警阈值设定与编辑
- DATA-04 信息发布:预报发布、预警发布,支持上级单位/上游水库信息发布,发布后在调度平台同步展示
- DATA-05 报表统计:水量报表、水压报表、流量报表、水质报表、液位报表
- CFG-01 指令配置:配置供水工程调度下发指令,定义常用调度指令内容,用于快速下发
- CFG-02 限值配置:配置管网压力、清水池液位等监测超限数值
- CFG-03 报警方案:新增报警方案(报警名称/通知方案/报警规则/报警级别),关联监测点
- CFG-04 值班安排:值班方案制定/值班班组配置/值班班次配置,按部门管理

技术实现:
- 新增 DataCenterController 统一管理数据中心与配置中心功能
- 新增 DataCenterService 实现核心业务逻辑
- 新增 AlarmSchemeService 实现报警方案管理
- 新增 DutyScheduleService 实现值班安排管理
- 新增相关 DTO、VO、Entity、Mapper 等支持类

#13 #数据中心 #配置中心
bot_dev1 4 日 前
コミット
dcd1cd758c

+ 365
- 0
wm-production/src/main/java/com/water/production/controller/DataCenterController.java ファイルの表示

1
+package com.water.production.controller;
2
+
3
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4
+import com.water.common.core.result.R;
5
+import com.water.production.dto.DataCenterQueryRequest;
6
+import com.water.production.dto.DataExportRequest;
7
+import com.water.production.entity.AlarmScheme;
8
+import com.water.production.entity.DispatchCommand;
9
+import com.water.production.entity.Threshold;
10
+import com.water.production.service.*;
11
+import io.swagger.v3.oas.annotations.Operation;
12
+import io.swagger.v3.oas.annotations.tags.Tag;
13
+import lombok.RequiredArgsConstructor;
14
+import org.springframework.web.bind.annotation.*;
15
+
16
+import java.time.LocalDate;
17
+import java.util.List;
18
+import java.util.Map;
19
+
20
+/**
21
+ * 数据中心与配置中心 Controller
22
+ * 对应 Issue 13: 供水生产管理平台 — 数据中心与配置中心
23
+ */
24
+@Tag(name = "数据中心与配置中心")
25
+@RestController
26
+@RequestMapping("/api/production/data-center")
27
+@RequiredArgsConstructor
28
+public class DataCenterController {
29
+
30
+    private final MonitorListService monitorListService;
31
+    private final DataCenterService dataCenterService;
32
+    private final ThresholdService thresholdService;
33
+    private final AlarmSchemeService alarmSchemeService;
34
+    private final DispatchCommandService dispatchCommandService;
35
+    private final DutyScheduleService dutyScheduleService;
36
+
37
+    // ========== DATA-01 数据查看 ==========
38
+    @Operation(summary = "自动+人工数据统一查看(只读)")
39
+    @GetMapping("/data-view")
40
+    public R<Map<String, Object>> dataView(DataCenterQueryRequest request) {
41
+        Map<String, Object> dataView = dataCenterService.getDataView(request);
42
+        return R.ok(dataView);
43
+    }
44
+
45
+    @Operation(summary = "获取历史数据(支持回溯)")
46
+    @GetMapping("/history")
47
+    public R<List<Map<String, Object>>> getHistoryData(
48
+            @RequestParam String dataType,
49
+            @RequestParam(required = false) String area,
50
+            @RequestParam String startTime,
51
+            @RequestParam String endTime) {
52
+        List<Map<String, Object>> historyData = dataCenterService.getHistoryData(dataType, area, startTime, endTime);
53
+        return R.ok(historyData);
54
+    }
55
+
56
+    @Operation(summary = "数据图表展示")
57
+    @GetMapping("/charts")
58
+    public R<Map<String, Object>> getChartData(
59
+            @RequestParam String chartType,
60
+            @RequestParam String period,
61
+            @RequestParam(required = false) String area,
62
+            @RequestParam(required = false) String deviceType) {
63
+        Map<String, Object> chartData = dataCenterService.getChartData(chartType, period, area, deviceType);
64
+        return R.ok(chartData);
65
+    }
66
+
67
+    // ========== DATA-02 设备管理 ==========
68
+    @Operation(summary = "设备列表(按名称/类型/时间/位置查询)")
69
+    @GetMapping("/devices")
70
+    public R<Page<Map<String, Object>>> listDevices(
71
+            @RequestParam(defaultValue = "1") int page,
72
+            @RequestParam(defaultValue = "10") int size,
73
+            @RequestParam(required = false) String deviceName,
74
+            @RequestParam(required = false) String deviceType,
75
+            @RequestParam(required = false) String area,
76
+            @RequestParam(required = false) String location,
77
+            @RequestParam(required = false) String status) {
78
+        Map<String, Object> deviceList = monitorListService.queryDeviceList(
79
+                deviceName, deviceType, area, location, status, page, size);
80
+        return R.ok((Page<Map<String, Object>>) deviceList.get("records"));
81
+    }
82
+
83
+    @Operation(summary = "设备详情")
84
+    @GetMapping("/devices/{deviceId}")
85
+    public R<Map<String, Object>> deviceDetail(@PathVariable Long deviceId) {
86
+        Map<String, Object> detail = monitorListService.getDeviceDetail(deviceId);
87
+        return R.ok(detail);
88
+    }
89
+
90
+    @Operation(summary = "获取设备类型列表")
91
+    @GetMapping("/device-types")
92
+    public R<List<String>> getDeviceTypes() {
93
+        return R.ok(monitorListService.getDeviceTypeList());
94
+    }
95
+
96
+    @Operation(summary = "获取区域列表")
97
+    @GetMapping("/areas")
98
+    public R<List<String>> getAreas() {
99
+        return R.ok(monitorListService.getAreaList());
100
+    }
101
+
102
+    // ========== DATA-03 阈值管理 ==========
103
+    @Operation(summary = "阈值配置列表")
104
+    @GetMapping("/thresholds")
105
+    public R<Page<Threshold>> getThresholds(
106
+            @RequestParam(defaultValue = "1") int page,
107
+            @RequestParam(defaultValue = "10") int size,
108
+            @RequestParam(required = false) String metricKey,
109
+            @RequestParam(required = false) String area) {
110
+        return R.ok(thresholdService.pageThresholds(page, size, metricKey, area));
111
+    }
112
+
113
+    @Operation(summary = "获取阈值详情")
114
+    @GetMapping("/thresholds/{id}")
115
+    public R<Threshold> getThresholdDetail(@PathVariable Long id) {
116
+        return R.ok(thresholdService.getThresholdById(id));
117
+    }
118
+
119
+    @Operation(summary = "创建阈值配置")
120
+    @PostMapping("/thresholds")
121
+    public R<Threshold> createThreshold(@RequestBody Threshold threshold) {
122
+        return R.ok(thresholdService.createThreshold(threshold));
123
+    }
124
+
125
+    @Operation(summary = "更新阈值配置")
126
+    @PutMapping("/thresholds/{id}")
127
+    public R<String> updateThreshold(@PathVariable Long id, @RequestBody Threshold threshold) {
128
+        thresholdService.updateThreshold(threshold);
129
+        return R.ok("更新成功");
130
+    }
131
+
132
+    @Operation(summary = "删除阈值配置")
133
+    @DeleteMapping("/thresholds/{id}")
134
+    public R<String> deleteThreshold(@PathVariable Long id) {
135
+        thresholdService.deleteThreshold(id);
136
+        return R.ok("删除成功");
137
+    }
138
+
139
+    // ========== DATA-04 信息发布 ==========
140
+    @Operation(summary = "发布预报信息")
141
+    @PostMapping("/forecast/publish")
142
+    public R<String> publishForecast(@RequestParam String title,
143
+                                    @RequestParam String content,
144
+                                    @RequestParam(required = false) String area,
145
+                                    @RequestParam Long publisherId) {
146
+        dataCenterService.publishForecast(title, content, area, publisherId);
147
+        return R.ok("预报已发布");
148
+    }
149
+
150
+    @Operation(summary = "发布预警信息")
151
+    @PostMapping("/warning/publish")
152
+    public R<String> publishWarning(@RequestParam String title,
153
+                                   @RequestParam String content,
154
+                                   @RequestParam(required = false) String area,
155
+                                   @RequestParam Long publisherId) {
156
+        dataCenterService.publishWarning(title, content, area, publisherId);
157
+        return R.ok("预警已发布");
158
+    }
159
+
160
+    @Operation(summary = "获取信息发布列表")
161
+    @GetMapping("/info-releases")
162
+    public R<List<Map<String, Object>>> getInfoReleases(
163
+            @RequestParam(required = false) String type,
164
+            @RequestParam(required = false) String area) {
165
+        List<Map<String, Object>> releases = dataCenterService.getInfoReleases(type, area);
166
+        return R.ok(releases);
167
+    }
168
+
169
+    // ========== DATA-05 报表统计 ==========
170
+    @Operation(summary = "水量报表")
171
+    @GetMapping("/reports/water-volume")
172
+    public R<Map<String, Object>> getWaterVolumeReport(
173
+            @RequestParam String period,
174
+            @RequestParam(required = false) String area) {
175
+        Map<String, Object> report = dataCenterService.generateReport("water_volume", period, area);
176
+        return R.ok(report);
177
+    }
178
+
179
+    @Operation(summary = "水压报表")
180
+    @GetMapping("/reports/water-pressure")
181
+    public R<Map<String, Object>> getWaterPressureReport(
182
+            @RequestParam String period,
183
+            @RequestParam(required = false) String area) {
184
+        Map<String, Object> report = dataCenterService.generateReport("water_pressure", period, area);
185
+        return R.ok(report);
186
+    }
187
+
188
+    @Operation(summary = "流量报表")
189
+    @GetMapping("/reports/flow-rate")
190
+    public R<Map<String, Object>> getFlowRateReport(
191
+            @RequestParam String period,
192
+            @RequestParam(required = false) String area) {
193
+        Map<String, Object> report = dataCenterService.generateReport("flow_rate", period, area);
194
+        return R.ok(report);
195
+    }
196
+
197
+    @Operation(summary = "水质报表")
198
+    @GetMapping("/reports/water-quality")
199
+    public R<Map<String, Object>> getWaterQualityReport(
200
+            @RequestParam String period,
201
+            @RequestParam(required = false) String area) {
202
+        Map<String, Object> report = dataCenterService.generateReport("water_quality", period, area);
203
+        return R.ok(report);
204
+    }
205
+
206
+    @Operation(summary = "液位报表")
207
+    @GetMapping("/reports/water-level")
208
+    public R<Map<String, Object>> getWaterLevelReport(
209
+            @RequestParam String period,
210
+            @RequestParam(required = false) String area) {
211
+        Map<String, Object> report = dataCenterService.generateReport("water_level", period, area);
212
+        return R.ok(report);
213
+    }
214
+
215
+    // ========== CFG-01 指令配置 ==========
216
+    @Operation(summary = "调度指令配置列表")
217
+    @GetMapping("/command-templates")
218
+    public R<List<DispatchCommand>> getCommandTemplates() {
219
+        return R.ok(dispatchCommandService.getTemplates());
220
+    }
221
+
222
+    @Operation(summary = "创建指令模板")
223
+    @PostMapping("/command-templates")
224
+    public R<DispatchCommand> createCommandTemplate(@RequestBody DispatchCommand command) {
225
+        command.setTemplate(true);
226
+        return R.ok(dispatchCommandService.createCommand(command));
227
+    }
228
+
229
+    @Operation(summary = "更新指令模板")
230
+    @PutMapping("/command-templates/{id}")
231
+    public R<String> updateCommandTemplate(@PathVariable Long id, @RequestBody DispatchCommand command) {
232
+        command.setId(id);
233
+        command.setTemplate(true);
234
+        dispatchCommandService.updateCommand(command);
235
+        return R.ok("更新成功");
236
+    }
237
+
238
+    @Operation(summary = "使用模板快速创建指令")
239
+    @PostMapping("/command-templates/{id}/use")
240
+    public R<DispatchCommand> useTemplate(@PathVariable Long id,
241
+                                          @RequestParam Long issuedBy,
242
+                                          @RequestParam(required = false) String remark) {
243
+        DispatchCommand template = dispatchCommandService.getCommandById(id);
244
+        DispatchCommand command = dispatchCommandService.createFromTemplate(template, issuedBy, remark);
245
+        return R.ok(command);
246
+    }
247
+
248
+    // ========== CFG-02 限值配置 ==========
249
+    @Operation(summary = "获取所有限值配置")
250
+    @GetMapping("/limit-configs")
251
+    public R<Map<String, Object>> getLimitConfigs() {
252
+        Map<String, Object> configs = dataCenterService.getAllLimitConfigs();
253
+        return R.ok(configs);
254
+    }
255
+
256
+    @Operation(summary = "更新管网压力限值")
257
+    @PutMapping("/limit-configs/pressure")
258
+    public R<String> updatePressureLimits(@RequestParam String area,
259
+                                         @RequestParam Double minPressure,
260
+                                         @RequestParam Double maxPressure) {
261
+        dataCenterService.updatePressureLimits(area, minPressure, maxPressure);
262
+        return R.ok("压力限值已更新");
263
+    }
264
+
265
+    @Operation(summary = "更新清水池液位限值")
266
+    @PutMapping("/limit-configs/level")
267
+    public R<String> updateLevelLimits(@RequestParam String area,
268
+                                       @RequestParam Double minLevel,
269
+                                       @RequestParam Double maxLevel) {
270
+        dataCenterService.updateLevelLimits(area, minLevel, maxLevel);
271
+        return R.ok("液位限值已更新");
272
+    }
273
+
274
+    // ========== CFG-03 报警方案 ==========
275
+    @Operation(summary = "报警方案列表")
276
+    @GetMapping("/alarm-schemes")
277
+    public R<Page<AlarmScheme>> getAlarmSchemes(
278
+            @RequestParam(defaultValue = "1") int page,
279
+            @RequestParam(defaultValue = "10") int size,
280
+            @RequestParam(required = false) String alarmName,
281
+            @RequestParam(required = false) String alarmLevel) {
282
+        return R.ok(alarmSchemeService.pageSchemes(page, size, alarmName, alarmLevel));
283
+    }
284
+
285
+    @Operation(summary = "创建报警方案")
286
+    @PostMapping("/alarm-schemes")
287
+    public R<AlarmScheme> createAlarmScheme(@RequestBody AlarmScheme scheme) {
288
+        return R.ok(alarmSchemeService.createScheme(scheme));
289
+    }
290
+
291
+    @Operation(summary = "更新报警方案")
292
+    @PutMapping("/alarm-schemes/{id}")
293
+    public R<String> updateAlarmScheme(@PathVariable Long id, @RequestBody AlarmScheme scheme) {
294
+        alarmSchemeService.updateScheme(id, scheme);
295
+        return R.ok("更新成功");
296
+    }
297
+
298
+    @Operation(summary = "删除报警方案")
299
+    @DeleteMapping("/alarm-schemes/{id}")
300
+    public R<String> deleteAlarmScheme(@PathVariable Long id) {
301
+        alarmSchemeService.deleteScheme(id);
302
+        return R.ok("删除成功");
303
+    }
304
+
305
+    // ========== CFG-04 值班安排 ==========
306
+    @Operation(summary = "值班方案列表")
307
+    @GetMapping("/duty-schemes")
308
+    public R<List<Map<String, Object>>> getDutySchemes() {
309
+        List<Map<String, Object>> schemes = dutyScheduleService.getDutySchemes();
310
+        return R.ok(schemes);
311
+    }
312
+
313
+    @Operation(summary = "创建值班方案")
314
+    @PostMapping("/duty-schemes")
315
+    public R<Map<String, Object>> createDutyScheme(@RequestBody Map<String, Object> scheme) {
316
+        Map<String, Object> result = dutyScheduleService.createDutyScheme(scheme);
317
+        return R.ok(result);
318
+    }
319
+
320
+    @Operation(summary = "值班班次配置")
321
+    @GetMapping("/duty-shifts")
322
+    public R<List<Map<String, Object>>> getDutyShifts() {
323
+        List<Map<String, Object>> shifts = dutyScheduleService.getDutyShifts();
324
+        return R.ok(shifts);
325
+    }
326
+
327
+    @Operation(summary = "创建值班班次")
328
+    @PostMapping("/duty-shifts")
329
+    public R<Map<String, Object>> createDutyShift(@RequestBody Map<String, Object> shift) {
330
+        Map<String, Object> result = dutyScheduleService.createDutyShift(shift);
331
+        return R.ok(result);
332
+    }
333
+
334
+    @Operation(summary = "获取今日值班安排")
335
+    @GetMapping("/duty/today")
336
+    public R<List<Map<String, Object>>> getTodayDuty() {
337
+        List<Map<String, Object>> todayDuty = dutyScheduleService.getTodayDuty();
338
+        return R.ok(todayDuty);
339
+    }
340
+
341
+    @Operation(summary = "值班安排(按日期)")
342
+    @GetMapping("/duty/schedule")
343
+    public R<List<Map<String, Object>>> getDutySchedule(
344
+            @RequestParam LocalDate date,
345
+            @RequestParam(required = false) String shiftType) {
346
+        List<Map<String, Object>> schedule = dutyScheduleService.getDutyScheduleByDate(date, shiftType);
347
+        return R.ok(schedule);
348
+    }
349
+
350
+    // ========== 数据导出 ==========
351
+    @Operation(summary = "导出数据报表")
352
+    @PostMapping("/export")
353
+    public R<byte[]> exportData(@RequestBody DataExportRequest request) {
354
+        byte[] data = dataCenterService.exportData(request);
355
+        return R.ok(data);
356
+    }
357
+
358
+    // ========== 统计概览 ==========
359
+    @Operation(summary = "数据中心概览统计")
360
+    @GetMapping("/overview")
361
+    public R<Map<String, Object>> getDataCenterOverview() {
362
+        Map<String, Object> overview = dataCenterService.getOverview();
363
+        return R.ok(overview);
364
+    }
365
+}

+ 48
- 0
wm-production/src/main/java/com/water/production/dto/DataCenterQueryRequest.java ファイルの表示

1
+package com.water.production.dto;
2
+
3
+import lombok.Data;
4
+import java.time.LocalDateTime;
5
+
6
+/**
7
+ * 数据中心查询请求
8
+ * 对应 Issue 13: 供水生产管理平台 — 数据中心与配置中心
9
+ */
10
+@Data
11
+public class DataCenterQueryRequest {
12
+    
13
+    /** 查询区域 */
14
+    private String area;
15
+    
16
+    /** 设备类型 */
17
+    private String deviceType;
18
+    
19
+    /** 数据类型 */
20
+    private String dataType;
21
+    
22
+    /** 开始时间 */
23
+    private LocalDateTime startTime;
24
+    
25
+    /** 结束时间 */
26
+    private LocalDateTime endTime;
27
+    
28
+    /** 关键字搜索 */
29
+    private String keyword;
30
+    
31
+    /** 设备状态 */
32
+    private String status;
33
+    
34
+    /** 报警级别 */
35
+    private String alertLevel;
36
+    
37
+    /** 页码 */
38
+    private Integer page = 1;
39
+    
40
+    /** 页大小 */
41
+    private Integer size = 10;
42
+    
43
+    /** 排序字段 */
44
+    private String sortBy = "created_at";
45
+    
46
+    /** 排序方向 */
47
+    private String sortOrder = "desc";
48
+}

+ 45
- 0
wm-production/src/main/java/com/water/production/dto/DataExportRequest.java ファイルの表示

1
+package com.water.production.dto;
2
+
3
+import lombok.Data;
4
+import java.time.LocalDateTime;
5
+
6
+/**
7
+ * 数据导出请求
8
+ * 对应 Issue 13: 供水生产管理平台 — 数据中心与配置中心
9
+ */
10
+@Data
11
+public class DataExportRequest {
12
+    
13
+    /** 导出类型 */
14
+    private String exportType = "excel";
15
+    
16
+    /** 区域 */
17
+    private String area;
18
+    
19
+    /** 设备类型 */
20
+    private String deviceType;
21
+    
22
+    /** 数据类型 */
23
+    private String dataType;
24
+    
25
+    /** 开始时间 */
26
+    private LocalDateTime startTime;
27
+    
28
+    /** 结束时间 */
29
+    private LocalDateTime endTime;
30
+    
31
+    /** 报表类型 */
32
+    private String reportType;
33
+    
34
+    /** 时间周期 */
35
+    private String period;
36
+    
37
+    /** 包含图表 */
38
+    private Boolean includeCharts = false;
39
+    
40
+    /** 文件名 */
41
+    private String fileName;
42
+    
43
+    /** 模板ID */
44
+    private Long templateId;
45
+}

+ 63
- 0
wm-production/src/main/java/com/water/production/entity/AlarmScheme.java ファイルの表示

1
+package com.water.production.entity;
2
+
3
+import com.baomidou.mybatisplus.annotation.*;
4
+import lombok.Data;
5
+
6
+import java.time.LocalDateTime;
7
+
8
+/**
9
+ * 报警方案
10
+ * 对应 Issue 13: CFG-03 报警方案
11
+ */
12
+@Data
13
+@TableName("prod_alarm_scheme")
14
+public class AlarmScheme {
15
+    
16
+    @TableId(type = IdType.AUTO)
17
+    private Long id;
18
+    
19
+    /** 报警方案名称 */
20
+    private String schemeName;
21
+    
22
+    /** 报警级别 */
23
+    private String alertLevel;
24
+    
25
+    /** 方案描述 */
26
+    private String description;
27
+    
28
+    /** 通知方案ID */
29
+    private Long notificationSchemeId;
30
+    
31
+    /** 报警规则JSON */
32
+    private String ruleJson;
33
+    
34
+    /** 关联监测点(逗号分隔的设备ID) */
35
+    private String deviceIds;
36
+    
37
+    /** 关联区域 */
38
+    private String area;
39
+    
40
+    /** 是否启用 */
41
+    private Boolean enabled = true;
42
+    
43
+    /** 创建人ID */
44
+    private Long createdBy;
45
+    
46
+    /** 创建人姓名 */
47
+    private String createdByName;
48
+    
49
+    /** 创建时间 */
50
+    private LocalDateTime createdAt;
51
+    
52
+    /** 更新人ID */
53
+    private Long updatedBy;
54
+    
55
+    /** 更新人姓名 */
56
+    private String updatedByName;
57
+    
58
+    /** 更新时间 */
59
+    private LocalDateTime updatedAt;
60
+    
61
+    /** 备注 */
62
+    private String remark;
63
+}

+ 14
- 0
wm-production/src/main/java/com/water/production/mapper/AlarmSchemeMapper.java ファイルの表示

1
+package com.water.production.mapper;
2
+
3
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4
+import com.water.production.entity.AlarmScheme;
5
+import org.apache.ibatis.annotations.Mapper;
6
+
7
+/**
8
+ * 报警方案 Mapper
9
+ * 对应 Issue 13: CFG-03 报警方案
10
+ */
11
+@Mapper
12
+public interface AlarmSchemeMapper extends BaseMapper<AlarmScheme> {
13
+    
14
+}

+ 63
- 0
wm-production/src/main/java/com/water/production/service/AlarmSchemeService.java ファイルの表示

1
+package com.water.production.service;
2
+
3
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4
+import com.water.production.entity.AlarmScheme;
5
+import com.water.production.vo.AlarmSchemeQueryVO;
6
+import java.util.List;
7
+
8
+/**
9
+ * 报警方案服务接口
10
+ * 对应 Issue 13: CFG-03 报警方案
11
+ */
12
+public interface AlarmSchemeService {
13
+    
14
+    /**
15
+     * 创建报警方案
16
+     */
17
+    AlarmScheme createScheme(AlarmScheme scheme);
18
+    
19
+    /**
20
+     * 更新报警方案
21
+     */
22
+    void updateScheme(Long id, AlarmScheme scheme);
23
+    
24
+    /**
25
+     * 删除报警方案
26
+     */
27
+    void deleteScheme(Long id);
28
+    
29
+    /**
30
+     * 获取报警方案详情
31
+     */
32
+    AlarmScheme getSchemeById(Long id);
33
+    
34
+    /**
35
+     * 分页查询报警方案
36
+     */
37
+    Page<AlarmScheme> pageSchemes(int page, int size, String alarmName, String alertLevel);
38
+    
39
+    /**
40
+     * 根据查询条件获取报警方案列表
41
+     */
42
+    List<AlarmScheme> getSchemesByQuery(AlarmSchemeQueryVO queryVO);
43
+    
44
+    /**
45
+     * 启用/禁用报警方案
46
+     */
47
+    void toggleSchemeStatus(Long id, Boolean enabled);
48
+    
49
+    /**
50
+     * 根据区域获取报警方案
51
+     */
52
+    List<AlarmScheme> getSchemesByArea(String area);
53
+    
54
+    /**
55
+     * 根据设备ID获取关联的报警方案
56
+     */
57
+    List<AlarmScheme> getSchemesByDeviceId(Long deviceId);
58
+    
59
+    /**
60
+     * 复制报警方案
61
+     */
62
+    AlarmScheme copyScheme(Long id);
63
+}

+ 95
- 0
wm-production/src/main/java/com/water/production/service/DutyScheduleService.java ファイルの表示

1
+package com.water.production.service;
2
+
3
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4
+import com.water.production.entity.DutySchedule;
5
+import com.water.production.vo.DutyScheduleQueryVO;
6
+import java.time.LocalDate;
7
+import java.util.List;
8
+import java.util.Map;
9
+
10
+/**
11
+ * 值班安排服务接口
12
+ * 对应 Issue 13: CFG-04 值班安排
13
+ */
14
+public interface DutyScheduleService {
15
+    
16
+    /**
17
+     * 创建值班安排
18
+     */
19
+    DutySchedule createDutySchedule(DutySchedule schedule);
20
+    
21
+    /**
22
+     * 更新值班安排
23
+     */
24
+    void updateDutySchedule(DutySchedule schedule);
25
+    
26
+    /**
27
+     * 删除值班安排
28
+     */
29
+    void deleteDutySchedule(Long id);
30
+    
31
+    /**
32
+     * 获取值班安排详情
33
+     */
34
+    DutySchedule getDutyScheduleById(Long id);
35
+    
36
+    /**
37
+     * 分页查询值班安排
38
+     */
39
+    Page<DutySchedule> pageDutySchedules(DutyScheduleQueryVO queryVO);
40
+    
41
+    /**
42
+     * 获取今日值班安排
43
+     */
44
+    List<DutySchedule> getTodayDutySchedules();
45
+    
46
+    /**
47
+     * 根据日期获取值班安排
48
+     */
49
+    List<DutySchedule> getDutySchedulesByDate(LocalDate date);
50
+    
51
+    /**
52
+     * 根据日期和班次类型获取值班安排
53
+     */
54
+    List<DutySchedule> getDutySchedulesByDateAndShift(LocalDate date, String shiftType);
55
+    
56
+    /**
57
+     * 获取值班人员列表
58
+     */
59
+    List<Map<String, Object>> getDutyPersonnel();
60
+    
61
+    /**
62
+     * 获取值班方案列表
63
+     */
64
+    List<Map<String, Object>> getDutySchemes();
65
+    
66
+    /**
67
+     * 创建值班方案
68
+     */
69
+    Map<String, Object> createDutyScheme(Map<String, Object> scheme);
70
+    
71
+    /**
72
+     * 获取值班班次配置
73
+     */
74
+    List<Map<String, Object>> getDutyShifts();
75
+    
76
+    /**
77
+     * 创建值班班次
78
+     */
79
+    Map<String, Object> createDutyShift(Map<String, Object> shift);
80
+    
81
+    /**
82
+     * 启用/禁用值班安排
83
+     */
84
+    void toggleDutyScheduleStatus(Long id, Integer status);
85
+    
86
+    /**
87
+     * 检查时间冲突
88
+     */
89
+    boolean hasTimeConflict(Long userId, LocalDate date, String shiftType, Long excludeId);
90
+    
91
+    /**
92
+     * 获取值班统计
93
+     */
94
+    Map<String, Object> getDutyStatistics(LocalDate startDate, LocalDate endDate);
95
+}

+ 170
- 0
wm-production/src/main/java/com/water/production/service/impl/AlarmSchemeServiceImpl.java ファイルの表示

1
+package com.water.production.service.impl;
2
+
3
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
4
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
5
+import com.water.production.entity.AlarmScheme;
6
+import com.water.production.mapper.AlarmSchemeMapper;
7
+import com.water.production.service.AlarmSchemeService;
8
+import com.water.production.vo.AlarmSchemeQueryVO;
9
+import lombok.RequiredArgsConstructor;
10
+import org.springframework.stereotype.Service;
11
+import org.springframework.transaction.annotation.Transactional;
12
+
13
+import java.time.LocalDateTime;
14
+import java.util.List;
15
+
16
+/**
17
+ * 报警方案服务实现
18
+ * 对应 Issue 13: CFG-03 报警方案
19
+ */
20
+@Service
21
+@RequiredArgsConstructor
22
+public class AlarmSchemeServiceImpl implements AlarmSchemeService {
23
+
24
+    private final AlarmSchemeMapper alarmSchemeMapper;
25
+
26
+    @Override
27
+    @Transactional(rollbackFor = Exception.class)
28
+    public AlarmScheme createScheme(AlarmScheme scheme) {
29
+        scheme.setCreatedAt(LocalDateTime.now());
30
+        scheme.setUpdatedAt(LocalDateTime.now());
31
+        alarmSchemeMapper.insert(scheme);
32
+        return scheme;
33
+    }
34
+
35
+    @Override
36
+    @Transactional(rollbackFor = Exception.class)
37
+    public void updateScheme(Long id, AlarmScheme scheme) {
38
+        AlarmScheme existingScheme = alarmSchemeMapper.selectById(id);
39
+        if (existingScheme == null) {
40
+            throw new RuntimeException("报警方案不存在");
41
+        }
42
+        
43
+        scheme.setId(id);
44
+        scheme.setUpdatedAt(LocalDateTime.now());
45
+        alarmSchemeMapper.updateById(scheme);
46
+    }
47
+
48
+    @Override
49
+    @Transactional(rollbackFor = Exception.class)
50
+    public void deleteScheme(Long id) {
51
+        AlarmScheme scheme = alarmSchemeMapper.selectById(id);
52
+        if (scheme == null) {
53
+            throw new RuntimeException("报警方案不存在");
54
+        }
55
+        
56
+        // 检查是否有关联的报警规则在使用
57
+        int关联规则数 = alarmSchemeMapper.selectCount(
58
+            new QueryWrapper<AlarmScheme>()
59
+                .eq("related_scheme_id", id)
60
+        );
61
+        
62
+        if (关联规则数 > 0) {
63
+            throw new RuntimeException("该报警方案已被引用,无法删除");
64
+        }
65
+        
66
+        alarmSchemeMapper.deleteById(id);
67
+    }
68
+
69
+    @Override
70
+    public AlarmScheme getSchemeById(Long id) {
71
+        return alarmSchemeMapper.selectById(id);
72
+    }
73
+
74
+    @Override
75
+    public Page<AlarmScheme> pageSchemes(int page, int size, String alarmName, String alertLevel) {
76
+        Page<AlarmScheme> pageParam = new Page<>(page, size);
77
+        QueryWrapper<AlarmScheme> queryWrapper = new QueryWrapper<>();
78
+        
79
+        if (alarmName != null && !alarmName.trim().isEmpty()) {
80
+            queryWrapper.like("scheme_name", alarmName);
81
+        }
82
+        
83
+        if (alertLevel != null && !alertLevel.trim().isEmpty()) {
84
+            queryWrapper.eq("alert_level", alertLevel);
85
+        }
86
+        
87
+        queryWrapper.orderByDesc("updated_at");
88
+        return alarmSchemeMapper.selectPage(pageParam, queryWrapper);
89
+    }
90
+
91
+    @Override
92
+    public List<AlarmScheme> getSchemesByQuery(AlarmSchemeQueryVO queryVO) {
93
+        QueryWrapper<AlarmScheme> queryWrapper = new QueryWrapper<>();
94
+        
95
+        if (queryVO.getSchemeName() != null && !queryVO.getSchemeName().trim().isEmpty()) {
96
+            queryWrapper.like("scheme_name", queryVO.getSchemeName());
97
+        }
98
+        
99
+        if (queryVO.getAlertLevel() != null && !queryVO.getAlertLevel().trim().isEmpty()) {
100
+            queryWrapper.eq("alert_level", queryVO.getAlertLevel());
101
+        }
102
+        
103
+        if (queryVO.getArea() != null && !queryVO.getArea().trim().isEmpty()) {
104
+            queryWrapper.eq("area", queryVO.getArea());
105
+        }
106
+        
107
+        if (queryVO.getEnabled() != null) {
108
+            queryWrapper.eq("enabled", queryVO.getEnabled());
109
+        }
110
+        
111
+        queryWrapper.orderByDesc("updated_at");
112
+        return alarmSchemeMapper.selectList(queryWrapper);
113
+    }
114
+
115
+    @Override
116
+    @Transactional(rollbackFor = Exception.class)
117
+    public void toggleSchemeStatus(Long id, Boolean enabled) {
118
+        AlarmScheme scheme = alarmSchemeMapper.selectById(id);
119
+        if (scheme == null) {
120
+            throw new RuntimeException("报警方案不存在");
121
+        }
122
+        
123
+        scheme.setEnabled(enabled);
124
+        scheme.setUpdatedAt(LocalDateTime.now());
125
+        alarmSchemeMapper.updateById(scheme);
126
+    }
127
+
128
+    @Override
129
+    public List<AlarmScheme> getSchemesByArea(String area) {
130
+        QueryWrapper<AlarmScheme> queryWrapper = new QueryWrapper<>();
131
+        queryWrapper.eq("area", area);
132
+        queryWrapper.eq("enabled", true);
133
+        queryWrapper.orderByDesc("updated_at");
134
+        return alarmSchemeMapper.selectList(queryWrapper);
135
+    }
136
+
137
+    @Override
138
+    public List<AlarmScheme> getSchemesByDeviceId(Long deviceId) {
139
+        QueryWrapper<AlarmScheme> queryWrapper = new QueryWrapper<>();
140
+        queryWrapper.apply("device_ids LIKE CONCAT('%',{0},'%')", deviceId.toString());
141
+        queryWrapper.eq("enabled", true);
142
+        queryWrapper.orderByDesc("updated_at");
143
+        return alarmSchemeMapper.selectList(queryWrapper);
144
+    }
145
+
146
+    @Override
147
+    @Transactional(rollbackFor = Exception.class)
148
+    public AlarmScheme copyScheme(Long id) {
149
+        AlarmScheme originalScheme = alarmSchemeMapper.selectById(id);
150
+        if (originalScheme == null) {
151
+            throw new RuntimeException("原始报警方案不存在");
152
+        }
153
+        
154
+        AlarmScheme newScheme = new AlarmScheme();
155
+        newScheme.setSchemeName(originalScheme.getSchemeName() + "_copy_" + System.currentTimeMillis());
156
+        newScheme.setAlertLevel(originalScheme.getAlertLevel());
157
+        newScheme.setDescription(originalScheme.getDescription());
158
+        newScheme.setNotificationSchemeId(originalScheme.getNotificationSchemeId());
159
+        newScheme.setRuleJson(originalScheme.getRuleJson());
160
+        newScheme.setDeviceIds(originalScheme.getDeviceIds());
161
+        newScheme.setArea(originalScheme.getArea());
162
+        newScheme.setEnabled(true);
163
+        newScheme.setCreatedBy(0L); // 系统复制
164
+        newScheme.setCreatedByName("系统");
165
+        newScheme.setRemark("从方案ID:" + id + " 复制");
166
+        
167
+        createScheme(newScheme);
168
+        return newScheme;
169
+    }
170
+}

+ 573
- 0
wm-production/src/main/java/com/water/production/service/impl/DataCenterServiceImpl.java ファイルの表示

1
+package com.water.production.service.impl;
2
+
3
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
4
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
5
+import com.water.common.core.page.PageResult;
6
+import com.water.production.dto.DataCenterQueryRequest;
7
+import com.water.production.dto.DataExportRequest;
8
+import com.water.production.entity.*;
9
+import com.water.production.mapper.*;
10
+import com.water.production.service.DataCenterService;
11
+import com.water.production.service.MonitorListService;
12
+import lombok.RequiredArgsConstructor;
13
+import org.apache.poi.ss.usermodel.*;
14
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
15
+import org.springframework.jdbc.core.JdbcTemplate;
16
+import org.springframework.stereotype.Service;
17
+import org.springframework.transaction.annotation.Transactional;
18
+
19
+import javax.servlet.ServletOutputStream;
20
+import java.io.ByteArrayOutputStream;
21
+import java.time.LocalDate;
22
+import java.time.LocalDateTime;
23
+import java.time.format.DateTimeFormatter;
24
+import java.util.*;
25
+import java.util.stream.Collectors;
26
+
27
+/**
28
+ * 数据中心服务实现
29
+ * 对应 Issue 13: 供水生产管理平台 — 数据中心与配置中心
30
+ */
31
+@Service
32
+@RequiredArgsConstructor
33
+public class DataCenterServiceImpl implements DataCenterService {
34
+
35
+    private final JdbcTemplate jdbc;
36
+    private final MonitorListMapper monitorListMapper;
37
+    private final ThresholdMapper thresholdMapper;
38
+    private final AlarmSchemeMapper alarmSchemeMapper;
39
+    private final DispatchCommandMapper dispatchCommandMapper;
40
+    private final DutyScheduleMapper dutyScheduleMapper;
41
+    private final AlarmEventMapper alarmEventMapper;
42
+    private final WaterQualityRecordMapper waterQualityRecordMapper;
43
+    private final ReadingMapper readingMapper;
44
+    private final DeviceMapper deviceMapper;
45
+
46
+    @Override
47
+    public Map<String, Object> getDataView(DataCenterQueryRequest request) {
48
+        Map<String, Object> result = new LinkedHashMap<>();
49
+        
50
+        // 实时数据概览
51
+        Map<String, Object> realtimeOverview = getRealtimeOverview(request.getArea());
52
+        result.put("realtimeData", realtimeOverview);
53
+        
54
+        // 关键指标
55
+        Map<String, Object> keyMetrics = getKeyMetrics(request.getArea());
56
+        result.put("keyMetrics", keyMetrics);
57
+        
58
+        // 设备状态统计
59
+        Map<String, Object> deviceStats = getDeviceStatistics(request.getArea());
60
+        result.put("deviceStats", deviceStats);
61
+        
62
+        // 报警状态
63
+        Map<String, Object> alertStats = getAlertStatistics(request.getArea());
64
+        result.put("alertStats", alertStats);
65
+        
66
+        result.put("queryTime", LocalDateTime.now());
67
+        
68
+        return result;
69
+    }
70
+
71
+    @Override
72
+    public List<Map<String, Object>> getHistoryData(String dataType, String area, String startTime, String endTime) {
73
+        String sql = buildHistoryDataSql(dataType, area);
74
+        return jdbc.queryForList(sql, area, startTime, endTime);
75
+    }
76
+
77
+    @Override
78
+    public Map<String, Object> getChartData(String chartType, String period, String area, String deviceType) {
79
+        String sql = buildChartDataSql(chartType, period, area, deviceType);
80
+        List<Map<String, Object>> data = jdbc.queryForList(sql);
81
+        
82
+        Map<String, Object> result = new LinkedHashMap<>();
83
+        result.put("chartType", chartType);
84
+        result.put("period", period);
85
+        result.put("data", data);
86
+        result.put("area", area);
87
+        result.put("deviceType", deviceType);
88
+        
89
+        return result;
90
+    }
91
+
92
+    @Override
93
+    public Map<String, Object> generateReport(String reportType, String period, String area) {
94
+        Map<String, Object> report = new LinkedHashMap<>();
95
+        report.put("reportType", reportType);
96
+        report.put("period", period);
97
+        report.put("area", area);
98
+        report.put("generatedAt", LocalDateTime.now());
99
+        
100
+        switch (reportType) {
101
+            case "water_volume":
102
+                report.put("data", generateWaterVolumeReport(period, area));
103
+                break;
104
+            case "water_pressure":
105
+                report.put("data", generateWaterPressureReport(period, area));
106
+                break;
107
+            case "flow_rate":
108
+                report.put("data", generateFlowRateReport(period, area));
109
+                break;
110
+            case "water_quality":
111
+                report.put("data", generateWaterQualityReport(period, area));
112
+                break;
113
+            case "water_level":
114
+                report.put("data", generateWaterLevelReport(period, area));
115
+                break;
116
+            default:
117
+                report.put("data", new ArrayList<>());
118
+        }
119
+        
120
+        return report;
121
+    }
122
+
123
+    @Override
124
+    public List<Threshold> getThresholdsByQuery(String area, String metricKey) {
125
+        QueryWrapper<Threshold> query = new QueryWrapper<>();
126
+        if (area != null && !area.isEmpty()) {
127
+            query.eq("area", area);
128
+        }
129
+        if (metricKey != null && !metricKey.isEmpty()) {
130
+            query.eq("metric_key", metricKey);
131
+        }
132
+        query.orderByDesc("updated_at");
133
+        return thresholdMapper.selectList(query);
134
+    }
135
+
136
+    @Override
137
+    public void updateThreshold(Long id, Double minValue, Double maxValue, String unit) {
138
+        Threshold threshold = thresholdMapper.selectById(id);
139
+        if (threshold != null) {
140
+            threshold.setMinValue(minValue);
141
+            threshold.setMaxValue(maxValue);
142
+            threshold.setUnit(unit);
143
+            threshold.setUpdatedAt(LocalDateTime.now());
144
+            thresholdMapper.updateById(threshold);
145
+        }
146
+    }
147
+
148
+    @Override
149
+    public void publishForecast(String title, String content, String area, Long publisherId) {
150
+        String sql = "INSERT INTO sys_info_release (type, title, content, area, publisher_id, publish_time, status) " +
151
+                     "VALUES ('forecast', ?, ?, ?, ?, NOW(), 'published')";
152
+        jdbc.update(sql, title, content, area, publisherId);
153
+    }
154
+
155
+    @Override
156
+    public void publishWarning(String title, String content, String area, Long publisherId) {
157
+        String sql = "INSERT INTO sys_info_release (type, title, content, area, publisher_id, publish_time, status) " +
158
+                     "VALUES ('warning', ?, ?, ?, ?, NOW(), 'published')";
159
+        jdbc.update(sql, title, content, area, publisherId);
160
+    }
161
+
162
+    @Override
163
+    public List<Map<String, Object>> getInfoReleases(String type, String area) {
164
+        QueryWrapper<InfoRelease> query = new QueryWrapper<>();
165
+        if (type != null) {
166
+            query.eq("type", type);
167
+        }
168
+        if (area != null && !area.isEmpty()) {
169
+            query.eq("area", area);
170
+        }
171
+        query.orderByDesc("publish_time");
172
+        
173
+        return infoReleasesToMap(alarmReleaseMapper.selectList(query));
174
+    }
175
+
176
+    @Override
177
+    public Map<String, Object> getAllLimitConfigs() {
178
+        Map<String, Object> configs = new LinkedHashMap<>();
179
+        
180
+        // 管网压力限值
181
+        List<Map<String, Object>> pressureLimits = jdbc.queryForList(
182
+            "SELECT area, min_pressure, max_pressure, unit FROM sys_pressure_limit WHERE enabled = true"
183
+        );
184
+        configs.put("pressureLimits", pressureLimits);
185
+        
186
+        // 清水池液位限值
187
+        List<Map<String, Object>> levelLimits = jdbc.queryForList(
188
+            "SELECT area, min_level, max_level, unit FROM sys_level_limit WHERE enabled = true"
189
+        );
190
+        configs.put("levelLimits", levelLimits);
191
+        
192
+        return configs;
193
+    }
194
+
195
+    @Override
196
+    public void updatePressureLimits(String area, Double minPressure, Double maxPressure) {
197
+        String sql = "INSERT INTO sys_pressure_limit (area, min_pressure, max_pressure, unit, enabled, updated_at) " +
198
+                     "VALUES (?, ?, ?, 'MPa', true, NOW()) " +
199
+                     "ON CONFLICT (area) DO UPDATE SET min_pressure = ?, max_pressure = ?, updated_at = NOW()";
200
+        jdbc.update(sql, area, minPressure, maxPressure, minPressure, maxPressure);
201
+    }
202
+
203
+    @Override
204
+    public void updateLevelLimits(String area, Double minLevel, Double maxLevel) {
205
+        String sql = "INSERT INTO sys_level_limit (area, min_level, max_level, unit, enabled, updated_at) " +
206
+                     "VALUES (?, ?, ?, 'm', true, NOW()) " +
207
+                     "ON CONFLICT (area) DO UPDATE SET min_level = ?, max_level = ?, updated_at = NOW()";
208
+        jdbc.update(sql, area, minLevel, maxLevel, minLevel, maxLevel);
209
+    }
210
+
211
+    @Override
212
+    public byte[] exportData(DataExportRequest request) {
213
+        try (Workbook workbook = new XSSFWorkbook()) {
214
+            Sheet sheet = workbook.createSheet("数据中心数据");
215
+            
216
+            // 创建标题行
217
+            Row headerRow = sheet.createRow(0);
218
+            String[] headers = {"时间", "区域", "设备类型", "指标", "数值", "单位", "状态"};
219
+            for (int i = 0; i < headers.length; i++) {
220
+                Cell cell = headerRow.createCell(i);
221
+                cell.setCellValue(headers[i]);
222
+            }
223
+            
224
+            // 填充数据
225
+            List<Map<String, Object>> data = getExportData(request);
226
+            int rowNum = 1;
227
+            for (Map<String, Object> row : data) {
228
+                Row dataRow = sheet.createRow(rowNum++);
229
+                dataRow.createCell(0).setCellValue((String) row.get("time"));
230
+                dataRow.createCell(1).setCellValue((String) row.get("area"));
231
+                dataRow.createCell(2).setCellValue((String) row.get("deviceType"));
232
+                dataRow.createCell(3).setCellValue((String) row.get("metric"));
233
+                dataRow.createCell(4).setCellValue((Double) row.get("value"));
234
+                dataRow.createCell(5).setCellValue((String) row.get("unit"));
235
+                dataRow.createCell(6).setCellValue((String) row.get("status"));
236
+            }
237
+            
238
+            // 自动调整列宽
239
+            for (int i = 0; i < headers.length; i++) {
240
+                sheet.autoSizeColumn(i);
241
+            }
242
+            
243
+            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
244
+            workbook.write(outputStream);
245
+            return outputStream.toByteArray();
246
+        } catch (Exception e) {
247
+            throw new RuntimeException("导出数据失败", e);
248
+        }
249
+    }
250
+
251
+    @Override
252
+    public Map<String, Object> getOverview() {
253
+        Map<String, Object> overview = new LinkedHashMap<>();
254
+        
255
+        // 设备总数
256
+        Integer totalDevices = jdbc.queryForObject("SELECT COUNT(*) FROM prod_monitor_device", Integer.class);
257
+        overview.put("totalDevices", totalDevices);
258
+        
259
+        // 在线设备数
260
+        Integer onlineDevices = jdbc.queryForObject(
261
+            "SELECT COUNT(*) FROM prod_monitor_device WHERE status = 'online'", Integer.class);
262
+        overview.put("onlineDevices", onlineDevices);
263
+        
264
+        // 报警总数
265
+        Integer totalAlerts = jdbc.queryForObject("SELECT COUNT(*) FROM alert_event WHERE status = 'active'", Integer.class);
266
+        overview.put("totalAlerts", totalAlerts);
267
+        
268
+        // 今日处理报警
269
+        Integer todayAlerts = jdbc.queryForObject(
270
+            "SELECT COUNT(*) FROM alert_event WHERE DATE(created_at) = CURRENT_DATE", Integer.class);
271
+        overview.put("todayAlerts", todayAlerts);
272
+        
273
+        // 待办指令数
274
+        Integer pendingCommands = jdbc.queryForObject(
275
+            "SELECT COUNT(*) FROM disp_dispatch_command WHERE status = 'pending'", Integer.class);
276
+        overview.put("pendingCommands", pendingCommands);
277
+        
278
+        return overview;
279
+    }
280
+
281
+    // ========== 私有方法 ==========
282
+    
283
+    private Map<String, Object> getRealtimeOverview(String area) {
284
+        Map<String, Object> overview = new LinkedHashMap<>();
285
+        
286
+        // 获取实时数据统计
287
+        String sql = "SELECT " +
288
+                     "COUNT(*) as totalDevices, " +
289
+                     "SUM(CASE WHEN status = 'online' THEN 1 ELSE 0 END) as onlineDevices, " +
290
+                     "SUM(CASE WHEN status = 'offline' THEN 1 ELSE 0 END) as offlineDevices, " +
291
+                     "SUM(CASE WHEN status = 'fault' THEN 1 ELSE 0 END) as faultDevices " +
292
+                     "FROM prod_monitor_device " +
293
+                     (area != null ? "WHERE area = ?" : "");
294
+        
295
+        Map<String, Object> stats = area != null ? 
296
+            jdbc.queryForList(sql, area).get(0) : 
297
+            jdbc.queryForList(sql).get(0);
298
+        
299
+        overview.putAll(stats);
300
+        
301
+        return overview;
302
+    }
303
+
304
+    private Map<String, Object> getKeyMetrics(String area) {
305
+        Map<String, Object> metrics = new LinkedHashMap<>();
306
+        
307
+        // 获取关键指标数据
308
+        String sql = "SELECT " +
309
+                     "AVG(metric_value) as avgValue, " +
310
+                     "MAX(metric_value) as maxValue, " +
311
+                     "MIN(metric_value) as minValue, " +
312
+                     "COUNT(*) as dataCount " +
313
+                     "FROM prod_monitor_realtime_data prd " +
314
+                     "JOIN prod_monitor_device pmd ON prd.device_id = pmd.id " +
315
+                     "WHERE prd.metric_key = ? " +
316
+                     (area != null ? "AND pmd.area = ?" : "") +
317
+                     " AND prd.collect_time >= NOW() - INTERVAL '1 hour'";
318
+        
319
+        // 流量指标
320
+        List<Map<String, Object>> flowData = area != null ?
321
+            jdbc.queryForList(sql, "flow", area) :
322
+            jdbc.queryForList(sql, "flow");
323
+        if (!flowData.isEmpty()) {
324
+            metrics.put("flow", flowData.get(0));
325
+        }
326
+        
327
+        // 压力指标
328
+        List<Map<String, Object>> pressureData = area != null ?
329
+            jdbc.queryForList(sql, "pressure", area) :
330
+            jdbc.queryForList(sql, "pressure");
331
+        if (!pressureData.isEmpty()) {
332
+            metrics.put("pressure", pressureData.get(0));
333
+        }
334
+        
335
+        return metrics;
336
+    }
337
+
338
+    private Map<String, Object> getDeviceStatistics(String area) {
339
+        Map<String, Object> stats = new LinkedHashMap<>();
340
+        
341
+        // 按状态统计
342
+        String statusSql = "SELECT status, COUNT(*) as count " +
343
+                           "FROM prod_monitor_device " +
344
+                           (area != null ? "WHERE area = ?" : "") +
345
+                           " GROUP BY status";
346
+        List<Map<String, Object>> statusStats = area != null ?
347
+            jdbc.queryForList(statusSql, area) :
348
+            jdbc.queryForList(statusSql);
349
+        stats.put("statusStats", statusStats);
350
+        
351
+        // 按类型统计
352
+        String typeSql = "SELECT device_type, COUNT(*) as count " +
353
+                         "FROM prod_monitor_device " +
354
+                         (area != null ? "WHERE area = ?" : "") +
355
+                         " GROUP BY device_type";
356
+        List<Map<String, Object>> typeStats = area != null ?
357
+            jdbc.queryForList(typeSql, area) :
358
+            jdbc.queryForList(typeSql);
359
+        stats.put("typeStats", typeStats);
360
+        
361
+        return stats;
362
+    }
363
+
364
+    private Map<String, Object> getAlertStatistics(String area) {
365
+        Map<String, Object> stats = new LinkedHashMap<>();
366
+        
367
+        // 报警级别统计
368
+        String levelSql = "SELECT alert_level, COUNT(*) as count " +
369
+                          "FROM alert_event " +
370
+                          (area != null ? "WHERE area = ?" : "") +
371
+                          " AND status = 'active' " +
372
+                          " GROUP BY alert_level";
373
+        List<Map<String, Object>> levelStats = area != null ?
374
+            jdbc.queryForList(levelSql, area) :
375
+            jdbc.queryForList(levelSql);
376
+        stats.put("levelStats", levelStats);
377
+        
378
+        // 今日报警趋势
379
+        String todaySql = "SELECT DATE_TRUNC('hour', created_at) as time_bucket, COUNT(*) as count " +
380
+                          "FROM alert_event " +
381
+                          "WHERE DATE(created_at) = CURRENT_DATE " +
382
+                          (area != null ? "AND area = ?" : "") +
383
+                          " GROUP BY time_bucket ORDER BY time_bucket";
384
+        List<Map<String, Object>> todayTrend = area != null ?
385
+            jdbc.queryForList(todaySql, area) :
386
+            jdbc.queryForList(todaySql);
387
+        stats.put("todayTrend", todayTrend);
388
+        
389
+        return stats;
390
+    }
391
+
392
+    private String buildHistoryDataSql(String dataType, String area) {
393
+        switch (dataType) {
394
+            case "water_flow":
395
+                return "SELECT * FROM rev_reading WHERE (area = ? OR ? IS NULL) AND created_at BETWEEN ? AND ? LIMIT 500";
396
+            case "water_quality":
397
+                return "SELECT * FROM water_quality_record WHERE (area = ? OR ? IS NULL) AND test_date BETWEEN ? AND ? LIMIT 500";
398
+            case "alerts":
399
+                return "SELECT * FROM alert_event WHERE (area = ? OR ? IS NULL) AND created_at BETWEEN ? AND ? LIMIT 500";
400
+            default:
401
+                return "SELECT * FROM prod_monitor_realtime_data prd JOIN prod_monitor_device pmd ON prd.device_id = pmd.id WHERE (pmd.area = ? OR ? IS NULL) AND prd.collect_time BETWEEN ? AND ? LIMIT 500";
402
+        }
403
+    }
404
+
405
+    private String buildChartDataSql(String chartType, String period, String area, String deviceType) {
406
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM");
407
+        LocalDate periodDate = LocalDate.parse(period, formatter);
408
+        
409
+        String timeCondition = "";
410
+        switch (period) {
411
+            case "day":
412
+                timeCondition = "DATE(created_at) = CURRENT_DATE";
413
+                break;
414
+            case "week":
415
+                timeCondition = "created_at >= NOW() - INTERVAL '7 days'";
416
+                break;
417
+            case "month":
418
+                timeCondition = "to_char(created_at, 'yyyy-MM') = ?";
419
+                break;
420
+            default:
421
+                timeCondition = "created_at >= NOW() - INTERVAL '30 days'";
422
+        }
423
+        
424
+        String sql = "SELECT " +
425
+                     "to_char(created_at, 'HH24:MI') as time_label, " +
426
+                     "metric_value as value, " +
427
+                     "device_name as label " +
428
+                     "FROM prod_monitor_realtime_data prd " +
429
+                     "JOIN prod_monitor_device pmd ON prd.device_id = pmd.id " +
430
+                     "WHERE pmd.device_type = ? AND " + timeCondition +
431
+                     (area != null ? " AND pmd.area = ?" : "") +
432
+                     " ORDER BY created ASC";
433
+        
434
+        return period.equals("month") ? 
435
+            (area != null ? sql + " AND ?" : sql + " AND ?") :
436
+            (area != null ? sql + " AND ?" : sql);
437
+    }
438
+
439
+    private List<Map<String, Object>> generateWaterVolumeReport(String period, String area) {
440
+        String sql = "SELECT " +
441
+                     "area, " +
442
+                     "SUM(consumption) as total_consumption, " +
443
+                     "AVG(consumption) as avg_consumption, " +
444
+                     "COUNT(*) as reading_count " +
445
+                     "FROM rev_reading " +
446
+                     "WHERE reading_period = ? " +
447
+                     (area != null ? "AND area = ?" : "") +
448
+                     " GROUP BY area";
449
+        
450
+        return area != null ? 
451
+            jdbc.queryForList(sql, period, area) : 
452
+            jdbc.queryForList(sql, period);
453
+    }
454
+
455
+    private List<Map<String, Object>> generateWaterPressureReport(String period, String area) {
456
+        String sql = "SELECT " +
457
+                     "pmd.area, " +
458
+                     "AVG(prd.metric_value) as avg_pressure, " +
459
+                     "MAX(prd.metric_value) as max_pressure, " +
460
+                     "MIN(prd.metric_value) as min_pressure, " +
461
+                     "COUNT(*) as data_points " +
462
+                     "FROM prod_monitor_realtime_data prd " +
463
+                     "JOIN prod_monitor_device pmd ON prd.device_id = pmd.id " +
464
+                     "WHERE prd.metric_key = 'pressure' " +
465
+                     "AND to_char(prd.collect_time, 'yyyy-MM') = ? " +
466
+                     (area != null ? "AND pmd.area = ?" : "") +
467
+                     " GROUP BY pmd.area";
468
+        
469
+        return area != null ? 
470
+            jdbc.queryForList(sql, period, area) : 
471
+            jdbc.queryForList(sql, period);
472
+    }
473
+
474
+    private List<Map<String, Object>> generateFlowRateReport(String period, String area) {
475
+        String sql = "SELECT " +
476
+                     "pmd.area, " +
477
+                     "AVG(prd.metric_value) as avg_flow, " +
478
+                     "MAX(prd.metric_value) as max_flow, " +
479
+                     "MIN(prd.metric_value) as min_flow, " +
480
+                     "COUNT(*) as data_points " +
481
+                     "FROM prod_monitor_realtime_data prd " +
482
+                     "JOIN prod_monitor_device pmd ON prd.device_id = pmd.id " +
483
+                     "WHERE prd.metric_key = 'flow' " +
484
+                     "AND to_char(prd.collect_time, 'yyyy-MM') = ? " +
485
+                     (area != null ? "AND pmd.area = ?" : "") +
486
+                     " GROUP BY pmd.area";
487
+        
488
+        return area != null ? 
489
+            jdbc.queryForList(sql, period, area) : 
490
+            jdbc.queryForList(sql, period);
491
+    }
492
+
493
+    private List<Map<String, Object>> generateWaterQualityReport(String period, String area) {
494
+        String sql = "SELECT " +
495
+                     "area, " +
496
+                     "AVG(turbidity) as avg_turbidity, " +
497
+                     "AVG(ph) as avg_ph, " +
498
+                     "AVG(residual_chlorine) as avg_residual_chlorine, " +
499
+                     "COUNT(*) as tests, " +
500
+                     "SUM(CASE WHEN is_qualified=1 THEN 1 ELSE 0 END)*100.0/NULLIF(COUNT(*),0) as pass_rate " +
501
+                     "FROM water_quality_record " +
502
+                     "WHERE to_char(test_date, 'yyyy-MM') = ? " +
503
+                     (area != null ? "AND area = ?" : "") +
504
+                     " GROUP BY area";
505
+        
506
+        return area != null ? 
507
+            jdbc.queryForList(sql, period, area) : 
508
+            jdbc.queryForList(sql, period);
509
+    }
510
+
511
+    private List<Map<String, Object>> generateWaterLevelReport(String period, String area) {
512
+        String sql = "SELECT " +
513
+                     "pmd.area, " +
514
+                     "AVG(prd.metric_value) as avg_level, " +
515
+                     "MAX(prd.metric_value) as max_level, " +
516
+                     "MIN(prd.metric_value) as min_level, " +
517
+                     "COUNT(*) as data_points " +
518
+                     "FROM prod_monitor_realtime_data prd " +
519
+                     "JOIN prod_monitor_device pmd ON prd.device_id = pmd.id " +
520
+                     "WHERE prd.metric_key = 'level' " +
521
+                     "AND to_char(prd.collect_time, 'yyyy-MM') = ? " +
522
+                     (area != null ? "AND pmd.area = ?" : "") +
523
+                     " GROUP BY pmd.area";
524
+        
525
+        return area != null ? 
526
+            jdbc.queryForList(sql, period, area) : 
527
+            jdbc.queryForList(sql, period);
528
+    }
529
+
530
+    private List<Map<String, Object>> getExportData(DataExportRequest request) {
531
+        String sql = "SELECT " +
532
+                     "to_char(prd.collect_time, 'yyyy-MM-dd HH24:MI:SS') as time, " +
533
+                     "pmd.area, " +
534
+                     "pmd.device_type, " +
535
+                     "prd.metric_key as metric, " +
536
+                     "prd.metric_value as value, " +
537
+                     "prd.unit, " +
538
+                     "prd.is_abnormal as status " +
539
+                     "FROM prod_monitor_realtime_data prd " +
540
+                     "JOIN prod_monitor_device pmd ON prd.device_id = pmd.id " +
541
+                     "WHERE prd.collect_time BETWEEN ? AND ? " +
542
+                     (request.getArea() != null ? "AND pmd.area = ?" : "") +
543
+                     (request.getDeviceType() != null ? " AND pmd.device_type = ?" : "") +
544
+                     " ORDER BY prd.collect_time DESC";
545
+        
546
+        List<Object> params = new ArrayList<>();
547
+        params.add(request.getStartTime());
548
+        params.add(request.getEndTime());
549
+        if (request.getArea() != null) {
550
+            params.add(request.getArea());
551
+        }
552
+        if (request.getDeviceType() != null) {
553
+            params.add(request.getDeviceType());
554
+        }
555
+        
556
+        return jdbc.queryForList(sql, params.toArray());
557
+    }
558
+
559
+    private List<Map<String, Object>> infoReleasesToMap(List<InfoRelease> releases) {
560
+        return releases.stream().map(release -> {
561
+            Map<String, Object> map = new LinkedHashMap<>();
562
+            map.put("id", release.getId());
563
+            map.put("type", release.getType());
564
+            map.put("title", release.getTitle());
565
+            map.put("content", release.getContent());
566
+            map.put("area", release.getArea());
567
+            map.put("publisherId", release.getPublisherId());
568
+            map.put("publishTime", release.getPublishTime());
569
+            map.put("status", release.getStatus());
570
+            return map;
571
+        }).collect(Collectors.toList());
572
+    }
573
+}

+ 41
- 0
wm-production/src/main/java/com/water/production/vo/AlarmSchemeQueryVO.java ファイルの表示

1
+package com.water.production.vo;
2
+
3
+import lombok.Data;
4
+
5
+/**
6
+ * 报警方案查询VO
7
+ * 对应 Issue 13: CFG-03 报警方案
8
+ */
9
+@Data
10
+public class AlarmSchemeQueryVO {
11
+    
12
+    /** 报警方案名称 */
13
+    private String schemeName;
14
+    
15
+    /** 报警级别 */
16
+    private String alertLevel;
17
+    
18
+    /** 区域 */
19
+    private String area;
20
+    
21
+    /** 是否启用 */
22
+    private Boolean enabled;
23
+    
24
+    /** 关键字搜索 */
25
+    private String keyword;
26
+    
27
+    /** 创建人ID */
28
+    private Long createdBy;
29
+    
30
+    /** 页码 */
31
+    private Integer page = 1;
32
+    
33
+    /** 页大小 */
34
+    private Integer size = 10;
35
+    
36
+    /** 排序字段 */
37
+    private String sortBy = "created_at";
38
+    
39
+    /** 排序方向 */
40
+    private String sortOrder = "desc";
41
+}