Przeglądaj źródła

feat(wm-config): #72 阈值管理+信息发布+设备管理

bot_dev2 5 dni temu
rodzic
commit
4a0fc1bf42
24 zmienionych plików z 1398 dodań i 0 usunięć
  1. 1
    0
      pom.xml
  2. 16
    0
      wm-config/pom.xml
  3. 15
    0
      wm-config/src/main/java/com/water/config/ConfigApplication.java
  4. 68
    0
      wm-config/src/main/java/com/water/config/controller/AnnouncementController.java
  5. 98
    0
      wm-config/src/main/java/com/water/config/controller/DeviceManageController.java
  6. 84
    0
      wm-config/src/main/java/com/water/config/controller/ThresholdController.java
  7. 35
    0
      wm-config/src/main/java/com/water/config/entity/Announcement.java
  8. 45
    0
      wm-config/src/main/java/com/water/config/entity/DeviceInfo.java
  9. 37
    0
      wm-config/src/main/java/com/water/config/entity/DeviceMaintenance.java
  10. 35
    0
      wm-config/src/main/java/com/water/config/entity/ThresholdChangeLog.java
  11. 38
    0
      wm-config/src/main/java/com/water/config/entity/ThresholdConfig.java
  12. 9
    0
      wm-config/src/main/java/com/water/config/mapper/AnnouncementMapper.java
  13. 17
    0
      wm-config/src/main/java/com/water/config/mapper/DeviceInfoMapper.java
  14. 14
    0
      wm-config/src/main/java/com/water/config/mapper/DeviceMaintenanceMapper.java
  15. 9
    0
      wm-config/src/main/java/com/water/config/mapper/ThresholdChangeLogMapper.java
  16. 17
    0
      wm-config/src/main/java/com/water/config/mapper/ThresholdConfigMapper.java
  17. 131
    0
      wm-config/src/main/java/com/water/config/service/AnnouncementService.java
  18. 150
    0
      wm-config/src/main/java/com/water/config/service/DeviceManageService.java
  19. 144
    0
      wm-config/src/main/java/com/water/config/service/ThresholdService.java
  20. 30
    0
      wm-config/src/main/resources/application.yml
  21. 102
    0
      wm-config/src/main/resources/db/schema.sql
  22. 100
    0
      wm-config/src/test/java/com/water/config/AnnouncementServiceTest.java
  23. 107
    0
      wm-config/src/test/java/com/water/config/DeviceManageServiceTest.java
  24. 96
    0
      wm-config/src/test/java/com/water/config/ThresholdServiceTest.java

+ 1
- 0
pom.xml Wyświetl plik

@@ -49,6 +49,7 @@
49 49
         <module>wm-dispatch</module>
50 50
         <module>wm-system</module>
51 51
         <module>wm-mobile-app</module>
52
+        <module>wm-config</module>
52 53
     </modules>
53 54
 
54 55
     <dependencyManagement>

+ 16
- 0
wm-config/pom.xml Wyświetl plik

@@ -0,0 +1,16 @@
1
+<?xml version="1.0" encoding="UTF-8"?>
2
+<project xmlns="http://maven.apache.org/POM/4.0.0"
3
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
5
+    <modelVersion>4.0.0</modelVersion>
6
+    <parent><groupId>com.water</groupId><artifactId>wm-parent</artifactId><version>1.0.0-SNAPSHOT</version></parent>
7
+    <artifactId>wm-config</artifactId>
8
+    <dependencies>
9
+        <dependency><groupId>com.water</groupId><artifactId>wm-common</artifactId></dependency>
10
+        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency>
11
+        <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId></dependency>
12
+        <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId></dependency>
13
+        <dependency><groupId>cn.dev33</groupId><artifactId>sa-token-spring-boot3-starter</artifactId></dependency>
14
+        <dependency><groupId>org.postgresql</groupId><artifactId>postgresql</artifactId></dependency>
15
+    </dependencies>
16
+</project>

+ 15
- 0
wm-config/src/main/java/com/water/config/ConfigApplication.java Wyświetl plik

@@ -0,0 +1,15 @@
1
+package com.water.config;
2
+
3
+import org.mybatis.spring.annotation.MapperScan;
4
+import org.springframework.boot.SpringApplication;
5
+import org.springframework.boot.autoconfigure.SpringBootApplication;
6
+import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
7
+
8
+@SpringBootApplication
9
+@EnableDiscoveryClient
10
+@MapperScan("com.water.config.mapper")
11
+public class ConfigApplication {
12
+    public static void main(String[] args) {
13
+        SpringApplication.run(ConfigApplication.class, args);
14
+    }
15
+}

+ 68
- 0
wm-config/src/main/java/com/water/config/controller/AnnouncementController.java Wyświetl plik

@@ -0,0 +1,68 @@
1
+package com.water.config.controller;
2
+
3
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4
+import com.water.common.core.result.R;
5
+import com.water.config.entity.Announcement;
6
+import com.water.config.service.AnnouncementService;
7
+import io.swagger.v3.oas.annotations.Operation;
8
+import io.swagger.v3.oas.annotations.tags.Tag;
9
+import lombok.RequiredArgsConstructor;
10
+import org.springframework.web.bind.annotation.*;
11
+
12
+@Tag(name = "公告通知管理")
13
+@RestController
14
+@RequestMapping("/api/config/announcement")
15
+@RequiredArgsConstructor
16
+public class AnnouncementController {
17
+
18
+    private final AnnouncementService announcementService;
19
+
20
+    @Operation(summary = "分页查询公告")
21
+    @GetMapping("/list")
22
+    public R<Page<Announcement>> list(@RequestParam(defaultValue = "1") int page,
23
+                                       @RequestParam(defaultValue = "10") int size,
24
+                                       @RequestParam(required = false) Integer type,
25
+                                       @RequestParam(required = false) Integer publishStatus) {
26
+        return R.ok(announcementService.pageAnnouncements(page, size, type, publishStatus));
27
+    }
28
+
29
+    @Operation(summary = "获取公告详情")
30
+    @GetMapping("/{id}")
31
+    public R<Announcement> getById(@PathVariable Long id) {
32
+        return R.ok(announcementService.getById(id));
33
+    }
34
+
35
+    @Operation(summary = "创建公告(草稿)")
36
+    @PostMapping
37
+    public R<Announcement> create(@RequestBody Announcement announcement) {
38
+        return R.ok(announcementService.createAnnouncement(announcement));
39
+    }
40
+
41
+    @Operation(summary = "更新公告")
42
+    @PutMapping("/{id}")
43
+    public R<String> update(@PathVariable Long id, @RequestBody Announcement announcement) {
44
+        announcementService.updateAnnouncement(id, announcement);
45
+        return R.ok("更新成功");
46
+    }
47
+
48
+    @Operation(summary = "发布公告")
49
+    @PostMapping("/{id}/publish")
50
+    public R<String> publish(@PathVariable Long id) {
51
+        announcementService.publish(id);
52
+        return R.ok("发布成功");
53
+    }
54
+
55
+    @Operation(summary = "撤回公告")
56
+    @PostMapping("/{id}/withdraw")
57
+    public R<String> withdraw(@PathVariable Long id) {
58
+        announcementService.withdraw(id);
59
+        return R.ok("撤回成功");
60
+    }
61
+
62
+    @Operation(summary = "删除公告")
63
+    @DeleteMapping("/{id}")
64
+    public R<String> delete(@PathVariable Long id) {
65
+        announcementService.deleteAnnouncement(id);
66
+        return R.ok("删除成功");
67
+    }
68
+}

+ 98
- 0
wm-config/src/main/java/com/water/config/controller/DeviceManageController.java Wyświetl plik

