From 4a0fc1bf427ddc4139ae8de14b5d0576f1890d2e Mon Sep 17 00:00:00 2001 From: bot_dev2 Date: Sun, 14 Jun 2026 15:37:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(wm-config):=20#72=20=E9=98=88=E5=80=BC?= =?UTF-8?q?=E7=AE=A1=E7=90=86+=E4=BF=A1=E6=81=AF=E5=8F=91=E5=B8=83+?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 1 + wm-config/pom.xml | 16 ++ .../com/water/config/ConfigApplication.java | 15 ++ .../controller/AnnouncementController.java | 68 ++++++++ .../controller/DeviceManageController.java | 98 ++++++++++++ .../controller/ThresholdController.java | 84 ++++++++++ .../com/water/config/entity/Announcement.java | 35 ++++ .../com/water/config/entity/DeviceInfo.java | 45 ++++++ .../config/entity/DeviceMaintenance.java | 37 +++++ .../config/entity/ThresholdChangeLog.java | 35 ++++ .../water/config/entity/ThresholdConfig.java | 38 +++++ .../config/mapper/AnnouncementMapper.java | 9 ++ .../water/config/mapper/DeviceInfoMapper.java | 17 ++ .../mapper/DeviceMaintenanceMapper.java | 14 ++ .../mapper/ThresholdChangeLogMapper.java | 9 ++ .../config/mapper/ThresholdConfigMapper.java | 17 ++ .../config/service/AnnouncementService.java | 131 +++++++++++++++ .../config/service/DeviceManageService.java | 150 ++++++++++++++++++ .../config/service/ThresholdService.java | 144 +++++++++++++++++ wm-config/src/main/resources/application.yml | 30 ++++ wm-config/src/main/resources/db/schema.sql | 102 ++++++++++++ .../water/config/AnnouncementServiceTest.java | 100 ++++++++++++ .../water/config/DeviceManageServiceTest.java | 107 +++++++++++++ .../water/config/ThresholdServiceTest.java | 96 +++++++++++ 24 files changed, 1398 insertions(+) create mode 100644 wm-config/pom.xml create mode 100644 wm-config/src/main/java/com/water/config/ConfigApplication.java create mode 100644 wm-config/src/main/java/com/water/config/controller/AnnouncementController.java create mode 100644 wm-config/src/main/java/com/water/config/controller/DeviceManageController.java create mode 100644 wm-config/src/main/java/com/water/config/controller/ThresholdController.java create mode 100644 wm-config/src/main/java/com/water/config/entity/Announcement.java create mode 100644 wm-config/src/main/java/com/water/config/entity/DeviceInfo.java create mode 100644 wm-config/src/main/java/com/water/config/entity/DeviceMaintenance.java create mode 100644 wm-config/src/main/java/com/water/config/entity/ThresholdChangeLog.java create mode 100644 wm-config/src/main/java/com/water/config/entity/ThresholdConfig.java create mode 100644 wm-config/src/main/java/com/water/config/mapper/AnnouncementMapper.java create mode 100644 wm-config/src/main/java/com/water/config/mapper/DeviceInfoMapper.java create mode 100644 wm-config/src/main/java/com/water/config/mapper/DeviceMaintenanceMapper.java create mode 100644 wm-config/src/main/java/com/water/config/mapper/ThresholdChangeLogMapper.java create mode 100644 wm-config/src/main/java/com/water/config/mapper/ThresholdConfigMapper.java create mode 100644 wm-config/src/main/java/com/water/config/service/AnnouncementService.java create mode 100644 wm-config/src/main/java/com/water/config/service/DeviceManageService.java create mode 100644 wm-config/src/main/java/com/water/config/service/ThresholdService.java create mode 100644 wm-config/src/main/resources/application.yml create mode 100644 wm-config/src/main/resources/db/schema.sql create mode 100644 wm-config/src/test/java/com/water/config/AnnouncementServiceTest.java create mode 100644 wm-config/src/test/java/com/water/config/DeviceManageServiceTest.java create mode 100644 wm-config/src/test/java/com/water/config/ThresholdServiceTest.java diff --git a/pom.xml b/pom.xml index 8dae7470..2cb55ca7 100644 --- a/pom.xml +++ b/pom.xml @@ -49,6 +49,7 @@ wm-dispatch wm-system wm-mobile-app + wm-config diff --git a/wm-config/pom.xml b/wm-config/pom.xml new file mode 100644 index 00000000..db5f2c1f --- /dev/null +++ b/wm-config/pom.xml @@ -0,0 +1,16 @@ + + + 4.0.0 + com.waterwm-parent1.0.0-SNAPSHOT + wm-config + + com.waterwm-common + org.springframework.bootspring-boot-starter-web + com.alibaba.cloudspring-cloud-starter-alibaba-nacos-discovery + com.baomidoumybatis-plus-spring-boot3-starter + cn.dev33sa-token-spring-boot3-starter + org.postgresqlpostgresql + + diff --git a/wm-config/src/main/java/com/water/config/ConfigApplication.java b/wm-config/src/main/java/com/water/config/ConfigApplication.java new file mode 100644 index 00000000..26db7723 --- /dev/null +++ b/wm-config/src/main/java/com/water/config/ConfigApplication.java @@ -0,0 +1,15 @@ +package com.water.config; + +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; + +@SpringBootApplication +@EnableDiscoveryClient +@MapperScan("com.water.config.mapper") +public class ConfigApplication { + public static void main(String[] args) { + SpringApplication.run(ConfigApplication.class, args); + } +} diff --git a/wm-config/src/main/java/com/water/config/controller/AnnouncementController.java b/wm-config/src/main/java/com/water/config/controller/AnnouncementController.java new file mode 100644 index 00000000..9cbeaee4 --- /dev/null +++ b/wm-config/src/main/java/com/water/config/controller/AnnouncementController.java @@ -0,0 +1,68 @@ +package com.water.config.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.water.common.core.result.R; +import com.water.config.entity.Announcement; +import com.water.config.service.AnnouncementService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "公告通知管理") +@RestController +@RequestMapping("/api/config/announcement") +@RequiredArgsConstructor +public class AnnouncementController { + + private final AnnouncementService announcementService; + + @Operation(summary = "分页查询公告") + @GetMapping("/list") + public R> list(@RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) Integer type, + @RequestParam(required = false) Integer publishStatus) { + return R.ok(announcementService.pageAnnouncements(page, size, type, publishStatus)); + } + + @Operation(summary = "获取公告详情") + @GetMapping("/{id}") + public R getById(@PathVariable Long id) { + return R.ok(announcementService.getById(id)); + } + + @Operation(summary = "创建公告(草稿)") + @PostMapping + public R create(@RequestBody Announcement announcement) { + return R.ok(announcementService.createAnnouncement(announcement)); + } + + @Operation(summary = "更新公告") + @PutMapping("/{id}") + public R update(@PathVariable Long id, @RequestBody Announcement announcement) { + announcementService.updateAnnouncement(id, announcement); + return R.ok("更新成功"); + } + + @Operation(summary = "发布公告") + @PostMapping("/{id}/publish") + public R publish(@PathVariable Long id) { + announcementService.publish(id); + return R.ok("发布成功"); + } + + @Operation(summary = "撤回公告") + @PostMapping("/{id}/withdraw") + public R withdraw(@PathVariable Long id) { + announcementService.withdraw(id); + return R.ok("撤回成功"); + } + + @Operation(summary = "删除公告") + @DeleteMapping("/{id}") + public R delete(@PathVariable Long id) { + announcementService.deleteAnnouncement(id); + return R.ok("删除成功"); + } +} diff --git a/wm-config/src/main/java/com/water/config/controller/DeviceManageController.java b/wm-config/src/main/java/com/water/config/controller/DeviceManageController.java new file mode 100644 index 00000000..707978d0 --- /dev/null +++ b/wm-config/src/main/java/com/water/config/controller/DeviceManageController.java @@ -0,0 +1,98 @@ +package com.water.config.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.water.common.core.result.R; +import com.water.config.entity.DeviceInfo; +import com.water.config.entity.DeviceMaintenance; +import com.water.config.service.DeviceManageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "设备管理") +@RestController +@RequestMapping("/api/config/device") +@RequiredArgsConstructor +public class DeviceManageController { + + private final DeviceManageService deviceManageService; + + @Operation(summary = "分页查询设备") + @GetMapping("/list") + public R> list(@RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String deviceName, + @RequestParam(required = false) Integer category, + @RequestParam(required = false) Integer deviceStatus) { + return R.ok(deviceManageService.pageDevices(page, size, deviceName, category, deviceStatus)); + } + + @Operation(summary = "获取设备详情") + @GetMapping("/{id}") + public R getById(@PathVariable Long id) { + return R.ok(deviceManageService.getById(id)); + } + + @Operation(summary = "创建设备") + @PostMapping + public R create(@RequestBody DeviceInfo device) { + return R.ok(deviceManageService.createDevice(device)); + } + + @Operation(summary = "更新设备") + @PutMapping("/{id}") + public R update(@PathVariable Long id, @RequestBody DeviceInfo device) { + deviceManageService.updateDevice(id, device); + return R.ok("更新成功"); + } + + @Operation(summary = "删除设备") + @DeleteMapping("/{id}") + public R delete(@PathVariable Long id) { + deviceManageService.removeById(id); + return R.ok("删除成功"); + } + + @Operation(summary = "更新设备状态") + @PutMapping("/{id}/status") + public R updateStatus(@PathVariable Long id, @RequestParam Integer deviceStatus) { + deviceManageService.updateDeviceStatus(id, deviceStatus); + return R.ok("状态更新成功"); + } + + @Operation(summary = "按分类查询设备") + @GetMapping("/category/{category}") + public R> getByCategory(@PathVariable Integer category) { + return R.ok(deviceManageService.getDevicesByCategory(category)); + } + + @Operation(summary = "按状态查询设备") + @GetMapping("/status/{status}") + public R> getByStatus(@PathVariable Integer status) { + return R.ok(deviceManageService.getDevicesByStatus(status)); + } + + @Operation(summary = "添加维保记录") + @PostMapping("/maintenance") + public R addMaintenance(@RequestBody DeviceMaintenance maintenance) { + return R.ok(deviceManageService.addMaintenance(maintenance)); + } + + @Operation(summary = "查询维保记录") + @GetMapping("/maintenance") + public R> pageMaintenances(@RequestParam(required = false) Long deviceId, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size) { + return R.ok(deviceManageService.pageMaintenances(deviceId, page, size)); + } + + @Operation(summary = "更新维保记录") + @PutMapping("/maintenance/{id}") + public R updateMaintenance(@PathVariable Long id, @RequestBody DeviceMaintenance maintenance) { + deviceManageService.updateMaintenance(id, maintenance); + return R.ok("更新成功"); + } +} diff --git a/wm-config/src/main/java/com/water/config/controller/ThresholdController.java b/wm-config/src/main/java/com/water/config/controller/ThresholdController.java new file mode 100644 index 00000000..a5507b29 --- /dev/null +++ b/wm-config/src/main/java/com/water/config/controller/ThresholdController.java @@ -0,0 +1,84 @@ +package com.water.config.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.water.common.core.result.R; +import com.water.config.entity.ThresholdChangeLog; +import com.water.config.entity.ThresholdConfig; +import com.water.config.service.ThresholdService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "阈值管理") +@RestController +@RequestMapping("/api/config/threshold") +@RequiredArgsConstructor +public class ThresholdController { + + private final ThresholdService thresholdService; + + @Operation(summary = "分页查询阈值配置") + @GetMapping("/list") + public R> list(@RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size, + @RequestParam(required = false) String metricCode, + @RequestParam(required = false) Integer level) { + return R.ok(thresholdService.pageThresholds(page, size, metricCode, level)); + } + + @Operation(summary = "获取阈值详情") + @GetMapping("/{id}") + public R getById(@PathVariable Long id) { + return R.ok(thresholdService.getById(id)); + } + + @Operation(summary = "创建阈值配置") + @PostMapping + public R create(@RequestBody ThresholdConfig config) { + return R.ok(thresholdService.createThreshold(config)); + } + + @Operation(summary = "更新阈值配置") + @PutMapping("/{id}") + public R update(@PathVariable Long id, @RequestBody ThresholdConfig config) { + thresholdService.updateThreshold(id, config); + return R.ok("更新成功"); + } + + @Operation(summary = "删除阈值配置") + @DeleteMapping("/{id}") + public R delete(@PathVariable Long id) { + thresholdService.deleteThreshold(id); + return R.ok("删除成功"); + } + + @Operation(summary = "启用/禁用阈值") + @PutMapping("/{id}/status") + public R toggleStatus(@PathVariable Long id, @RequestParam Integer status) { + thresholdService.toggleStatus(id, status); + return R.ok(status == 1 ? "已启用" : "已禁用"); + } + + @Operation(summary = "获取指标的全局阈值(多级)") + @GetMapping("/global/{metricCode}") + public R> getGlobalThresholds(@PathVariable String metricCode) { + return R.ok(thresholdService.getGlobalThresholds(metricCode)); + } + + @Operation(summary = "获取设备阈值配置") + @GetMapping("/device/{deviceId}") + public R> getDeviceThresholds(@PathVariable Long deviceId) { + return R.ok(thresholdService.getDeviceThresholds(deviceId)); + } + + @Operation(summary = "获取阈值变更历史") + @GetMapping("/history") + public R> getChangeHistory(@RequestParam(required = false) Long thresholdId, + @RequestParam(defaultValue = "1") int page, + @RequestParam(defaultValue = "10") int size) { + return R.ok(thresholdService.getChangeHistory(thresholdId, page, size)); + } +} diff --git a/wm-config/src/main/java/com/water/config/entity/Announcement.java b/wm-config/src/main/java/com/water/config/entity/Announcement.java new file mode 100644 index 00000000..4a1ce99d --- /dev/null +++ b/wm-config/src/main/java/com/water/config/entity/Announcement.java @@ -0,0 +1,35 @@ +package com.water.config.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 公告通知 + */ +@Data +@TableName("config_announcement") +public class Announcement { + @TableId(type = IdType.AUTO) + private Long id; + /** 标题 */ + private String title; + /** 内容 */ + private String content; + /** 类型: 1-系统公告 2-维护通知 3-紧急通知 */ + private Integer type; + /** 发布状态: 0-草稿 1-已发布 2-已撤回 */ + private Integer publishStatus; + /** 发布渠道(JSON数组): ["sms","push","site"] */ + private String channels; + /** 发布人 */ + private String publisher; + /** 发布时间 */ + private LocalDateTime publishTime; + /** 撤回时间 */ + private LocalDateTime withdrawTime; + @TableLogic + private Integer deleted; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/wm-config/src/main/java/com/water/config/entity/DeviceInfo.java b/wm-config/src/main/java/com/water/config/entity/DeviceInfo.java new file mode 100644 index 00000000..76d84794 --- /dev/null +++ b/wm-config/src/main/java/com/water/config/entity/DeviceInfo.java @@ -0,0 +1,45 @@ +package com.water.config.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 设备台账 + */ +@Data +@TableName("config_device_info") +public class DeviceInfo { + @TableId(type = IdType.AUTO) + private Long id; + /** 设备编码 */ + private String deviceCode; + /** 设备名称 */ + private String deviceName; + /** 设备分类: 1-水表 2-压力传感器 3-流量计 4-水质监测仪 5-阀门 9-其他 */ + private Integer category; + /** 品牌 */ + private String brand; + /** 型号 */ + private String model; + /** 安装位置 */ + private String location; + /** 经度 */ + private Double longitude; + /** 纬度 */ + private Double latitude; + /** 设备状态: 0-离线 1-在线 2-故障 3-维修中 */ + private Integer deviceStatus; + /** 安装日期 */ + private LocalDateTime installDate; + /** 最后维护时间 */ + private LocalDateTime lastMaintenanceTime; + /** 负责人 */ + private String responsiblePerson; + /** 备注 */ + private String remark; + @TableLogic + private Integer deleted; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/wm-config/src/main/java/com/water/config/entity/DeviceMaintenance.java b/wm-config/src/main/java/com/water/config/entity/DeviceMaintenance.java new file mode 100644 index 00000000..d566af13 --- /dev/null +++ b/wm-config/src/main/java/com/water/config/entity/DeviceMaintenance.java @@ -0,0 +1,37 @@ +package com.water.config.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 设备维保记录 + */ +@Data +@TableName("config_device_maintenance") +public class DeviceMaintenance { + @TableId(type = IdType.AUTO) + private Long id; + /** 设备ID */ + private Long deviceId; + /** 维保类型: 1-日常巡检 2-定期保养 3-故障维修 4-更换配件 */ + private Integer maintenanceType; + /** 维保描述 */ + private String description; + /** 维保人 */ + private String operator; + /** 维保开始时间 */ + private LocalDateTime startTime; + /** 维保结束时间 */ + private LocalDateTime endTime; + /** 维保结果: 0-未完成 1-已完成 2-需要返修 */ + private Integer result; + /** 费用 */ + private Double cost; + /** 附件(JSON) */ + private String attachments; + @TableLogic + private Integer deleted; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/wm-config/src/main/java/com/water/config/entity/ThresholdChangeLog.java b/wm-config/src/main/java/com/water/config/entity/ThresholdChangeLog.java new file mode 100644 index 00000000..cadd348b --- /dev/null +++ b/wm-config/src/main/java/com/water/config/entity/ThresholdChangeLog.java @@ -0,0 +1,35 @@ +package com.water.config.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 阈值变更记录 + */ +@Data +@TableName("config_threshold_change_log") +public class ThresholdChangeLog { + @TableId(type = IdType.AUTO) + private Long id; + /** 关联阈值ID */ + private Long thresholdId; + /** 变更前最小值 */ + private BigDecimal oldMinValue; + /** 变更前最大值 */ + private BigDecimal oldMaxValue; + /** 变更后最小值 */ + private BigDecimal newMinValue; + /** 变更后最大值 */ + private BigDecimal newMaxValue; + /** 变更前级别 */ + private Integer oldLevel; + /** 变更后级别 */ + private Integer newLevel; + /** 变更人 */ + private String operator; + /** 变更原因 */ + private String reason; + private LocalDateTime createdAt; +} diff --git a/wm-config/src/main/java/com/water/config/entity/ThresholdConfig.java b/wm-config/src/main/java/com/water/config/entity/ThresholdConfig.java new file mode 100644 index 00000000..3eae99f7 --- /dev/null +++ b/wm-config/src/main/java/com/water/config/entity/ThresholdConfig.java @@ -0,0 +1,38 @@ +package com.water.config.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 阈值配置 + */ +@Data +@TableName("config_threshold") +public class ThresholdConfig { + @TableId(type = IdType.AUTO) + private Long id; + /** 指标编码 */ + private String metricCode; + /** 指标名称 */ + private String metricName; + /** 设备ID(可选,null表示全局) */ + private Long deviceId; + /** 阈值级别: 1-预警 2-报警 3-紧急 */ + private Integer level; + /** 最小值 */ + private BigDecimal minValue; + /** 最大值 */ + private BigDecimal maxValue; + /** 单位 */ + private String unit; + /** 启用状态: 0-禁用 1-启用 */ + private Integer status; + /** 备注 */ + private String remark; + @TableLogic + private Integer deleted; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; +} diff --git a/wm-config/src/main/java/com/water/config/mapper/AnnouncementMapper.java b/wm-config/src/main/java/com/water/config/mapper/AnnouncementMapper.java new file mode 100644 index 00000000..95d92801 --- /dev/null +++ b/wm-config/src/main/java/com/water/config/mapper/AnnouncementMapper.java @@ -0,0 +1,9 @@ +package com.water.config.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.water.config.entity.Announcement; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface AnnouncementMapper extends BaseMapper { +} diff --git a/wm-config/src/main/java/com/water/config/mapper/DeviceInfoMapper.java b/wm-config/src/main/java/com/water/config/mapper/DeviceInfoMapper.java new file mode 100644 index 00000000..63ad028c --- /dev/null +++ b/wm-config/src/main/java/com/water/config/mapper/DeviceInfoMapper.java @@ -0,0 +1,17 @@ +package com.water.config.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.water.config.entity.DeviceInfo; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; +import java.util.List; + +@Mapper +public interface DeviceInfoMapper extends BaseMapper { + + @Select("SELECT * FROM config_device_info WHERE device_status = #{status} AND deleted = 0") + List selectByDeviceStatus(Integer status); + + @Select("SELECT * FROM config_device_info WHERE category = #{category} AND deleted = 0 ORDER BY device_code") + List selectByCategory(Integer category); +} diff --git a/wm-config/src/main/java/com/water/config/mapper/DeviceMaintenanceMapper.java b/wm-config/src/main/java/com/water/config/mapper/DeviceMaintenanceMapper.java new file mode 100644 index 00000000..45a39323 --- /dev/null +++ b/wm-config/src/main/java/com/water/config/mapper/DeviceMaintenanceMapper.java @@ -0,0 +1,14 @@ +package com.water.config.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.water.config.entity.DeviceMaintenance; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; +import java.util.List; + +@Mapper +public interface DeviceMaintenanceMapper extends BaseMapper { + + @Select("SELECT * FROM config_device_maintenance WHERE device_id = #{deviceId} AND deleted = 0 ORDER BY start_time DESC") + List selectByDeviceId(Long deviceId); +} diff --git a/wm-config/src/main/java/com/water/config/mapper/ThresholdChangeLogMapper.java b/wm-config/src/main/java/com/water/config/mapper/ThresholdChangeLogMapper.java new file mode 100644 index 00000000..9d37f034 --- /dev/null +++ b/wm-config/src/main/java/com/water/config/mapper/ThresholdChangeLogMapper.java @@ -0,0 +1,9 @@ +package com.water.config.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.water.config.entity.ThresholdChangeLog; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface ThresholdChangeLogMapper extends BaseMapper { +} diff --git a/wm-config/src/main/java/com/water/config/mapper/ThresholdConfigMapper.java b/wm-config/src/main/java/com/water/config/mapper/ThresholdConfigMapper.java new file mode 100644 index 00000000..8e57c1f1 --- /dev/null +++ b/wm-config/src/main/java/com/water/config/mapper/ThresholdConfigMapper.java @@ -0,0 +1,17 @@ +package com.water.config.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.water.config.entity.ThresholdConfig; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Select; +import java.util.List; + +@Mapper +public interface ThresholdConfigMapper extends BaseMapper { + + @Select("SELECT * FROM config_threshold WHERE metric_code = #{metricCode} AND device_id IS NULL AND status = 1 AND deleted = 0 ORDER BY level") + List selectGlobalByMetricCode(String metricCode); + + @Select("SELECT * FROM config_threshold WHERE device_id = #{deviceId} AND status = 1 AND deleted = 0 ORDER BY metric_code, level") + List selectByDeviceId(Long deviceId); +} diff --git a/wm-config/src/main/java/com/water/config/service/AnnouncementService.java b/wm-config/src/main/java/com/water/config/service/AnnouncementService.java new file mode 100644 index 00000000..6091d726 --- /dev/null +++ b/wm-config/src/main/java/com/water/config/service/AnnouncementService.java @@ -0,0 +1,131 @@ +package com.water.config.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.water.common.core.exception.BusinessException; +import com.water.config.entity.Announcement; +import com.water.config.mapper.AnnouncementMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; + +/** + * 公告通知服务 + */ +@Slf4j +@Service +public class AnnouncementService extends ServiceImpl { + + /** + * 分页查询公告 + */ + public Page pageAnnouncements(int page, int size, Integer type, Integer publishStatus) { + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + if (type != null) { + qw.eq(Announcement::getType, type); + } + if (publishStatus != null) { + qw.eq(Announcement::getPublishStatus, publishStatus); + } + qw.orderByDesc(Announcement::getCreatedAt); + return this.page(new Page<>(page, size), qw); + } + + /** + * 创建公告(草稿) + */ + public Announcement createAnnouncement(Announcement announcement) { + announcement.setPublishStatus(0); + this.save(announcement); + return announcement; + } + + /** + * 更新公告(仅草稿可更新) + */ + public void updateAnnouncement(Long id, Announcement announcement) { + Announcement existing = this.getById(id); + if (existing == null) { + throw new BusinessException("公告不存在"); + } + if (existing.getPublishStatus() != 0) { + throw new BusinessException("已发布的公告不可修改"); + } + announcement.setId(id); + this.updateById(announcement); + } + + /** + * 发布公告 + */ + @Transactional + public void publish(Long id) { + Announcement announcement = this.getById(id); + if (announcement == null) { + throw new BusinessException("公告不存在"); + } + if (announcement.getPublishStatus() != 0) { + throw new BusinessException("只有草稿状态的公告可以发布"); + } + announcement.setPublishStatus(1); + announcement.setPublishTime(LocalDateTime.now()); + this.updateById(announcement); + // 多渠道发布 + dispatchChannels(announcement); + } + + /** + * 撤回公告 + */ + @Transactional + public void withdraw(Long id) { + Announcement announcement = this.getById(id); + if (announcement == null) { + throw new BusinessException("公告不存在"); + } + if (announcement.getPublishStatus() != 1) { + throw new BusinessException("只有已发布的公告可以撤回"); + } + announcement.setPublishStatus(2); + announcement.setWithdrawTime(LocalDateTime.now()); + this.updateById(announcement); + } + + /** + * 删除公告 + */ + public void deleteAnnouncement(Long id) { + Announcement existing = this.getById(id); + if (existing == null) { + throw new BusinessException("公告不存在"); + } + if (existing.getPublishStatus() == 1) { + throw new BusinessException("已发布的公告不可删除,请先撤回"); + } + this.removeById(id); + } + + /** + * 多渠道分发(模拟) + */ + private void dispatchChannels(Announcement announcement) { + String channels = announcement.getChannels(); + if (channels == null || channels.isEmpty()) { + log.info("公告 {} 无渠道配置,仅站内信发布", announcement.getId()); + return; + } + log.info("公告 {} 发布渠道: {}", announcement.getId(), channels); + if (channels.contains("sms")) { + log.info("→ 短信渠道已触发"); + } + if (channels.contains("push")) { + log.info("→ APP推送渠道已触发"); + } + if (channels.contains("site")) { + log.info("→ 站内信渠道已触发"); + } + } +} diff --git a/wm-config/src/main/java/com/water/config/service/DeviceManageService.java b/wm-config/src/main/java/com/water/config/service/DeviceManageService.java new file mode 100644 index 00000000..839f8733 --- /dev/null +++ b/wm-config/src/main/java/com/water/config/service/DeviceManageService.java @@ -0,0 +1,150 @@ +package com.water.config.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.water.common.core.exception.BusinessException; +import com.water.config.entity.DeviceInfo; +import com.water.config.entity.DeviceMaintenance; +import com.water.config.mapper.DeviceInfoMapper; +import com.water.config.mapper.DeviceMaintenanceMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 设备管理服务 + */ +@Service +@RequiredArgsConstructor +public class DeviceManageService extends ServiceImpl { + + private final DeviceMaintenanceMapper maintenanceMapper; + + /** + * 分页查询设备 + */ + public Page pageDevices(int page, int size, String deviceName, Integer category, Integer deviceStatus) { + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + if (deviceName != null && !deviceName.isEmpty()) { + qw.like(DeviceInfo::getDeviceName, deviceName); + } + if (category != null) { + qw.eq(DeviceInfo::getCategory, category); + } + if (deviceStatus != null) { + qw.eq(DeviceInfo::getDeviceStatus, deviceStatus); + } + qw.orderByDesc(DeviceInfo::getCreatedAt); + return this.page(new Page<>(page, size), qw); + } + + /** + * 创建设备 + */ + public DeviceInfo createDevice(DeviceInfo device) { + // 检查设备编码唯一性 + long count = this.count(new LambdaQueryWrapper() + .eq(DeviceInfo::getDeviceCode, device.getDeviceCode())); + if (count > 0) { + throw new BusinessException("设备编码已存在"); + } + this.save(device); + return device; + } + + /** + * 更新设备 + */ + public void updateDevice(Long id, DeviceInfo device) { + DeviceInfo existing = this.getById(id); + if (existing == null) { + throw new BusinessException("设备不存在"); + } + device.setId(id); + this.updateById(device); + } + + /** + * 更新设备状态 + */ + public void updateDeviceStatus(Long id, Integer deviceStatus) { + DeviceInfo device = this.getById(id); + if (device == null) { + throw new BusinessException("设备不存在"); + } + device.setDeviceStatus(deviceStatus); + this.updateById(device); + } + + /** + * 按分类查询设备 + */ + public List getDevicesByCategory(Integer category) { + return baseMapper.selectByCategory(category); + } + + /** + * 按状态查询设备 + */ + public List getDevicesByStatus(Integer status) { + return baseMapper.selectByDeviceStatus(status); + } + + /** + * 添加维保记录 + */ + @Transactional + public DeviceMaintenance addMaintenance(DeviceMaintenance maintenance) { + DeviceInfo device = this.getById(maintenance.getDeviceId()); + if (device == null) { + throw new BusinessException("设备不存在"); + } + maintenanceMapper.insert(maintenance); + // 如果维保完成,更新设备最后维护时间 + if (maintenance.getResult() != null && maintenance.getResult() == 1) { + device.setLastMaintenanceTime(LocalDateTime.now()); + if (device.getDeviceStatus() == 3) { + device.setDeviceStatus(1); // 维修中 -> 在线 + } + this.updateById(device); + } + return maintenance; + } + + /** + * 查询设备维保历史 + */ + public Page pageMaintenances(Long deviceId, int page, int size) { + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + if (deviceId != null) { + qw.eq(DeviceMaintenance::getDeviceId, deviceId); + } + qw.orderByDesc(DeviceMaintenance::getStartTime); + return maintenanceMapper.selectPage(new Page<>(page, size), qw); + } + + /** + * 更新维保记录 + */ + public void updateMaintenance(Long id, DeviceMaintenance maintenance) { + maintenance.setId(id); + maintenanceMapper.updateById(maintenance); + } + + /** + * 获取设备统计(按分类) + */ + public List getDeviceCountByCategory() { + // 简化版:返回各分类的设备数量 + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + qw.select(DeviceInfo::getCategory); + qw.groupBy(DeviceInfo::getCategory); + return this.listMaps(qw).stream() + .map(m -> ((Number) m.get("category")).longValue()) + .toList(); + } +} diff --git a/wm-config/src/main/java/com/water/config/service/ThresholdService.java b/wm-config/src/main/java/com/water/config/service/ThresholdService.java new file mode 100644 index 00000000..550940b6 --- /dev/null +++ b/wm-config/src/main/java/com/water/config/service/ThresholdService.java @@ -0,0 +1,144 @@ +package com.water.config.service; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.water.config.entity.ThresholdChangeLog; +import com.water.config.entity.ThresholdConfig; +import com.water.config.mapper.ThresholdChangeLogMapper; +import com.water.config.mapper.ThresholdConfigMapper; +import com.water.common.core.exception.BusinessException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +/** + * 阈值管理服务 + */ +@Service +@RequiredArgsConstructor +public class ThresholdService extends ServiceImpl { + + private final ThresholdChangeLogMapper changeLogMapper; + + /** + * 分页查询阈值配置 + */ + public Page pageThresholds(int page, int size, String metricCode, Integer level) { + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + if (metricCode != null && !metricCode.isEmpty()) { + qw.like(ThresholdConfig::getMetricCode, metricCode); + } + if (level != null) { + qw.eq(ThresholdConfig::getLevel, level); + } + qw.orderByAsc(ThresholdConfig::getMetricCode, ThresholdConfig::getLevel); + return this.page(new Page<>(page, size), qw); + } + + /** + * 创建阈值配置 + */ + @Transactional + public ThresholdConfig createThreshold(ThresholdConfig config) { + validateThreshold(config); + this.save(config); + recordChangeLog(config, null, "新建阈值配置"); + return config; + } + + /** + * 更新阈值配置(记录变更) + */ + @Transactional + public void updateThreshold(Long id, ThresholdConfig config) { + ThresholdConfig old = this.getById(id); + if (old == null) { + throw new BusinessException("阈值配置不存在"); + } + config.setId(id); + validateThreshold(config); + this.updateById(config); + recordChangeLog(config, old, "更新阈值配置"); + } + + /** + * 删除阈值配置 + */ + @Transactional + public void deleteThreshold(Long id) { + ThresholdConfig old = this.getById(id); + if (old == null) { + throw new BusinessException("阈值配置不存在"); + } + this.removeById(id); + recordChangeLog(old, old, "删除阈值配置"); + } + + /** + * 获取某指标的全局阈值(多级) + */ + public List getGlobalThresholds(String metricCode) { + return baseMapper.selectGlobalByMetricCode(metricCode); + } + + /** + * 获取某设备的阈值配置 + */ + public List getDeviceThresholds(Long deviceId) { + return baseMapper.selectByDeviceId(deviceId); + } + + /** + * 获取阈值变更历史 + */ + public Page getChangeHistory(Long thresholdId, int page, int size) { + LambdaQueryWrapper qw = new LambdaQueryWrapper<>(); + if (thresholdId != null) { + qw.eq(ThresholdChangeLog::getThresholdId, thresholdId); + } + qw.orderByDesc(ThresholdChangeLog::getCreatedAt); + return changeLogMapper.selectPage(new Page<>(page, size), qw); + } + + /** + * 启用/禁用阈值 + */ + public void toggleStatus(Long id, Integer status) { + ThresholdConfig config = this.getById(id); + if (config == null) { + throw new BusinessException("阈值配置不存在"); + } + config.setStatus(status); + this.updateById(config); + } + + private void validateThreshold(ThresholdConfig config) { + if (config.getMinValue() != null && config.getMaxValue() != null) { + if (config.getMinValue().compareTo(config.getMaxValue()) > 0) { + throw new BusinessException("最小值不能大于最大值"); + } + } + if (config.getLevel() != null && (config.getLevel() < 1 || config.getLevel() > 3)) { + throw new BusinessException("阈值级别必须在1-3之间"); + } + } + + private void recordChangeLog(ThresholdConfig newConfig, ThresholdConfig oldConfig, String reason) { + ThresholdChangeLog log = new ThresholdChangeLog(); + log.setThresholdId(newConfig.getId()); + if (oldConfig != null) { + log.setOldMinValue(oldConfig.getMinValue()); + log.setOldMaxValue(oldConfig.getMaxValue()); + log.setOldLevel(oldConfig.getLevel()); + } + log.setNewMinValue(newConfig.getMinValue()); + log.setNewMaxValue(newConfig.getMaxValue()); + log.setNewLevel(newConfig.getLevel()); + log.setReason(reason); + log.setOperator("system"); + changeLogMapper.insert(log); + } +} diff --git a/wm-config/src/main/resources/application.yml b/wm-config/src/main/resources/application.yml new file mode 100644 index 00000000..f1cbf385 --- /dev/null +++ b/wm-config/src/main/resources/application.yml @@ -0,0 +1,30 @@ +server: + port: 8090 + +spring: + application: + name: wm-config + datasource: + driver-class-name: org.postgresql.Driver + url: jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:water}?currentSchema=public + username: ${DB_USER:postgres} + password: ${DB_PASS:postgres} + cloud: + nacos: + discovery: + server-addr: ${NACOS_ADDR:localhost:8848} + +mybatis-plus: + configuration: + map-underscore-to-camel-case: true + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + global-config: + db-config: + logic-delete-field: deleted + logic-delete-value: 1 + logic-not-delete-value: 0 + +sa-token: + token-name: Authorization + timeout: 86400 + active-timeout: 1800 diff --git a/wm-config/src/main/resources/db/schema.sql b/wm-config/src/main/resources/db/schema.sql new file mode 100644 index 00000000..f58cf900 --- /dev/null +++ b/wm-config/src/main/resources/db/schema.sql @@ -0,0 +1,102 @@ +-- ============================================================ +-- wm-config DDL: 阈值管理 + 信息发布 + 设备管理 +-- ============================================================ + +-- 阈值配置表 +CREATE TABLE IF NOT EXISTS config_threshold ( + id BIGSERIAL PRIMARY KEY, + metric_code VARCHAR(64) NOT NULL, + metric_name VARCHAR(128) NOT NULL, + device_id BIGINT, + level SMALLINT NOT NULL DEFAULT 1, -- 1-预警 2-报警 3-紧急 + min_value NUMERIC(12,4), + max_value NUMERIC(12,4), + unit VARCHAR(32), + status SMALLINT NOT NULL DEFAULT 1, -- 0-禁用 1-启用 + remark VARCHAR(500), + deleted SMALLINT NOT NULL DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); +COMMENT ON TABLE config_threshold IS '阈值配置表'; +CREATE INDEX idx_threshold_metric ON config_threshold(metric_code); +CREATE INDEX idx_threshold_device ON config_threshold(device_id); + +-- 阈值变更记录表 +CREATE TABLE IF NOT EXISTS config_threshold_change_log ( + id BIGSERIAL PRIMARY KEY, + threshold_id BIGINT NOT NULL, + old_min_value NUMERIC(12,4), + old_max_value NUMERIC(12,4), + new_min_value NUMERIC(12,4), + new_max_value NUMERIC(12,4), + old_level SMALLINT, + new_level SMALLINT, + operator VARCHAR(64), + reason VARCHAR(500), + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); +COMMENT ON TABLE config_threshold_change_log IS '阈值变更记录表'; +CREATE INDEX idx_change_log_threshold ON config_threshold_change_log(threshold_id); + +-- 公告通知表 +CREATE TABLE IF NOT EXISTS config_announcement ( + id BIGSERIAL PRIMARY KEY, + title VARCHAR(256) NOT NULL, + content TEXT, + type SMALLINT NOT NULL DEFAULT 1, -- 1-系统公告 2-维护通知 3-紧急通知 + publish_status SMALLINT NOT NULL DEFAULT 0, -- 0-草稿 1-已发布 2-已撤回 + channels VARCHAR(256), -- JSON: ["sms","push","site"] + publisher VARCHAR(64), + publish_time TIMESTAMP, + withdraw_time TIMESTAMP, + deleted SMALLINT NOT NULL DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); +COMMENT ON TABLE config_announcement IS '公告通知表'; +CREATE INDEX idx_announcement_status ON config_announcement(publish_status); + +-- 设备台账表 +CREATE TABLE IF NOT EXISTS config_device_info ( + id BIGSERIAL PRIMARY KEY, + device_code VARCHAR(64) NOT NULL UNIQUE, + device_name VARCHAR(128) NOT NULL, + category SMALLINT NOT NULL DEFAULT 9, -- 1-水表 2-压力传感器 3-流量计 4-水质监测仪 5-阀门 9-其他 + brand VARCHAR(64), + model VARCHAR(64), + location VARCHAR(256), + longitude DOUBLE PRECISION, + latitude DOUBLE PRECISION, + device_status SMALLINT NOT NULL DEFAULT 0, -- 0-离线 1-在线 2-故障 3-维修中 + install_date TIMESTAMP, + last_maintenance_time TIMESTAMP, + responsible_person VARCHAR(64), + remark VARCHAR(500), + deleted SMALLINT NOT NULL DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); +COMMENT ON TABLE config_device_info IS '设备台账表'; +CREATE INDEX idx_device_code ON config_device_info(device_code); +CREATE INDEX idx_device_category ON config_device_info(category); +CREATE INDEX idx_device_status ON config_device_info(device_status); + +-- 设备维保记录表 +CREATE TABLE IF NOT EXISTS config_device_maintenance ( + id BIGSERIAL PRIMARY KEY, + device_id BIGINT NOT NULL, + maintenance_type SMALLINT NOT NULL DEFAULT 1, -- 1-日常巡检 2-定期保养 3-故障维修 4-更换配件 + description TEXT, + operator VARCHAR(64), + start_time TIMESTAMP, + end_time TIMESTAMP, + result SMALLINT NOT NULL DEFAULT 0, -- 0-未完成 1-已完成 2-需要返修 + cost DOUBLE PRECISION, + attachments TEXT, -- JSON + deleted SMALLINT NOT NULL DEFAULT 0, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); +COMMENT ON TABLE config_device_maintenance IS '设备维保记录表'; +CREATE INDEX idx_maintenance_device ON config_device_maintenance(device_id); diff --git a/wm-config/src/test/java/com/water/config/AnnouncementServiceTest.java b/wm-config/src/test/java/com/water/config/AnnouncementServiceTest.java new file mode 100644 index 00000000..85481eae --- /dev/null +++ b/wm-config/src/test/java/com/water/config/AnnouncementServiceTest.java @@ -0,0 +1,100 @@ +package com.water.config; + +import com.water.common.core.exception.BusinessException; +import com.water.config.entity.Announcement; +import com.water.config.mapper.AnnouncementMapper; +import com.water.config.service.AnnouncementService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class AnnouncementServiceTest { + + @Mock + private AnnouncementMapper announcementMapper; + @InjectMocks + private AnnouncementService announcementService; + + private Announcement draft; + + @BeforeEach + void setUp() { + draft = new Announcement(); + draft.setTitle("系统维护通知"); + draft.setContent("今晚22:00-次日06:00系统维护"); + draft.setType(2); + draft.setChannels("[\"site\",\"sms\"]"); + } + + @Test + void createAnnouncement_setsDraftStatus() { + when(announcementMapper.insert(any())).thenReturn(1); + + Announcement result = announcementService.createAnnouncement(draft); + + assertEquals(0, result.getPublishStatus()); + verify(announcementMapper).insert(any(Announcement.class)); + } + + @Test + void publish_draft_success() { + Announcement existing = new Announcement(); + existing.setId(1L); + existing.setPublishStatus(0); + existing.setChannels("[\"site\"]"); + + when(announcementMapper.selectById(1L)).thenReturn(existing); + when(announcementMapper.updateById(any())).thenReturn(1); + + assertDoesNotThrow(() -> announcementService.publish(1L)); + verify(announcementMapper).updateById(argThat(a -> + a.getPublishStatus() == 1 && a.getPublishTime() != null)); + } + + @Test + void publish_alreadyPublished_throws() { + Announcement existing = new Announcement(); + existing.setId(1L); + existing.setPublishStatus(1); + + when(announcementMapper.selectById(1L)).thenReturn(existing); + + BusinessException ex = assertThrows(BusinessException.class, + () -> announcementService.publish(1L)); + assertEquals("只有草稿状态的公告可以发布", ex.getMessage()); + } + + @Test + void withdraw_notPublished_throws() { + Announcement existing = new Announcement(); + existing.setId(1L); + existing.setPublishStatus(0); + + when(announcementMapper.selectById(1L)).thenReturn(existing); + + BusinessException ex = assertThrows(BusinessException.class, + () -> announcementService.withdraw(1L)); + assertEquals("只有已发布的公告可以撤回", ex.getMessage()); + } + + @Test + void delete_published_throws() { + Announcement existing = new Announcement(); + existing.setId(1L); + existing.setPublishStatus(1); + + when(announcementMapper.selectById(1L)).thenReturn(existing); + + BusinessException ex = assertThrows(BusinessException.class, + () -> announcementService.deleteAnnouncement(1L)); + assertEquals("已发布的公告不可删除,请先撤回", ex.getMessage()); + } +} diff --git a/wm-config/src/test/java/com/water/config/DeviceManageServiceTest.java b/wm-config/src/test/java/com/water/config/DeviceManageServiceTest.java new file mode 100644 index 00000000..d62c5340 --- /dev/null +++ b/wm-config/src/test/java/com/water/config/DeviceManageServiceTest.java @@ -0,0 +1,107 @@ +package com.water.config; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.water.common.core.exception.BusinessException; +import com.water.config.entity.DeviceInfo; +import com.water.config.entity.DeviceMaintenance; +import com.water.config.mapper.DeviceInfoMapper; +import com.water.config.mapper.DeviceMaintenanceMapper; +import com.water.config.service.DeviceManageService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class DeviceManageServiceTest { + + @Mock + private DeviceInfoMapper deviceInfoMapper; + @Mock + private DeviceMaintenanceMapper maintenanceMapper; + @InjectMocks + private DeviceManageService deviceManageService; + + private DeviceInfo device; + + @BeforeEach + void setUp() { + device = new DeviceInfo(); + device.setDeviceCode("WM-001"); + device.setDeviceName("1号水表"); + device.setCategory(1); + device.setBrand("海天"); + device.setModel("HT-200"); + device.setDeviceStatus(0); + } + + @Test + void createDevice_success() { + when(deviceInfoMapper.selectCount(any())).thenReturn(0L); + when(deviceInfoMapper.insert(any())).thenReturn(1); + + DeviceInfo result = deviceManageService.createDevice(device); + + assertNotNull(result); + assertEquals("WM-001", result.getDeviceCode()); + verify(deviceInfoMapper).insert(any(DeviceInfo.class)); + } + + @Test + void createDevice_duplicateCode_throws() { + when(deviceInfoMapper.selectCount(any())).thenReturn(1L); + + BusinessException ex = assertThrows(BusinessException.class, + () -> deviceManageService.createDevice(device)); + assertEquals("设备编码已存在", ex.getMessage()); + } + + @Test + void updateDevice_notFound_throws() { + when(deviceInfoMapper.selectById(999L)).thenReturn(null); + + BusinessException ex = assertThrows(BusinessException.class, + () -> deviceManageService.updateDevice(999L, device)); + assertEquals("设备不存在", ex.getMessage()); + } + + @Test + void addMaintenance_deviceNotFound_throws() { + DeviceMaintenance maintenance = new DeviceMaintenance(); + maintenance.setDeviceId(999L); + + when(deviceInfoMapper.selectById(999L)).thenReturn(null); + + BusinessException ex = assertThrows(BusinessException.class, + () -> deviceManageService.addMaintenance(maintenance)); + assertEquals("设备不存在", ex.getMessage()); + } + + @Test + void addMaintenance_completed_updatesDeviceTime() { + DeviceInfo existingDevice = new DeviceInfo(); + existingDevice.setId(1L); + existingDevice.setDeviceStatus(3); // 维修中 + + DeviceMaintenance maintenance = new DeviceMaintenance(); + maintenance.setDeviceId(1L); + maintenance.setResult(1); // 已完成 + maintenance.setDescription("更换电池"); + + when(deviceInfoMapper.selectById(1L)).thenReturn(existingDevice); + when(maintenanceMapper.insert(any())).thenReturn(1); + when(deviceInfoMapper.updateById(any())).thenReturn(1); + + DeviceMaintenance result = deviceManageService.addMaintenance(maintenance); + + assertNotNull(result); + verify(deviceInfoMapper).updateById(argThat(d -> + d.getLastMaintenanceTime() != null && d.getDeviceStatus() == 1)); + } +} diff --git a/wm-config/src/test/java/com/water/config/ThresholdServiceTest.java b/wm-config/src/test/java/com/water/config/ThresholdServiceTest.java new file mode 100644 index 00000000..6042f259 --- /dev/null +++ b/wm-config/src/test/java/com/water/config/ThresholdServiceTest.java @@ -0,0 +1,96 @@ +package com.water.config; + +import com.water.common.core.exception.BusinessException; +import com.water.config.entity.ThresholdChangeLog; +import com.water.config.entity.ThresholdConfig; +import com.water.config.mapper.ThresholdChangeLogMapper; +import com.water.config.mapper.ThresholdConfigMapper; +import com.water.config.service.ThresholdService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.math.BigDecimal; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class ThresholdServiceTest { + + @Mock + private ThresholdConfigMapper thresholdConfigMapper; + @Mock + private ThresholdChangeLogMapper changeLogMapper; + @InjectMocks + private ThresholdService thresholdService; + + private ThresholdConfig validConfig; + + @BeforeEach + void setUp() { + validConfig = new ThresholdConfig(); + validConfig.setMetricCode("water_pressure"); + validConfig.setMetricName("水压"); + validConfig.setLevel(1); + validConfig.setMinValue(new BigDecimal("0.1")); + validConfig.setMaxValue(new BigDecimal("0.8")); + validConfig.setUnit("MPa"); + validConfig.setStatus(1); + } + + @Test + void createThreshold_success() { + when(thresholdConfigMapper.insert(any())).thenReturn(1); + when(changeLogMapper.insert(any())).thenReturn(1); + + ThresholdConfig result = thresholdService.createThreshold(validConfig); + + assertNotNull(result); + assertEquals("water_pressure", result.getMetricCode()); + assertEquals(1, result.getLevel()); + verify(thresholdConfigMapper).insert(any(ThresholdConfig.class)); + verify(changeLogMapper).insert(any(ThresholdChangeLog.class)); + } + + @Test + void createThreshold_minGreaterThanMax_throws() { + validConfig.setMinValue(new BigDecimal("1.0")); + validConfig.setMaxValue(new BigDecimal("0.5")); + + BusinessException ex = assertThrows(BusinessException.class, + () -> thresholdService.createThreshold(validConfig)); + assertEquals("最小值不能大于最大值", ex.getMessage()); + } + + @Test + void createThreshold_invalidLevel_throws() { + validConfig.setLevel(5); + + BusinessException ex = assertThrows(BusinessException.class, + () -> thresholdService.createThreshold(validConfig)); + assertEquals("阈值级别必须在1-3之间", ex.getMessage()); + } + + @Test + void updateThreshold_notFound_throws() { + when(thresholdConfigMapper.selectById(999L)).thenReturn(null); + + BusinessException ex = assertThrows(BusinessException.class, + () -> thresholdService.updateThreshold(999L, validConfig)); + assertEquals("阈值配置不存在", ex.getMessage()); + } + + @Test + void deleteThreshold_notFound_throws() { + when(thresholdConfigMapper.selectById(999L)).thenReturn(null); + + BusinessException ex = assertThrows(BusinessException.class, + () -> thresholdService.deleteThreshold(999L)); + assertEquals("阈值配置不存在", ex.getMessage()); + } +}