@@ -0,0 +1,98 @@
1
+package com.water.config.controller;
2
+
3
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4
+import com.water.common.core.result.R;
5
+import com.water.config.entity.DeviceInfo;
6
+import com.water.config.entity.DeviceMaintenance;
7
+import com.water.config.service.DeviceManageService;
8
+import io.swagger.v3.oas.annotations.Operation;
9
+import io.swagger.v3.oas.annotations.tags.Tag;
10
+import lombok.RequiredArgsConstructor;
11
+import org.springframework.web.bind.annotation.*;
12
+
13
+import java.util.List;
14
+
15
+@Tag(name = "设备管理")
16
+@RestController
17
+@RequestMapping("/api/config/device")
18
+@RequiredArgsConstructor
19
+public class DeviceManageController {
20
+
21
+    private final DeviceManageService deviceManageService;
22
+
23
+    @Operation(summary = "分页查询设备")
24
+    @GetMapping("/list")
25
+    public R<Page<DeviceInfo>> list(@RequestParam(defaultValue = "1") int page,
26
+                                     @RequestParam(defaultValue = "10") int size,
27
+                                     @RequestParam(required = false) String deviceName,
28
+                                     @RequestParam(required = false) Integer category,
29
+                                     @RequestParam(required = false) Integer deviceStatus) {
30
+        return R.ok(deviceManageService.pageDevices(page, size, deviceName, category, deviceStatus));
31
+    }
32
+
33
+    @Operation(summary = "获取设备详情")
34
+    @GetMapping("/{id}")
35
+    public R<DeviceInfo> getById(@PathVariable Long id) {
36
+        return R.ok(deviceManageService.getById(id));
37
+    }
38
+
39
+    @Operation(summary = "创建设备")
40
+    @PostMapping
41
+    public R<DeviceInfo> create(@RequestBody DeviceInfo device) {
42
+        return R.ok(deviceManageService.createDevice(device));
43
+    }
44
+
45
+    @Operation(summary = "更新设备")
46
+    @PutMapping("/{id}")
47
+    public R<String> update(@PathVariable Long id, @RequestBody DeviceInfo device) {
48
+        deviceManageService.updateDevice(id, device);
49
+        return R.ok("更新成功");
50
+    }
51
+
52
+    @Operation(summary = "删除设备")
53
+    @DeleteMapping("/{id}")
54
+    public R<String> delete(@PathVariable Long id) {
55
+        deviceManageService.removeById(id);
56
+        return R.ok("删除成功");
57
+    }
58
+
59
+    @Operation(summary = "更新设备状态")
60
+    @PutMapping("/{id}/status")
61
+    public R<String> updateStatus(@PathVariable Long id, @RequestParam Integer deviceStatus) {
62
+        deviceManageService.updateDeviceStatus(id, deviceStatus);
63
+        return R.ok("状态更新成功");
64
+    }
65
+
66
+    @Operation(summary = "按分类查询设备")
67
+    @GetMapping("/category/{category}")
68
+    public R<List<DeviceInfo>> getByCategory(@PathVariable Integer category) {
69
+        return R.ok(deviceManageService.getDevicesByCategory(category));
70
+    }
71
+
72
+    @Operation(summary = "按状态查询设备")
73
+    @GetMapping("/status/{status}")
74
+    public R<List<DeviceInfo>> getByStatus(@PathVariable Integer status) {
75
+        return R.ok(deviceManageService.getDevicesByStatus(status));
76
+    }
77
+
78
+    @Operation(summary = "添加维保记录")
79
+    @PostMapping("/maintenance")
80
+    public R<DeviceMaintenance> addMaintenance(@RequestBody DeviceMaintenance maintenance) {
81
+        return R.ok(deviceManageService.addMaintenance(maintenance));
82
+    }
83
+
84
+    @Operation(summary = "查询维保记录")
85
+    @GetMapping("/maintenance")
86
+    public R<Page<DeviceMaintenance>> pageMaintenances(@RequestParam(required = false) Long deviceId,
87
+                                                        @RequestParam(defaultValue = "1") int page,
88
+                                                        @RequestParam(defaultValue = "10") int size) {
89
+        return R.ok(deviceManageService.pageMaintenances(deviceId, page, size));
90
+    }
91
+
92
+    @Operation(summary = "更新维保记录")
93
+    @PutMapping("/maintenance/{id}")
94
+    public R<String> updateMaintenance(@PathVariable Long id, @RequestBody DeviceMaintenance maintenance) {
95
+        deviceManageService.updateMaintenance(id, maintenance);
96
+        return R.ok("更新成功");
97
+    }
98
+}

+ 84
- 0
wm-config/src/main/java/com/water/config/controller/ThresholdController.java Wyświetl plik

@@ -0,0 +1,84 @@
1
+package com.water.config.controller;
2
+
3
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
4
+import com.water.common.core.result.R;
5
+import com.water.config.entity.ThresholdChangeLog;
6
+import com.water.config.entity.ThresholdConfig;
7
+import com.water.config.service.ThresholdService;
8
+import io.swagger.v3.oas.annotations.Operation;
9
+import io.swagger.v3.oas.annotations.tags.Tag;
10
+import lombok.RequiredArgsConstructor;
11
+import org.springframework.web.bind.annotation.*;
12
+
13
+import java.util.List;
14
+
15
+@Tag(name = "阈值管理")
16
+@RestController
17
+@RequestMapping("/api/config/threshold")
18
+@RequiredArgsConstructor
19
+public class ThresholdController {
20
+
21
+    private final ThresholdService thresholdService;
22
+
23
+    @Operation(summary = "分页查询阈值配置")
24
+    @GetMapping("/list")
25
+    public R<Page<ThresholdConfig>> list(@RequestParam(defaultValue = "1") int page,
26
+                                          @RequestParam(defaultValue = "10") int size,
27
+                                          @RequestParam(required = false) String metricCode,
28
+                                          @RequestParam(required = false) Integer level) {
29
+        return R.ok(thresholdService.pageThresholds(page, size, metricCode, level));
30
+    }
31
+
32
+    @Operation(summary = "获取阈值详情")
33
+    @GetMapping("/{id}")
34
+    public R<ThresholdConfig> getById(@PathVariable Long id) {
35
+        return R.ok(thresholdService.getById(id));
36
+    }
37
+
38
+    @Operation(summary = "创建阈值配置")
39
+    @PostMapping
40
+    public R<ThresholdConfig> create(@RequestBody ThresholdConfig config) {
41
+        return R.ok(thresholdService.createThreshold(config));
42
+    }
43
+
44
+    @Operation(summary = "更新阈值配置")
45
+    @PutMapping("/{id}")
46
+    public R<String> update(@PathVariable Long id, @RequestBody ThresholdConfig config) {
47
+        thresholdService.updateThreshold(id, config);
48
+        return R.ok("更新成功");
49
+    }
50
+
51
+    @Operation(summary = "删除阈值配置")
52
+    @DeleteMapping("/{id}")
53
+    public R<String> delete(@PathVariable Long id) {
54
+        thresholdService.deleteThreshold(id);
55
+        return R.ok("删除成功");
56
+    }
57
+
58
+    @Operation(summary = "启用/禁用阈值")
59
+    @PutMapping("/{id}/status")
60
+    public R<String> toggleStatus(@PathVariable Long id, @RequestParam Integer status) {
61
+        thresholdService.toggleStatus(id, status);
62
+        return R.ok(status == 1 ? "已启用" : "已禁用");
63
+    }
64
+
65
+    @Operation(summary = "获取指标的全局阈值(多级)")
66
+    @GetMapping("/global/{metricCode}")
67
+    public R<List<ThresholdConfig>> getGlobalThresholds(@PathVariable String metricCode) {
68
+        return R.ok(thresholdService.getGlobalThresholds(metricCode));
69
+    }
70
+
71
+    @Operation(summary = "获取设备阈值配置")
72
+    @GetMapping("/device/{deviceId}")
73
+    public R<List<ThresholdConfig>> getDeviceThresholds(@PathVariable Long deviceId) {
74
+        return R.ok(thresholdService.getDeviceThresholds(deviceId));
75
+    }
76
+
77
+    @Operation(summary = "获取阈值变更历史")
78
+    @GetMapping("/history")
79
+    public R<Page<ThresholdChangeLog>> getChangeHistory(@RequestParam(required = false) Long thresholdId,
80
+                                                         @RequestParam(defaultValue = "1") int page,
81
+                                                         @RequestParam(defaultValue = "10") int size) {
82
+        return R.ok(thresholdService.getChangeHistory(thresholdId, page, size));
83
+    }
84
+}

+ 35
- 0
wm-config/src/main/java/com/water/config/entity/Announcement.java Wyświetl plik

@@ -0,0 +1,35 @@
1
+package com.water.config.entity;
2
+
3
+import com.baomidou.mybatisplus.annotation.*;
4
+import lombok.Data;
5
+import java.time.LocalDateTime;
6
+
7
+/**
8
+ * 公告通知
9
+ */
10
+@Data
11
+@TableName("config_announcement")
12
+public class Announcement {
13
+    @TableId(type = IdType.AUTO)
14
+    private Long id;
15
+    /** 标题 */
16
+    private String title;
17
+    /** 内容 */
18
+    private String content;
19
+    /** 类型: 1-系统公告 2-维护通知 3-紧急通知 */
20
+    private Integer type;
21
+    /** 发布状态: 0-草稿 1-已发布 2-已撤回 */
22
+    private Integer publishStatus;
23
+    /** 发布渠道(JSON数组): ["sms","push","site"] */
24
+    private String channels;
25
+    /** 发布人 */
26
+    private String publisher;
27
+    /** 发布时间 */
28
+    private LocalDateTime publishTime;
29
+    /** 撤回时间 */
30
+    private LocalDateTime withdrawTime;
31
+    @TableLogic
32
+    private Integer deleted;
33
+    private LocalDateTime createdAt;
34
+    private LocalDateTime updatedAt;
35
+}

+ 45
- 0
wm-config/src/main/java/com/water/config/entity/DeviceInfo.java Wyświetl plik

@@ -0,0 +1,45 @@
1
+package com.water.config.entity;
2
+
3
+import com.baomidou.mybatisplus.annotation.*;
4
+import lombok.Data;
5
+import java.time.LocalDateTime;
6
+
7
+/**
8
+ * 设备台账
9
+ */
10
+@Data
11
+@TableName("config_device_info")
12
+public class DeviceInfo {
13
+    @TableId(type = IdType.AUTO)
14
+    private Long id;
15
+    /** 设备编码 */
16
+    private String deviceCode;
17
+    /** 设备名称 */
18
+    private String deviceName;
19
+    /** 设备分类: 1-水表 2-压力传感器 3-流量计 4-水质监测仪 5-阀门 9-其他 */
20
+    private Integer category;
21
+    /** 品牌 */
22
+    private String brand;
23
+    /** 型号 */
24
+    private String model;
25
+    /** 安装位置 */
26
+    private String location;
27
+    /** 经度 */
28
+    private Double longitude;
29
+    /** 纬度 */
30
+    private Double latitude;
31
+    /** 设备状态: 0-离线 1-在线 2-故障 3-维修中 */
32
+    private Integer deviceStatus;
33
+    /** 安装日期 */
34
+    private LocalDateTime installDate;
35
+    /** 最后维护时间 */
36
+    private LocalDateTime lastMaintenanceTime;
37
+    /** 负责人 */
38
+    private String responsiblePerson;
39
+    /** 备注 */
40
+    private String remark;
41
+    @TableLogic
42
+    private Integer deleted;
43
+    private LocalDateTime createdAt;
44
+    private LocalDateTime updatedAt;
45
+}

+ 37
- 0
wm-config/src/main/java/com/water/config/entity/DeviceMaintenance.java Wyświetl plik

@@ -0,0 +1,37 @@
1
+package com.water.config.entity;
2
+
3
+import com.baomidou.mybatisplus.annotation.*;
4
+import lombok.Data;
5
+import java.time.LocalDateTime;
6
+
7
+/**
8
+ * 设备维保记录
9
+ */
10
+@Data
11
+@TableName("config_device_maintenance")
12
+public class DeviceMaintenance {
13
+    @TableId(type = IdType.AUTO)
14
+    private Long id;
15
+    /** 设备ID */
16
+    private Long deviceId;
17
+    /** 维保类型: 1-日常巡检 2-定期保养 3-故障维修 4-更换配件 */
18
+    private Integer maintenanceType;
19
+    /** 维保描述 */
20
+    private String description;
21
+    /** 维保人 */
22
+    private String operator;
23
+    /** 维保开始时间 */
24
+    private LocalDateTime startTime;
25
+    /** 维保结束时间 */
26
+    private LocalDateTime endTime;
27
+    /** 维保结果: 0-未完成 1-已完成 2-需要返修 */
28
+    private Integer result;
29
+    /** 费用 */
30
+    private Double cost;
31
+    /** 附件(JSON) */
32
+    private String attachments;
33
+    @TableLogic
34
+    private Integer deleted;
35
+    private LocalDateTime createdAt;
36
+    private LocalDateTime updatedAt;
37
+}

+ 35
- 0
wm-config/src/main/java/com/water/config/entity/ThresholdChangeLog.java Wyświetl plik

@@ -0,0 +1,35 @@
1
+package com.water.config.entity;
2
+
3
+import com.baomidou.mybatisplus.annotation.*;
4
+import lombok.Data;
5
+import java.math.BigDecimal;
6
+import java.time.LocalDateTime;
7
+
8
+/**
9
+ * 阈值变更记录
10
+ */
11
+@Data
12
+@TableName("config_threshold_change_log")
13
+public class ThresholdChangeLog {
14
+    @TableId(type = IdType.AUTO)
15
+    private Long id;
16
+    /** 关联阈值ID */
17
+    private Long thresholdId;
18
+    /** 变更前最小值 */
19
+    private BigDecimal oldMinValue;
20
+    /** 变更前最大值 */
21
+    private BigDecimal oldMaxValue;
22
+    /** 变更后最小值 */
23
+    private BigDecimal newMinValue;
24
+    /** 变更后最大值 */
25
+    private BigDecimal newMaxValue;
26
+    /** 变更前级别 */
27
+    private Integer oldLevel;
28
+    /** 变更后级别 */
29
+    private Integer newLevel;
30
+    /** 变更人 */
31
+    private String operator;
32
+    /** 变更原因 */
33
+    private String reason;
34
+    private LocalDateTime createdAt;
35
+}

+ 38
- 0
wm-config/src/main/java/com/water/config/entity/ThresholdConfig.java Wyświetl plik

@@ -0,0 +1,38 @@
1
+package com.water.config.entity;
2
+
3
+import com.baomidou.mybatisplus.annotation.*;
4
+import lombok.Data;
5
+import java.math.BigDecimal;
6
+import java.time.LocalDateTime;
7
+
8
+/**
9
+ * 阈值配置
10
+ */
11
+@Data
12
+@TableName("config_threshold")
13
+public class ThresholdConfig {
14
+    @TableId(type = IdType.AUTO)
15
+    private Long id;
16
+    /** 指标编码 */
17
+    private String metricCode;
18
+    /** 指标名称 */
19
+    private String metricName;
20
+    /** 设备ID(可选,null表示全局) */
21
+    private Long deviceId;
22
+    /** 阈值级别: 1-预警 2-报警 3-紧急 */
23
+    private Integer level;
24
+    /** 最小值 */
25
+    private BigDecimal minValue;
26
+    /** 最大值 */
27
+    private BigDecimal maxValue;
28
+    /** 单位 */
29
+    private String unit;
30
+    /** 启用状态: 0-禁用 1-启用 */
31
+    private Integer status;
32
+    /** 备注 */
33
+    private String remark;
34
+    @TableLogic
35
+    private Integer deleted;
36
+    private LocalDateTime createdAt;
37
+    private LocalDateTime updatedAt;
38
+}

+ 9
- 0
wm-config/src/main/java/com/water/config/mapper/AnnouncementMapper.java Wyświetl plik

@@ -0,0 +1,9 @@
1
+package com.water.config.mapper;
2
+
3
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4
+import com.water.config.entity.Announcement;
5
+import org.apache.ibatis.annotations.Mapper;
6
+
7
+@Mapper
8
+public interface AnnouncementMapper extends BaseMapper<Announcement> {
9
+}

+ 17
- 0
wm-config/src/main/java/com/water/config/mapper/DeviceInfoMapper.java Wyświetl plik

@@ -0,0 +1,17 @@
1
+package com.water.config.mapper;
2
+
3
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4
+import com.water.config.entity.DeviceInfo;
5
+import org.apache.ibatis.annotations.Mapper;
6
+import org.apache.ibatis.annotations.Select;
7
+import java.util.List;
8
+
9
+@Mapper
10
+public interface DeviceInfoMapper extends BaseMapper<DeviceInfo> {
11
+
12
+    @Select("SELECT * FROM config_device_info WHERE device_status = #{status} AND deleted = 0")
13
+    List<DeviceInfo> selectByDeviceStatus(Integer status);
14
+
15
+    @Select("SELECT * FROM config_device_info WHERE category = #{category} AND deleted = 0 ORDER BY device_code")
16
+    List<DeviceInfo> selectByCategory(Integer category);
17
+}

+ 14
- 0
wm-config/src/main/java/com/water/config/mapper/DeviceMaintenanceMapper.java Wyświetl plik

@@ -0,0 +1,14 @@
1
+package com.water.config.mapper;
2
+
3
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4
+import com.water.config.entity.DeviceMaintenance;
5
+import org.apache.ibatis.annotations.Mapper;
6
+import org.apache.ibatis.annotations.Select;
7
+import java.util.List;
8
+
9
+@Mapper
10
+public interface DeviceMaintenanceMapper extends BaseMapper<DeviceMaintenance> {
11
+
12
+    @Select("SELECT * FROM config_device_maintenance WHERE device_id = #{deviceId} AND deleted = 0 ORDER BY start_time DESC")
13
+    List<DeviceMaintenance> selectByDeviceId(Long deviceId);
14
+}

+ 9
- 0
wm-config/src/main/java/com/water/config/mapper/ThresholdChangeLogMapper.java Wyświetl plik

@@ -0,0 +1,9 @@
1
+package com.water.config.mapper;
2
+
3
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4
+import com.water.config.entity.ThresholdChangeLog;
5
+import org.apache.ibatis.annotations.Mapper;
6
+
7
+@Mapper
8
+public interface ThresholdChangeLogMapper extends BaseMapper<ThresholdChangeLog> {
9
+}

+ 17
- 0
wm-config/src/main/java/com/water/config/mapper/ThresholdConfigMapper.java Wyświetl plik

@@ -0,0 +1,17 @@
1
+package com.water.config.mapper;
2
+
3
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
4
+import com.water.config.entity.ThresholdConfig;
5
+import org.apache.ibatis.annotations.Mapper;
6
+import org.apache.ibatis.annotations.Select;
7
+import java.util.List;
8
+
9
+@Mapper
10
+public interface ThresholdConfigMapper extends BaseMapper<ThresholdConfig> {
11
+
12
+    @Select("SELECT * FROM config_threshold WHERE metric_code = #{metricCode} AND device_id IS NULL AND status = 1 AND deleted = 0 ORDER BY level")
13
+    List<ThresholdConfig> selectGlobalByMetricCode(String metricCode);
14
+
15
+    @Select("SELECT * FROM config_threshold WHERE device_id = #{deviceId} AND status = 1 AND deleted = 0 ORDER BY metric_code, level")
16
+    List<ThresholdConfig> selectByDeviceId(Long deviceId);
17
+}

+ 131
- 0
wm-config/src/main/java/com/water/config/service/AnnouncementService.java Wyświetl plik

@@ -0,0 +1,131 @@
1
+package com.water.config.service;
2
+
3
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
5
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
6
+import com.water.common.core.exception.BusinessException;
7
+import com.water.config.entity.Announcement;
8
+import com.water.config.mapper.AnnouncementMapper;
9
+import lombok.extern.slf4j.Slf4j;
10
+import org.springframework.stereotype.Service;
11
+import org.springframework.transaction.annotation.Transactional;
12
+
13
+import java.time.LocalDateTime;
14
+
15
+/**
16
+ * 公告通知服务
17
+ */
18
+@Slf4j
19
+@Service
20
+public class AnnouncementService extends ServiceImpl<AnnouncementMapper, Announcement> {
21
+
22
+    /**
23
+     * 分页查询公告
24
+     */
25
+    public Page<Announcement> pageAnnouncements(int page, int size, Integer type, Integer publishStatus) {
26
+        LambdaQueryWrapper<Announcement> qw = new LambdaQueryWrapper<>();
27
+        if (type != null) {
28
+            qw.eq(Announcement::getType, type);
29
+        }
30
+        if (publishStatus != null) {
31
+            qw.eq(Announcement::getPublishStatus, publishStatus);
32
+        }
33
+        qw.orderByDesc(Announcement::getCreatedAt);
34
+        return this.page(new Page<>(page, size), qw);
35
+    }
36
+
37
+    /**
38
+     * 创建公告(草稿)
39
+     */
40
+    public Announcement createAnnouncement(Announcement announcement) {
41
+        announcement.setPublishStatus(0);
42
+        this.save(announcement);
43
+        return announcement;
44
+    }
45
+
46
+    /**
47
+     * 更新公告(仅草稿可更新)
48
+     */
49
+    public void updateAnnouncement(Long id, Announcement announcement) {
50
+        Announcement existing = this.getById(id);
51
+        if (existing == null) {
52
+            throw new BusinessException("公告不存在");
53
+        }
54
+        if (existing.getPublishStatus() != 0) {
55
+            throw new BusinessException("已发布的公告不可修改");
56
+        }
57
+        announcement.setId(id);
58
+        this.updateById(announcement);
59
+    }
60
+
61
+    /**
62
+     * 发布公告
63
+     */
64
+    @Transactional
65
+    public void publish(Long id) {
66
+        Announcement announcement = this.getById(id);
67
+        if (announcement == null) {
68
+            throw new BusinessException("公告不存在");
69
+        }
70
+        if (announcement.getPublishStatus() != 0) {
71
+            throw new BusinessException("只有草稿状态的公告可以发布");
72
+        }
73
+        announcement.setPublishStatus(1);
74
+        announcement.setPublishTime(LocalDateTime.now());
75
+        this.updateById(announcement);
76
+        // 多渠道发布
77
+        dispatchChannels(announcement);
78
+    }
79
+
80
+    /**
81
+     * 撤回公告
82
+     */
83
+    @Transactional
84
+    public void withdraw(Long id) {
85
+        Announcement announcement = this.getById(id);
86
+        if (announcement == null) {
87
+            throw new BusinessException("公告不存在");
88
+        }
89
+        if (announcement.getPublishStatus() != 1) {
90
+            throw new BusinessException("只有已发布的公告可以撤回");
91
+        }
92
+        announcement.setPublishStatus(2);
93
+        announcement.setWithdrawTime(LocalDateTime.now());
94
+        this.updateById(announcement);
95
+    }
96
+
97
+    /**
98
+     * 删除公告
99
+     */
100
+    public void deleteAnnouncement(Long id) {
101
+        Announcement existing = this.getById(id);
102
+        if (existing == null) {
103
+            throw new BusinessException("公告不存在");
104
+        }
105
+        if (existing.getPublishStatus() == 1) {
106
+            throw new BusinessException("已发布的公告不可删除,请先撤回");
107
+        }
108
+        this.removeById(id);
109
+    }
110
+
111
+    /**
112
+     * 多渠道分发(模拟)
113
+     */
114
+    private void dispatchChannels(Announcement announcement) {
115
+        String channels = announcement.getChannels();
116
+        if (channels == null || channels.isEmpty()) {
117
+            log.info("公告 {} 无渠道配置,仅站内信发布", announcement.getId());
118
+            return;
119
+        }
120
+        log.info("公告 {} 发布渠道: {}", announcement.getId(), channels);
121
+        if (channels.contains("sms")) {
122
+            log.info("→ 短信渠道已触发");
123
+        }
124
+        if (channels.contains("push")) {
125
+            log.info("→ APP推送渠道已触发");
126
+        }
127
+        if (channels.contains("site")) {
128
+            log.info("→ 站内信渠道已触发");
129
+        }
130
+    }
131
+}

+ 150
- 0
wm-config/src/main/java/com/water/config/service/DeviceManageService.java Wyświetl plik

@@ -0,0 +1,150 @@
1
+package com.water.config.service;
2
+
3
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
5
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
6
+import com.water.common.core.exception.BusinessException;
7
+import com.water.config.entity.DeviceInfo;
8
+import com.water.config.entity.DeviceMaintenance;
9
+import com.water.config.mapper.DeviceInfoMapper;
10
+import com.water.config.mapper.DeviceMaintenanceMapper;
11
+import lombok.RequiredArgsConstructor;
12
+import org.springframework.stereotype.Service;
13
+import org.springframework.transaction.annotation.Transactional;
14
+
15
+import java.time.LocalDateTime;
16
+import java.util.List;
17
+
18
+/**
19
+ * 设备管理服务
20
+ */
21
+@Service
22
+@RequiredArgsConstructor
23
+public class DeviceManageService extends ServiceImpl<DeviceInfoMapper, DeviceInfo> {
24
+
25
+    private final DeviceMaintenanceMapper maintenanceMapper;
26
+
27
+    /**
28
+     * 分页查询设备
29
+     */
30
+    public Page<DeviceInfo> pageDevices(int page, int size, String deviceName, Integer category, Integer deviceStatus) {
31
+        LambdaQueryWrapper<DeviceInfo> qw = new LambdaQueryWrapper<>();
32
+        if (deviceName != null && !deviceName.isEmpty()) {
33
+            qw.like(DeviceInfo::getDeviceName, deviceName);
34
+        }
35
+        if (category != null) {
36
+            qw.eq(DeviceInfo::getCategory, category);
37
+        }
38
+        if (deviceStatus != null) {
39
+            qw.eq(DeviceInfo::getDeviceStatus, deviceStatus);
40
+        }
41
+        qw.orderByDesc(DeviceInfo::getCreatedAt);
42
+        return this.page(new Page<>(page, size), qw);
43
+    }
44
+
45
+    /**
46
+     * 创建设备
47
+     */
48
+    public DeviceInfo createDevice(DeviceInfo device) {
49
+        // 检查设备编码唯一性
50
+        long count = this.count(new LambdaQueryWrapper<DeviceInfo>()
51
+                .eq(DeviceInfo::getDeviceCode, device.getDeviceCode()));
52
+        if (count > 0) {
53
+            throw new BusinessException("设备编码已存在");
54
+        }
55
+        this.save(device);
56
+        return device;
57
+    }
58
+
59
+    /**
60
+     * 更新设备
61
+     */
62
+    public void updateDevice(Long id, DeviceInfo device) {
63
+        DeviceInfo existing = this.getById(id);
64
+        if (existing == null) {
65
+            throw new BusinessException("设备不存在");
66
+        }
67
+        device.setId(id);
68
+        this.updateById(device);
69
+    }
70
+
71
+    /**
72
+     * 更新设备状态
73
+     */
74
+    public void updateDeviceStatus(Long id, Integer deviceStatus) {
75
+        DeviceInfo device = this.getById(id);
76
+        if (device == null) {
77
+            throw new BusinessException("设备不存在");
78
+        }
79
+        device.setDeviceStatus(deviceStatus);
80
+        this.updateById(device);
81
+    }
82
+
83
+    /**
84
+     * 按分类查询设备
85
+     */
86
+    public List<DeviceInfo> getDevicesByCategory(Integer category) {
87
+        return baseMapper.selectByCategory(category);
88
+    }
89
+
90
+    /**
91
+     * 按状态查询设备
92
+     */
93
+    public List<DeviceInfo> getDevicesByStatus(Integer status) {
94
+        return baseMapper.selectByDeviceStatus(status);
95
+    }
96
+
97
+    /**
98
+     * 添加维保记录
99
+     */
100
+    @Transactional
101
+    public DeviceMaintenance addMaintenance(DeviceMaintenance maintenance) {
102
+        DeviceInfo device = this.getById(maintenance.getDeviceId());
103
+        if (device == null) {
104
+            throw new BusinessException("设备不存在");
105
+        }
106
+        maintenanceMapper.insert(maintenance);
107
+        // 如果维保完成,更新设备最后维护时间
108
+        if (maintenance.getResult() != null && maintenance.getResult() == 1) {
109
+            device.setLastMaintenanceTime(LocalDateTime.now());
110
+            if (device.getDeviceStatus() == 3) {
111
+                device.setDeviceStatus(1); // 维修中 -> 在线
112
+            }
113
+            this.updateById(device);
114
+        }
115
+        return maintenance;
116
+    }
117
+
118
+    /**
119
+     * 查询设备维保历史
120
+     */
121
+    public Page<DeviceMaintenance> pageMaintenances(Long deviceId, int page, int size) {
122
+        LambdaQueryWrapper<DeviceMaintenance> qw = new LambdaQueryWrapper<>();
123
+        if (deviceId != null) {
124
+            qw.eq(DeviceMaintenance::getDeviceId, deviceId);
125
+        }
126
+        qw.orderByDesc(DeviceMaintenance::getStartTime);
127
+        return maintenanceMapper.selectPage(new Page<>(page, size), qw);
128
+    }
129
+
130
+    /**
131
+     * 更新维保记录
132
+     */
133
+    public void updateMaintenance(Long id, DeviceMaintenance maintenance) {
134
+        maintenance.setId(id);
135
+        maintenanceMapper.updateById(maintenance);
136
+    }
137
+
138
+    /**
139
+     * 获取设备统计(按分类)
140
+     */
141
+    public List<Long> getDeviceCountByCategory() {
142
+        // 简化版:返回各分类的设备数量
143
+        LambdaQueryWrapper<DeviceInfo> qw = new LambdaQueryWrapper<>();
144
+        qw.select(DeviceInfo::getCategory);
145
+        qw.groupBy(DeviceInfo::getCategory);
146
+        return this.listMaps(qw).stream()
147
+                .map(m -> ((Number) m.get("category")).longValue())
148
+                .toList();
149
+    }
150
+}

+ 144
- 0
wm-config/src/main/java/com/water/config/service/ThresholdService.java Wyświetl plik

@@ -0,0 +1,144 @@
1
+package com.water.config.service;
2
+
3
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
5
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
6
+import com.water.config.entity.ThresholdChangeLog;
7
+import com.water.config.entity.ThresholdConfig;
8
+import com.water.config.mapper.ThresholdChangeLogMapper;
9
+import com.water.config.mapper.ThresholdConfigMapper;
10
+import com.water.common.core.exception.BusinessException;
11
+import lombok.RequiredArgsConstructor;
12
+import org.springframework.stereotype.Service;
13
+import org.springframework.transaction.annotation.Transactional;
14
+
15
+import java.util.List;
16
+
17
+/**
18
+ * 阈值管理服务
19
+ */
20
+@Service
21
+@RequiredArgsConstructor
22
+public class ThresholdService extends ServiceImpl<ThresholdConfigMapper, ThresholdConfig> {
23
+
24
+    private final ThresholdChangeLogMapper changeLogMapper;
25
+
26
+    /**
27
+     * 分页查询阈值配置
28
+     */
29
+    public Page<ThresholdConfig> pageThresholds(int page, int size, String metricCode, Integer level) {
30
+        LambdaQueryWrapper<ThresholdConfig> qw = new LambdaQueryWrapper<>();
31
+        if (metricCode != null && !metricCode.isEmpty()) {
32
+            qw.like(ThresholdConfig::getMetricCode, metricCode);
33
+        }
34
+        if (level != null) {
35
+            qw.eq(ThresholdConfig::getLevel, level);
36
+        }
37
+        qw.orderByAsc(ThresholdConfig::getMetricCode, ThresholdConfig::getLevel);
38
+        return this.page(new Page<>(page, size), qw);
39
+    }
40
+
41
+    /**
42
+     * 创建阈值配置
43
+     */
44
+    @Transactional
45
+    public ThresholdConfig createThreshold(ThresholdConfig config) {
46
+        validateThreshold(config);
47
+        this.save(config);
48
+        recordChangeLog(config, null, "新建阈值配置");
49
+        return config;
50
+    }
51
+
52
+    /**
53
+     * 更新阈值配置(记录变更)
54
+     */
55
+    @Transactional
56
+    public void updateThreshold(Long id, ThresholdConfig config) {
57
+        ThresholdConfig old = this.getById(id);
58
+        if (old == null) {
59
+            throw new BusinessException("阈值配置不存在");
60
+        }
61
+        config.setId(id);
62
+        validateThreshold(config);
63
+        this.updateById(config);
64
+        recordChangeLog(config, old, "更新阈值配置");
65
+    }
66
+
67
+    /**
68
+     * 删除阈值配置
69
+     */
70
+    @Transactional
71
+    public void deleteThreshold(Long id) {
72
+        ThresholdConfig old = this.getById(id);
73
+        if (old == null) {
74
+            throw new BusinessException("阈值配置不存在");
75
+        }
76
+        this.removeById(id);
77
+        recordChangeLog(old, old, "删除阈值配置");
78
+    }
79
+
80
+    /**
81
+     * 获取某指标的全局阈值(多级)
82
+     */
83
+    public List<ThresholdConfig> getGlobalThresholds(String metricCode) {
84
+        return baseMapper.selectGlobalByMetricCode(metricCode);
85
+    }
86
+
87
+    /**
88
+     * 获取某设备的阈值配置
89
+     */
90
+    public List<ThresholdConfig> getDeviceThresholds(Long deviceId) {
91
+        return baseMapper.selectByDeviceId(deviceId);
92
+    }
93
+
94
+    /**
95
+     * 获取阈值变更历史
96
+     */
97
+    public Page<ThresholdChangeLog> getChangeHistory(Long thresholdId, int page, int size) {
98
+        LambdaQueryWrapper<ThresholdChangeLog> qw = new LambdaQueryWrapper<>();
99
+        if (thresholdId != null) {
100
+            qw.eq(ThresholdChangeLog::getThresholdId, thresholdId);
101
+        }
102
+        qw.orderByDesc(ThresholdChangeLog::getCreatedAt);
103
+        return changeLogMapper.selectPage(new Page<>(page, size), qw);
104
+    }
105
+
106
+    /**
107
+     * 启用/禁用阈值
108
+     */
109
+    public void toggleStatus(Long id, Integer status) {
110
+        ThresholdConfig config = this.getById(id);
111
+        if (config == null) {
112
+            throw new BusinessException("阈值配置不存在");
113
+        }
114
+        config.setStatus(status);
115
+        this.updateById(config);
116
+    }
117
+
118
+    private void validateThreshold(ThresholdConfig config) {
119
+        if (config.getMinValue() != null && config.getMaxValue() != null) {
120
+            if (config.getMinValue().compareTo(config.getMaxValue()) > 0) {
121
+                throw new BusinessException("最小值不能大于最大值");
122
+            }
123
+        }
124
+        if (config.getLevel() != null && (config.getLevel() < 1 || config.getLevel() > 3)) {
125
+            throw new BusinessException("阈值级别必须在1-3之间");
126
+        }
127
+    }
128
+
129
+    private void recordChangeLog(ThresholdConfig newConfig, ThresholdConfig oldConfig, String reason) {
130
+        ThresholdChangeLog log = new ThresholdChangeLog();
131
+        log.setThresholdId(newConfig.getId());
132
+        if (oldConfig != null) {
133
+            log.setOldMinValue(oldConfig.getMinValue());
134
+            log.setOldMaxValue(oldConfig.getMaxValue());
135
+            log.setOldLevel(oldConfig.getLevel());
136
+        }
137
+        log.setNewMinValue(newConfig.getMinValue());
138
+        log.setNewMaxValue(newConfig.getMaxValue());
139
+        log.setNewLevel(newConfig.getLevel());
140
+        log.setReason(reason);
141
+        log.setOperator("system");
142
+        changeLogMapper.insert(log);
143
+    }
144
+}

+ 30
- 0
wm-config/src/main/resources/application.yml Wyświetl plik

@@ -0,0 +1,30 @@
1
+server:
2
+  port: 8090
3
+
4
+spring:
5
+  application:
6
+    name: wm-config
7
+  datasource:
8
+    driver-class-name: org.postgresql.Driver
9
+    url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:water}?currentSchema=public
10
+    username: ${DB_USER:postgres}
11
+    password: ${DB_PASS:postgres}
12
+  cloud:
13
+    nacos:
14
+      discovery:
15
+        server-addr: ${NACOS_ADDR:localhost:8848}
16
+
17
+mybatis-plus:
18
+  configuration:
19
+    map-underscore-to-camel-case: true
20
+    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
21
+  global-config:
22
+    db-config:
23
+      logic-delete-field: deleted
24
+      logic-delete-value: 1
25
+      logic-not-delete-value: 0
26
+
27
+sa-token:
28
+  token-name: Authorization
29
+  timeout: 86400
30
+  active-timeout: 1800

+ 102
- 0
wm-config/src/main/resources/db/schema.sql Wyświetl plik

@@ -0,0 +1,102 @@
1
+-- ============================================================
2
+-- wm-config DDL: 阈值管理 + 信息发布 + 设备管理
3
+-- ============================================================
4
+
5
+-- 阈值配置表
6
+CREATE TABLE IF NOT EXISTS config_threshold (
7
+    id              BIGSERIAL PRIMARY KEY,
8
+    metric_code     VARCHAR(64)     NOT NULL,
9
+    metric_name     VARCHAR(128)    NOT NULL,
10
+    device_id       BIGINT,
11
+    level           SMALLINT        NOT NULL DEFAULT 1,  -- 1-预警 2-报警 3-紧急
12
+    min_value       NUMERIC(12,4),
13
+    max_value       NUMERIC(12,4),
14
+    unit            VARCHAR(32),
15
+    status          SMALLINT        NOT NULL DEFAULT 1,  -- 0-禁用 1-启用
16
+    remark          VARCHAR(500),
17
+    deleted         SMALLINT        NOT NULL DEFAULT 0,
18
+    created_at      TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP,
19
+    updated_at      TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP
20
+);
21
+COMMENT ON TABLE config_threshold IS '阈值配置表';
22
+CREATE INDEX idx_threshold_metric ON config_threshold(metric_code);
23
+CREATE INDEX idx_threshold_device ON config_threshold(device_id);
24
+
25
+-- 阈值变更记录表
26
+CREATE TABLE IF NOT EXISTS config_threshold_change_log (
27
+    id              BIGSERIAL PRIMARY KEY,
28
+    threshold_id    BIGINT          NOT NULL,
29
+    old_min_value   NUMERIC(12,4),
30
+    old_max_value   NUMERIC(12,4),
31
+    new_min_value   NUMERIC(12,4),
32
+    new_max_value   NUMERIC(12,4),
33
+    old_level       SMALLINT,
34
+    new_level       SMALLINT,
35
+    operator        VARCHAR(64),
36
+    reason          VARCHAR(500),
37
+    created_at      TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP
38
+);
39
+COMMENT ON TABLE config_threshold_change_log IS '阈值变更记录表';
40
+CREATE INDEX idx_change_log_threshold ON config_threshold_change_log(threshold_id);
41
+
42
+-- 公告通知表
43
+CREATE TABLE IF NOT EXISTS config_announcement (
44
+    id              BIGSERIAL PRIMARY KEY,
45
+    title           VARCHAR(256)    NOT NULL,
46
+    content         TEXT,
47
+    type            SMALLINT        NOT NULL DEFAULT 1,  -- 1-系统公告 2-维护通知 3-紧急通知
48
+    publish_status  SMALLINT        NOT NULL DEFAULT 0,  -- 0-草稿 1-已发布 2-已撤回
49
+    channels        VARCHAR(256),    -- JSON: ["sms","push","site"]
50
+    publisher       VARCHAR(64),
51
+    publish_time    TIMESTAMP,
52
+    withdraw_time   TIMESTAMP,
53
+    deleted         SMALLINT        NOT NULL DEFAULT 0,
54
+    created_at      TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP,
55
+    updated_at      TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP
56
+);
57
+COMMENT ON TABLE config_announcement IS '公告通知表';
58
+CREATE INDEX idx_announcement_status ON config_announcement(publish_status);
59
+
60
+-- 设备台账表
61
+CREATE TABLE IF NOT EXISTS config_device_info (
62
+    id                      BIGSERIAL PRIMARY KEY,
63
+    device_code             VARCHAR(64)     NOT NULL UNIQUE,
64
+    device_name             VARCHAR(128)    NOT NULL,
65
+    category                SMALLINT        NOT NULL DEFAULT 9, -- 1-水表 2-压力传感器 3-流量计 4-水质监测仪 5-阀门 9-其他
66
+    brand                   VARCHAR(64),
67
+    model                   VARCHAR(64),
68
+    location                VARCHAR(256),
69
+    longitude               DOUBLE PRECISION,
70
+    latitude                DOUBLE PRECISION,
71
+    device_status           SMALLINT        NOT NULL DEFAULT 0, -- 0-离线 1-在线 2-故障 3-维修中
72
+    install_date            TIMESTAMP,
73
+    last_maintenance_time   TIMESTAMP,
74
+    responsible_person      VARCHAR(64),
75
+    remark                  VARCHAR(500),
76
+    deleted                 SMALLINT        NOT NULL DEFAULT 0,
77
+    created_at              TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP,
78
+    updated_at              TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP
79
+);
80
+COMMENT ON TABLE config_device_info IS '设备台账表';
81
+CREATE INDEX idx_device_code ON config_device_info(device_code);
82
+CREATE INDEX idx_device_category ON config_device_info(category);
83
+CREATE INDEX idx_device_status ON config_device_info(device_status);
84
+
85
+-- 设备维保记录表
86
+CREATE TABLE IF NOT EXISTS config_device_maintenance (
87
+    id                  BIGSERIAL PRIMARY KEY,
88
+    device_id           BIGINT          NOT NULL,
89
+    maintenance_type    SMALLINT        NOT NULL DEFAULT 1, -- 1-日常巡检 2-定期保养 3-故障维修 4-更换配件
90
+    description         TEXT,
91
+    operator            VARCHAR(64),
92
+    start_time          TIMESTAMP,
93
+    end_time            TIMESTAMP,
94
+    result              SMALLINT        NOT NULL DEFAULT 0, -- 0-未完成 1-已完成 2-需要返修
95
+    cost                DOUBLE PRECISION,
96
+    attachments         TEXT,           -- JSON
97
+    deleted             SMALLINT        NOT NULL DEFAULT 0,
98
+    created_at          TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP,
99
+    updated_at          TIMESTAMP       NOT NULL DEFAULT CURRENT_TIMESTAMP
100
+);
101
+COMMENT ON TABLE config_device_maintenance IS '设备维保记录表';
102
+CREATE INDEX idx_maintenance_device ON config_device_maintenance(device_id);

+ 100
- 0
wm-config/src/test/java/com/water/config/AnnouncementServiceTest.java Wyświetl plik

@@ -0,0 +1,100 @@
1
+package com.water.config;
2
+
3
+import com.water.common.core.exception.BusinessException;
4
+import com.water.config.entity.Announcement;
5
+import com.water.config.mapper.AnnouncementMapper;
6
+import com.water.config.service.AnnouncementService;
7
+import org.junit.jupiter.api.BeforeEach;
8
+import org.junit.jupiter.api.Test;
9
+import org.junit.jupiter.api.extension.ExtendWith;
10
+import org.mockito.InjectMocks;
11
+import org.mockito.Mock;
12
+import org.mockito.junit.jupiter.MockitoExtension;
13
+
14
+import static org.junit.jupiter.api.Assertions.*;
15
+import static org.mockito.ArgumentMatchers.any;
16
+import static org.mockito.Mockito.*;
17
+
18
+@ExtendWith(MockitoExtension.class)
19
+class AnnouncementServiceTest {
20
+
21
+    @Mock
22
+    private AnnouncementMapper announcementMapper;
23
+    @InjectMocks
24
+    private AnnouncementService announcementService;
25
+
26
+    private Announcement draft;
27
+
28
+    @BeforeEach
29
+    void setUp() {
30
+        draft = new Announcement();
31
+        draft.setTitle("系统维护通知");
32
+        draft.setContent("今晚22:00-次日06:00系统维护");
33
+        draft.setType(2);
34
+        draft.setChannels("[\"site\",\"sms\"]");
35
+    }
36
+
37
+    @Test
38
+    void createAnnouncement_setsDraftStatus() {
39
+        when(announcementMapper.insert(any())).thenReturn(1);
40
+
41
+        Announcement result = announcementService.createAnnouncement(draft);
42
+
43
+        assertEquals(0, result.getPublishStatus());
44
+        verify(announcementMapper).insert(any(Announcement.class));
45
+    }
46
+
47
+    @Test
48
+    void publish_draft_success() {
49
+        Announcement existing = new Announcement();
50
+        existing.setId(1L);
51
+        existing.setPublishStatus(0);
52
+        existing.setChannels("[\"site\"]");
53
+
54
+        when(announcementMapper.selectById(1L)).thenReturn(existing);
55
+        when(announcementMapper.updateById(any())).thenReturn(1);
56
+
57
+        assertDoesNotThrow(() -> announcementService.publish(1L));
58
+        verify(announcementMapper).updateById(argThat(a ->
59
+                a.getPublishStatus() == 1 && a.getPublishTime() != null));
60
+    }
61
+
62
+    @Test
63
+    void publish_alreadyPublished_throws() {
64
+        Announcement existing = new Announcement();
65
+        existing.setId(1L);
66
+        existing.setPublishStatus(1);
67
+
68
+        when(announcementMapper.selectById(1L)).thenReturn(existing);
69
+
70
+        BusinessException ex = assertThrows(BusinessException.class,
71
+                () -> announcementService.publish(1L));
72
+        assertEquals("只有草稿状态的公告可以发布", ex.getMessage());
73
+    }
74
+
75
+    @Test
76
+    void withdraw_notPublished_throws() {
77
+        Announcement existing = new Announcement();
78
+        existing.setId(1L);
79
+        existing.setPublishStatus(0);
80
+
81
+        when(announcementMapper.selectById(1L)).thenReturn(existing);
82
+
83
+        BusinessException ex = assertThrows(BusinessException.class,
84
+                () -> announcementService.withdraw(1L));
85
+        assertEquals("只有已发布的公告可以撤回", ex.getMessage());
86
+    }
87
+
88
+    @Test
89
+    void delete_published_throws() {
90
+        Announcement existing = new Announcement();
91
+        existing.setId(1L);
92
+        existing.setPublishStatus(1);
93
+
94
+        when(announcementMapper.selectById(1L)).thenReturn(existing);
95
+
96
+        BusinessException ex = assertThrows(BusinessException.class,
97
+                () -> announcementService.deleteAnnouncement(1L));
98
+        assertEquals("已发布的公告不可删除,请先撤回", ex.getMessage());
99
+    }
100
+}

+ 107
- 0
wm-config/src/test/java/com/water/config/DeviceManageServiceTest.java Wyświetl plik

@@ -0,0 +1,107 @@
1
+package com.water.config;
2
+
3
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4
+import com.water.common.core.exception.BusinessException;
5
+import com.water.config.entity.DeviceInfo;
6
+import com.water.config.entity.DeviceMaintenance;
7
+import com.water.config.mapper.DeviceInfoMapper;
8
+import com.water.config.mapper.DeviceMaintenanceMapper;
9
+import com.water.config.service.DeviceManageService;
10
+import org.junit.jupiter.api.BeforeEach;
11
+import org.junit.jupiter.api.Test;
12
+import org.junit.jupiter.api.extension.ExtendWith;
13
+import org.mockito.InjectMocks;
14
+import org.mockito.Mock;
15
+import org.mockito.junit.jupiter.MockitoExtension;
16
+
17
+import static org.junit.jupiter.api.Assertions.*;
18
+import static org.mockito.ArgumentMatchers.any;
19
+import static org.mockito.Mockito.*;
20
+
21
+@ExtendWith(MockitoExtension.class)
22
+class DeviceManageServiceTest {
23
+
24
+    @Mock
25
+    private DeviceInfoMapper deviceInfoMapper;
26
+    @Mock
27
+    private DeviceMaintenanceMapper maintenanceMapper;
28
+    @InjectMocks
29
+    private DeviceManageService deviceManageService;
30
+
31
+    private DeviceInfo device;
32
+
33
+    @BeforeEach
34
+    void setUp() {
35
+        device = new DeviceInfo();
36
+        device.setDeviceCode("WM-001");
37
+        device.setDeviceName("1号水表");
38
+        device.setCategory(1);
39
+        device.setBrand("海天");
40
+        device.setModel("HT-200");
41
+        device.setDeviceStatus(0);
42
+    }
43
+
44
+    @Test
45
+    void createDevice_success() {
46
+        when(deviceInfoMapper.selectCount(any())).thenReturn(0L);
47
+        when(deviceInfoMapper.insert(any())).thenReturn(1);
48
+
49
+        DeviceInfo result = deviceManageService.createDevice(device);
50
+
51
+        assertNotNull(result);
52
+        assertEquals("WM-001", result.getDeviceCode());
53
+        verify(deviceInfoMapper).insert(any(DeviceInfo.class));
54
+    }
55
+
56
+    @Test
57
+    void createDevice_duplicateCode_throws() {
58
+        when(deviceInfoMapper.selectCount(any())).thenReturn(1L);
59
+
60
+        BusinessException ex = assertThrows(BusinessException.class,
61
+                () -> deviceManageService.createDevice(device));
62
+        assertEquals("设备编码已存在", ex.getMessage());
63
+    }
64
+
65
+    @Test
66
+    void updateDevice_notFound_throws() {
67
+        when(deviceInfoMapper.selectById(999L)).thenReturn(null);
68
+
69
+        BusinessException ex = assertThrows(BusinessException.class,
70
+                () -> deviceManageService.updateDevice(999L, device));
71
+        assertEquals("设备不存在", ex.getMessage());
72
+    }
73
+
74
+    @Test
75
+    void addMaintenance_deviceNotFound_throws() {
76
+        DeviceMaintenance maintenance = new DeviceMaintenance();
77
+        maintenance.setDeviceId(999L);
78
+
79
+        when(deviceInfoMapper.selectById(999L)).thenReturn(null);
80
+
81
+        BusinessException ex = assertThrows(BusinessException.class,
82
+                () -> deviceManageService.addMaintenance(maintenance));
83
+        assertEquals("设备不存在", ex.getMessage());
84
+    }
85
+
86
+    @Test
87
+    void addMaintenance_completed_updatesDeviceTime() {
88
+        DeviceInfo existingDevice = new DeviceInfo();
89
+        existingDevice.setId(1L);
90
+        existingDevice.setDeviceStatus(3); // 维修中
91
+
92
+        DeviceMaintenance maintenance = new DeviceMaintenance();
93
+        maintenance.setDeviceId(1L);
94
+        maintenance.setResult(1); // 已完成
95
+        maintenance.setDescription("更换电池");
96
+
97
+        when(deviceInfoMapper.selectById(1L)).thenReturn(existingDevice);
98
+        when(maintenanceMapper.insert(any())).thenReturn(1);
99
+        when(deviceInfoMapper.updateById(any())).thenReturn(1);
100
+
101
+        DeviceMaintenance result = deviceManageService.addMaintenance(maintenance);
102
+
103
+        assertNotNull(result);
104
+        verify(deviceInfoMapper).updateById(argThat(d ->
105
+                d.getLastMaintenanceTime() != null && d.getDeviceStatus() == 1));
106
+    }
107
+}

+ 96
- 0
wm-config/src/test/java/com/water/config/ThresholdServiceTest.java Wyświetl plik

@@ -0,0 +1,96 @@
1
+package com.water.config;
2
+
3
+import com.water.common.core.exception.BusinessException;
4
+import com.water.config.entity.ThresholdChangeLog;
5
+import com.water.config.entity.ThresholdConfig;
6
+import com.water.config.mapper.ThresholdChangeLogMapper;
7
+import com.water.config.mapper.ThresholdConfigMapper;
8
+import com.water.config.service.ThresholdService;
9
+import org.junit.jupiter.api.BeforeEach;
10
+import org.junit.jupiter.api.Test;
11
+import org.junit.jupiter.api.extension.ExtendWith;
12
+import org.mockito.InjectMocks;
13
+import org.mockito.Mock;
14
+import org.mockito.junit.jupiter.MockitoExtension;
15
+
16
+import java.math.BigDecimal;
17
+
18
+import static org.junit.jupiter.api.Assertions.*;
19
+import static org.mockito.ArgumentMatchers.any;
20
+import static org.mockito.Mockito.*;
21
+
22
+@ExtendWith(MockitoExtension.class)
23
+class ThresholdServiceTest {
24
+
25
+    @Mock
26
+    private ThresholdConfigMapper thresholdConfigMapper;
27
+    @Mock
28
+    private ThresholdChangeLogMapper changeLogMapper;
29
+    @InjectMocks
30
+    private ThresholdService thresholdService;
31
+
32
+    private ThresholdConfig validConfig;
33
+
34
+    @BeforeEach
35
+    void setUp() {
36
+        validConfig = new ThresholdConfig();
37
+        validConfig.setMetricCode("water_pressure");
38
+        validConfig.setMetricName("水压");
39
+        validConfig.setLevel(1);
40
+        validConfig.setMinValue(new BigDecimal("0.1"));
41
+        validConfig.setMaxValue(new BigDecimal("0.8"));
42
+        validConfig.setUnit("MPa");
43
+        validConfig.setStatus(1);
44
+    }
45
+
46
+    @Test
47
+    void createThreshold_success() {
48
+        when(thresholdConfigMapper.insert(any())).thenReturn(1);
49
+        when(changeLogMapper.insert(any())).thenReturn(1);
50
+
51
+        ThresholdConfig result = thresholdService.createThreshold(validConfig);
52
+
53
+        assertNotNull(result);
54
+        assertEquals("water_pressure", result.getMetricCode());
55
+        assertEquals(1, result.getLevel());
56
+        verify(thresholdConfigMapper).insert(any(ThresholdConfig.class));
57
+        verify(changeLogMapper).insert(any(ThresholdChangeLog.class));
58
+    }
59
+
60
+    @Test
61
+    void createThreshold_minGreaterThanMax_throws() {
62
+        validConfig.setMinValue(new BigDecimal("1.0"));
63
+        validConfig.setMaxValue(new BigDecimal("0.5"));
64
+
65
+        BusinessException ex = assertThrows(BusinessException.class,
66
+                () -> thresholdService.createThreshold(validConfig));
67
+        assertEquals("最小值不能大于最大值", ex.getMessage());
68
+    }
69
+
70
+    @Test
71
+    void createThreshold_invalidLevel_throws() {
72
+        validConfig.setLevel(5);
73
+
74
+        BusinessException ex = assertThrows(BusinessException.class,
75
+                () -> thresholdService.createThreshold(validConfig));
76
+        assertEquals("阈值级别必须在1-3之间", ex.getMessage());
77
+    }
78
+
79
+    @Test
80
+    void updateThreshold_notFound_throws() {
81
+        when(thresholdConfigMapper.selectById(999L)).thenReturn(null);
82
+
83
+        BusinessException ex = assertThrows(BusinessException.class,
84
+                () -> thresholdService.updateThreshold(999L, validConfig));
85
+        assertEquals("阈值配置不存在", ex.getMessage());
86
+    }
87
+
88
+    @Test
89
+    void deleteThreshold_notFound_throws() {
90
+        when(thresholdConfigMapper.selectById(999L)).thenReturn(null);
91
+
92
+        BusinessException ex = assertThrows(BusinessException.class,
93
+                () -> thresholdService.deleteThreshold(999L));
94
+        assertEquals("阈值配置不存在", ex.getMessage());
95
+    }
96
+}