Ver código fonte

Merge branch 'master' into usky-ems

james 1 dia atrás
pai
commit
903b603613
85 arquivos alterados com 6227 adições e 899 exclusões
  1. 11 33
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/AlarmDataController.java
  2. 5 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/BaseDataController.java
  3. 1 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/CdiDeliveryLogService.java
  4. 6 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/config/DeviceFieldConfig.java
  5. 4 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/AlarmType.java
  6. 2 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/MqttTopics.java
  7. 15 8
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataSyncService.java
  8. 31 146
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataTransferService.java
  9. 53 16
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseDataTransferService.java
  10. 368 221
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/CdiDeliveryLogServiceImpl.java
  11. 358 29
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java
  12. 381 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/AirDefenseSimulator.java
  13. 177 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/BasementClimateUtil.java
  14. 202 45
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataQuery.java
  15. 5 5
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataSyncService.java
  16. 49 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/FixedWaterLevelGenerator.java
  17. 32 4
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/HttpClientUtils.java
  18. 49 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/PeopleFlowData.java
  19. 10 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/SnowflakeIdGenerator.java
  20. 6 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/IotDataTransferVO.java
  21. 2 4
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/alarm/AlarmMessageVO.java
  22. 42 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/BaseMqttInfo.java
  23. 2 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/EngineeringBaseVO.java
  24. 5 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FacilityDeviceVO.java
  25. 2 15
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FloorPlaneVO.java
  26. 54 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/HeadcountVO.java
  27. 2 15
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/ProtectiveUnitVO.java
  28. 39 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/WaterLevelVO.java
  29. 2 0
      service-eg/service-eg-api/src/main/java/com/usky/eg/RemoteEgService.java
  30. 5 1
      service-eg/service-eg-api/src/main/java/com/usky/eg/factory/RemoteEgFallbackFactory.java
  31. 25 3
      service-eg/service-eg-biz/src/main/java/com/usky/eg/controller/web/EgDeviceController.java
  32. 41 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/controller/web/EgDeviceHeartbeatController.java
  33. 11 13
      service-eg/service-eg-biz/src/main/java/com/usky/eg/domain/EgDevice.java
  34. 84 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/domain/EgDeviceHeartbeat.java
  35. 34 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/domain/EgDevicePersonBind.java
  36. 1 1
      service-eg/service-eg-biz/src/main/java/com/usky/eg/domain/EgRecord.java
  37. 98 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/domain/SysPerson.java
  38. 18 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/mapper/EgDeviceHeartbeatMapper.java
  39. 44 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/mapper/EgDevicePersonBindMapper.java
  40. 22 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/mapper/SysPersonMapper.java
  41. 19 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/service/EgDeviceHeartbeatService.java
  42. 15 2
      service-eg/service-eg-biz/src/main/java/com/usky/eg/service/EgDeviceService.java
  43. 91 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/service/impl/EgDeviceHeartbeatServiceImpl.java
  44. 425 133
      service-eg/service-eg-biz/src/main/java/com/usky/eg/service/impl/EgDeviceServiceImpl.java
  45. 17 10
      service-eg/service-eg-biz/src/main/java/com/usky/eg/service/impl/EgRecordServiceImpl.java
  46. 21 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/service/vo/EgDeviceBindFacePersonQueryVO.java
  47. 21 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/service/vo/EgDeviceBindFacePersonVO.java
  48. 9 0
      service-eg/service-eg-biz/src/main/java/com/usky/eg/service/vo/EgDeviceRequestVO.java
  49. 20 0
      service-eg/service-eg-biz/src/main/resources/mapper/eg/EgDeviceHeartbeatMapper.xml
  50. 1 2
      service-eg/service-eg-biz/src/main/resources/mapper/eg/EgDeviceMapper.xml
  51. 62 0
      service-eg/service-eg-biz/src/main/resources/mapper/eg/EgDevicePersonBindMapper.xml
  52. 32 0
      service-eg/service-eg-biz/src/main/resources/mapper/eg/SysPersonMapper.xml
  53. 6 0
      service-job/pom.xml
  54. 9 0
      service-job/src/main/java/com/ruoyi/job/task/RyTask.java
  55. 0 10
      service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/domain/EgDevice.java
  56. 0 2
      service-meeting/service-meeting-biz/src/main/resources/mapper/meeting/EgDeviceMapper.xml
  57. 5 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/controller/web/SasAgboxConfigController.java
  58. 21 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/controller/web/SasPatrolUserParamController.java
  59. 53 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/domain/SasPatrolUserParam.java
  60. 16 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/mapper/SasPatrolUserParamMapper.java
  61. 191 149
      service-sas/service-sas-biz/src/main/java/com/usky/sas/mqtt/MqttService.java
  62. 2 1
      service-sas/service-sas-biz/src/main/java/com/usky/sas/mqtt/dto/CollectionEventMessage.java
  63. 16 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/SasPatrolUserParamService.java
  64. 127 13
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/dto/agbox/JsonRpcRequest.java
  65. 12 9
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasDeviceServiceImpl.java
  66. 20 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasPatrolUserParamServiceImpl.java
  67. 2 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasSystemConfigServiceImpl.java
  68. 285 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/AlarmEventInfo.java
  69. 63 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/AlarmEventInfoResult.java
  70. 107 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/AlarmEventInfoVo.java
  71. 125 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/AlarmEventResult.java
  72. 372 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/ParkingEventInfo.java
  73. 63 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/ParkingEventInfoResult.java
  74. 107 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/ParkingEventInfoVo.java
  75. 125 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/ParkingEventResult.java
  76. 319 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/PatrolEventInfo.java
  77. 64 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/PatrolEventInfoResult.java
  78. 107 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/PatrolEventInfoVo.java
  79. 104 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/PatrolEventResult.java
  80. 411 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/SnapEventInfo.java
  81. 63 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/SnapEventInfoResult.java
  82. 107 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/SnapEventInfoVo.java
  83. 168 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/SnapEventResult.java
  84. 111 0
      service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/UserParams.java
  85. 15 0
      service-sas/service-sas-biz/src/main/resources/mapper/pm/SasPatrolUserParamMapper.xml

+ 11 - 33
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/AlarmDataController.java

@@ -2,9 +2,8 @@ package com.usky.cdi.controller;
 
 import com.usky.cdi.service.impl.AlarmDataTransferService;
 import com.usky.cdi.service.vo.alarm.AlarmMessageVO;
-import com.usky.cdi.service.vo.alarm.AlarmMessage1VO;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
+import com.usky.common.core.bean.ApiResult;
+import lombok.RequiredArgsConstructor;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
@@ -12,50 +11,29 @@ import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
 /**
- * 基础类数据传输控制器
- * 提供基础类数据上报的接口
+ * 告警数据 HTTP 入口:将告警上报至市适配平台(MQTT)。
  *
  * @author han
  * @date 2025/12/08
  */
-@Slf4j
 @RestController
 @RequestMapping("/api/alarm")
 @ConditionalOnProperty(prefix = "mqtt", value = {"enabled"}, havingValue = "true")
+@RequiredArgsConstructor
 public class AlarmDataController {
-    @Autowired
-    private AlarmDataTransferService alarmDataTransferService;
 
-    /**
-     * 上报人防工程基础信息
-     */
-    @PostMapping("/alarmMessage")
-    public String sendAlarmMessage(@RequestBody AlarmMessageVO vo) {
-        boolean success = alarmDataTransferService.sendAlarmMessage(vo);
-        return success ? "上报成功" : "上报失败";
-    }
+    private final AlarmDataTransferService alarmDataTransferService;
 
-    /**
-     * 上报人防工程基础信息
-     */
-    @PostMapping("/alarmMessage1")
-    public String sendAlarmMessage1(@RequestBody AlarmMessageVO vo) {
-        boolean success = alarmDataTransferService.sendAlarmMessage1(vo);
-        return success ? "上报成功" : "上报失败";
+    private static ApiResult<String> toSubmitResult(boolean success) {
+        return success ? ApiResult.success("上报成功") : ApiResult.error("上报失败");
     }
 
     /**
-     * 上报倾斜、位移、裂缝监测事件
+     * 告警推送
      */
-    @PostMapping("/alarmMessage2")
-    public String sendAlarmMessage2(@RequestBody AlarmMessageVO vo) {
-        boolean success = alarmDataTransferService.sendAlarmMessage2(vo);
-        return success ? "上报成功" : "上报失败";
+    @PostMapping("/alarmMessage")
+    public ApiResult<String> alarmMessage(@RequestBody AlarmMessageVO<?> vo) {
+        return toSubmitResult(alarmDataTransferService.publishAlarm(vo));
     }
 
-    @PostMapping("/alarmMessage3")
-    public String sendAlarmMessage3(@RequestBody AlarmMessageVO vo) {
-        boolean success = alarmDataTransferService.sendEngineeringBase(vo);
-        return success ? "上报成功" : "上报失败";
-    }
 }

+ 5 - 2
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/BaseDataController.java

@@ -78,8 +78,11 @@ public class BaseDataController {
      * 批量上报智能监管物联设施信息
      */
     @GetMapping("/sensorInfos")
-    public String batchSendSensorInfos(@RequestParam(value = "tenantId",required = false) Integer tenantId) {
-        Map<String, Integer> map = baseDataTransferService.batchSendSensorInfos(tenantId);
+    public String batchSendSensorInfos(@RequestParam(value = "tenantId",required = false) Integer tenantId,
+                                       @RequestParam(value = "engineeringId") Long engineeringId,
+                                       @RequestParam(value = "username") String username,
+                                       @RequestParam(value = "password") String password) {
+        Map<String, Integer> map = baseDataTransferService.batchSendSensorInfos(tenantId, engineeringId, username, password);
         return String.format("上报成功 %d", map.get("success"));
     }
 }

+ 1 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/CdiDeliveryLogService.java

@@ -27,5 +27,5 @@ public interface CdiDeliveryLogService extends CrudService<CdiDeliveryLog> {
 
     // 存储日志
     void saveLog(String topic, String dataTypeName, Integer dataType, Integer tenantId, Long engineeringId, LocalDateTime now, long startTime, long endTime,
-                 int total, int success, int failure, int notSynced, int pushFlag);
+                 int total, int success, int failure, int notSynced, int pushFlag, String userName);
 }

+ 6 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/config/DeviceFieldConfig.java

@@ -32,6 +32,9 @@ public class DeviceFieldConfig {
         // 电气火灾(704)
         fieldMapping.put("704", "voltage_a,voltage_b,voltage_c,current_a,current_b,current_c,temperature_a,temperature_b,temperature_c,current_residual,active_power");
 
+        // 电能采集(705)
+        fieldMapping.put("705", "electrical_energy");
+
         // 温度传感器(707)
         fieldMapping.put("707", "wd");
 
@@ -56,6 +59,9 @@ public class DeviceFieldConfig {
         // 位移传感器
         fieldMapping.put("714", "wy");
 
+        // 液位
+        fieldMapping.put("716", "sensorValue");
+
         return fieldMapping;
     }
 }

+ 4 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/AlarmType.java

@@ -46,7 +46,10 @@ public enum AlarmType {
     TILT("712", "34.1", Category.STRUCTURE_MONITORING, "有倾斜"),
 
     // 裂缝检测告警
-    CRACK("713", "36.1", Category.STRUCTURE_MONITORING, "有裂缝");
+    CRACK("713", "36.1", Category.STRUCTURE_MONITORING, "有裂缝"),
+
+    // 集水井水位偏高
+    SEWAGE_LEVEL("716", "11.1", Category.STRUCTURE_MONITORING, "集水井水位偏高");
 
     private final String originalCode;
     private final String mappedCode;

+ 2 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/MqttTopics.java

@@ -148,6 +148,8 @@ public final class MqttTopics {
                 return IotInfo.WATER_LEAK;
             case 714:
                 return IotInfo.DEVIATION;
+            case 719:
+                return IotInfo.PERSON;
             default:
                 return IotInfo.MONITORING_DATA;
         }

+ 15 - 8
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataSyncService.java

@@ -12,6 +12,7 @@ import com.usky.cdi.service.mqtt.MqttConnectionTool;
 import com.usky.cdi.service.util.SnowflakeIdGenerator;
 import com.usky.cdi.service.vo.alarm.AlarmMessageVO;
 import com.usky.cdi.service.enums.AlarmType;
+import com.usky.common.security.utils.SecurityUtils;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -21,11 +22,9 @@ import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.PostConstruct;
+import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
 import java.util.stream.Collectors;
 
 /**
@@ -49,6 +48,7 @@ public class AlarmDataSyncService {
     private static final String WARTIME = "wartime";
     private static final String ALARM_DELIVERY_KEY_PREFIX = "alarm:delivery:";
     private final MqttConnectionTool mqttConnectionTool;
+    private final SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
 
     @Autowired
     private StringRedisTemplate stringRedisTemplate;
@@ -69,8 +69,8 @@ public class AlarmDataSyncService {
     /**
      * 获取当前时间
      */
-    private LocalDateTime getCurrentTime() {
-        return LocalDateTime.now();
+    private String getCurrentTime() {
+        return timeFormat.format(new Date());
     }
 
     /**
@@ -117,6 +117,13 @@ public class AlarmDataSyncService {
         String topic = MqttTopics.Alarm.MESSAGE.getTopic();
         String desc = MqttTopics.Alarm.MESSAGE.getDesc();
 
+        String userName = "自动同步";
+        try {
+            userName = SecurityUtils.getUsername();
+        } catch (Exception e) {
+            log.error("定时任务无法获取用户名,使用默认‘自动同步’", e);
+        }
+
         try {
             // 2.创建MQTT连接
             mqttConnectionTool.connectOrRefresh(username, password);
@@ -198,7 +205,7 @@ public class AlarmDataSyncService {
             endTime = System.currentTimeMillis();
 
             cdiDeliveryLogService.saveLog(topic, desc, 5, tenantId, engineeringId, now, startTime, endTime, size,
-                    successCount, failureCount, size - successCount - failureCount, 1);
+                    successCount, failureCount, size - successCount - failureCount, 1, userName);
         } catch (Exception e) {
             log.error("租户{}的告警数据推送定时任务执行失败:{}", tenantId, e.getMessage(), e);
         } finally {
@@ -206,7 +213,7 @@ public class AlarmDataSyncService {
             log.info("结束时间:{}, 耗时:{}ms", getCurrentTime(), endTime - startTime);
 
             cdiDeliveryLogService.saveLog(topic, desc, 5, tenantId, engineeringId, now, startTime, endTime, size,
-                    successCount, failureCount, size - successCount - failureCount, 0);
+                    successCount, failureCount, size - successCount - failureCount, 0, userName);
         }
     }
 

+ 31 - 146
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataTransferService.java

@@ -1,26 +1,17 @@
 package com.usky.cdi.service.impl;
 
 import com.alibaba.fastjson.JSON;
-import com.alibaba.fastjson.JSONObject;
-// import com.alibaba.nacos.shaded.com.google.gson.Gson;
-import com.usky.cdi.service.config.mqtt.MqttOutConfig;
 import com.usky.cdi.service.mqtt.MqttConnectionTool;
 import com.usky.cdi.service.util.SnowflakeIdGenerator;
 import com.usky.cdi.service.vo.alarm.AlarmMessageVO;
 import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
 
-import javax.annotation.Resource;
-import java.text.SimpleDateFormat;
 import java.time.LocalDateTime;
-import java.util.Date;
-import java.util.HashMap;
+import java.time.format.DateTimeFormatter;
 
 /**
- * 告警类数据传输服务
- * 负责向市适配平台发送告警类数据
+ * 告警类数据传输服务:向市适配平台通过 MQTT 上报告警数据。
  *
  * @author han
  * @date 2025/12/08
@@ -29,162 +20,56 @@ import java.util.HashMap;
 @Service
 public class AlarmDataTransferService {
 
-    @Autowired
-    private MqttConnectionTool mqttConnectionTool;
-    @Resource
-    private MqttOutConfig.MqttGateway mqttGateway;
+    private static final String MQTT_USERNAME = "3101100021";
+    private static final String MQTT_PASSWORD = "SIixzph1";
+    private static final String ALARM_TOPIC = "alarm/message";
+    private static final DateTimeFormatter PUBLISH_TIME_FORMAT =
+            DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
 
-    private final SnowflakeIdGenerator idGenerator;
-    private final SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+    private final MqttConnectionTool mqttConnectionTool;
+    private final SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(3L, 3L);
 
-    public AlarmDataTransferService() {
-        // 使用默认的workerId和datacenterId,实际项目中可以从配置读取
-        this.idGenerator = new SnowflakeIdGenerator(3L, 3L);
+    public AlarmDataTransferService(MqttConnectionTool mqttConnectionTool) {
+        this.mqttConnectionTool = mqttConnectionTool;
     }
 
-    /**
-     * 获取当前时间字符串
-     */
     private String getCurrentTime() {
-        return timeFormat.format(new Date());
+        return LocalDateTime.now().format(PUBLISH_TIME_FORMAT);
     }
 
-    /**
-     * 生成数据包ID
-     */
-    private Long generateDataPacketID() {
+    private long generateDataPacketId() {
         return idGenerator.nextPacketId();
     }
 
     /**
-     * 发送告警信息
-     * Topic: base/floorPlane
+     * 上报告警消息至 MQTT(topic: alarm/message)。
      *
-     * @param vo 楼层平面图信息
-     * @return 是否发送成功
+     * @return 是否已成功投递到底层网关(不代表敌方平台一定消费成功)
      */
-    public boolean sendAlarmMessage(AlarmMessageVO vo) {
-        try {
-            if (vo.getDataPacketID() == null) {
-                vo.setDataPacketID(generateDataPacketID());
-            }
-            if (vo.getPublishTime() == null) {
-                vo.setPublishTime(LocalDateTime.now());
-            }
-
-//            HashMap<String, Object> map = new HashMap<>();
-//            map.put("dataPacketID", vo.getDataPacketID());
-//            map.put("engineeringID", vo.getEngineeringID());
-//            map.put("floor", vo.getFloor());
-//            map.put("floorFileID", vo.getFloorFileID());
-//            map.put("floorFileName", vo.getFloorFileName());
-//            map.put("floorFileSuffix", vo.getFloorFileSuffix());
-//            map.put("filePixWidth", vo.getFilePixWidth());
-//            map.put("filePixHeight", vo.getFilePixHeight());
-//            map.put("floorFile", imageBytes);
-//            map.put("publishTime", vo.getPublishTime());
-//            Gson gson = new Gson();
-            JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
-            String json = jsonObject.toJSONString();
-            System.out.println(json);
-            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101070011", "5RqhJ7VG");
-            String topic = "alarm/message";
-            IQeIRyXG.sendToMqtt(topic, json);
-
-            return true;
-        } catch (Exception e) {
-            log.error("发送告警信息失败,AlarmID: {}", vo.getAlarmID(), e);
-            return false;
-        }
-    }
-
-    /**
-     * 发送告警信息
-     * Topic: base/floorPlane
-     *
-     * @param vo 楼层平面图信息
-     * @return 是否发送成功
-     */
-    public boolean sendAlarmMessage1(AlarmMessageVO vo) {
-        MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101070011", "5RqhJ7VG");
-
+    public boolean publishAlarm(AlarmMessageVO<?> vo) {
         try {
-            if (vo.getDataPacketID() == null) {
-                vo.setDataPacketID(generateDataPacketID());
-            }
-            if (vo.getPublishTime() == null) {
-                vo.setPublishTime(LocalDateTime.now());
-            }
-
-            HashMap<String, Object> map = new HashMap<>();
-//            map.put("dataPacketID", vo.getDataPacketID());
-//            map.put("engineeringID", vo.getEngineeringID());
-//            map.put("floor", vo.getFloor());
-//            map.put("floorFileID", vo.getFloorFileID());
-//            map.put("floorFileName", vo.getFloorFileName());
-//            map.put("floorFileSuffix", vo.getFloorFileSuffix());
-//            map.put("filePixWidth", vo.getFilePixWidth());
-//            map.put("filePixHeight", vo.getFilePixHeight());
-//            map.put("floorFile", imageBytes);
-//            map.put("publishTime", vo.getPublishTime());
-//             Gson gson = new Gson();
-            JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
-            String json = jsonObject.toJSONString();
-            System.out.println(json);
-            String topic = "alarm/message";
-            IQeIRyXG.sendToMqtt(topic, json);
-
+            fillDefaults(vo);
+            String json = JSON.toJSONString(vo);
+            log.info("告警 MQTT 载荷: {}", json);
+            MqttConnectionTool.MqttGateway gateway =
+                    mqttConnectionTool.connectOrRefresh(MQTT_USERNAME, MQTT_PASSWORD);
+            gateway.sendToMqtt(ALARM_TOPIC, json);
             return true;
         } catch (Exception e) {
-            log.error("发送告警信息失败,AlarmID: {}", vo.getAlarmID(), e);
+            log.error("发送告警信息失败, alarmID: {}, engineeringID: {}",
+                    vo != null ? vo.getAlarmID() : null,
+                    vo != null ? vo.getEngineeringID() : null,
+                    e);
             return false;
         }
     }
 
-    public boolean sendAlarmMessage2(AlarmMessageVO vo) {
-        try {
-            if (vo.getDataPacketID() == null) {
-                vo.setDataPacketID(generateDataPacketID());
-            }
-            if (vo.getPublishTime() == null) {
-                vo.setPublishTime(LocalDateTime.now());
-            }
-
-            JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
-            String json = jsonObject.toJSONString();
-            System.out.println(json);
-            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101070011", "5RqhJ7VG");
-            String topic = "alarm/message";
-            IQeIRyXG.sendToMqtt(topic, json);
-
-            return true;
-        } catch (Exception e) {
-            log.error("发送告警信息失败,AlarmID: {}", vo.getAlarmID(), e);
-            return false;
+    private void fillDefaults(AlarmMessageVO<?> vo) {
+        if (vo.getDataPacketID() == null) {
+            vo.setDataPacketID(generateDataPacketId());
         }
-    }
-
-    public boolean sendEngineeringBase(AlarmMessageVO vo) {
-        try {
-            if (vo.getDataPacketID() == null) {
-                vo.setDataPacketID(generateDataPacketID());
-            }
-            if (vo.getPublishTime() == null) {
-                vo.setPublishTime(LocalDateTime.now());
-            }
-
-            MqttConnectionTool.MqttGateway IQeIRyXG = mqttConnectionTool.connectOrRefresh("3101070011", "5RqhJ7VG");
-
-            JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
-            String json = jsonObject.toJSONString();
-            String topic = "alarm/message";
-            System.out.println("推送的数据: " + json);
-            IQeIRyXG.sendToMqtt(topic, json);
-
-            return true;
-        } catch (Exception e) {
-            log.error("发送电流告警信息失败,EngineeringID: {}", vo.getEngineeringID(), e);
-            return false;
+        if (vo.getPublishTime() == null) {
+            vo.setPublishTime(getCurrentTime());
         }
     }
 }

+ 53 - 16
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseDataTransferService.java

@@ -3,13 +3,15 @@ package com.usky.cdi.service.impl;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
 import com.alibaba.nacos.shaded.com.google.gson.Gson;
+import com.alibaba.nacos.shaded.com.google.gson.GsonBuilder;
+import com.alibaba.nacos.shaded.com.google.gson.LongSerializationPolicy;
 import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
 import com.usky.cdi.domain.BaseBuildFacility;
 import com.usky.cdi.domain.DmpDevice;
 import com.usky.cdi.service.BaseBuildFacilityService;
-import com.usky.cdi.service.CdiDeliveryLogService;
 import com.usky.cdi.service.DmpDeviceInfoService;
 import com.usky.cdi.service.config.mqtt.MqttOutConfig;
+import com.usky.cdi.service.mqtt.MqttConnectionTool;
 import com.usky.cdi.service.util.SnowflakeIdGenerator;
 import com.usky.cdi.service.vo.info.EngineeringBaseVO;
 import com.usky.cdi.service.vo.info.FacilityDeviceVO;
@@ -22,9 +24,14 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Service;
 
 import javax.annotation.Resource;
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 
 /**
@@ -48,8 +55,11 @@ public class BaseDataTransferService {
     @Resource
     private MqttOutConfig.MqttGateway mqttGateway;
 
-    @Value("${config.engineeringID}")
-    private String engineeringID;
+    // @Value("${config.engineeringID}")
+    // private String engineeringID;
+
+    @Autowired
+    private MqttConnectionTool mqttConnectionTool;
 
     private final SnowflakeIdGenerator idGenerator;
     private final SimpleDateFormat timeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
@@ -59,9 +69,21 @@ public class BaseDataTransferService {
         this.idGenerator = new SnowflakeIdGenerator(1L, 1L);
     }
 
-    /**
-     * 获取当前时间字符串
-     */
+    private String convertFloor(String floor) {
+        if (floor == null || floor.trim().isEmpty()) {
+            return floor;
+        }
+        try {
+            int floorNum = Integer.parseInt(floor.trim());
+            if (floorNum < 0) {
+                return "B" + Math.abs(floorNum);
+            }
+        } catch (NumberFormatException e) {
+            log.warn("楼层格式转换失败,原始值: {}", floor);
+        }
+        return floor;
+    }
+
     private String getCurrentTime() {
         return timeFormat.format(new Date());
     }
@@ -122,7 +144,8 @@ public class BaseDataTransferService {
             String topic = "base/protectiveUnit";
 
             log.info("发送防护单元基础信息,Topic: {}, Data: {}", topic, json);
-            mqttGateway.sendToMqtt(topic, json);
+            MqttConnectionTool.MqttGateway gateway = mqttConnectionTool.connectOrRefresh(vo.getUserName(), vo.getPassword());
+            gateway.sendToMqtt(topic, json);
 
             return true;
         } catch (Exception e) {
@@ -140,6 +163,7 @@ public class BaseDataTransferService {
      */
     public boolean sendFloorPlane(FloorPlaneVO vo) {
         try {
+            // ========== 1. 基础参数填充 ==========
             if (vo.getDataPacketID() == null) {
                 vo.setDataPacketID(generateDataPacketID());
             }
@@ -147,7 +171,7 @@ public class BaseDataTransferService {
                 vo.setPublishTime(getCurrentTime());
             }
 
-            String imagePath = "D://games/3492.jpg";
+            String imagePath = "D://BSP0-0103.jpg";
             // 将图片文件读取为字节数组
             byte[] imageBytes = Files.readAllBytes(Paths.get(imagePath));
 
@@ -186,7 +210,9 @@ public class BaseDataTransferService {
             log.info("发送楼层平面图信息,Topic: {}, FileID: {}, FileSize: {} bytes",
                     topic, vo.getFloorFileID(),
                     vo.getFloorFile() != null ? vo.getFloorFile().length : 0);
-            mqttGateway.sendToMqtt(topic, gson.toJson(map));
+
+            MqttConnectionTool.MqttGateway gateway = mqttConnectionTool.connectOrRefresh(vo.getUserName(), vo.getPassword());
+            gateway.sendToMqtt(topic, gson.toJson(map));
 
             return true;
         } catch (Exception e) {
@@ -221,21 +247,28 @@ public class BaseDataTransferService {
         try {
             Map<Integer, Integer> userIdToName = new HashMap<>();
             userIdToName.put(702, 31);
+            // 30:网络监控摄像机(可兼用平时摄像机)
+            // userIdToName.put(717, 30);
+            // 20:人员统计-掩蔽人数双目统计摄像机
+            // userIdToName.put(719, 20);
+            // 33:人员闯入-人员闯入监测传感器
             userIdToName.put(703, 33);
             userIdToName.put(704, 11);
+            userIdToName.put(705, 11);
             userIdToName.put(707, 19);
             userIdToName.put(708, 19);
             userIdToName.put(709, 15);
             userIdToName.put(710, 16);
             userIdToName.put(711, 2);
-            //userIdToName.put(712, 34);
-            //userIdToName.put(713, 36);
+            // userIdToName.put(712, 34);
+            // userIdToName.put(713, 36);
             userIdToName.put(714, 37);
+            userIdToName.put(716, 26);
 
             HashMap<String, Object> map = new HashMap<>();
             map.put("dataPacketID", generateDataPacketID());
-            map.put("engineeringID", Long.parseLong(engineeringID));
-            map.put("floor", "B2");
+            map.put("engineeringID", vo.getEngineeringID());
+            map.put("floor", convertFloor(vo.getFloor()));
             map.put("floorFileID", 1);
             map.put("sensorID", Integer.parseInt(vo.getDeviceId()));
             map.put("sensorNo", vo.getDeviceUuid());
@@ -252,8 +285,9 @@ public class BaseDataTransferService {
             Gson gson = new Gson();
             String topic = "base/sensorInfo";
             System.out.println(gson.toJson(map));
-//            log.info("发送智能监管物联设施信息,Topic: {}, SensorID: {}", topic, vo.getSensorID());
-            mqttGateway.sendToMqtt(topic, gson.toJson(map));
+            log.info("发送智能监管物联设施信息,Topic: {}, SensorID: {}", topic, vo.getDeviceId());
+            MqttConnectionTool.MqttGateway gateway = mqttConnectionTool.connectOrRefresh(vo.getUserName(), vo.getPassword());
+            gateway.sendToMqtt(topic, gson.toJson(map));
 
             return true;
         } catch (Exception e) {
@@ -290,7 +324,7 @@ public class BaseDataTransferService {
      * @param tenantId 租户ID
      * @return 成功发送的数量
      */
-    public Map<String, Integer> batchSendSensorInfos(Integer tenantId) {
+    public Map<String, Integer> batchSendSensorInfos(Integer tenantId, Long engineeringId, String username, String password) {
         List<BaseBuildFacility> list = baseBuildFacilityService.facilityInfo(tenantId);
         List<DmpDevice> list1 = dmpDeviceInfoService.deviceInfo(tenantId);
         List<FacilityDeviceVO> list2 = new ArrayList<>();
@@ -309,6 +343,9 @@ public class BaseDataTransferService {
                         facilityDeviceVO.setDeviceUuid(list1.get(k).getDeviceUuid());
                         facilityDeviceVO.setFacilityDesc(list.get(j).getFacilityDesc());
                         facilityDeviceVO.setDeviceType(list1.get(k).getDeviceType());
+                        facilityDeviceVO.setEngineeringID(engineeringId);
+                        facilityDeviceVO.setUserName(username);
+                        facilityDeviceVO.setPassword(password);
                         list2.add(facilityDeviceVO);
                     }
                 }

+ 368 - 221
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/CdiDeliveryLogServiceImpl.java

@@ -3,12 +3,13 @@ package com.usky.cdi.service.impl;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
-import cn.hutool.json.JSONArray;
 import cn.hutool.json.JSONObject;
 import cn.hutool.json.JSONUtil;
 import com.alibaba.fastjson.JSON;
+import com.alibaba.nacos.shaded.com.google.gson.Gson;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -17,6 +18,7 @@ import com.usky.cdi.service.enums.MqttTopics;
 import com.usky.cdi.mapper.*;
 import com.usky.cdi.service.CdiDeliveryLogService;
 import com.usky.cdi.service.mqtt.MqttConnectionTool;
+import com.usky.cdi.service.util.SnowflakeIdGenerator;
 import com.usky.cdi.service.vo.SyncTaskStatisticsVO;
 import com.usky.cdi.service.vo.info.FloorPlaneVO;
 import com.usky.cdi.service.vo.info.ProtectiveUnitVO;
@@ -29,15 +31,20 @@ import org.apache.commons.collections4.CollectionUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
+import org.springframework.beans.factory.annotation.Value;
 
+import javax.annotation.PostConstruct;
 import javax.imageio.ImageIO;
 import java.awt.image.BufferedImage;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
 import java.math.BigDecimal;
+import java.net.HttpURLConnection;
 import java.net.URL;
 import java.net.URLConnection;
+import java.net.URLEncoder;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
@@ -84,108 +91,60 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
     @Autowired
     private MqttConnectionTool mqttConnectionTool;
 
+    // 从配置文件读取Snowflake参数,默认值为1
+    @Value("${snowflake.worker-id:1}")
+    private long workerId;
+
+    @Value("${snowflake.data-center-id:1}")
+    private long dataCenterId;
+
+    private SnowflakeIdGenerator idGenerator;
+
+    @PostConstruct
+    public void init() {
+        this.idGenerator = new SnowflakeIdGenerator(workerId, dataCenterId);
+    }
+
+    /**
+     * 生成数据包ID
+     */
+    private Long generateDataPacketID() {
+        return idGenerator.nextPacketId();
+    }
+
     @Override
     public List<SyncTaskStatisticsVO> selectById(Long id) {
-        // 1. 租户ID校验(必须非空,无租户直接返回空列表)
         Integer tenantId = SecurityUtils.getTenantId();
-        if (tenantId == null) {
+        if (tenantId == null || tenantId <= 0) {
             log.warn("未获取到当前租户ID,无法查询人防投递日志");
             return Collections.emptyList();
         }
 
-        // 2. 动态构建查询条件:id为null时只查租户,id不为null时租户+id精准查
-        // 【小优化】按ID倒序,后续取最新数据更直观(ID自增则大ID是最新)
         List<CdiDeliveryLog> logList = lambdaQuery()
                 .eq(CdiDeliveryLog::getTenantId, tenantId)
                 .eq(id != null, CdiDeliveryLog::getId, id)
-                .orderByDesc(CdiDeliveryLog::getId) // 改为倒序,优先最新数据
+                .orderByDesc(CdiDeliveryLog::getId)
                 .list();
 
         LambdaQueryWrapper<CdiDefenseProject> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.eq(CdiDefenseProject::getTenantId, tenantId);
-        // 【空指针防护】新增非空判断,避免selectOne返回null时报错
         CdiDefenseProject defenseProject = cdiDefenseProjectMapper.selectOne(queryWrapper);
         boolean isEnable = defenseProject != null && defenseProject.getIsEnable() == 0;
 
         List<SyncTaskStatisticsVO> finalResult = new ArrayList<>();
 
-        // 3. 日志集合判空:无数据查询设备表(原逻辑不变)
-        if (CollectionUtils.isEmpty(logList)) {
-
-            // 单元数据
-            List<BaseBuildUnit> buildUnitList = getBuildUnitList(tenantId);
-            SyncTaskStatisticsVO vo1 = new SyncTaskStatisticsVO();
-            vo1.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
-            vo1.setTopic(MqttTopics.Base.PROTECTIVE_UNIT.getTopic());
-            vo1.setDataType(1);
-            vo1.setTotal(buildUnitList.size());
-            vo1.setSuccessNumber(0);
-            vo1.setFailNumber(0);
-            vo1.setNotSynced(0);
-            vo1.setState(isEnable ? 1 : 0);
-            finalResult.add(vo1);
-
-            // 平面图
-            List<Integer> buildIdList = getBuildList(tenantId).stream().map(BaseBuild::getId).collect(Collectors.toList());
-            List<BaseBuildPlane> buildPlaneList = getBuildPlaneList(buildIdList);
-            SyncTaskStatisticsVO vo4 = new SyncTaskStatisticsVO();
-            vo4.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
-            vo4.setTopic(MqttTopics.Base.FLOOR_PLANE.getTopic());
-            vo4.setDataType(2);
-            vo4.setTotal(buildPlaneList.size());
-            vo4.setSuccessNumber(0);
-            vo4.setFailNumber(0);
-            vo4.setNotSynced(0);
-            vo4.setState(isEnable ? 1 : 0);
-            finalResult.add(vo4);
-
-            // 设施数据
-            List<BaseBuildFacility> buildFacilityList = getBuildFacilityList(tenantId);
-            SyncTaskStatisticsVO vo2 = new SyncTaskStatisticsVO();
-            vo2.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
-            vo2.setTopic(MqttTopics.Base.SENSOR_INFO.getTopic());
-            vo2.setDataType(3);
-            vo2.setTotal(buildFacilityList.size());
-            vo2.setSuccessNumber(0);
-            vo2.setFailNumber(0);
-            vo2.setNotSynced(0);
-            vo2.setState(isEnable ? 1 : 0);
-            finalResult.add(vo2);
-
-            // 监测数据
-            SyncTaskStatisticsVO vo3 = new SyncTaskStatisticsVO();
-            vo3.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
-            vo3.setTopic(MqttTopics.IotInfo.MONITORING_DATA.getTopic());
-            vo3.setDataType(4);
-            vo3.setTotal(buildFacilityList.size());
-            vo3.setSuccessNumber(0);
-            vo3.setFailNumber(0);
-            vo3.setNotSynced(0);
-            vo3.setState(isEnable ? 1 : 0);
-            finalResult.add(vo3);
-
-            SyncTaskStatisticsVO vo5 = new SyncTaskStatisticsVO();
-            vo5.setDataTypeName(MqttTopics.Alarm.MESSAGE.getDesc());
-            vo5.setTopic(MqttTopics.Alarm.MESSAGE.getTopic());
-            vo5.setDataType(5);
-            vo5.setTotal(0);
-            vo5.setSuccessNumber(0);
-            vo5.setFailNumber(0);
-            vo5.setNotSynced(0);
-            vo5.setState(isEnable ? 1 : 0);
-            finalResult.add(vo5);
+        // 预先查询各类型的基础数据量(无日志时使用)
+        List<BaseBuildUnit> buildUnitList = getBuildUnitList(tenantId);
+        List<BaseBuild> buildList = getBuildList(tenantId);
+        List<Integer> buildIdList = buildList.stream().map(BaseBuild::getId).collect(Collectors.toList());
+        List<BaseBuildPlane> buildPlaneList = buildIdList.isEmpty() ? Collections.emptyList() : getBuildPlaneList(buildIdList);
+        List<BaseBuildFacility> buildFacilityList = getBuildFacilityList(tenantId);
 
+        if (CollectionUtils.isEmpty(logList)) {
+            fillEmptyStatistics(finalResult, tenantId, isEnable);
             return finalResult;
         }
 
-        // 优先自动解析:JSON数组直接转VO列表,简洁高效
-        //     JSONArray jsonArray = JSONUtil.parseArray(jsonContent);
-        //     List<SyncTaskStatisticsVO> autoParseList = JSONUtil.toList(jsonArray, SyncTaskStatisticsVO.class);
-        //     finalResult.addAll(autoParseList);
-        //     log.info("租户ID:{} 日志ID:{} 自动解析成功,解析出{}条同步统计数据", tenantId, logId, autoParseList.size());
-
-        // ########## 核心改造:按dataType分组,取每种类型最新的一条日志 ##########
-        // 步骤1:先过滤出infoContent非空的有效日志(提前过滤,减少分组计算量)
         List<CdiDeliveryLog> validLogList = logList.stream()
                 .filter(logEntity -> logEntity != null
                         && StrUtil.isNotBlank(logEntity.getInfoContent())
@@ -193,130 +152,185 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 .collect(Collectors.toList());
 
         if (CollectionUtils.isEmpty(validLogList)) {
-            log.warn("租户ID:{} 查询到{}条日志,但所有日志的JSON内容为空或dataType为空,查询ID:{}", tenantId, logList.size(), id);
-            return Collections.emptyList();
+            log.warn("租户ID:{} 查询到{}条日志,但所有日志的JSON内容为空或dataType为空", tenantId, logList.size());
+            fillEmptyStatistics(finalResult, tenantId, isEnable);
+            return finalResult;
         }
 
-        // 步骤2:按dataType分组,每组按ID倒序(最新)取第一条,保证1-4类型各一条
-        Map<Integer, CdiDeliveryLog> latestLogByType = validLogList.stream()
-                .collect(Collectors.groupingBy(
-                        CdiDeliveryLog::getDataType, // 分组键:dataType(1-4)
-                        Collectors.collectingAndThen(
-                                Collectors.maxBy(Comparator.comparingLong(CdiDeliveryLog::getId)), // 取组内ID最大的(最新)
-                                opt -> opt.orElse(null) // 空值处理
-                        )
-                ));
+        // ★ 改动1:按 dataType 分组,保留每种类型的全部日志
+        Map<Integer, List<CdiDeliveryLog>> allLogsByType = validLogList.stream()
+                .collect(Collectors.groupingBy(CdiDeliveryLog::getDataType));
 
-        // 步骤3:转换为列表,仅保留1-4类型的最新日志(过滤非目标类型)
-        List<CdiDeliveryLog> finalValidLogList = new ArrayList<>();
         for (int type = 1; type <= 5; type++) {
-            CdiDeliveryLog latestLog = latestLogByType.get(type);
-            if (latestLog != null) {
-                finalValidLogList.add(latestLog);
+            List<CdiDeliveryLog> logsForType = allLogsByType.get(type);
+
+            // 创建基础 VO,设置 dataType 和 topic
+            SyncTaskStatisticsVO vo = createEmptyVO(type, isEnable);
+
+            // ★ total 始终从数据库查询获取(单元表、平面图表等),与日志记录无关
+            switch (type) {
+                case 1:
+                    vo.setTotal(buildUnitList.size());
+                    break;
+                case 2:
+                    vo.setTotal(buildPlaneList.size());
+                    break;
+                case 3:
+                case 4:
+                    vo.setTotal(buildFacilityList.isEmpty() ? 0 : buildFacilityList.size());
+                    break;
+                case 5:
+                default:
+                    break;
             }
-            // 若某类型无日志,无需处理:后续解析后若该类型无数据,是否补空VO可按需求调整,原逻辑是返回解析到的内容
-        }
 
-        if (CollectionUtils.isEmpty(finalValidLogList)) {
-            log.warn("租户ID:{} 无1-4类型的有效日志,查询ID:{}", tenantId, id);
-            return Collections.emptyList();
-        }
-        // ########## 核心改造结束 ##########
-
-        // 5. 遍历【每种类型最新的日志】,解析JSON并合并结果(单日志解析失败不影响其他)
-        for (CdiDeliveryLog deliveryLog : finalValidLogList) {
-            Long logId = deliveryLog.getId();
-            String jsonContent = deliveryLog.getInfoContent();
-            try {
-                JSONArray array = JSONUtil.parseArray(jsonContent);
-                List<SyncTaskStatisticsVO> manualParseList = new ArrayList<>(array.size());
-                for (int i = 0; i < array.size(); i++) {
-                    JSONObject obj = array.getJSONObject(i);
-                    SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
-                    // 手动映射字段:按实际入库的JSON字段名匹配
-                    vo.setId(obj.getLong("id"));
-                    vo.setTopic(obj.getStr("topic"));
-                    switch (obj.getInt("dataType")) {
-                        case 1:
-                            vo.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
-                            break;
-                        case 2:
-                            vo.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
-                            break;
-                        case 3:
-                            vo.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
-                            break;
-                        case 4:
-                            vo.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
-                            break;
-                        case 5:
-                            vo.setDataTypeName(MqttTopics.Alarm.MESSAGE.getDesc());
-                            break;
+            if (logsForType != null && !logsForType.isEmpty()) {
+                // ★ 有日志时:按类型累加 success / fail / notSynced
+                int sumSuccess = 0, sumFail = 0, sumNotSynced = 0;
+
+                for (CdiDeliveryLog logEntry : logsForType) {
+                    try {
+                        // ★ infoContent 是 JSON 对象,用 parseObj 解析
+                        JSONObject o = JSONUtil.parseObj(logEntry.getInfoContent());
+                        sumSuccess += o.getInt("successNumber");
+                        sumFail += o.getInt("failNumber");
+                        sumNotSynced += o.getInt("notSynced");
+                    } catch (Exception ignored) {
+                        // 单条解析失败不影响其他日志的累加
+                    }
+                }
+
+                vo.setSuccessNumber(sumSuccess);
+                vo.setFailNumber(sumFail);
+                vo.setNotSynced(sumNotSynced);
+
+                // 取最新一条日志作为元数据来源(createTime / costTime 等)
+                CdiDeliveryLog latestLog = logsForType.stream()
+                        .max(Comparator.comparingLong(CdiDeliveryLog::getId))
+                        .orElse(null);
+
+                if (latestLog != null) {
+                    try {
+                        // ★ infoContent 是 JSON 对象,用 parseObj 解析
+                        JSONObject obj = JSONUtil.parseObj(latestLog.getInfoContent());
+                        vo.setId(obj.getLong("id"));
+                        vo.setCreateTime(obj.getStr("createTime"));
+                        vo.setCostTime(obj.getFloat("costTime"));
+                        vo.setState(isEnable ? 1 : obj.getInt("state"));
+                    } catch (Exception ex) {
+                        log.error("租户ID:{} dataType:{} 解析最新日志元数据失败", tenantId, type, ex);
                     }
-                    vo.setDataType(obj.getInt("dataType"));
-                    vo.setTotal(obj.getInt("total"));
-                    vo.setSuccessNumber(obj.getInt("successNumber"));
-                    vo.setFailNumber(obj.getInt("failNumber"));
-                    vo.setNotSynced(obj.getInt("notSynced"));
-                    vo.setCreateTime(obj.getStr("createTime"));
-                    vo.setCostTime(obj.getFloat("costTime"));
-                    vo.setState(isEnable ? 1 : obj.getInt("state"));
-                    manualParseList.add(vo);
                 }
-                finalResult.addAll(manualParseList);
-                log.info("租户ID:{} 日志ID:{} 手动解析成功,解析出{}条同步统计数据", tenantId, logId, manualParseList.size());
-            } catch (Exception ex) {
-                // 单日志解析失败,仅打印日志,继续解析其他日志
-                log.error("租户ID:{} 日志ID:{} 解析失败,跳过该日志", tenantId, logId, ex);
+
+                log.info("租户ID:{} dataType:{} 共{}条日志, 聚合结果: total={}, success={}, fail={}, notSynced={}",
+                        tenantId, type, logsForType.size(), vo.getTotal(), sumSuccess, sumFail, sumNotSynced);
+
+            } else {
+                // ★ 无日志时:success/fail/notSynced 保持为0(createEmptyVO已设置)
             }
+
+            finalResult.add(vo);
         }
 
-        // // 6. 【可选优化】若解析后部分类型缺失,补全空VO(和无日志时格式完全一致)
-        // // 提取已解析的dataType
-        // Set<Integer> parsedTypes = finalResult.stream()
-        //         .map(SyncTaskStatisticsVO::getDataType)
-        //         .filter(Objects::nonNull)
-        //         .collect(Collectors.toSet());
-        // // 补全1-4中缺失的类型,设置默认值(和无日志时一致)
-        // for (int type = 1; type <= 3; type++) {
-        //     if (!parsedTypes.contains(type)) {
-        //         SyncTaskStatisticsVO emptyVo = new SyncTaskStatisticsVO();
-        //         // 按类型设置名称、主题,和情况一保持一致
-        //         switch (type) {
-        //             case 1:
-        //                 emptyVo.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
-        //                 emptyVo.setTopic(MqttTopics.Base.PROTECTIVE_UNIT.getTopic());
-        //                 break;
-        //             case 2:
-        //                 emptyVo.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
-        //                 emptyVo.setTopic(MqttTopics.Base.FLOOR_PLANE.getTopic());
-        //                 break;
-        //             case 3:
-        //                 emptyVo.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
-        //                 emptyVo.setTopic(MqttTopics.Base.SENSOR_INFO.getTopic());
-        //                 break;
-        //             // case 4:
-        //             //     emptyVo.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
-        //             //     emptyVo.setTopic(MqttTopics.IotInfo.MONITORING_DATA.getTopic());
-        //             //     break;
-        //         }
-        //         emptyVo.setDataType(type);
-        //         emptyVo.setTotal(0);
-        //         emptyVo.setSuccessNumber(0);
-        //         emptyVo.setFailNumber(0);
-        //         emptyVo.setNotSynced(0);
-        //         emptyVo.setState(isEnable ? 1 : 0);
-        //         finalResult.add(emptyVo);
-        //     }
-        // }
-
-        // 对结果按dataType排序(1-4),和情况一返回顺序一致
         finalResult.sort(Comparator.comparingInt(SyncTaskStatisticsVO::getDataType));
-
-        // 6. 返回结果
         return finalResult;
     }
 
+    private void fillEmptyStatistics(List<SyncTaskStatisticsVO> result, Integer tenantId, boolean isEnable) {
+        List<BaseBuildUnit> buildUnitList = getBuildUnitList(tenantId);
+        SyncTaskStatisticsVO vo1 = createEmptyVO(1, isEnable);
+        vo1.setTotal(buildUnitList.size());
+        result.add(vo1);
+
+        List<Integer> buildIdList = getBuildList(tenantId).stream().map(BaseBuild::getId).collect(Collectors.toList());
+        List<BaseBuildPlane> buildPlaneList = buildIdList.isEmpty() ? Collections.emptyList() : getBuildPlaneList(buildIdList);
+        SyncTaskStatisticsVO vo2 = createEmptyVO(2, isEnable);
+        vo2.setTotal(buildPlaneList.size());
+        result.add(vo2);
+
+        List<BaseBuildFacility> buildFacilityList = getBuildFacilityList(tenantId);
+        SyncTaskStatisticsVO vo3 = createEmptyVO(3, isEnable);
+        vo3.setTotal(buildFacilityList.isEmpty() ? 0 : buildFacilityList.size());
+        result.add(vo3);
+
+        SyncTaskStatisticsVO vo4 = createEmptyVO(4, isEnable);
+        vo4.setTotal(buildFacilityList.isEmpty() ? 0 : buildFacilityList.size());
+        result.add(vo4);
+
+        SyncTaskStatisticsVO vo5 = createEmptyVO(5, isEnable);
+        result.add(vo5);
+    }
+
+    private SyncTaskStatisticsVO createEmptyVO(int dataType, boolean isEnable) {
+        SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
+        vo.setDataType(dataType);
+        vo.setSuccessNumber(0);
+        vo.setFailNumber(0);
+        vo.setNotSynced(0);
+        vo.setState(isEnable ? 1 : 0);
+
+        switch (dataType) {
+            case 1:
+                vo.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
+                vo.setTopic(MqttTopics.Base.PROTECTIVE_UNIT.getTopic());
+                break;
+            case 2:
+                vo.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
+                vo.setTopic(MqttTopics.Base.FLOOR_PLANE.getTopic());
+                break;
+            case 3:
+                vo.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
+                vo.setTopic(MqttTopics.Base.SENSOR_INFO.getTopic());
+                break;
+            case 4:
+                vo.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
+                vo.setTopic(MqttTopics.IotInfo.MONITORING_DATA.getTopic());
+                break;
+            case 5:
+                vo.setDataTypeName(MqttTopics.Alarm.MESSAGE.getDesc());
+                vo.setTopic(MqttTopics.Alarm.MESSAGE.getTopic());
+                break;
+        }
+        return vo;
+    }
+
+    private SyncTaskStatisticsVO parseSyncStatisticsVO(JSONObject obj, boolean isEnable) {
+        SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
+        vo.setId(obj.getLong("id"));
+        vo.setTopic(obj.getStr("topic"));
+
+        int dataType = obj.getInt("dataType");
+        vo.setDataType(dataType);
+
+        switch (dataType) {
+            case 1:
+                vo.setDataTypeName(MqttTopics.Base.PROTECTIVE_UNIT.getDesc());
+                break;
+            case 2:
+                vo.setDataTypeName(MqttTopics.Base.FLOOR_PLANE.getDesc());
+                break;
+            case 3:
+                vo.setDataTypeName(MqttTopics.Base.SENSOR_INFO.getDesc());
+                break;
+            case 4:
+                vo.setDataTypeName(MqttTopics.IotInfo.MONITORING_DATA.getDesc());
+                break;
+            case 5:
+                vo.setDataTypeName(MqttTopics.Alarm.MESSAGE.getDesc());
+                break;
+        }
+
+        vo.setTotal(obj.getInt("total"));
+        vo.setSuccessNumber(obj.getInt("successNumber"));
+        vo.setFailNumber(obj.getInt("failNumber"));
+        vo.setNotSynced(obj.getInt("notSynced"));
+        vo.setCreateTime(obj.getStr("createTime"));
+        vo.setCostTime(obj.getFloat("costTime"));
+        vo.setState(isEnable ? 1 : obj.getInt("state"));
+
+        return vo;
+    }
+
     @Override
     public CommonPage<CdiDeliveryLog> logList(Long id, Integer pageNum, Integer pageSize, Integer dataType, Integer
             logType, String startTime, String endTime) {
@@ -432,6 +446,13 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
         Integer tenantId = one.getTenantId();
         LocalDateTime now = LocalDateTime.now();
 
+        String userName = "自动同步";
+        try {
+            userName = SecurityUtils.getUsername();
+        } catch (Exception e) {
+            log.error("无法获取用户名或姓名,使用默认‘自动同步’", e);
+        }
+
         switch (vo.getDataType()) {
             // 单元信息
             case 1:
@@ -460,7 +481,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 endTime = System.currentTimeMillis();
 
                 notSynced = total - success - failure;
-                saveLog(topic, desc, 1, tenantId, engineeringId, now, startTime, endTime, total, success, failure, notSynced, failure > 0 ? 0 : 1);
+                saveLog(topic, desc, 1, tenantId, engineeringId, now, startTime, endTime, total, success, failure, notSynced, failure > 0 ? 0 : 1, userName);
 
                 break;
             // 平面图信息
@@ -472,10 +493,16 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 int total2 = 0, success2 = 0, failure2 = 0, notSynced2 = 0;
 
                 List<FloorPlaneVO> floorPlaneVOS = buildPlanes(tenantId, engineeringId);
+                if (CollUtil.isEmpty(floorPlaneVOS)) {
+                    log.error("未找到楼层平面图信息!");
+                    break;
+                }
                 total2 = floorPlaneVOS.size();
                 iotDataTransferService.createMqttConnection(username, password);
                 String topic1 = MqttTopics.Base.FLOOR_PLANE.getTopic();
                 String desc1 = MqttTopics.Base.FLOOR_PLANE.getDesc();
+
+
                 for (FloorPlaneVO floorPlaneVO : floorPlaneVOS) {
                     try {
                         iotDataTransferService.sendMqttMessage(topic1, floorPlaneVO, desc1, username);
@@ -488,7 +515,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 endTime2 = System.currentTimeMillis();
                 notSynced2 = total2 - success2 - failure2;
                 saveLog(topic1, desc1, 2, tenantId, engineeringId, now, startTime2, endTime2, total2, success2,
-                        failure2, notSynced2, failure2 > 0 ? 0 : 1);
+                        failure2, notSynced2, failure2 > 0 ? 0 : 1, userName);
 
                 break;
             // 推送设施信息
@@ -498,11 +525,11 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
                 long startTime3, endTime3;
                 startTime3 = System.currentTimeMillis();
 
-                Map<String, Integer> map = baseDataTransferService.batchSendSensorInfos(tenantId);
+                Map<String, Integer> map = baseDataTransferService.batchSendSensorInfos(tenantId, engineeringId, username, password);
 
                 endTime3 = System.currentTimeMillis();
                 saveLog(MqttTopics.Base.SENSOR_INFO.getTopic(), MqttTopics.Base.SENSOR_INFO.getDesc(), 3, tenantId, engineeringId,
-                        now, startTime3, endTime3, map.get("total"), map.get("success"), map.get("failure"), map.get("notSynced"), map.get("failure") > 0 ? 0 : 1);
+                        now, startTime3, endTime3, map.get("total"), map.get("success"), map.get("failure"), map.get("notSynced"), map.get("failure") > 0 ? 0 : 1, userName);
 
                 break;
             // 推送监测数据
@@ -518,7 +545,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
     @Override
     @Async("asyncServiceExecutor")
     public void saveLog(String topic, String dataTypeName, Integer dataType, Integer tenantId, Long engineeringId, LocalDateTime now, long startTime, long endTime,
-                        int total, int success, int failure, int notSynced, int pushFlag) {
+                        int total, int success, int failure, int notSynced, int pushFlag, String userName) {
 
         SyncTaskStatisticsVO vo = new SyncTaskStatisticsVO();
         vo.setDataType(dataType);
@@ -537,8 +564,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
         log.setDataType(dataType);
         log.setTopic(topic);
         log.setDataTypeName(vo.getDataTypeName());
-        log.setUserName(SecurityUtils.getUsername() == null ? "自动同步" : SecurityUtils.getUsername());
-        log.setNickName(SecurityUtils.getUsername() == null ? "自动同步" : SecurityUtils.getLoginUser().getSysUser().getNickName());
+        log.setUserName(userName);
         log.setCreateTime(now);
         log.setTenantId(tenantId);
         log.setPushFlag(pushFlag);
@@ -559,6 +585,7 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
         List<ProtectiveUnitVO> result = new ArrayList<>(buildUnitList.size());
         for (BaseBuildUnit buildUnit : buildUnitList) {
             ProtectiveUnitVO vo = new ProtectiveUnitVO();
+            vo.setDataPacketID(generateDataPacketID());
             vo.setEngineeringID(engineeringId);
             vo.setUnitName(buildUnit.getUnitName());
             vo.setFloor(buildUnit.getFloor());
@@ -586,21 +613,42 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
         List<Integer> buildIds = buildList.stream().map(BaseBuild::getId).collect(Collectors.toList());
         List<BaseBuildPlane> buildPlaneList = getBuildPlaneList(buildIds);
 
-        String time = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS");
+        if (CollUtil.isEmpty(buildPlaneList)) {
+            return Collections.emptyList();
+        }
+
+        Map<String, BaseBuildPlane> latestPlaneByFloor = buildPlaneList.stream()
+                .collect(Collectors.groupingBy(
+                        BaseBuildPlane::getFloor,
+                        Collectors.collectingAndThen(
+                                Collectors.maxBy(Comparator.comparingInt(BaseBuildPlane::getId)),
+                                opt -> opt.orElse(null)
+                        )
+                ));
 
-        List<FloorPlaneVO> result = new ArrayList<>(buildPlaneList.size());
-        for (BaseBuildPlane buildPlane : buildPlaneList) {
+        List<BaseBuildPlane> filteredPlaneList = latestPlaneByFloor.values().stream()
+                .filter(Objects::nonNull)
+                .sorted(Comparator.comparing(BaseBuildPlane::getFloor))
+                .collect(Collectors.toList());
 
-            String planeViewUrl = buildPlane.getPlaneViewUrl();
+        log.info("楼层平面图数据过滤:原始{}条,按楼层去重后{}条", buildPlaneList.size(), filteredPlaneList.size());
 
+        String time = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS");
 
+        List<FloorPlaneVO> result = new ArrayList<>(filteredPlaneList.size());
+        for (BaseBuildPlane buildPlane : filteredPlaneList) {
+            String planeViewUrl = buildPlane.getPlaneViewUrl();
             FloorPlaneVO vo = new FloorPlaneVO();
             checkFileSize(vo, planeViewUrl);
+            fillImageInfo(vo, planeViewUrl);
+
+            vo.setDataPacketID(generateDataPacketID());
             vo.setEngineeringID(engineeringId);
             vo.setFloor(buildPlane.getFloor());
             vo.setFloorFileID(Long.valueOf(buildPlane.getId()));
-            fillImageInfo(vo, planeViewUrl);
             vo.setPublishTime(time);
+
+            result.add(vo);
         }
 
         return result;
@@ -613,42 +661,141 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
     private void checkFileSize(FloorPlaneVO vo, String filePath) {
         Assert.notBlank(filePath, "文件路径不能为空");
 
-        long size = FileUtil.size(new File(filePath));
-        if (size > MAX_FILE_SIZE_BYTES) {
-            double sizeMB = size / 1024.0 / 1024.0;
-            throw new BusinessException(
-                    StrUtil.format("楼层平面图大小超过{}MB!当前:{:.2f}MB", MAX_FILE_SIZE_MB, sizeMB)
-            );
+        byte[] fileBytes;
+
+        if (filePath.startsWith("http://") || filePath.startsWith("https://")) {
+            fileBytes = downloadFileFromUrl(filePath);
+        } else {
+            File localFile = new File(filePath);
+            if (!localFile.exists()) {
+                log.warn("本地文件不存在: {}", filePath);
+                throw new BusinessException("楼层平面图文件不存在:" + filePath);
+            }
+
+            long size = FileUtil.size(localFile);
+            if (size > MAX_FILE_SIZE_BYTES) {
+                double sizeMB = size / 1024.0 / 1024.0;
+                throw new BusinessException(
+                        StrUtil.format("楼层平面图大小超过{}MB!当前:{:.2f}MB", MAX_FILE_SIZE_MB, sizeMB)
+                );
+            }
+            fileBytes = FileUtil.readBytes(localFile);
+        }
+
+        vo.setFloorFile(fileBytes);
+    }
+
+    private byte[] downloadFileFromUrl(String fileUrl) {
+        try {
+            String encodedUrl = encodeUrl(fileUrl);
+            URL url = new URL(encodedUrl);
+            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+            conn.setRequestMethod("GET");
+            conn.setConnectTimeout(5000);
+            conn.setReadTimeout(10000);
+            conn.setRequestProperty("User-Agent", "Mozilla/5.0");
+
+            int responseCode = conn.getResponseCode();
+            if (responseCode != HttpURLConnection.HTTP_OK) {
+                log.error("下载文件失败,HTTP状态码: {}, URL: {}", responseCode, fileUrl);
+                throw new BusinessException("下载楼层平面图失败,服务器返回状态码:" + responseCode);
+            }
+
+            int contentLength = conn.getContentLength();
+            if (contentLength > MAX_FILE_SIZE_BYTES) {
+                double sizeMB = contentLength / 1024.0 / 1024.0;
+                throw new BusinessException(
+                        StrUtil.format("楼层平面图大小超过{}MB!当前:{:.2f}MB", MAX_FILE_SIZE_MB, sizeMB)
+                );
+            }
+
+            try (InputStream inputStream = conn.getInputStream()) {
+                return IoUtil.readBytes(inputStream);
+            }
+        } catch (BusinessException e) {
+            throw e;
+        } catch (IOException e) {
+            log.error("从URL下载文件失败: {}", fileUrl, e);
+            throw new BusinessException("下载楼层平面图失败:" + e.getMessage());
         }
-        vo.setFloorFile(FileUtil.readBytes(filePath));
     }
 
-    // 获取图片信息
+    private String encodeUrl(String url) throws UnsupportedEncodingException {
+        if (StrUtil.isBlank(url)) {
+            return url;
+        }
+
+        if (!(url.startsWith("http://") || url.startsWith("https://"))) {
+            return url;
+        }
+
+        int protocolEnd = url.indexOf("://");
+        String protocol = url.substring(0, protocolEnd + 3);
+        String rest = url.substring(protocolEnd + 3);
+
+        int firstSlash = rest.indexOf("/");
+        if (firstSlash == -1) {
+            return url;
+        }
+
+        String hostAndPort = rest.substring(0, firstSlash);
+        String path = rest.substring(firstSlash);
+
+        String[] pathSegments = path.split("/");
+        StringBuilder encodedPath = new StringBuilder();
+        for (int i = 0; i < pathSegments.length; i++) {
+            if (i > 0) {
+                encodedPath.append("/");
+            }
+            encodedPath.append(URLEncoder.encode(pathSegments[i], "UTF-8"));
+        }
+
+        return protocol + hostAndPort + encodedPath.toString();
+    }
+
     private void fillImageInfo(FloorPlaneVO vo, String imageUrl) {
         if (StrUtil.isBlank(imageUrl)) {
             return;
         }
 
-        // 提取文件名信息
         String fileName = FileUtil.getName(imageUrl);
         vo.setFloorFileName(FileUtil.mainName(fileName));
         vo.setFloorFileSuffix(FileUtil.extName(fileName));
 
-        // 读取像素尺寸(带超时控制)
         try {
-            URLConnection conn = new URL(imageUrl).openConnection();
-            conn.setConnectTimeout(3000);
-            conn.setReadTimeout(5000);
-
-            try (InputStream in = conn.getInputStream()) {
-                BufferedImage image = ImageIO.read(in);
-                if (image != null) {
-                    vo.setFilePixWidth(image.getWidth());
-                    vo.setFilePixHeight(image.getHeight());
+            BufferedImage image;
+            if (imageUrl.startsWith("http://") || imageUrl.startsWith("https://")) {
+                String encodedUrl = encodeUrl(imageUrl);
+                URL url = new URL(encodedUrl);
+                URLConnection conn = url.openConnection();
+                conn.setConnectTimeout(3000);
+                conn.setReadTimeout(5000);
+
+                try (InputStream in = conn.getInputStream()) {
+                    image = ImageIO.read(in);
                 }
+            } else {
+                File imageFile = new File(imageUrl);
+                if (imageFile.exists()) {
+                    image = ImageIO.read(imageFile);
+                } else {
+                    log.warn("图片文件不存在: {}", imageUrl);
+                    vo.setFilePixWidth(7016);
+                    vo.setFilePixHeight(9933);
+                    return;
+                }
+            }
+
+            if (image != null) {
+                vo.setFilePixWidth(image.getWidth());
+                vo.setFilePixHeight(image.getHeight());
+            } else {
+                log.warn("无法读取图片尺寸: {}", imageUrl);
+                vo.setFilePixWidth(7016);
+                vo.setFilePixHeight(9933);
             }
         } catch (IOException e) {
-            log.error("获取图片尺寸失败: {}", imageUrl);
+            log.error("获取图片尺寸失败: {}", imageUrl, e);
             vo.setFilePixWidth(7016);
             vo.setFilePixHeight(9933);
         }

+ 358 - 29
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java

@@ -2,20 +2,14 @@ package com.usky.cdi.service.impl;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.alibaba.nacos.shaded.com.google.gson.Gson;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
-import com.usky.cdi.domain.CdiDefenseProject;
-import com.usky.cdi.domain.CdiDeliveryLog;
-import com.usky.cdi.domain.DmpDevice;
-import com.usky.cdi.domain.DmpProduct;
-import com.usky.cdi.mapper.CdiDefenseProjectMapper;
-import com.usky.cdi.mapper.CdiDeliveryLogMapper;
-import com.usky.cdi.mapper.DmpDeviceMapper;
-import com.usky.cdi.mapper.DmpProductMapper;
-import com.usky.cdi.service.CdiDeliveryLogService;
-import com.usky.cdi.service.config.mqtt.MqttOutConfig;
+import com.usky.cdi.domain.*;
+import com.usky.cdi.mapper.*;
 import com.usky.cdi.service.enums.MqttTopics;
 import com.usky.cdi.service.mqtt.MqttConnectionTool;
+import com.usky.cdi.service.util.AirDefenseSimulator;
 import com.usky.cdi.service.util.DeviceDataQuery;
 import com.usky.cdi.service.util.SnowflakeIdGenerator;
 import com.usky.cdi.service.vo.IotDataTransferVO;
@@ -39,6 +33,7 @@ import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.stream.Collectors;
 
 /**
@@ -50,8 +45,6 @@ import java.util.stream.Collectors;
 @Service
 public class IotDataTransferService {
 
-    private MqttOutConfig.MqttGateway mqttGateway;
-
     @Autowired
     private MqttConnectionTool mqttConnectionTool;
 
@@ -96,6 +89,9 @@ public class IotDataTransferService {
     @Autowired
     private CdiDeliveryLogMapper cdiDeliveryLogMapper;
 
+    @Autowired
+    private BaseBuildUnitMapper baseBuildUnitMapper;
+
     @PostConstruct
     public void init() {
         this.idGenerator = new SnowflakeIdGenerator(workerId, dataCenterId);
@@ -164,6 +160,9 @@ public class IotDataTransferService {
                     continue;
                 }
 
+                // 为每条数据的监测时间添加毫秒级微差
+                dataEndTime = addTimeOffset(dataEndTime);
+
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
                 Integer value = deviceDataItem.getInteger("leach_status");
                 if (value == null) {
@@ -181,6 +180,7 @@ public class IotDataTransferService {
                 vo.setDataEndTime(dataEndTime);
 
                 try {
+                    log.info("【水浸数据】开始推送,设备:{},数据:{}", deviceId, JSON.toJSONString(vo));
                     sendMqttMessage(MqttTopics.IotInfo.WATER_LEAK.getTopic(), vo, MqttTopics.IotInfo.WATER_LEAK.getDesc(), transferVO.getUsername());
                     result.put("successCount", result.get("successCount") + 1);
                 } catch (Exception e) {
@@ -242,6 +242,7 @@ public class IotDataTransferService {
 
             Long engineeringId = transferVO.getEngineeringId();
 
+            int skippedNullField = 0;
             for (JSONObject deviceDataItem : deviceData) {
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
                 LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
@@ -251,6 +252,35 @@ public class IotDataTransferService {
                     continue;
                 }
 
+                // 为每条数据的监测时间添加毫秒级微差
+                dataEndTime = addTimeOffset(dataEndTime);
+
+                // 检查业务字段是否为空,为空则计入失败(修复:原逻辑静默跳过并误计为成功)
+                boolean fieldMissing = false;
+                switch (deviceType) {
+                    case 707:
+                        if (deviceDataItem.getFloat("wd") == null) fieldMissing = true;
+                        break;
+                    case 708:
+                        if (deviceDataItem.getFloat("sd") == null) fieldMissing = true;
+                        break;
+                    case 709:
+                        if (deviceDataItem.getFloat("o2") == null) fieldMissing = true;
+                        break;
+                    case 710:
+                        if (deviceDataItem.getFloat("co2") == null) fieldMissing = true;
+                        break;
+                    case 711:
+                        if (deviceDataItem.getFloat("co") == null) fieldMissing = true;
+                        break;
+                }
+                if (fieldMissing) {
+                    log.warn("设备{}[类型{}]的业务字段数据为空,跳过推送,计入失败", deviceId, deviceType);
+                    result.put("failureCount", result.get("failureCount") + 1);
+                    skippedNullField++;
+                    continue;
+                }
+
                 boolean deviceSuccess = true;
                 try {
                     switch (deviceType) {
@@ -271,7 +301,7 @@ public class IotDataTransferService {
                             break;
                     }
                 } catch (Exception e) {
-                    log.warn("设备{}推送失败:{}", deviceId, e.getMessage());
+                    log.warn("设备{}[类型{}]推送异常:{}", deviceId, deviceType, e.getMessage(), e);
                     deviceSuccess = false;
                 }
 
@@ -282,6 +312,10 @@ public class IotDataTransferService {
                 }
             }
 
+            if (skippedNullField > 0) {
+                log.warn("[类型{}] 因业务字段为空而跳过的设备数量:{}", deviceType, skippedNullField);
+            }
+
             int success = result.get("successCount");
             int failure = result.get("failureCount");
             int notSynced = totalDevices - success - failure;
@@ -360,6 +394,88 @@ public class IotDataTransferService {
         return cdiDefenseProject.getTenantId();
     }
 
+    /**
+     * 发送人员统计情况(703)-掩蔽人数
+     *
+     * @return 推送结果,包含成功数和失败数
+     **/
+    public Map<String, Integer> sendPersonCount(IotDataTransferVO transferVO) {
+        Map<String, Integer> result = new HashMap<>();
+        result.put("successCount", 0);
+        result.put("failureCount", 0);
+
+        if (!validateMqttGateway(transferVO.getUsername())) {
+            return result;
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        long startTime = System.currentTimeMillis();
+
+        // 模拟人员统计
+        log.info("开始执行【人员统计】数据推送,工程ID:{}", transferVO.getEngineeringId());
+        try {
+            LambdaQueryWrapper<BaseBuildUnit> buildUnitQuery = new LambdaQueryWrapper<>();
+            buildUnitQuery.eq(BaseBuildUnit::getTenantId, transferVO.getTenantId());
+            List<BaseBuildUnit> buildUnitList = baseBuildUnitMapper.selectList(buildUnitQuery);
+
+            // 修复3:单元为空,打印日志+标记失败,不静默返回
+            if (buildUnitList.isEmpty()) {
+                log.warn("【人员统计】未查询到建筑单元,租户ID:{},推送终止", transferVO.getTenantId());
+                result.put("failureCount", 1);
+                return result;
+            }
+
+            log.info("【人员统计】获取到建筑单元数量:{}", buildUnitList.size());
+            for (BaseBuildUnit buildUnit : buildUnitList) {
+                int peopleCount = AirDefenseSimulator.calculatePeopleCount(now);
+                HeadcountVO headcountVO = new HeadcountVO();
+                headcountVO.setDataPacketID(generateDataPacketID());
+                headcountVO.setEngineeringID(transferVO.getEngineeringId());
+                headcountVO.setSensorValue(peopleCount);
+                // headcountVO.setSensorID(buildUnit.getId());
+
+                // 为每条数据的监测时间添加毫秒级微差
+                LocalDateTime dataEndTime = addTimeOffset(now);
+
+                headcountVO.setDataEndTime(dataEndTime);
+                headcountVO.setPublishTime(getCurrentTime());
+                headcountVO.setUnitName(buildUnit.getUnitName());
+                headcountVO.setFloor(buildUnit.getFloor());
+
+                try {
+                    log.info("【人员统计】开始推送,单元:{},数据:{}", buildUnit.getUnitName(), JSON.toJSONString(headcountVO));
+                    sendMqttMessage(
+                            MqttTopics.IotInfo.PERSON.getTopic(),
+                            headcountVO,
+                            MqttTopics.IotInfo.PERSON.getDesc(),
+                            transferVO.getUsername()
+                    );
+                    // 添加2S延时
+                    Thread.sleep(2000);
+                    result.put("successCount", result.get("successCount") + 1);
+                    log.info("【人员统计】推送成功,单元:{},人数:{}", buildUnit.getUnitName(), peopleCount);
+                } catch (Exception e) {
+                    log.warn("【人员统计】数据推送失败,单元:{},异常:{}", buildUnit.getUnitName(), e.getMessage());
+                    result.put("failureCount", result.get("failureCount") + 1);
+                }
+            }
+
+            // 修复4:统计推送完成,打印汇总日志
+            log.info("【人员统计】推送完成,成功:{},失败:{}", result.get("successCount"), result.get("failureCount"));
+            // 可选:和闯入一样保存汇总日志(推荐加上)
+            long endTime = System.currentTimeMillis();
+            saveLog(transferVO, now, startTime, endTime, buildUnitList.size(),
+                    result.get("successCount"), result.get("failureCount"), 0,
+                    result.get("successCount") > 0 ? 1 : 0, SecurityUtils.getUsername());
+
+        } catch (Exception e) {
+            // 修复5:全局异常兜底,绝不丢失
+            log.error("【人员统计】推送发生全局异常", e);
+            result.put("failureCount", result.getOrDefault("failureCount", 0) + 1);
+        }
+        return result;
+    }
+
     /**
      * 发送人员闯入情况(703)
      *
@@ -376,7 +492,6 @@ public class IotDataTransferService {
 
         LocalDateTime now = LocalDateTime.now();
         long startTime = System.currentTimeMillis();
-
         try {
             List<JSONObject> deviceData = deviceDataQuery.getDeviceData(transferVO);
             Integer deviceType = transferVO.getDeviceType();
@@ -407,6 +522,9 @@ public class IotDataTransferService {
                     continue;
                 }
 
+                // 处理数据时间
+                dataEndTime = addTimeOffset(dataEndTime);
+
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
 
                 PersonPresenceVO vo = new PersonPresenceVO();
@@ -418,6 +536,7 @@ public class IotDataTransferService {
                 vo.setSensorValue(0); // 固定值(根据业务需求)
 
                 try {
+                    log.info("【人员闯入】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(vo));
                     sendMqttMessage(
                             MqttTopics.IotInfo.PERSON_PRESENCE.getTopic(),
                             vo,
@@ -506,6 +625,7 @@ public class IotDataTransferService {
                     result.put("failureCount", result.get("failureCount") + 1);
                     continue;
                 }
+                dataEndTime = addTimeOffset(dataEndTime);
 
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
 
@@ -533,6 +653,7 @@ public class IotDataTransferService {
                 vo.setTotalPower(totalPower);
 
                 try {
+                    log.info("【人防用电负荷情况】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(vo));
                     sendMqttMessage(
                             MqttTopics.IotInfo.ELECTRICITY_LOAD.getTopic(),
                             vo,
@@ -576,7 +697,7 @@ public class IotDataTransferService {
     }
 
     /**
-     * 推送温度信息(701
+     * 推送温度信息(707
      *
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
@@ -597,11 +718,14 @@ public class IotDataTransferService {
         tempVO.setPublishTime(getCurrentTime());
         tempVO.setSensorValue(value);
         tempVO.setDataEndTime(dataEndTime);
+        System.out.println("监测时间:" + dataEndTime);
+
+        log.info("【温度】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(tempVO));
         sendMqttMessage(MqttTopics.IotInfo.TEMP.getTopic(), tempVO, MqttTopics.IotInfo.TEMP.getDesc(), username);
     }
 
     /**
-     * 推送湿度信息(702
+     * 推送湿度信息(708
      *
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
@@ -622,6 +746,8 @@ public class IotDataTransferService {
         humidityVO.setPublishTime(getCurrentTime());
         humidityVO.setSensorValue(value);
         humidityVO.setDataEndTime(dataEndTime);
+
+        log.info("【湿度】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(humidityVO));
         sendMqttMessage(MqttTopics.IotInfo.HUMIDITY.getTopic(), humidityVO, MqttTopics.IotInfo.HUMIDITY.getDesc(), username);
     }
 
@@ -647,11 +773,13 @@ public class IotDataTransferService {
         oxygenVO.setPublishTime(getCurrentTime());
         oxygenVO.setSensorValue(value);
         oxygenVO.setDataEndTime(dataEndTime);
+
+        log.info("【氧气浓度】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(oxygenVO));
         sendMqttMessage(MqttTopics.IotInfo.OXYGEN.getTopic(), oxygenVO, MqttTopics.IotInfo.OXYGEN.getDesc(), username);
     }
 
     /**
-     * 推送一氧化碳浓度信息(706
+     * 推送一氧化碳浓度信息(711
      *
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
@@ -672,11 +800,13 @@ public class IotDataTransferService {
         coVO.setPublishTime(getCurrentTime());
         coVO.setSensorValue(value);
         coVO.setDataEndTime(dataEndTime);
+
+        log.info("【一氧化碳浓度】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(coVO));
         sendMqttMessage(MqttTopics.IotInfo.CO.getTopic(), coVO, MqttTopics.IotInfo.CO.getDesc(), username);
     }
 
     /**
-     * 推送二氧化碳浓度信息(707
+     * 推送二氧化碳浓度信息(710)
      *
      * @param deviceDataItem 设备数据
      * @param deviceId 设备ID
@@ -695,8 +825,14 @@ public class IotDataTransferService {
         co2VO.setSensorID(deviceId);
         co2VO.setEngineeringID(engineeringID);
         co2VO.setPublishTime(getCurrentTime());
-        co2VO.setSensorValue(value);
+        // 将value除以10000并保留三位小数
+        Float processedValue = new java.math.BigDecimal(value / 10000f)
+                .setScale(3, java.math.RoundingMode.HALF_UP)
+                .floatValue();
+        co2VO.setSensorValue(processedValue);
         co2VO.setDataEndTime(dataEndTime);
+
+        log.info("【二氧化碳浓度】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(co2VO));
         sendMqttMessage(MqttTopics.IotInfo.CO2.getTopic(), co2VO, MqttTopics.IotInfo.CO2.getDesc(), username);
     }
 
@@ -884,6 +1020,7 @@ public class IotDataTransferService {
                     result.put("failureCount", result.get("failureCount") + 1);
                     continue;
                 }
+                dataEndTime = addTimeOffset(dataEndTime);
 
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
                 Integer value = deviceDataItem.getInteger("wy");
@@ -902,6 +1039,7 @@ public class IotDataTransferService {
                 vo.setSensorValue(value == 0 ? 0 : 1);
 
                 try {
+                    log.info("【位移数据】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(vo));
                     sendMqttMessage(MqttTopics.IotInfo.DEVIATION.getTopic(), vo, MqttTopics.IotInfo.DEVIATION.getDesc(), transferVO.getUsername());
                     result.put("successCount", result.get("successCount") + 1);
                 } catch (Exception e) {
@@ -929,6 +1067,93 @@ public class IotDataTransferService {
         }
     }
 
+    /**
+     * 发送液位数据(716)
+     *
+     * @return 推送结果,包含成功数和失败数
+     **/
+    public Map<String, Integer> sendWaterLevel(IotDataTransferVO transferVO) {
+        Map<String, Integer> result = new HashMap<>();
+        result.put("successCount", 0);
+        result.put("failureCount", 0);
+
+        if (!validateMqttGateway(transferVO.getUsername())) {
+            return result;
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        long startTime = System.currentTimeMillis();
+        long endTime;
+
+        List<JSONObject> deviceData = deviceDataQuery.getDeviceData(transferVO);
+        Integer deviceType = transferVO.getDeviceType();
+        Integer totalDevices = transferVO.getDevices().size();
+
+        try {
+
+            log.info("开始推送集水井水位数据,设备类型:{},设备数量:{},获取到的数据条数:{}",
+                    deviceType, totalDevices, deviceData.size());
+
+            if (deviceData.isEmpty()) {
+                log.warn("没有获取到位移数据!设备类型:{}", deviceType);
+                result.put("failureCount", totalDevices);
+                return result;
+            }
+
+            Long engineeringId = transferVO.getEngineeringId();
+            for (JSONObject deviceDataItem : deviceData) {
+                LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
+                if (dataEndTime == null) {
+                    result.put("failureCount", result.get("failureCount") + 1);
+                    continue;
+                }
+                dataEndTime = addTimeOffset(dataEndTime);
+
+                Integer deviceId = deviceDataItem.getIntValue("device_id");
+                Double value = deviceDataItem.getDouble("sensorValue");
+                if (value == null) {
+                    log.warn("设备{}的水位数据为空", deviceId);
+                    result.put("failureCount", result.get("failureCount") + 1);
+                    continue;
+                }
+
+                WaterLevelVO vo = new WaterLevelVO();
+                vo.setDataPacketID(generateDataPacketID());
+                vo.setSensorID(deviceId);
+                vo.setEngineeringID(engineeringId);
+                vo.setPublishTime(getCurrentTime());
+                vo.setDataEndTime(dataEndTime);
+                vo.setSensorValue(value);
+
+                try {
+                    log.info("【水位数据】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(vo));
+                    sendMqttMessage(MqttTopics.IotInfo.SEWAGE_LEVEL.getTopic(), vo, MqttTopics.IotInfo.SEWAGE_LEVEL.getDesc(), transferVO.getUsername());
+                    result.put("successCount", result.get("successCount") + 1);
+                } catch (Exception e) {
+                    log.warn("设备{}的水位数据推送失败:{}", deviceId, e.getMessage());
+                    result.put("failureCount", result.get("failureCount") + 1);
+                }
+            }
+
+            log.info("水位数据推送完成,设备类型:{},成功:{},失败:{}",
+                    deviceType, result.get("successCount"), result.get("failureCount"));
+
+            endTime = System.currentTimeMillis();
+            saveLog(transferVO, now, startTime, endTime, totalDevices,
+                    result.get("successCount"), result.get("failureCount"),
+                    totalDevices - result.get("successCount") - result.get("failureCount"), 1, SecurityUtils.getUsername());
+            return result;
+        } catch (Exception e) {
+            log.error("水位数据推送发生异常", e);
+            result.put("failureCount", transferVO.getDevices().size());
+            endTime = System.currentTimeMillis();
+            saveLog(transferVO, now, startTime, endTime, totalDevices,
+                    result.get("successCount"), result.get("failureCount"),
+                    totalDevices - result.get("successCount") - result.get("failureCount"), 0, SecurityUtils.getUsername());
+            return result;
+        }
+    }
+
     /**
      * 同步设备数据
      * @param tenantId 租户ID
@@ -970,6 +1195,7 @@ public class IotDataTransferService {
             transferVO.setDevices(devices);
             transferVO.setEngineeringId(engineeringId);
             transferVO.setUsername(username); // 保存当前任务的用户名
+            transferVO.setTenantId(tenantId);
             transferList.add(transferVO);
         });
 
@@ -1015,6 +1241,10 @@ public class IotDataTransferService {
                     result = sendWaterLeak(transferVO);
                     break;
                 case 703:
+                    if (transferVO.getEngineeringId() == 3101100024L) {
+                        result = sendPersonCount(transferVO);
+                        break;
+                    }
                     result = sendPersonPresence(transferVO);
                     break;
                 case 704:
@@ -1035,15 +1265,28 @@ public class IotDataTransferService {
                 case 714:
                     result = sendDeviationData(transferVO);
                     break;
-
+                case 716:
+                    result = sendWaterLevel(transferVO);
+                    break;
+                case 719:
+                    result = sendPersonCount(transferVO);
+                    break;
                 default:
                     log.debug("不支持的设备类型:{}", deviceType);
                     continue;
             }
 
             // 累加成功数和失败数
-            totalSuccessCount += result.getOrDefault("successCount", 0);
-            totalFailureCount += result.getOrDefault("failureCount", 0);
+            int typeSuccess = result.getOrDefault("successCount", 0);
+            int typeFailure = result.getOrDefault("failureCount", 0);
+            totalSuccessCount += typeSuccess;
+            totalFailureCount += typeFailure;
+
+            // 分类型诊断日志:精确定位每种类型的成功/失败/未同步
+            int typeTotal = transferVO.getDevices() != null ? transferVO.getDevices().size() : 0;
+            int typeNotSynced = typeTotal - typeSuccess - typeFailure;
+            log.info("[类型分诊] deviceType={} | 总设备={} | 成功={} | 失败={} | 未同步={}",
+                    deviceType, typeTotal, typeSuccess, typeFailure, typeNotSynced);
         }
 
         // 任务完成总结
@@ -1136,12 +1379,21 @@ public class IotDataTransferService {
      * @return 解析后的时间,如果解析失败返回null
      */
     private LocalDateTime parseDataTime(JSONObject deviceDataItem) {
-        // log.warn("解析的json{}", deviceDataItem.toString());
-        Long dataTime = deviceDataItem.getLong("realtime");
-        if (dataTime == null) {
-            log.warn("设备{}的time为空", deviceDataItem.getString("device_id"));
-            return null;
+        Object raw = deviceDataItem.get("realtime");
+        Long dataTime = null;
+        if (raw instanceof Long) {
+            dataTime = (Long) raw;
+        } else if (raw instanceof Integer) {
+            dataTime = ((Integer) raw).longValue();
+        } else if (raw instanceof Number) {
+            dataTime = ((Number) raw).longValue();
+        } else if (raw instanceof String) {
+            try {
+                dataTime = Long.parseLong((String) raw);
+            } catch (Exception ignored) {
+            }
         }
+        if (dataTime == null) return null;
         return LocalDateTime.ofInstant(Instant.ofEpochMilli(dataTime), ZoneId.systemDefault());
     }
 
@@ -1153,8 +1405,17 @@ public class IotDataTransferService {
      * @param username 用户名
      */
     void sendMqttMessage(String topic, Object vo, String messageType, String username) {
-        String json = JSON.toJSONString(vo);
-        // 不再记录每条数据的详情,只记录发送操作
+        String json;
+
+        // 针对楼层平面图特殊处理:将 byte[] 转为 Base64 字符串
+        if (vo instanceof com.usky.cdi.service.vo.info.FloorPlaneVO) {
+            json = serializeFloorPlaneVO((com.usky.cdi.service.vo.info.FloorPlaneVO) vo);
+        } else {
+            json = JSON.toJSONString(vo);
+        }
+
+        log.info("发送MQTT消息,Topic: {}, 消息类型: {}, JSON长度: {}", topic, messageType, json.length());
+
         MqttConnectionTool.MqttGateway gateway = mqttGatewayMap.get(username);
         if (gateway != null) {
             gateway.sendToMqtt(topic, json);
@@ -1163,8 +1424,76 @@ public class IotDataTransferService {
         }
     }
 
+    /**
+     * 序列化楼层平面图VO(将 floorFile byte[] 转为 Base64 字符串)
+     */
+    private String serializeFloorPlaneVO(com.usky.cdi.service.vo.info.FloorPlaneVO vo) {
+        com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
+
+        jsonObject.put("dataPacketID", vo.getDataPacketID());
+        jsonObject.put("engineeringID", vo.getEngineeringID());
+        jsonObject.put("floor", vo.getFloor());
+        jsonObject.put("floorFileID", vo.getFloorFileID());
+        jsonObject.put("floorFileName", vo.getFloorFileName());
+        jsonObject.put("floorFileSuffix", vo.getFloorFileSuffix());
+        jsonObject.put("filePixWidth", vo.getFilePixWidth());
+        jsonObject.put("filePixHeight", vo.getFilePixHeight());
+        jsonObject.put("publishTime", vo.getPublishTime());
+
+        // 关键:将 byte[] 转为 Base64 字符串
+        if (vo.getFloorFile() != null) {
+            String base64File = java.util.Base64.getEncoder().encodeToString(vo.getFloorFile());
+            jsonObject.put("floorFile", base64File);
+            log.info("平面图文件转换Base64成功,FileID: {}, 原始大小: {} bytes, Base64长度: {}",
+                    vo.getFloorFileID(), vo.getFloorFile().length, base64File.length());
+        } else {
+            jsonObject.put("floorFile", "");
+            log.warn("平面图文件为空,FileID: {}", vo.getFloorFileID());
+        }
+
+        Gson gson = new Gson();
+        return gson.toJson(jsonObject);
+    }
+
     public void allData(Long engineeringId, String username, String password) {
         Integer tenantId = 0;
         synchronizeDeviceData(tenantId, engineeringId, username, password);
     }
+
+    /**
+     * 为监测时间添加随机偏移(用于模拟真实采集场景)
+     * @param originalTime 原始监测时间
+     * @return 偏移后的时间
+     */
+    private LocalDateTime addTimeOffset(LocalDateTime originalTime) {
+        if (originalTime == null) {
+            return null;
+        }
+
+        // 获取当前时间作为上限
+        LocalDateTime now = LocalDateTime.now();
+
+        // 如果原始时间已经是未来时间,先修正为当前时间
+        if (originalTime.isAfter(now)) {
+            log.warn("检测到未来时间的监测数据,已修正为当前时间:{}", originalTime);
+            originalTime = now;
+        }
+
+        // 随机秒偏移:0、1、2 秒(向前偏移,使用减法)
+        int secondsOffset = ThreadLocalRandom.current().nextInt(0, 3);
+        // 随机毫秒偏移:0 ~ 999 毫秒(向前偏移,使用减法)
+        int millisOffset = ThreadLocalRandom.current().nextInt(0, 1000);
+
+        LocalDateTime offsetTime = originalTime.minusSeconds(secondsOffset)
+                .minusNanos(millisOffset * 1_000_000L);
+
+        // 最终校验:确保不会晚于当前时间
+        if (offsetTime.isAfter(now)) {
+            log.warn("偏移后时间仍然晚于当前时间,已修正:原始={}, 偏移后={}, 当前={}",
+                    originalTime, offsetTime, now);
+            return now;
+        }
+
+        return offsetTime;
+    }
 }

+ 381 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/AirDefenseSimulator.java

@@ -0,0 +1,381 @@
+package com.usky.cdi.service.util;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * 人防掩蔽单元人数模拟工具类(动态自适应版)
+ * <p>
+ * 核心特性:
+ * 1. 即时计算:根据当前时间动态返回合理总人数,无预生成/硬编码条数限制
+ * 2. 自适应调用管理器:跟踪调用频率,动态评估并自动扩容阈值
+ * 3. 分时段基准曲线:每时段独立配置基准人数与波动范围,支持平滑插值
+ * 4. 边界安全:防负数、防无限循环、防溢出
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/5/30
+ */
+public class AirDefenseSimulator {
+
+    // ===================== 可配置常量 =====================
+    private static final int MIN_PEOPLE = 0;                    // 最小人数(禁止负数)
+    private static final int DEFAULT_MAX_CALLS = 48;            // 默认最大调用次数(向下兼容)
+    private static final int MAX_EXPANSION_LIMIT = 288;         // 扩容上限(=24h*12次/5min,即5分钟粒度全天)
+    private static final double EXPANSION_FACTOR = 2.0;         // 每次扩容倍数
+    private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
+
+    // ===================== 自适应调用管理器(单例状态) =====================
+    /** 已调用次数计数器 */
+    private static final AtomicInteger callCount = new AtomicInteger(0);
+    /** 当前动态阈值(初始为默认值,可自动扩容) */
+    private static final AtomicInteger dynamicThreshold = new AtomicInteger(DEFAULT_MAX_CALLS);
+    /** 上次调用时间戳(用于检测异常高频调用) */
+    private static final AtomicLong lastCallTimestamp = new AtomicLong(0);
+    /** 是否已触发过扩容标志 */
+    private static volatile boolean expanded = false;
+
+    // ===================== 时段规则定义(基准人数 + 波动范围)=====================
+    /**
+     * 时段规则:[起始小时, 结束小时) → {基准人数, 最小波动, 最大波动}
+     * 基准人数代表该时段中心时刻的典型值,波动范围为±随机偏移量
+     */
+    private static final int[][] TIME_SLOT_RULES = {
+        //  起始H   结束H   基准   波动Min  波动Max   规则说明
+            {0,      5,      0,     0,       0},      // 规则1: 00:00-05:00  人数为0
+            {5,      6,      3,     2,       3},      // 规则2: 05:00-06:00  缓慢上升
+            {6,      8,      12,    4,       6},      // 规则3: 06:00-08:00  攀升至第一高点
+            {8,      9,      25,    2,       5},      // 规则3延:08:00-09:00 第一高点后短暂调整
+            {9,      11,     31,    2,       5},      // 规则4: 09:00-11:00  趋于稳定
+            {11,     12,     19,    1,       3},      // 规则5: 11:00-12:00  大量流出(就餐)
+            {12,     13,     12,    1,       3},      // 规则5延:12:00-13:00 午间低谷
+            {13,     14,     21,    3,       6},      // 规则6: 13:00-14:00  开始回流
+            {14,     15,     30,    4,       7},      // 规则7: 14:00-15:00  达到第二高点
+            {15,     19,     31,    2,       5},      // 规则8: 15:00-19:00  保持稳定
+            {19,     21,     18,    3,       6},      // 规则9: 19:00-21:00  持续流出
+            {21,     22,     12,    2,       5},      // 规则9延:21:00-22:00  继续清场
+            {22,     24,     6,     1,       3}       // 规则10:22:00-24:00  逐渐降至0
+    };
+
+    // ===================== 公开API:获取当前时刻掩蔽单元内的总人数 =====================
+
+    /**
+     * 【核心方法】根据当前系统时间,动态计算并返回掩蔽单元内应有的模拟总人数。
+     * <p>
+     * 无需预生成数据,即时根据24小时时间规则曲线计算出合理人数,
+     * 支持任意时间粒度的连续调用,通过自适应管理器自动扩容保护。
+     *
+     * @return 当前时刻的模拟人数(int, >= 0)
+     */
+    public static int getCurrentPeopleCount() {
+        return calculatePeopleCount(LocalDateTime.now());
+    }
+
+    /**
+     * 【核心方法-指定时间版本】根据指定时间,动态计算掩蔽单元内应有的模拟总人数。
+     *
+     * @param targetTime 目标时间
+     * @return 该时刻的模拟人数(int, >= 0)
+     */
+    public static int calculatePeopleCount(LocalDateTime targetTime) {
+        // 1. 自适应检查:是否需要扩容
+        adaptiveCheck();
+
+        int hour = targetTime.getHour();
+        int minute = targetTime.getMinute();
+
+        // 2. 匹配对应时段规则
+        int[] rule = matchTimeRule(hour);
+        int baseCount = rule[2];
+        int fluctuationMin = rule[3];
+        int fluctuationMax = rule[4];
+
+        // 3. 时段内线性插值:使同一小时内不同分钟的人数更平滑自然
+        // 特殊处理:基准为0的时段(如00:00-05:00无人期)不参与插值,强制保持0
+        double minuteRatio = minute / 60.0;
+        int interpolatedBase = (baseCount == 0) ? 0 : interpolateWithinHour(hour, baseCount, minuteRatio);
+
+        // 4. 叠加随机波动
+        int fluctuation = ThreadLocalRandom.current().nextInt(fluctuationMin, fluctuationMax + 1);
+        boolean positiveTrend = isInRisingPhase(hour);
+        int peopleCount = interpolatedBase + (positiveTrend ? fluctuation : -fluctuation);
+
+        // 5. 边界约束
+        peopleCount = Math.max(peopleCount, MIN_PEOPLE);
+
+        // 6. 记录调用
+        recordCall();
+
+        return peopleCount / 4;
+    }
+
+    // ===================== 兼容接口:生成全天模拟数据列表(动态条数)=====================
+
+    /**
+     * 生成当天全天的模拟数据列表。
+     * <p>
+     * 条数由自适应管理器的当前阈值决定(默认48条/半小时粒度),
+     * 当业务需求更高频时可自动扩容至更细粒度(如288条/5分钟粒度)。
+     *
+     * @return 模拟数据列表
+     */
+    public static List<PeopleFlowData> generateAllDayData() {
+        return generateAllDayData(dynamicThreshold.get());
+    }
+
+    /**
+     * 生成全天模拟数据列表,自定义数据条数。
+     * <p>
+     * 动态扩容策略:
+     * - requestedCount <= DEFAULT_MAX_CALLS(48): 正常生成,半小时粒度
+     * - 48 < requestedCount <= MAX_EXPANSION_LIMIT(288): 自动扩容,最小粒度5分钟
+     * - requestedCount > 288: 截断到上限,防止内存溢出
+     *
+     * @param requestedCount 请求数据条数
+     * @return 模拟数据列表(实际条数可能因边界截断而小于请求值)
+     */
+    public static List<PeopleFlowData> generateAllDayData(int requestedCount) {
+        // 边界约束:防止非法参数导致问题
+        int actualCount = normalizeRequestedCount(requestedCount);
+
+        List<PeopleFlowData> dataList = new ArrayList<>(actualCount);
+        long totalMinutes = 24 * 60;
+        long intervalMinutes = totalMinutes / actualCount;
+
+        LocalDateTime currentTime = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0);
+        int lastPeople = 0;
+
+        for (int i = 0; i < actualCount; i++) {
+            int currentPeople = calculatePeopleCount(currentTime);
+
+            // 反推流入流出(保持数据完整性)
+            int delta = currentPeople - lastPeople;
+            int inFlow, outFlow;
+            if (delta >= 0) {
+                inFlow = delta + ThreadLocalRandom.current().nextInt(0, 2);
+                outFlow = ThreadLocalRandom.current().nextInt(0, 2);
+            } else {
+                inFlow = ThreadLocalRandom.current().nextInt(0, 2);
+                outFlow = -delta + ThreadLocalRandom.current().nextInt(0, 2);
+            }
+
+            String timeStr = currentTime.format(TIME_FORMATTER);
+            dataList.add(new PeopleFlowData(timeStr, lastPeople, inFlow, outFlow, currentPeople));
+
+            lastPeople = currentPeople;
+            currentTime = currentTime.plusMinutes(intervalMinutes);
+        }
+        return dataList;
+    }
+
+    // ===================== 自适应调用管理器 =====================
+
+    /**
+     * 自适应检查:在每次调用前评估是否需要触发扩容
+     */
+    private static void adaptiveCheck() {
+        int calls = callCount.incrementAndGet();
+        int threshold = dynamicThreshold.get();
+
+        if (calls > threshold && !expanded) {
+            synchronized (AirDefenseSimulator.class) {
+                // 双检锁,防止并发重复扩容
+                if (calls > dynamicThreshold.get() && !expanded) {
+                    int newThreshold = expandThreshold(threshold);
+                    dynamicThreshold.set(newThreshold);
+                    expanded = true;
+                }
+            }
+        }
+    }
+
+    /**
+     * 动态扩容策略:按指数因子扩展阈值,但不超过硬性上限
+     *
+     * @param currentThreshold 当前阈值
+     * @return 扩容后的新阈值
+     */
+    private static int expandThreshold(int currentThreshold) {
+        int newThreshold = (int) Math.min(currentThreshold * EXPANSION_FACTOR, MAX_EXPANSION_LIMIT);
+        return Math.max(newThreshold, currentThreshold + 1); // 至少+1保证递增
+    }
+
+    /**
+     * 记录一次有效调用
+     */
+    private static void recordCall() {
+        lastCallTimestamp.set(System.currentTimeMillis());
+    }
+
+    /**
+     * 归置自适应管理器状态(测试/重启场景使用)
+     */
+    public static void resetAdaptiveManager() {
+        callCount.set(0);
+        dynamicThreshold.set(DEFAULT_MAX_CALLS);
+        lastCallTimestamp.set(0);
+        expanded = false;
+    }
+
+    // ===================== 管理器状态查询API =====================
+
+    /** 获取已调用次数 */
+    public static int getCallCount() { return callCount.get(); }
+
+    /** 获取当前动态阈值 */
+    public static int getDynamicThreshold() { return dynamicThreshold.get(); }
+
+    /** 获取扩容上限 */
+    public static int getMaxExpansionLimit() { return MAX_EXPANSION_LIMIT; }
+
+    /** 是否已触发过扩容 */
+    public static boolean isExpanded() { return expanded; }
+
+    // ===================== 私有工具方法 =====================
+
+    /**
+     * 根据小时数匹配对应的时段规则
+     */
+    private static int[] matchTimeRule(int hour) {
+        for (int[] rule : TIME_SLOT_RULES) {
+            if (hour >= rule[0] && hour < rule[1]) {
+                return rule;
+            }
+        }
+        // 兜底:返回午夜规则
+        return TIME_SLOT_RULES[0];
+    }
+
+    /**
+     * 判断当前时段是否处于人数上升期(用于决定波动的正负方向)
+     */
+    private static boolean isInRisingPhase(int hour) {
+        return (hour >= 5 && hour < 9) || (hour >= 13 && hour < 15);
+    }
+
+    /**
+     * 时段内线性插值:根据分钟位置微调基准值,实现同小时内平滑过渡
+     * <p>
+     * 例如:6:00基准15人→8:00基准30人,6:30应约为22人而非突变为30人
+     */
+    private static int interpolateWithinHour(int hour, int baseCount, double minuteRatio) {
+        int[] currentRule = matchTimeRule(hour);
+        int nextHour = (hour + 1) % 24;
+        int[] nextRule = matchTimeRule(nextHour);
+        int nextBase = nextRule[2];
+
+        // 线性插值:当前基准 + (下一基准-当前基准) * 分钟占比
+        int interpolated = (int) Math.round(baseCount + (nextBase - baseCount) * minuteRatio * 0.3);
+        return Math.max(interpolated, MIN_PEOPLE);
+    }
+
+    /**
+     * 规范化请求数据条数:边界条件处理,防止越界
+     */
+    private static int normalizeRequestedCount(int requestedCount) {
+        if (requestedCount <= 0) {
+            return DEFAULT_MAX_CALLS; // 非法值回退默认
+        }
+        if (requestedCount > MAX_EXPANSION_LIMIT) {
+            return MAX_EXPANSION_LIMIT; // 截断到上限
+        }
+        return requestedCount;
+    }
+
+    // ===================== 测试主方法 =====================
+    public static void main(String[] args) {
+        System.out.println("========== 自适应人防掩蔽单元人数模拟器 ==========");
+        System.out.println("初始阈值: " + DEFAULT_MAX_CALLS + ", 上限: " + MAX_EXPANSION_LIMIT);
+
+        // ========== 核心:60次模拟调用测试 ==========
+        test60Calls();
+    }
+
+    /**
+     * 模拟60次调用 calculatePeopleCount,每次传入不同的时间点,
+     * 验证自适应扩容机制和时段规则的正确性
+     */
+    private static void test60Calls() {
+        final int TOTAL_CALLS = 60;
+
+        // 重置管理器,确保从干净状态开始
+        resetAdaptiveManager();
+
+        System.out.println("\n========== 【60次模拟调用测试】开始 ==========");
+        System.out.printf("初始状态 → 阈值:%d, 已调用:%d, 已扩容:%s%n",
+                getDynamicThreshold(), getCallCount(), isExpanded());
+        printSeparator(70);
+        System.out.printf("%-6s %-8s %-10s %-15s %-10s%n", "序号", "时间点", "返回人数", "时段规则描述", "当前阈值");
+        printSeparator(70);
+
+        LocalDateTime baseDate = LocalDateTime.now().toLocalDate().atStartOfDay();
+
+        for (int i = 0; i < TOTAL_CALLS; i++) {
+            // 每24分钟推进一次(60次 × 24min = 1440min = 24小时,刚好覆盖全天)
+            int totalMinutesElapsed = i * 24;
+            int hour = (totalMinutesElapsed / 60) % 24;
+            int minute = totalMinutesElapsed % 60;
+
+            LocalDateTime targetTime = baseDate.plusMinutes(totalMinutesElapsed);
+            int peopleCount = calculatePeopleCount(targetTime);
+
+            // 获取时段描述
+            String timeSlotDesc = getTimeSlotDescription(hour);
+            String timeStr = String.format("%02d:%02d", hour, minute);
+
+            System.out.printf("%-6d %-8s %-10d %-15s %-10d%n",
+                    i + 1, timeStr, peopleCount, timeSlotDesc, getDynamicThreshold());
+        }
+
+        printSeparator(70);
+        System.out.printf("最终状态 → 总调用:%d, 阈值:%d, 已扩容:%b%n",
+                getCallCount(), getDynamicThreshold(), isExpanded());
+
+        // 验证结论
+        if (getCallCount() >= TOTAL_CALLS) {
+            System.out.println("[PASS] 60次调用全部完成,自适应机制正常工作");
+        } else {
+            System.out.println("[WARN] 调用次数异常: 期望" + TOTAL_CALLS + ", 实际" + getCallCount());
+        }
+        if (getDynamicThreshold() > DEFAULT_MAX_CALLS) {
+            System.out.println("[PASS] 阈值已自动扩容: " + DEFAULT_MAX_CALLS + " → " + getDynamicThreshold()
+                    + "(因调用次数超过原阈值触发)");
+        }
+        System.out.println("========== 【60次模拟调用测试】结束 ==========");
+    }
+
+    /**
+     * Java8兼容:打印指定长度的分隔线(替代 String.repeat())
+     */
+    private static void printSeparator(int length) {
+        StringBuilder sb = new StringBuilder(length);
+        for (int i = 0; i < length; i++) {
+            sb.append('─');
+        }
+        System.out.println(sb.toString());
+    }
+
+    /**
+     * 根据小时数返回时段规则描述(用于测试输出)
+     */
+    private static String getTimeSlotDescription(int hour) {
+        if (hour < 5) return "00-05无人";
+        if (hour < 6) return "05-06缓升";
+        if (hour < 8) return "06-08第一高点";
+        if (hour < 9) return "08-09调整";
+        if (hour < 11) return "09-11稳定";
+        if (hour < 12) return "11-12就餐流出";
+        if (hour < 13) return "12-13午间低谷";
+        if (hour < 14) return "13-14回流";
+        if (hour < 15) return "14-15第二高点";
+        if (hour < 19) return "15-19稳定";
+        if (hour < 21) return "19-21持续流出";
+        if (hour < 22) return "21-22清场";
+        return "22-00降至0";
+    }
+}

+ 177 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/BasementClimateUtil.java

@@ -0,0 +1,177 @@
+package com.usky.cdi.service.util;
+
+import java.time.LocalDate;
+import java.time.Month;
+import java.util.Random;
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/4/23
+ */
+
+/**
+ * 上海地区 地下室温湿度常量 + 四季判断工具
+ */
+public class BasementClimateUtil {
+
+    private static final Random RANDOM = new Random();
+
+    // ====================== 春季(3-5月)======================
+    public static final double SPRING_TEMP_MIN = 16.0;
+    public static final double SPRING_TEMP_MAX = 20.0;
+    public static final double SPRING_HUMI_MIN = 70.0;
+    public static final double SPRING_HUMI_MAX = 74.0;
+
+    // ====================== 夏季(6-8月)======================
+    public static final double SUMMER_TEMP_MIN = 22.0;
+    public static final double SUMMER_TEMP_MAX = 26.0;
+    public static final double SUMMER_HUMI_MIN = 80.0;
+    public static final double SUMMER_HUMI_MAX = 84.0;
+
+    // ====================== 秋季(9-11月)=====================
+    public static final double AUTUMN_TEMP_MIN = 17.0;
+    public static final double AUTUMN_TEMP_MAX = 21.0;
+    public static final double AUTUMN_HUMI_MIN = 68.0;
+    public static final double AUTUMN_HUMI_MAX = 72.0;
+
+    // ====================== 冬季(12-1-2月)===================
+    public static final double WINTER_TEMP_MIN = 12.0;
+    public static final double WINTER_TEMP_MAX = 16.0;
+    public static final double WINTER_HUMI_MIN = 60.0;
+    public static final double WINTER_HUMI_MAX = 64.0;
+
+    /**
+     * 获取当前系统时间的季节
+     * 0=春 1=夏 2=秋 3=冬
+     */
+    public static int getSeason() {
+        return getSeason(LocalDate.now());
+    }
+
+    /**
+     * 根据日期判断季节(JDK8 标准写法)
+     */
+    public static int getSeason(LocalDate date) {
+        Month month = date.getMonth();
+        int monthValue = month.getValue();
+
+        if (monthValue >= 3 && monthValue <= 5) {
+            return 0; // 春
+        } else if (monthValue >= 6 && monthValue <= 8) {
+            return 1; // 夏
+        } else if (monthValue >= 9 && monthValue <= 11) {
+            return 2; // 秋
+        } else {
+            return 3; // 冬(12、1、2月)
+        }
+    }
+
+    /**
+     * 获取当前季节的温度范围 [最小值, 最大值]
+     */
+    public static double getCurrentTempRange() {
+        return randomBetween(getTempRange(getSeason())[0], getTempRange(getSeason())[1]);
+    }
+
+    /**
+     * 获取当前季节的湿度范围 [最小值, 最大值]
+     */
+    public static double getCurrentHumiRange() {
+        return randomBetween(getHumiRange(getSeason())[0], getHumiRange(getSeason())[1]);
+    }
+
+    /**
+     * 根据季节获取温度范围
+     */
+    public static double[] getTempRange(int season) {
+        if (season == 0) {
+            return new double[]{SPRING_TEMP_MIN, SPRING_TEMP_MAX};
+        } else if (season == 1) {
+            return new double[]{SUMMER_TEMP_MIN, SUMMER_TEMP_MAX};
+        } else if (season == 2) {
+            return new double[]{AUTUMN_TEMP_MIN, AUTUMN_TEMP_MAX};
+        } else if (season == 3) {
+            return new double[]{WINTER_TEMP_MIN, WINTER_TEMP_MAX};
+        } else {
+            throw new IllegalArgumentException("无效季节:" + season);
+        }
+    }
+
+    /**
+     * 根据季节获取湿度范围
+     */
+    public static double[] getHumiRange(int season) {
+        if (season == 0) {
+            return new double[]{SPRING_HUMI_MIN, SPRING_HUMI_MAX};
+        } else if (season == 1) {
+            return new double[]{SUMMER_HUMI_MIN, SUMMER_HUMI_MAX};
+        } else if (season == 2) {
+            return new double[]{AUTUMN_HUMI_MIN, AUTUMN_HUMI_MAX};
+        } else if (season == 3) {
+            return new double[]{WINTER_HUMI_MIN, WINTER_HUMI_MAX};
+        } else {
+            throw new IllegalArgumentException("无效季节:" + season);
+        }
+    }
+
+    /**
+     * 获取地下室相对室外的温度差值
+     * 负=地下室更冷,正=地下室更暖
+     */
+    public static double getTempDiffWithOutdoor() {
+        return getTempDiffWithOutdoor(getSeason());
+    }
+
+    /**
+     * 根据季节获取温度差值
+     */
+    public static double getTempDiffWithOutdoor(int season) {
+        // 所有区间范围都 < 2.0
+        switch (season) {
+            case 0: // 春:地下室略低
+                return randomBetween(-1.0, -0.5);
+            case 1: // 夏:地下室明显低
+                return randomBetween(-6.0, -5.5);
+            case 2: // 秋:低于室外
+                return randomBetween(-3.5, -3.0);
+            case 3: // 冬:地下室更高
+                return randomBetween(4.5, 5.0);
+            default:
+                return 0.0;
+        }
+    }
+
+    /**
+     * 获取地下室相对室外的湿度差值
+     */
+    public static double getHumiDiffWithOutdoor() {
+        return getHumiDiffWithOutdoor(getSeason());
+    }
+
+    /**
+     * 根据季节获取湿度差值
+     */
+    public static double getHumiDiffWithOutdoor(int season) {
+        switch (season) {
+            case 0:
+                return randomBetween(12.0, 13.0);
+            case 1:
+                return randomBetween(19.0, 20.0);
+            case 2:
+                return randomBetween(9.0, 10.0);
+            case 3:
+                return randomBetween(7.0, 8.0);
+            default:
+                return 0.0;
+        }
+    }
+
+    /**
+     * 生成 [min, max] 之间的随机小数,保留 2 位
+     */
+    private static double randomBetween(double min, double max) {
+        double val = min + (max - min) * RANDOM.nextDouble();
+        return Math.round(val * 100) / 100.0;
+    }
+}

+ 202 - 45
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataQuery.java

@@ -3,6 +3,7 @@ package com.usky.cdi.service.util;
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONArray;
 import com.alibaba.fastjson.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.usky.cdi.domain.DmpDevice;
 import com.usky.cdi.mapper.DmpDeviceMapper;
 import com.usky.cdi.service.config.DeviceFieldConfig;
@@ -11,7 +12,6 @@ import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
 import java.text.DecimalFormat;
@@ -40,9 +40,14 @@ public class DeviceDataQuery {
     @Autowired
     private DeviceFieldConfig deviceFieldConfig;
 
+    private static final Set<Integer> ALLOWED_DEVICE_TYPES = new HashSet<>(Arrays.asList(
+            711, 710, 709, 708, 707
+    ));
+
     // 定义各参数的格式化器(整数位,小数位)
     private static final DecimalFormat FORMAT_2_2 = new DecimalFormat("00.00"); // 2位整数+2位小数
     private static final DecimalFormat FORMAT_0_3 = new DecimalFormat("0.000"); // 0位整数+3位小数
+    private static final DecimalFormat FORMAT_1_2 = new DecimalFormat("0.00");  // 1位整数+2位小数
     private static final DecimalFormat FORMAT_3_2 = new DecimalFormat("000.00"); // 3位整数+2位小数
     private static final DecimalFormat FORMAT_4_2 = new DecimalFormat("0000.00"); // 4位整数+2位小数
 
@@ -79,6 +84,8 @@ public class DeviceDataQuery {
     private static final double FLOATING_RANGE_MAX = 0.5;
     private static final double FLOATING_RANGE_MIN1 = 0.0;
     private static final double FLOATING_RANGE_MAX1 = 1.0;
+    private static final double SEWAGE_LEVEL_MIN = 0.0;
+    private static final double SEWAGE_LEVEL_MAX = 0.1;
 
     private static int callCount = 0;
 
@@ -104,14 +111,56 @@ public class DeviceDataQuery {
 
             log.debug("请求设备数据接口,设备数量:{}", deviceUuids.size());
             String response = HttpClientUtils.doPostJson(baseUrl, requestBody.toJSONString());
-            log.warn("接口返回数据:{}", response);
+            log.info("接口返回数据:{}", response);
 
             List<JSONObject> resultList = parseResponseData(response, transferVO.getDeviceType(), transferVO.getDevices());
+            boolean useXiangyuMock = false;
+            List<JSONObject> xiangyuDataList = new ArrayList<>();
+
+            // 当租户数据为空时获取桃浦象屿相同设备数据进行模拟(修复版:不修改原始设备列表)
+            if (resultList.isEmpty() && ALLOWED_DEVICE_TYPES.contains(transferVO.getDeviceType())) {
+                LambdaQueryWrapper<DmpDevice> queryWrapper = new LambdaQueryWrapper<>();
+                queryWrapper.eq(DmpDevice::getTenantId, 1205)
+                        .eq(DmpDevice::getDeviceType, transferVO.getDeviceType())
+                        .orderByAsc(DmpDevice::getId)
+                        .last("LIMIT 1");
+                DmpDevice xiangyuDevice = dmpDeviceMapper.selectOne(queryWrapper);
+
+                if (xiangyuDevice != null) {
+                    JSONObject xiangyuReq = new JSONObject();
+                    xiangyuReq.put("deviceuuid", Collections.singletonList(xiangyuDevice.getDeviceUuid()));
+                    String xiangyuResp = HttpClientUtils.doPostJson(baseUrl, xiangyuReq.toJSONString());
+
+                    JSONObject responseObj = JSON.parseObject(xiangyuResp);
+                    // 修复:象屿接口可能返回null/空串/非JSON,导致responseObj为null引发NPE
+                    if (responseObj != null && responseObj.getJSONArray("data") != null && !responseObj.getJSONArray("data").isEmpty()) {
+                        JSONArray dataArray = responseObj.getJSONArray("data");
+                        JSONObject deviceData = dataArray.getJSONObject(0);
+                        JSONObject metrics = deviceData.getJSONObject("metrics");
+                        Object realtime = metrics.get("realtime");
+                        System.out.println("接口返回数据时间戳对象:" + realtime);
+                        Long timestamp = realtime instanceof Long ? (Long) realtime : null;
+                        System.out.println("接口返回数据时间戳:" + timestamp);
+
+                        if (timestamp != null && timestamp >= (System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000L)) {
+                            // 修复:不修改原始设备列表,直接生成模拟模板
+                            useXiangyuMock = true;
+                            xiangyuDataList = parseResponseData(xiangyuResp, transferVO.getDeviceType(),
+                                    Collections.singletonList(xiangyuDevice));
+                        }
+                    }
+                }
+            }
 
             // 若返回数据为空且开启模拟模式,则生成模拟数据
             if (resultList.isEmpty() && simulation) {
                 log.info("接口返回数据为空,生成模拟数据,设备类型:{}", transferVO.getDeviceType());
-                resultList = generateSimulationData(transferVO.getDeviceType(), transferVO.getDevices(), null);
+                if (useXiangyuMock && !xiangyuDataList.isEmpty()) {
+                    // 使用象屿数据作为模板生成全量模拟
+                    resultList = generateSimulationData(transferVO.getDeviceType(), transferVO.getDevices(), xiangyuDataList.get(0));
+                } else {
+                    resultList = generateSimulationData(transferVO.getDeviceType(), transferVO.getDevices(), null);
+                }
             } else if (resultList.size() < transferVO.getDevices().size()) {
                 log.warn("接口返回数据数量与请求数量不一致,设备类型:{}", transferVO.getDeviceType());
 
@@ -136,22 +185,22 @@ public class DeviceDataQuery {
                 List<DmpDevice> missingDevices = missingDeviceIds.stream()
                         .map(requestDeviceMap::get)
                         .collect(Collectors.toList());
-                List<JSONObject> missingSimulationData = generateSimulationData(transferVO.getDeviceType(), missingDevices, resultList.get(0));
-                // 将模拟数据与返回数据结合
-                resultList.addAll(missingSimulationData);
 
-                // 校验结合后的数据是否与请求的device_id一一对应
-                Set<String> combinedDeviceIds = resultList.stream()
-                        .map(data -> data.getString("device_id"))
-                        .filter(Objects::nonNull)
-                        .collect(Collectors.toSet());
+                JSONObject templateData = resultList.isEmpty() ?
+                        (xiangyuDataList.isEmpty() ? null : xiangyuDataList.get(0))
+                        : resultList.get(0);
 
-                if (combinedDeviceIds.size() != transferVO.getDevices().size()) {
-                    log.warn("数据整合后仍存在缺失,请求设备数量:{},返回设备数量:{}",
-                            transferVO.getDevices().size(), combinedDeviceIds.size());
-                } else {
-                    log.debug("数据整合完成,设备数量与请求一致:{}", combinedDeviceIds.size());
-                }
+                List<JSONObject> missingSimulationData = generateSimulationData(transferVO.getDeviceType(), missingDevices, templateData);
+                // 修复:不再清空原有数据
+                resultList.addAll(missingSimulationData);
+            }
+
+            // 最终校验
+            if (resultList.size() != transferVO.getDevices().size()) {
+                log.warn("数据整合后仍存在缺失,请求设备数量:{},返回设备数量:{}",
+                        transferVO.getDevices().size(), resultList.size());
+            } else {
+                log.debug("数据整合完成,设备数量与请求一致:{}", resultList.size());
             }
 
             return resultList;
@@ -231,8 +280,15 @@ public class DeviceDataQuery {
                 }
 
                 // 添加设备标识信息
-                targetData.put("deviceuuid", deviceUuid);
                 String deviceId = deviceUuidToIdMap.get(deviceUuid);
+
+                // 如果找不到,直接丢弃这条数据,不放入结果
+                if (deviceId == null) {
+                    continue;
+                }
+
+                // 能走到这里,说明一定有 deviceId
+                targetData.put("deviceuuid", deviceUuid);
                 targetData.put("device_id", deviceId);
 
                 if (hasValidData) {
@@ -287,10 +343,15 @@ public class DeviceDataQuery {
     private List<JSONObject> generateSimulationData(Integer deviceType, List<DmpDevice> devices, JSONObject standard) {
         List<JSONObject> simulationList = new ArrayList<>();
         long currentTime = System.currentTimeMillis();
+        // System.out.println("深拷贝当前时间戳:" + currentTime);
 
         // 获取天气数据(仅在需要时)
+        // 期望:只有“象屿模板为空/缺字段”时,才用天气数据对温湿度做修正;有象屿模板就不要再相加
+        boolean needWeatherForTemp = (deviceType != null && deviceType == 707) && (standard == null || standard.get("wd") == null);
+        boolean needWeatherForHumi = (deviceType != null && deviceType == 708) && (standard == null || standard.get("sd") == null);
+        boolean needWeatherForElectric = (deviceType != null && deviceType == 704) && (standard == null);
         Map<String, Double> weatherData = null;
-        if (standard == null && (deviceType == 707 || deviceType == 704)) {
+        if (needWeatherForTemp || needWeatherForHumi || needWeatherForElectric) {
             weatherData = WeatherFetcher.fetchWeather();
         }
 
@@ -298,18 +359,46 @@ public class DeviceDataQuery {
             JSONObject simData;
 
             if (standard != null) {
-                // ✅ 使用标准模板进行模拟(深拷贝)
-                simData = JSONObject.parseObject(standard.toJSONString());
+                simData = new JSONObject();
+                simData.putAll(standard);
+
+                // 【关键】先移除,确保 FastJSON 内部缓存失效
+                simData.remove("realtime");
+                simData.remove("device_id");
+                simData.remove("deviceuuid");
+
+                // 再重新 put,强制类型正确
                 simData.put("realtime", currentTime);
                 simData.put("device_id", device.getDeviceId());
-                // 可选:替换 device_uuid(如果模板中有)
-                if (device.getDeviceUuid() != null && simData.containsKey("device_uuid")) {
-                    simData.put("device_uuid", device.getDeviceUuid());
+                // System.out.println("深拷贝时间戳1:" + simData.getLong("realtime"));
+
+                if (device.getDeviceUuid() != null && standard.containsKey("deviceuuid")) {
+                    simData.put("deviceuuid", device.getDeviceUuid());
                 }
 
-                // 对数值字段添加微小扰动(±2% 或固定范围)
-                addNoiseToNumericFields(simData, deviceType);
+                // 若象屿模板里缺少温湿度字段,则再回退到天气+差值逻辑补齐(避免“所有情况都相加”)
+                if (deviceType != null && deviceType == 707 && simData.get("wd") == null) {
+                    double temp707;
+                    if (weatherData != null && !weatherData.isEmpty()) {
+                        double floating = BasementClimateUtil.getTempDiffWithOutdoor();
+                        temp707 = weatherData.get("temperature") + floating;
+                    } else {
+                        temp707 = BasementClimateUtil.getCurrentTempRange();
+                    }
+                    simData.put("wd", temp707);
+                } else if (deviceType != null && deviceType == 708 && simData.get("sd") == null) {
+                    double humi708;
+                    if (weatherData != null && !weatherData.isEmpty()) {
+                        double floating = BasementClimateUtil.getHumiDiffWithOutdoor();
+                        humi708 = weatherData.get("humidity") + floating;
+                    } else {
+                        humi708 = BasementClimateUtil.getCurrentHumiRange();
+                    }
+                    simData.put("sd", humi708);
+                }
 
+                addNoiseToNumericFields(simData, deviceType);
+                // System.out.println("深拷贝时间戳2:" + simData.getLong("realtime"));
             } else {
                 // ❌ 无标准模板,走原始随机逻辑
                 simData = new JSONObject();
@@ -320,16 +409,23 @@ public class DeviceDataQuery {
                     case 707:
                         double temp707 = 0.0;
                         if (weatherData != null && !weatherData.isEmpty()) {
-                            double floating = ThreadLocalRandom.current().nextDouble(FLOATING_RANGE_MIN, FLOATING_RANGE_MAX);
-                            temp707 = weatherData.get("temperature") + 0.5 + floating;
+                            double floating = BasementClimateUtil.getTempDiffWithOutdoor();
+                            temp707 = weatherData.get("temperature") + floating;
                         } else {
-                            temp707 = ThreadLocalRandom.current().nextDouble(TEMP_RANGE_MIN, TEMP_RANGE_MAX);
+                            temp707 = BasementClimateUtil.getCurrentTempRange();
                         }
-                        simData.put("wd", formatNumber(temp707, FORMAT_2_2));
+                        simData.put("wd", temp707);
                         break;
 
                     case 708:
-                        simData.put("sd", formatNumber(ThreadLocalRandom.current().nextDouble(HUMIDITY_RANGE_MIN, HUMIDITY_RANGE_MAX), FORMAT_2_2));
+                        double humi708 = 0.0;
+                        if (weatherData != null && !weatherData.isEmpty()) {
+                            double floating = BasementClimateUtil.getHumiDiffWithOutdoor();
+                            humi708 = weatherData.get("humidity") + floating;
+                        } else {
+                            humi708 = BasementClimateUtil.getCurrentHumiRange();
+                        }
+                        simData.put("sd", humi708);
                         break;
 
                     case 709:
@@ -355,6 +451,7 @@ public class DeviceDataQuery {
                         break;
 
                     case 704:
+                    case 705:
                         double aVoltage = ThreadLocalRandom.current().nextDouble(VOLTAGE_RANGE_MIN, VOLTAGE_RANGE_MAX);
                         simData.put("aVoltage", formatNumber(aVoltage, FORMAT_3_2));
                         double bVoltage = ThreadLocalRandom.current().nextDouble(VOLTAGE_RANGE_MIN, VOLTAGE_RANGE_MAX);
@@ -399,6 +496,12 @@ public class DeviceDataQuery {
                         simData.put("wy", 0);
                         break;
 
+                    case 716:
+                        String deviceId1 = device.getDeviceId();
+                        double value = FixedWaterLevelGenerator.getSensorValue(deviceId1);
+                        simData.put("sensorValue", formatNumber(value, FORMAT_1_2));
+                        break;
+
                     default:
                         log.warn("未知设备类型:{},无法生成模拟数据", deviceType);
                         continue;
@@ -413,33 +516,87 @@ public class DeviceDataQuery {
     }
 
     private void addNoiseToNumericFields(JSONObject data, Integer deviceType) {
-        // 定义各字段的噪声比例或范围(可根据设备类型定制)
         Map<String, Double> noiseConfig = getNoiseConfigByDeviceType(deviceType);
 
+        // 这些字段永远不加噪
+        Set<String> skipFields = new HashSet<>(Arrays.asList("realtime", "device_id", "deviceuuid"));
+
         for (String key : data.keySet()) {
+            if (skipFields.contains(key)) continue;
+            if (isDiscreteField(key)) continue;
+
             Object value = data.get(key);
-            if (value instanceof Number) {
-                double original = ((Number) value).doubleValue();
-                // 默认 ±2%
-                double noiseFactor = noiseConfig.getOrDefault(key, 0.02);
+            Double original = null;
 
-                // 避免对状态类字段(如 0/1)加噪
-                if (isDiscreteField(key)) {
-                    continue;
+            // 【关键】支持 Number 和 String 两种类型
+            if (value instanceof Number) {
+                original = ((Number) value).doubleValue();
+            } else if (value instanceof String) {
+                try {
+                    original = Double.parseDouble((String) value);
+                } catch (NumberFormatException e) {
+                    System.out.println("跳过字段(非数字): " + key + " = " + value);
+                    continue; // 不是数字字符串,跳过
                 }
+            }
 
-                double noise = original * noiseFactor * (ThreadLocalRandom.current().nextDouble(-1, 1));
-                double newValue = original + noise;
+            if (original == null) continue;
 
-                // 根据原格式保留小数位(这里简化处理,也可记录原格式)
+            // 每个设备、每个字段、每次调用都是独立的随机噪声 ✅
+            double noiseFactor = noiseConfig.getOrDefault(key, 0.02);
+            double noise = original * noiseFactor * (ThreadLocalRandom.current().nextDouble(-1, 1));
+            double newValue = original + noise;
+
+            System.out.println("加噪: " + key + " | 原值=" + original + " | 噪声=" + noise + " | 新值=" + newValue);
+            log.info("加噪: {} | 原值={} | 噪声={} | 新值={}", key, original, noise, newValue);
+
+            // 保持原类型:原来是 String 就格式化回 String,原来是 Number 就放 Double
+            if (value instanceof String) {
+                data.put(key, formatNumber(newValue, getFormatByKey(key)));
+            } else {
                 data.put(key, newValue);
             }
         }
     }
 
+    /**
+     * 根据字段名获取对应的格式化器
+     */
+    private DecimalFormat getFormatByKey(String key) {
+        switch (key) {
+            case "aVoltage":
+            case "bVoltage":
+            case "cVoltage":
+            case "aElectricity":
+            case "bElectricity":
+            case "cElectricity":
+                return FORMAT_3_2;
+            case "totalPower":
+                return FORMAT_4_2;
+            case "line1TEMP":
+            case "Line2TEMP":
+            case "Line3TEMP":
+            case "Line4TEMP":
+            case "wd":
+            case "sd":
+            case "o2":
+                return FORMAT_2_2;
+            case "co2":
+                return FORMAT_0_3;
+            case "leakageCurrent":
+                return FORMAT_4_2;
+            case "sensorValue":
+                return FORMAT_1_2;
+            default:
+                return null; // 不格式化,保持原样
+        }
+    }
+
     private boolean isDiscreteField(String field) {
-        // 这些字段是状态码,不应加噪
-        return field.equalsIgnoreCase("leach_status")
+        return field.equalsIgnoreCase("realtime")
+                || field.equalsIgnoreCase("device_id")
+                || field.equalsIgnoreCase("deviceuuid")
+                || field.equalsIgnoreCase("leach_status")
                 || field.equalsIgnoreCase("sensorValue")
                 || field.equalsIgnoreCase("qx")
                 || field.equalsIgnoreCase("cd")
@@ -452,7 +609,7 @@ public class DeviceDataQuery {
         Map<String, Double> config = new HashMap<>();
         switch (deviceType) {
             case 707: // 温度
-                config.put("wd", 0.03); // ±3%
+                config.put("wd", 0.03); //
                 break;
             case 708: // 湿度
                 config.put("sd", 0.05);

+ 5 - 5
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataSyncService.java

@@ -26,12 +26,12 @@ public class DeviceDataSyncService {
      * fixedDelay:任务执行完成后固定延迟29分钟执行下一次
      * initialDelay:初始化后立即执行第一次任务
      */
-    // @Scheduled(fixedDelay = 26 * 60 * 1000, initialDelay = 0)
+    // @Scheduled(fixedDelay = 10 * 60 * 1000, initialDelay = 0)
     // public void scheduledDeviceDataSync() {
-    //     Integer tenantId = 1205;
-    //     Long engineeringId = 3101070011L;
-    //     String username = "3101070011";
-    //     String password = "5RqhJ7VG";
+    //     Integer tenantId = 1222;
+    //     Long engineeringId = 3101100021L;
+    //     String username = "3101100021";
+    //     String password = "SIixzph1";
     //     log.info("开始执行桃浦象屿人防设备数据同步定时任务,租户ID:{},工程ID:{}", tenantId, engineeringId);
     //
     //     try {

+ 49 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/FixedWaterLevelGenerator.java

@@ -0,0 +1,49 @@
+package com.usky.cdi.service.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Random;
+
+/**
+ * 固定水位生成器 - 为液位传感器(716)生成模拟数据
+ * <p>
+ * 修复说明:
+ * 1. HashMap 改为 ConcurrentHashMap,解决并发安全问题
+ * 2. 移除 containsValue 死循环风险,改用基于设备ID哈希的确定性算法,
+ *    保证每个设备获得唯一且稳定的水位值,不受设备数量限制
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/4/28
+ */
+
+/**
+ * 固定水位值生成器:
+ * 1. 每台设备首次调用时基于设备ID生成一个随机水位值
+ * 2. 同一设备后续调用直接取缓存,保持一致
+ * 3. 不同设备之间数值随机分散
+ */
+@Slf4j
+public class FixedWaterLevelGenerator {
+
+    private static final Map<String, Double> DEVICE_LEVEL_MAP = new ConcurrentHashMap<>();
+
+    private static final double MIN_VALUE = 0.02;
+    private static final double MAX_VALUE = 0.05;
+
+    public static double getSensorValue(String deviceId) {
+        return DEVICE_LEVEL_MAP.computeIfAbsent(deviceId, FixedWaterLevelGenerator::computeFixedValue);
+    }
+
+    private static double computeFixedValue(String deviceId) {
+        // 乘以一个大的质数,打乱原本连续ID的哈希值分布
+        long seed = (deviceId.hashCode() * 2654435761L) & 0xFFFFFFFFL;
+        Random random = new Random(seed);
+
+        double value = MIN_VALUE + random.nextDouble() * (MAX_VALUE - MIN_VALUE);
+        log.info("生成设备[{}]的固定水位值:{}", deviceId, value);
+        return Math.round(value * 1000) / 1000.0;
+    }
+}

+ 32 - 4
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/HttpClientUtils.java

@@ -20,6 +20,7 @@ import java.net.URI;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 
 @Slf4j
 public class HttpClientUtils {
@@ -111,7 +112,9 @@ public class HttpClientUtils {
     }
 
     public static String doPostJson(String url, String json) {
-        log.info(">>> 请求URL={}, 请求体={}", url, json);
+        final String requestId = UUID.randomUUID().toString();
+        final long start = System.currentTimeMillis();
+        log.info("[{}] >>> POST JSON 请求开始 URL={}, body={}", requestId, url, json);
 
         CloseableHttpResponse response = null;
         String resultString = "";
@@ -128,19 +131,44 @@ public class HttpClientUtils {
             httpPost.setEntity(entity);
             // 执行http请求
             response = HTTP_CLIENT.execute(httpPost);
-            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
+            int statusCode = response.getStatusLine() != null ? response.getStatusLine().getStatusCode() : -1;
+            resultString = response.getEntity() == null ? "" : EntityUtils.toString(response.getEntity(), "utf-8");
+
+            long costMs = System.currentTimeMillis() - start;
+            String preview = resultString == null ? "null" : (resultString.length() > 500 ? resultString.substring(0, 500) + "...(truncated)" : resultString);
+
+            if (statusCode != 200) {
+                log.warn("[{}] <<< POST JSON 响应异常 status={} costMs={} preview={}", requestId, statusCode, costMs, preview);
+            } else if (resultString == null || resultString.trim().isEmpty()) {
+                log.warn("[{}] <<< POST JSON 响应为空 status=200 costMs={}", requestId, costMs);
+            } else {
+                log.info("[{}] <<< POST JSON 响应成功 status=200 costMs={} preview={}", requestId, costMs, preview);
+            }
         } catch (Exception e) {
-            e.printStackTrace();
+            long costMs = System.currentTimeMillis() - start;
+            String root = buildRootCause(e);
+            log.error("[{}] !!! POST JSON 请求异常 costMs={} URL={} body={} exType={} exMsg={} rootCause={}",
+                    requestId, costMs, url, json, e.getClass().getName(), e.getMessage(), root, e);
         } finally {
             try {
                 if (response != null) {
                     response.close();
                 }
             } catch (IOException e) {
-                e.printStackTrace();
+                log.warn("[{}] 关闭HTTP响应失败: {}", requestId, e.getMessage(), e);
             }
         }
 
         return resultString;
     }
+
+    private static String buildRootCause(Throwable t) {
+        if (t == null) return "null";
+        Throwable cur = t;
+        while (cur.getCause() != null && cur.getCause() != cur) {
+            cur = cur.getCause();
+        }
+        String msg = cur.getMessage();
+        return cur.getClass().getName() + (msg == null ? "" : (": " + msg));
+    }
 }

+ 49 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/PeopleFlowData.java

@@ -0,0 +1,49 @@
+package com.usky.cdi.service.util;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/5/30
+ */
+/**
+ * 人防掩蔽单元人员流量数据实体
+ */
+public class PeopleFlowData {
+    // 采样时间(HH:mm)
+    private String sampleTime;
+    // 上一时刻人数
+    private int lastPeopleCount;
+    // 本期监测入口流入人数
+    private int inFlow;
+    // 本期全出口流出人数
+    private int outFlow;
+    // 当前现有人数
+    private int currentPeopleCount;
+
+    // 构造器
+    public PeopleFlowData(String sampleTime, int lastPeopleCount, int inFlow, int outFlow, int currentPeopleCount) {
+        this.sampleTime = sampleTime;
+        this.lastPeopleCount = lastPeopleCount;
+        this.inFlow = inFlow;
+        this.outFlow = outFlow;
+        this.currentPeopleCount = currentPeopleCount;
+    }
+
+    // toString(方便控制台打印)
+    @Override
+    public String toString() {
+        return "采样时间:" + sampleTime +
+                " | 上一时刻人数:" + lastPeopleCount +
+                " | 本期流入:" + inFlow +
+                " | 本期流出:" + outFlow +
+                " | 当前现有人数:" + currentPeopleCount;
+    }
+
+    // Getter & Setter
+    public String getSampleTime() {return sampleTime;}
+    public int getLastPeopleCount() {return lastPeopleCount;}
+    public int getInFlow() {return inFlow;}
+    public int getOutFlow() {return outFlow;}
+    public int getCurrentPeopleCount() {return currentPeopleCount;}
+}

+ 10 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/SnowflakeIdGenerator.java

@@ -141,5 +141,15 @@ public class SnowflakeIdGenerator {
         // 取后15位,确保不超过15位
         return id % 1000000000000000L;
     }
+
+    /**
+     * 生成10位数据包ID(取后10位)
+     * */
+
+    public long nextPacketId10() {
+        long id = nextId();
+        // 取后10位,确保不超过10位
+        return id % 10000000000L;
+    }
 }
 

+ 6 - 1
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/IotDataTransferVO.java

@@ -25,9 +25,14 @@ public class IotDataTransferVO {
      * 产品ID
      */
     private Integer deviceType;
-    
+
     /**
      * MQTT用户名
      */
     private String username;
+
+    /**
+     * 租户ID
+     */
+    private Integer tenantId;
 }

+ 2 - 4
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/alarm/AlarmMessageVO.java

@@ -1,6 +1,6 @@
 package com.usky.cdi.service.vo.alarm;
 
-import com.fasterxml.jackson.annotation.JsonFormat;
+import com.alibaba.fastjson.annotation.JSONField;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import lombok.Data;
 
@@ -93,7 +93,6 @@ public class AlarmMessageVO<T extends Number> implements Serializable {
      * 查询告警表 base_alarm 时新增告警取 alarm_type 字段,更新数据则取 handle_time 字段
      * 时间型(带毫秒),格式为 yyyy-MM-DD hh :mm :ss.SSS
      **/
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS", timezone = "GMT+8")
     private String alarmUpdateTime;
 
     /**
@@ -113,8 +112,7 @@ public class AlarmMessageVO<T extends Number> implements Serializable {
      * 获取当前时间
      * 时间型(带毫秒),格式为 yyyy-MM-DD hh :mm :ss.SSS
      **/
-    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS", timezone = "GMT+8")
-    private LocalDateTime publishTime;
+    private String publishTime;
 
     /** 告警数据字段 必填、通用
      * 水浸(Integer) 0:无水、1:有水

+ 42 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/BaseMqttInfo.java

@@ -0,0 +1,42 @@
+package com.usky.cdi.service.vo.base;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/4/10
+ */
+@Data
+public class BaseMqttInfo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 数据包ID
+     */
+    private Long dataPacketID;
+
+    /**
+     *  工程id
+     */
+    private Long engineeringID;
+
+    /**
+     * 上报时间
+     */
+    private String publishTime;
+
+    /**
+     *  MQTT用户名
+     */
+    private String userName;
+
+    /**
+     *  MQTT密码
+     */
+    private String password;
+}

+ 2 - 2
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/EngineeringBaseVO.java

@@ -1,7 +1,7 @@
 package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseMqttInfo;
 import lombok.Data;
-import java.io.Serializable;
 
 /**
  * 人防工程基础信息VO
@@ -11,7 +11,7 @@ import java.io.Serializable;
  * @date 2025/03/20
  */
 @Data
-public class EngineeringBaseVO implements Serializable {
+public class EngineeringBaseVO extends BaseMqttInfo {
     private static final long serialVersionUID = 1L;
 
     /**

+ 5 - 2
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FacilityDeviceVO.java

@@ -1,18 +1,20 @@
 package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseMqttInfo;
 import lombok.Data;
+
 import java.io.Serializable;
 import java.time.LocalDateTime;
 
 /**
  * 楼层平面图信息VO
  * Topic: base/floorPlane
- * 
+ *
  * @author han
  * @date 2025/03/20
  */
 @Data
-public class FacilityDeviceVO implements Serializable {
+public class FacilityDeviceVO extends BaseMqttInfo {
     private static final long serialVersionUID = 1L;
 
     private Integer id;
@@ -157,5 +159,6 @@ public class FacilityDeviceVO implements Serializable {
      * 设备UUID
      */
     private String deviceUuid;
+
 }
 

+ 2 - 15
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/FloorPlaneVO.java

@@ -1,5 +1,6 @@
 package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseMqttInfo;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -12,19 +13,9 @@ import java.io.Serializable;
  * @date 2025/03/20
  */
 @Data
-public class FloorPlaneVO implements Serializable {
+public class FloorPlaneVO extends BaseMqttInfo {
     private static final long serialVersionUID = 1L;
 
-    /**
-     * 数据包ID
-     */
-    private Long dataPacketID;
-
-    /**
-     * 人防工程ID
-     */
-    private Long engineeringID;
-
     /**
      * 楼层
      */
@@ -60,9 +51,5 @@ public class FloorPlaneVO implements Serializable {
      */
     private byte[] floorFile;
 
-    /**
-     * 上报时间
-     */
-    private String publishTime;
 }
 

+ 54 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/HeadcountVO.java

@@ -0,0 +1,54 @@
+package com.usky.cdi.service.vo.info;
+
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 人员统计情况推送VO(兼容2021版)
+ * 用途:每半小时上报最新人员统计状态,设防时段超过掩蔽人数需同步作为告警事件上传
+ * MQTT Topic:iotInfo/personPresence
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/11/20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class HeadcountVO extends BaseEnvMonitorPushVO {
+
+    private static final long serialVersionUID = 1L;
+
+    // ====================== 专属字段(父类已包含公共字段)======================
+    /**
+     * 当前掩蔽人员数量(必填)
+     * 类型:Int,长度1(取值参考 SensorValueEnum)
+     */
+    private Integer sensorValue;
+
+    /**
+     * 当前单元名称(必填)
+     * 类型:String,长度1-30
+     */
+    private String unitName;
+
+    /**
+     * 当前楼层名称(必填)
+     * 类型:String,长度1-10
+     */
+    private String floor;
+
+    // ====================== 实现父类抽象方法 ======================
+    @Override
+    public Number getSensorValue() {
+        return sensorValue;
+    }
+
+    @Override
+    protected void validateSensorValue() {
+        // 1. 非空校验
+        if (sensorValue == null) {
+            throw new IllegalArgumentException("当前单元掩蔽人数(sensorValue)为必填项");
+        }
+
+    }
+}

+ 2 - 15
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/ProtectiveUnitVO.java

@@ -1,5 +1,6 @@
 package com.usky.cdi.service.vo.info;
 
+import com.usky.cdi.service.vo.base.BaseMqttInfo;
 import lombok.Data;
 
 import java.io.Serializable;
@@ -13,19 +14,9 @@ import java.math.BigDecimal;
  * @date 2025/03/20
  */
 @Data
-public class ProtectiveUnitVO implements Serializable {
+public class ProtectiveUnitVO extends BaseMqttInfo {
     private static final long serialVersionUID = 1L;
 
-    /**
-     * 数据包ID
-     */
-    private Long dataPacketID;
-
-    /**
-     * 人防工程ID
-     */
-    private Long engineeringID;
-
     /**
      * 楼层
      */
@@ -61,9 +52,5 @@ public class ProtectiveUnitVO implements Serializable {
      */
     private String unitotherexit;
 
-    /**
-     * 上报时间
-     */
-    private String publishTime;
 }
 

+ 39 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/WaterLevelVO.java

@@ -0,0 +1,39 @@
+package com.usky.cdi.service.vo.info;
+
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
+import lombok.Data;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/4/27
+ */
+@Data
+public class WaterLevelVO extends BaseEnvMonitorPushVO {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final Double MIN_SENSOR_VALUE = 0.0;
+    private static final Double MAX_SENSOR_VALUE = 2.0;
+
+    /**
+     * 水位高度
+     * 类型:double
+     */
+    private Double sensorValue;
+
+    @Override
+    public Number getSensorValue() {
+        return sensorValue;
+    }
+
+    @Override
+    protected void validateSensorValue() {
+        // 位移值校验规则,根据实际业务需求调整
+        if (sensorValue == null || sensorValue < MIN_SENSOR_VALUE || sensorValue > MAX_SENSOR_VALUE) {
+            throw new IllegalArgumentException("水位高度(sensorValue)为必填项,且必须在" + MIN_SENSOR_VALUE + "到" + MAX_SENSOR_VALUE + "之间");
+        }
+    }
+
+}

+ 2 - 0
service-eg/service-eg-api/src/main/java/com/usky/eg/RemoteEgService.java

@@ -13,4 +13,6 @@ import java.util.Map;
 @FeignClient(contextId = "remoteEgService", value = "service-eg", fallbackFactory = RemoteEgFallbackFactory.class)
 public interface RemoteEgService {
 
+    @GetMapping("/egDeviceStatus")
+    void egDeviceStatus();
 }

+ 5 - 1
service-eg/service-eg-api/src/main/java/com/usky/eg/factory/RemoteEgFallbackFactory.java

@@ -1,5 +1,6 @@
 package com.usky.eg.factory;
 
+import com.usky.common.core.exception.FeignBadRequestException;
 import com.usky.eg.RemoteEgService;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -23,7 +24,10 @@ public class RemoteEgFallbackFactory implements FallbackFactory<RemoteEgService>
     {
         log.error("用户服务调用失败:{}", throwable.getMessage());
         return new RemoteEgService() {
-
+            @Override
+            public void egDeviceStatus() {
+                throw new FeignBadRequestException(500,"同步门禁设备状态异常"+throwable.getMessage());
+            }
         };
     }
 }

+ 25 - 3
service-eg/service-eg-biz/src/main/java/com/usky/eg/controller/web/EgDeviceController.java

@@ -7,6 +7,8 @@ import com.usky.common.log.annotation.Log;
 import com.usky.common.log.enums.BusinessType;
 import com.usky.eg.domain.EgDevice;
 import com.usky.eg.service.EgDeviceService;
+import com.usky.eg.service.vo.EgDeviceBindFacePersonQueryVO;
+import com.usky.eg.service.vo.EgDeviceBindFacePersonVO;
 import com.usky.eg.service.vo.EgDeviceRequestVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
@@ -32,6 +34,11 @@ public class EgDeviceController {
         return ApiResult.success(egDeviceService.page(requestVO));
     }
 
+    @PostMapping("/bindFacePerson/page")
+    public ApiResult<CommonPage<EgDeviceBindFacePersonVO>> pageBindFacePersons(@RequestBody EgDeviceBindFacePersonQueryVO queryVO) {
+        return ApiResult.success(egDeviceService.pageBindFacePersons(queryVO));
+    }
+
     @PostMapping("wePage")
     public ApiResult<CommonPage<EgDevice>> wePage(@RequestBody EgDeviceRequestVO requestVO){
         return ApiResult.success(egDeviceService.wePage(requestVO));
@@ -81,6 +88,20 @@ public class EgDeviceController {
         return ApiResult.success();
     }
 
+    /**
+     * 设备绑定人员
+     * @param params 包含 deviceId, personIds, isLoginNotify 的参数对象
+     * @return
+     */
+    @PostMapping("/bindPerson")
+    public ApiResult<Void> bindPerson(@RequestBody Map<String, Object> params){
+        Integer deviceId = (Integer) params.get("deviceId");
+        String personIds = (String) params.get("personIds");
+        Integer isLoginNotify = params.get("isLoginNotify") != null ? (Integer) params.get("isLoginNotify") : 0;
+        egDeviceService.bindPerson(deviceId, personIds, isLoginNotify);
+        return ApiResult.success();
+    }
+
     /**
      * 下发设备控制命令
      */
@@ -90,12 +111,13 @@ public class EgDeviceController {
                                                  @RequestParam("deviceUuid") String deviceUuid,
                                                  @RequestParam("commandCode") String commandCode,
                                                  @RequestParam("commandValue") String commandValue,
-                                                 @RequestParam(value = "domain",required = false) String domain,
                                                  @RequestParam(value = "userId",required = false) Long userId,
                                                  @RequestParam(value = "userName",required = false) String userName,
                                                  @RequestParam(value = "categoryType",required = false , defaultValue = "1") Integer categoryType,
-                                                 @RequestParam(value = "gatewayUuid",required = false) String gatewayUuid){
-        return ApiResult.success(egDeviceService.control(productCode,deviceUuid,commandCode,commandValue,domain,userId,userName,categoryType,gatewayUuid));
+                                                 @RequestParam(value = "gatewayUuid",required = false) String gatewayUuid,
+                                                 @RequestParam(value = "skipCheck",required = false, defaultValue = "false") Boolean skipCheck,
+                                                 @RequestParam(value = "passType",required = false) Integer passType){
+        return ApiResult.success(egDeviceService.control(productCode,deviceUuid,commandCode,commandValue,userId,userName,categoryType,gatewayUuid,skipCheck,passType));
     }
 
 }

+ 41 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/controller/web/EgDeviceHeartbeatController.java

@@ -0,0 +1,41 @@
+package com.usky.eg.controller.web;
+
+
+import com.usky.common.core.bean.ApiResult;
+import com.usky.eg.domain.EgDeviceHeartbeat;
+import com.usky.eg.service.EgDeviceHeartbeatService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * <p>
+ * 门禁设备心跳表 前端控制器
+ * </p>
+ *
+ * @author fhs
+ * @since 2026-02-03
+ */
+@RestController
+@RequestMapping("/egDeviceHeartbeat")
+public class EgDeviceHeartbeatController {
+    private static final Logger log = LoggerFactory.getLogger(EgDeviceHeartbeatController.class);
+    @Autowired
+    private EgDeviceHeartbeatService heartbeatService;
+
+    @PostMapping("/escalation")
+    public ApiResult<Void> heartbeatEscalation(@RequestBody EgDeviceHeartbeat heartbeat) {
+        try {
+            heartbeatService.heartbeatEscalation(heartbeat);
+        } catch (Exception e) {
+            log.error("设备心跳异常", e);
+            return ApiResult.error("设备心跳入库异常!");
+        }
+        return ApiResult.success();
+    }
+}

+ 11 - 13
service-eg/service-eg-biz/src/main/java/com/usky/eg/domain/EgDevice.java

@@ -6,7 +6,6 @@ import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import java.time.LocalDateTime;
 import java.io.Serializable;
-import java.util.List;
 
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -62,16 +61,6 @@ public class EgDevice implements Serializable {
      */
     private String deviceIp;
 
-    /**
-     * 端口
-     */
-    private Integer devicePort;
-
-    /**
-     * 门禁号
-     */
-    private String egNumber;
-
     /**
      * 绑定人脸信息
      */
@@ -108,6 +97,12 @@ public class EgDevice implements Serializable {
      */
     private Integer tenantId;
 
+    /**
+     * 绑定人员信息(从 eg_device_person_bind 表汇总的 person_id 列表,以逗号分隔)
+     */
+    @TableField("bind_person")
+    private String bindPerson;
+
     /**
      * 屏保
      */
@@ -134,10 +129,13 @@ public class EgDevice implements Serializable {
     private String workStatus;
 
     /**
+     * 设备code
+     */
+    private String deviceCode;
 
     /**
-     * 用户人脸信息记录
+     * 设备状态;1:在线,0:离线(从心跳表获取,不映射到数据库)
      */
     @TableField(exist = false)
-    private List<MeetingFace> meetingFaceList;
+    private Integer deviceStatus;
 }

+ 84 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/domain/EgDeviceHeartbeat.java

@@ -0,0 +1,84 @@
+package com.usky.eg.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+
+import java.time.LocalDateTime;
+import java.io.Serializable;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 门禁设备心跳表
+ * </p>
+ *
+ * @author fhs
+ * @since 2026-02-03
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class EgDeviceHeartbeat implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 门禁设备心跳表主键ID
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 设备code
+     */
+    private String deviceCode;
+
+    /**
+     * 设备ip地址
+     */
+    private String ipAddr;
+
+    /**
+     * 设备mac地址
+     */
+    private String macAddr;
+
+    /**
+     * 设备类型(1.会议屏 2.信息发布屏 3.综合屏)
+     */
+    private Boolean deviceType;
+
+    /**
+     * 创建日期
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private LocalDateTime createTime;
+
+    /**
+     * 设备型号
+     */
+    private String model;
+
+    /**
+     *  设备厂商
+     */
+    private String manuFacturer;
+
+    /**
+     * 设备版本号
+     */
+    private String version;
+
+    /**
+     * 设备sdk
+     */
+    private String sdk;
+
+    /**
+     * 设备状态;1:在线,0:离线
+     */
+    private Integer deviceStatus;
+
+}

+ 34 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/domain/EgDevicePersonBind.java

@@ -0,0 +1,34 @@
+package com.usky.eg.domain;
+
+import java.io.Serializable;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 门禁设备人员绑定表 eg_device_person_bind
+ * </p>
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class EgDevicePersonBind implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 设备主键ID
+     */
+    private Integer deviceId;
+
+    /**
+     * 人员ID
+     */
+    private Integer personId;
+
+    /**
+     * 是否打开登录通知(1 表示是,0 表示否,默认0)
+     */
+    private Integer isLoginNotify;
+}
+

+ 1 - 1
service-eg/service-eg-biz/src/main/java/com/usky/eg/domain/EgRecord.java

@@ -46,7 +46,7 @@ public class EgRecord implements Serializable {
     private Integer egDeviceId;
 
     /**
-     * 通行方式(1、人脸 2、刷卡 3、手机)
+     * 通行方式(0、其它 1、人脸 2、刷卡 3、手机 4、电脑)
      */
     private Integer passType;
 

+ 98 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/domain/SysPerson.java

@@ -0,0 +1,98 @@
+package com.usky.eg.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * <p>
+ * 人员信息表
+ * </p>
+ *
+ * @author zyj
+ * @since 2024-11-27
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("sys_person")
+public class SysPerson implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @TableId(value = "id", type = IdType.AUTO)
+    private Integer id;
+
+    /** 姓名 */
+    private String fullName;
+
+    /** 年龄 */
+    private Integer age;
+
+    /** 性别;1男、2女 */
+    private Integer gender;
+
+    /** 家庭住址 */
+    private String address;
+
+    /** 文化程度 */
+    private Integer educationDegree;
+
+    /** 身份证号 */
+    private String idNumber;
+
+    /** 联系方式 */
+    private String linkPhone;
+
+    /** 岗位ID */
+    private Long postId;
+
+    /** 部门ID */
+    private Long deptId;
+
+    /** 入职时间 */
+    private LocalDateTime entryTime;
+
+    /** 证书1 */
+    private String certificateUrl1;
+
+    /** 证书2 */
+    private String certificateUrl2;
+
+    /** 证书3 */
+    private String certificateUrl3;
+
+    /** 创建人 */
+    private String creator;
+
+    /** 创建时间 */
+    private LocalDateTime createTime;
+
+    /** 更新人 */
+    private String updatePerson;
+
+    /** 更新时间 */
+    private LocalDateTime updateTime;
+
+    /** 图片数据(base64编码) */
+    private String faceBase;
+
+    /** 验证次数(默认0) */
+    private Integer vefNum;
+
+    /** 人脸名称 */
+    private String faceName;
+
+    /** 人脸备注 */
+    private String remark;
+
+    /** 人脸状态(0=可用,1=不可用) */
+    private Byte faceStatus;
+
+    /** 卡号 */
+    private String cardNum;
+}

+ 18 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/mapper/EgDeviceHeartbeatMapper.java

@@ -0,0 +1,18 @@
+package com.usky.eg.mapper;
+
+import com.usky.eg.domain.EgDeviceHeartbeat;
+import com.usky.common.mybatis.core.CrudMapper;
+import org.springframework.stereotype.Repository;
+
+/**
+ * <p>
+ * 门禁设备心跳表 Mapper 接口
+ * </p>
+ *
+ * @author fhs
+ * @since 2026-02-03
+ */
+@Repository
+public interface EgDeviceHeartbeatMapper extends CrudMapper<EgDeviceHeartbeat> {
+
+}

+ 44 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/mapper/EgDevicePersonBindMapper.java

@@ -0,0 +1,44 @@
+package com.usky.eg.mapper;
+
+import com.usky.eg.domain.EgDevicePersonBind;
+import org.apache.ibatis.annotations.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface EgDevicePersonBindMapper {
+
+    /**
+     * 根据多个设备ID查询绑定关系
+     */
+    List<EgDevicePersonBind> selectByDeviceIds(@Param("deviceIds") List<Integer> deviceIds);
+
+    /**
+     * 统计某个设备下的绑定人数
+     */
+    Integer countByDeviceId(@Param("deviceId") Integer deviceId);
+
+    /**
+     * 根据用户ID和设备ID统计绑定关系
+     * 通过 sys_user_person -> sys_person -> eg_device_person_bind 进行关联
+     */
+    Integer countByUserIdAndDeviceId(@Param("userId") Long userId, @Param("deviceId") Integer deviceId);
+
+    /**
+     * 根据用户ID查询其绑定的设备ID列表
+     * 通过 sys_user_person -> sys_person -> eg_device_person_bind 进行关联
+     */
+    List<Integer> selectDeviceIdsByUserId(@Param("userId") Long userId);
+
+    /**
+     * 根据设备ID删除绑定关系
+     */
+    void deleteByDeviceId(@Param("deviceId") Integer deviceId);
+
+    /**
+     * 新增绑定关系
+     */
+    void insert(EgDevicePersonBind bind);
+}
+

+ 22 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/mapper/SysPersonMapper.java

@@ -0,0 +1,22 @@
+package com.usky.eg.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.usky.eg.domain.SysPerson;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+/**
+ * 人员信息表 Mapper
+ */
+@Mapper
+public interface SysPersonMapper extends BaseMapper<SysPerson> {
+
+    /**
+     * 查询所有有效的人脸数据(face_base不为空且face_status=0)
+     */
+    @Select("SELECT id, full_name, face_base, face_name, card_num FROM sys_person WHERE face_base IS NOT NULL AND face_base != '' AND face_status = 0")
+    List<SysPerson> selectAllValidFace();
+}

+ 19 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/service/EgDeviceHeartbeatService.java

@@ -0,0 +1,19 @@
+package com.usky.eg.service;
+
+import com.usky.eg.domain.EgDeviceHeartbeat;
+import com.usky.common.mybatis.core.CrudService;
+
+/**
+ * <p>
+ * 门禁设备心跳表 服务类
+ * </p>
+ *
+ * @author fhs
+ * @since 2026-02-03
+ */
+public interface EgDeviceHeartbeatService extends CrudService<EgDeviceHeartbeat> {
+
+    void heartbeatEscalation(EgDeviceHeartbeat heartbeat);
+
+    void updateEgDeviceStatus();
+}

+ 15 - 2
service-eg/service-eg-biz/src/main/java/com/usky/eg/service/EgDeviceService.java

@@ -3,8 +3,9 @@ package com.usky.eg.service;
 import com.usky.common.core.bean.CommonPage;
 import com.usky.eg.domain.EgDevice;
 import com.usky.common.mybatis.core.CrudService;
+import com.usky.eg.service.vo.EgDeviceBindFacePersonQueryVO;
+import com.usky.eg.service.vo.EgDeviceBindFacePersonVO;
 import com.usky.eg.service.vo.EgDeviceRequestVO;
-import org.springframework.web.bind.annotation.RequestBody;
 
 import java.util.Map;
 
@@ -33,5 +34,17 @@ public interface EgDeviceService extends CrudService<EgDevice> {
 
     boolean checkDeviceNameUnique(EgDevice egDevice);
 
-    Map<String,Object> control(String productCode, String deviceUuid, String commandCode, String commandValue, String domain, Long userId, String userName, Integer categoryType, String gatewayUuid);
+    Map<String,Object> control(String productCode, String deviceUuid, String commandCode, String commandValue, Long userId, String userName, Integer categoryType, String gatewayUuid, Boolean skipCheck, Integer passType);
+
+    /**
+     * 设备与人员绑定
+     *
+     * @param deviceId      设备主键ID
+     * @param personIds     人员ID,多个以逗号分隔
+     * @param isLoginNotify 是否打开登录通知(1 表示是,0 表示否)
+     */
+    void bindPerson(Integer deviceId, String personIds, Integer isLoginNotify);
+
+    /** 绑定表人员分页(可选姓名、人脸状态) */
+    CommonPage<EgDeviceBindFacePersonVO> pageBindFacePersons(EgDeviceBindFacePersonQueryVO queryVO);
 }

+ 91 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/service/impl/EgDeviceHeartbeatServiceImpl.java

@@ -0,0 +1,91 @@
+package com.usky.eg.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.usky.common.core.exception.BusinessException;
+import com.usky.eg.domain.EgDeviceHeartbeat;
+import com.usky.eg.mapper.EgDeviceHeartbeatMapper;
+import com.usky.eg.service.EgDeviceHeartbeatService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * <p>
+ * 门禁设备心跳表 服务实现类
+ * </p>
+ *
+ * @author fhs
+ * @since 2026-02-03
+ */
+@Service
+public class EgDeviceHeartbeatServiceImpl extends AbstractCrudService<EgDeviceHeartbeatMapper, EgDeviceHeartbeat> implements EgDeviceHeartbeatService {
+
+    @Transactional(rollbackFor = Exception.class)
+    @Override
+    public void heartbeatEscalation(EgDeviceHeartbeat heartbeat) {
+        if (StringUtils.isBlank(heartbeat.getDeviceCode())) {
+            throw new BusinessException("设备编码不能为空!");
+        }
+        if (StringUtils.isBlank(heartbeat.getIpAddr())) {
+            throw new BusinessException("设备IP不能为空!");
+        }
+        if (heartbeat.getCreateTime() == null) {
+            heartbeat.setCreateTime(LocalDateTime.now());
+        }
+        if (heartbeat.getDeviceType() == null) {
+            throw new BusinessException("设备类型不能为空!");
+        }
+
+        // 查询是否存在该设备的心跳记录
+        LambdaQueryWrapper<EgDeviceHeartbeat> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.eq(EgDeviceHeartbeat::getDeviceCode, heartbeat.getDeviceCode());
+        EgDeviceHeartbeat existingHeartbeat = baseMapper.selectOne(queryWrapper);
+        
+        if (existingHeartbeat != null) {
+            // 如果存在,更新记录
+            heartbeat.setId(existingHeartbeat.getId());
+            baseMapper.updateById(heartbeat);
+        } else {
+            // 如果不存在,插入新记录
+            baseMapper.insert(heartbeat);
+        }
+    }
+
+    /**
+     * 更新设备状态定时任务服务接口
+     * 从心跳表中获取设备状态并同步更新
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateEgDeviceStatus() {
+        try {
+            LocalDateTime now = LocalDateTime.now();
+            
+            // 1. 查询所有当前状态为在线的心跳记录
+            LambdaQueryWrapper<EgDeviceHeartbeat> heartbeatQueryWrapper = Wrappers.lambdaQuery();
+            heartbeatQueryWrapper.eq(EgDeviceHeartbeat::getDeviceStatus, 1);
+            List<EgDeviceHeartbeat> heartbeatList = baseMapper.selectList(heartbeatQueryWrapper);
+
+            // 2. 遍历在线记录,若心跳时间超过5分钟则更新为离线
+            for (EgDeviceHeartbeat heartbeat : heartbeatList) {
+                LocalDateTime heartbeatTime = heartbeat.getCreateTime();
+                if (heartbeatTime != null && !heartbeatTime.isAfter(now.minusMinutes(5))) {
+                    // 心跳超时,更新心跳表中的状态为离线
+                    LambdaUpdateWrapper<EgDeviceHeartbeat> updateWrapper = Wrappers.lambdaUpdate();
+                    updateWrapper.set(EgDeviceHeartbeat::getDeviceStatus, 0)
+                            .eq(EgDeviceHeartbeat::getId, heartbeat.getId());
+                    baseMapper.update(null, updateWrapper);
+                }
+            }
+        } catch (Exception e) {
+            // 定时任务失败不影响主流程,记录日志即可
+            throw new BusinessException("更新设备状态失败:" + (e.getMessage() != null ? e.getMessage() : "未知错误"));
+        }
+    }
+}

+ 425 - 133
service-eg/service-eg-biz/src/main/java/com/usky/eg/service/impl/EgDeviceServiceImpl.java

@@ -13,22 +13,28 @@ import com.usky.common.core.exception.BusinessException;
 import com.usky.common.core.util.UUIDUtils;
 import com.usky.common.security.utils.SecurityUtils;
 import com.usky.eg.domain.EgDevice;
-import com.usky.eg.domain.MeetingFace;
-import com.usky.eg.domain.MeetingFaceDevice;
+import com.usky.eg.domain.EgDeviceHeartbeat;
+import com.usky.eg.domain.EgDevicePersonBind;
+import com.usky.eg.domain.EgRecord;
+import com.usky.eg.domain.SysPerson;
 import com.usky.eg.mapper.EgDeviceMapper;
-import com.usky.eg.mapper.MeetingFaceDeviceMapper;
-import com.usky.eg.mapper.MeetingFaceMapper;
+import com.usky.eg.mapper.EgDeviceHeartbeatMapper;
+import com.usky.eg.mapper.EgDevicePersonBindMapper;
+import com.usky.eg.mapper.EgRecordMapper;
+import com.usky.eg.mapper.SysPersonMapper;
 import com.usky.eg.service.EgDeviceService;
 import com.usky.common.mybatis.core.AbstractCrudService;
+import com.usky.eg.service.vo.EgDeviceBindFacePersonQueryVO;
+import com.usky.eg.service.vo.EgDeviceBindFacePersonVO;
 import com.usky.eg.service.vo.EgDeviceRequestVO;
 import com.usky.iot.RemoteIotTaskService;
 import com.usky.transfer.RemoteTransferService;
-import io.swagger.models.auth.In;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.concurrent.CompletableFuture;
 
 /**
  * <p>
@@ -40,25 +46,35 @@ import java.util.*;
  */
 @Service
 public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgDevice> implements EgDeviceService {
-    @Autowired
-    private MeetingFaceDeviceMapper meetingFaceDeviceMapper;
-    @Autowired
-    private MeetingFaceMapper meetingFaceMapper;
     @Autowired
     private EgDeviceMapper egDeviceMapper;
     @Autowired
+    private EgDeviceHeartbeatMapper egDeviceHeartbeatMapper;
+    @Autowired
     private RemoteIotTaskService remoteIotTaskService;
-
     @Autowired
     private RemoteTransferService remoteTransferService;
+    @Autowired
+    private EgDevicePersonBindMapper egDevicePersonBindMapper;
+    @Autowired
+    private EgRecordMapper egRecordMapper;
+    @Autowired
+    private SysPersonMapper sysPersonMapper;
 
     @Override
     public CommonPage<EgDevice> page(EgDeviceRequestVO requestVO){
         IPage<EgDevice> page = new Page<>(requestVO.getCurrent(),requestVO.getSize());
-        Integer tenantId ;
+        Integer tenantId;
 
-        if(StringUtils.isNotBlank(requestVO.getDomain())){
-            tenantId = egDeviceMapper.sysTenantId(requestVO.getDomain());
+        if(StringUtils.isNotBlank(requestVO.getDeviceCode())){
+            LambdaQueryWrapper<EgDevice> tempQueryWrapper = new LambdaQueryWrapper<>();
+            tempQueryWrapper.eq(EgDevice::getDeviceCode, requestVO.getDeviceCode());
+            EgDevice tempDevice = egDeviceMapper.selectOne(tempQueryWrapper);
+            if(tempDevice == null){
+                // 未查询到数据,返回空数组
+                return new CommonPage<>(new ArrayList<>(), 0L, requestVO.getSize(), requestVO.getCurrent());
+            }
+            tenantId = tempDevice.getTenantId();
         }else{
             tenantId = SecurityUtils.getTenantId();
         }
@@ -69,34 +85,70 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
                 .eq(null != requestVO.getServiceStatus(),EgDevice::getServiceStatus,requestVO.getServiceStatus())
                 .eq(null != requestVO.getId(),EgDevice::getId,requestVO.getId())
                 .eq(null != requestVO.getDeviceUuid(),EgDevice::getDeviceUuid, requestVO.getDeviceUuid())
+                .eq(StringUtils.isNotBlank(requestVO.getDeviceCode()),EgDevice::getDeviceCode, requestVO.getDeviceCode())
                 .eq(EgDevice::getTenantId,tenantId)
                 .orderByDesc(EgDevice::getId);
         page = this.page(page,queryWrapper);
+
         if(!page.getRecords().isEmpty()){
+            // 检查设备心跳
+            List<String> validDeviceCodes = new ArrayList<>();
+            Map<String, EgDevice> deviceCodeMap = new HashMap<>();
+            for (EgDevice device : page.getRecords()) {
+                String deviceCode = device.getDeviceCode();
+                if (StringUtils.isNotBlank(deviceCode)) {
+                    validDeviceCodes.add(deviceCode);
+                    deviceCodeMap.put(deviceCode, device);
+                } else {
+                    // deviceCode为空,直接设置为离线
+                    device.setDeviceStatus(0);
+                }
+            }
 
-            LambdaQueryWrapper<MeetingFace> meetingFaceQuery = Wrappers.lambdaQuery();
-            meetingFaceQuery.select(MeetingFace::getFid,MeetingFace::getCreateTime,MeetingFace::getVefNum,MeetingFace::getFaceName,MeetingFace::getRemark,MeetingFace::getFaceStatus,MeetingFace::getCardNum,MeetingFace::getBindDevice,MeetingFace::getDeptId,MeetingFace::getTenantId,MeetingFace::getUserId)
-                    .eq(MeetingFace::getTenantId,tenantId);
-            List<MeetingFace> meetingAllFaceList = meetingFaceMapper.selectList(meetingFaceQuery);
-
-            for (int i = 0; i < page.getRecords().size(); i++) {
-                if(Objects.nonNull(page.getRecords().get(i).getBindFace()) ||StringUtils.isNotBlank(page.getRecords().get(i).getBindFace())){
-                    String[] fidListStr = page.getRecords().get(i).getBindFace().split(",");
-                    Integer[] fidList = Arrays.stream(fidListStr).map(Integer::parseInt).toArray(Integer[]::new);
-
-                    List<MeetingFace> meetingFaceList = new ArrayList<>();
-                    for (int j = 0; j < fidList.length; j++) {
-                        for (int k = 0; k < meetingAllFaceList.size(); k++) {
-                            if(fidList[j] == meetingAllFaceList.get(k).getFid()){
-                                meetingFaceList.add(meetingAllFaceList.get(k));
-                                break;
+            // 批量查询心跳数据
+            if (!validDeviceCodes.isEmpty()) {
+                LambdaQueryWrapper<EgDeviceHeartbeat> heartbeatQueryWrapper = Wrappers.lambdaQuery();
+                heartbeatQueryWrapper.in(EgDeviceHeartbeat::getDeviceCode, validDeviceCodes)
+                        .orderByDesc(EgDeviceHeartbeat::getCreateTime);
+                List<EgDeviceHeartbeat> heartbeatList = egDeviceHeartbeatMapper.selectList(heartbeatQueryWrapper);
+                
+                // 构建deviceCode到最新心跳记录的映射,并同时设置deviceStatus
+                Map<String, EgDeviceHeartbeat> deviceHeartbeatMap = new HashMap<>();
+                for (EgDeviceHeartbeat heartbeat : heartbeatList) {
+                    String deviceCode = heartbeat.getDeviceCode();
+                    if (StringUtils.isNotBlank(deviceCode)) {
+                        // 如果已存在,保留最新的(因为已按createTime降序排列)
+                        if (deviceHeartbeatMap.putIfAbsent(deviceCode, heartbeat) == null) {
+                            // 首次遇到该deviceCode,从心跳表直接获取deviceStatus并设置到EgDevice
+                            EgDevice device = deviceCodeMap.get(deviceCode);
+                            if (device != null && heartbeat.getDeviceStatus() != null) {
+                                device.setDeviceStatus(heartbeat.getDeviceStatus());
                             }
                         }
                     }
-                    page.getRecords().get(i).setMeetingFaceList(meetingFaceList);
                 }
-
+                
+                // 如果未找到对应的设备心跳记录,则设置DeviceStatus为0(离线)
+                for (String deviceCode : validDeviceCodes) {
+                    if (!deviceHeartbeatMap.containsKey(deviceCode)) {
+                        EgDevice device = deviceCodeMap.get(deviceCode);
+                        if (device != null) {
+                            device.setDeviceStatus(0);
+                        }
+                    }
+                }
+            } else {
+                // 如果没有有效的deviceCode,将所有设备的deviceStatus设置为0
+                for (EgDevice device : page.getRecords()) {
+                    device.setDeviceStatus(0);
+                }
             }
+
+        }
+
+        // 查询并填充绑定人员信息(来自 eg_device_person_bind,person_id 逗号分隔后写入 bindPerson 字段)
+        if (!page.getRecords().isEmpty()) {
+            fillBindPerson(page.getRecords());
         }
 
         return new CommonPage<>(page.getRecords(),page.getTotal(),requestVO.getSize(),requestVO.getCurrent());
@@ -105,86 +157,150 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
     @Override
     public CommonPage<EgDevice> wePage(EgDeviceRequestVO requestVO){
         long userId = SecurityUtils.getUserId();
-        //人员设备权限校验,校验通过,可以下发命令控制设备
-        Integer fid = baseMapper.getMeetingFaceData(userId);
-//        if(fid == null){
-//            throw new BusinessException("人脸卡号信息未注册");
-//        }
-        Integer[] deviceFid = baseMapper.getMeetingFaceDeviceList(fid);
-//        if(deviceFid.length == 0){
-//            throw new BusinessException("人员未绑定设备,请检查");
-//        }
+        // 通过 sys_user_person -> sys_person -> eg_device_person_bind -> eg_device 查询当前用户绑定的设备ID列表
+        List<Integer> deviceIds = egDevicePersonBindMapper.selectDeviceIdsByUserId(userId);
+        if (CollectionUtils.isEmpty(deviceIds)) {
+            // 未绑定任何设备,返回空分页
+            return new CommonPage<>(new ArrayList<>(), 0L, requestVO.getSize(), requestVO.getCurrent());
+        }
 
         IPage<EgDevice> page = new Page<>(requestVO.getCurrent(),requestVO.getSize());
-        if(deviceFid.length > 0){
-            LambdaQueryWrapper<EgDevice> queryWrapper = Wrappers.lambdaQuery();
-            queryWrapper.like(StringUtils.isNotBlank(requestVO.getDeviceName()),EgDevice::getDeviceName,requestVO.getDeviceName())
-                    .like(StringUtils.isNotBlank(requestVO.getInstallAddress()),EgDevice::getInstallAddress,requestVO.getInstallAddress())
-                    .eq(null != requestVO.getServiceStatus(),EgDevice::getServiceStatus,requestVO.getServiceStatus())
-                    .eq(null != requestVO.getId(),EgDevice::getId,requestVO.getId())
-                    .in(EgDevice::getId,deviceFid)
-                    .eq(EgDevice::getTenantId,SecurityUtils.getTenantId())
-                    .orderByDesc(EgDevice::getId);
-            page = this.page(page,queryWrapper);
-        }
+        LambdaQueryWrapper<EgDevice> queryWrapper = Wrappers.lambdaQuery();
+        queryWrapper.like(StringUtils.isNotBlank(requestVO.getDeviceName()),EgDevice::getDeviceName,requestVO.getDeviceName())
+                .like(StringUtils.isNotBlank(requestVO.getInstallAddress()),EgDevice::getInstallAddress,requestVO.getInstallAddress())
+                .eq(null != requestVO.getServiceStatus(),EgDevice::getServiceStatus,requestVO.getServiceStatus())
+                .eq(null != requestVO.getId(),EgDevice::getId,requestVO.getId())
+                .in(EgDevice::getId, deviceIds)
+                .eq(EgDevice::getTenantId,SecurityUtils.getTenantId())
+                .orderByDesc(EgDevice::getId);
+        page = this.page(page,queryWrapper);
 
+        // 查询并填充绑定人员信息
+        if (!page.getRecords().isEmpty()) {
+            fillBindPerson(page.getRecords());
+        }
 
         return new CommonPage<>(page.getRecords(),page.getTotal(),requestVO.getSize(),requestVO.getCurrent());
     }
 
     @Override
-    public void add(EgDevice egDevice){
-        if(checkNameUnique(egDevice)){
-            throw new BusinessException("新增门禁门号设备'"+egDevice.getDeviceId()+","+egDevice.getEgNumber()+"'失败,设备已存在");
+    public void add(EgDevice egDevice) {
+        LocalDateTime now = LocalDateTime.now();
+        
+        // 1. 校验设备名称唯一性
+        if (checkDeviceNameUnique(egDevice)) {
+            throw new BusinessException("新增门禁设备'" + egDevice.getDeviceName() + "'失败,设备已存在");
         }
-        if(checkDeviceNameUnique(egDevice)){
-            throw new BusinessException("新增门禁设备'"+egDevice.getDeviceName()+"'失败,设备已存在");
+
+        // 2. 校验设备IP是否已被绑定
+        if (StringUtils.isNotBlank(egDevice.getDeviceIp())) {
+            LambdaQueryWrapper<EgDevice> ipCheckWrapper = Wrappers.lambdaQuery();
+            ipCheckWrapper.eq(EgDevice::getDeviceIp, egDevice.getDeviceIp())
+                    .eq(EgDevice::getTenantId, SecurityUtils.getTenantId());
+            EgDevice existingDeviceByIp = this.getOne(ipCheckWrapper);
+            if (existingDeviceByIp != null) {
+                throw new BusinessException("设备IP'" + egDevice.getDeviceIp() + "'已被设备'" 
+                        + existingDeviceByIp.getDeviceName() + "'绑定,请更换IP或检查设备配置!");
+            }
+        }
+
+        // 3. 若未传deviceCode但传了deviceIp,尝试从心跳表中获取deviceCode
+        if (StringUtils.isBlank(egDevice.getDeviceCode()) && StringUtils.isNotBlank(egDevice.getDeviceIp())) {
+            LambdaQueryWrapper<EgDeviceHeartbeat> heartbeatCheckWrapper = Wrappers.lambdaQuery();
+            heartbeatCheckWrapper.eq(EgDeviceHeartbeat::getIpAddr, egDevice.getDeviceIp())
+                    .eq(EgDeviceHeartbeat::getDeviceStatus, 1);
+            EgDeviceHeartbeat heartbeat = egDeviceHeartbeatMapper.selectOne(heartbeatCheckWrapper);
+            if (heartbeat != null && StringUtils.isNotBlank(heartbeat.getDeviceCode())) {
+                egDevice.setDeviceCode(heartbeat.getDeviceCode());
+            }
         }
 
+        // 4. 校验设备编码是否已被绑定
+        if (StringUtils.isNotBlank(egDevice.getDeviceCode())) {
+            LambdaQueryWrapper<EgDevice> codeCheckWrapper = Wrappers.lambdaQuery();
+            codeCheckWrapper.eq(EgDevice::getDeviceCode, egDevice.getDeviceCode())
+                    .eq(EgDevice::getTenantId, SecurityUtils.getTenantId());
+            EgDevice existingDeviceByCode = this.getOne(codeCheckWrapper);
+            if (existingDeviceByCode != null) {
+                throw new BusinessException("设备编码'" + egDevice.getDeviceCode() + "'已被设备'" 
+                        + existingDeviceByCode.getDeviceName() + "'绑定,请更换设备编码或检查设备配置!");
+            }
+        }
+
+        // 5. 设置设备基本信息
         egDevice.setDeviceUuid(UUIDUtils.uuid());
         egDevice.setCreateBy(SecurityUtils.getUsername());
-        egDevice.setCreateTime(LocalDateTime.now());
+        egDevice.setCreateTime(now);
         egDevice.setTenantId(SecurityUtils.getTenantId());
+        egDevice.setOpenMode("人脸");
+        egDevice.setWorkStatus("4");
 
+        // 6. 保存设备
         this.save(egDevice);
 
-        String[] fids = new String[0];
-        if(Objects.nonNull(egDevice.getBindFace()) || StringUtils.isNotBlank(egDevice.getBindFace())){
-            fids = egDevice.getBindFace().split(",");
-        }
-        if(fids.length > 0){
-            for (int i = 0; i < fids.length; i++) {
-                egDeviceMapper.insertMeetingFaceDevice(Integer.parseInt(fids[i]),egDevice.getId());
-            }
+        // 7. 异步调用远程服务添加设备信息,静默处理异常,不影响设备保存成功
+        String deviceUuid = egDevice.getDeviceUuid();
+        String deviceName = egDevice.getDeviceName();
+        if (StringUtils.isNotBlank(deviceUuid) && StringUtils.isNotBlank(deviceName)) {
+            String installAddress = StringUtils.isNotBlank(egDevice.getInstallAddress()) 
+                    ? egDevice.getInstallAddress() 
+                    : "";
+            Integer serviceStatus = egDevice.getServiceStatus() != null 
+                    ? egDevice.getServiceStatus() 
+                    : 1;
+            
+            CompletableFuture.runAsync(() -> {
+                try {
+                    remoteIotTaskService.addDeviceInfo("502_USKY", deviceUuid, deviceUuid, 
+                            deviceName, installAddress, serviceStatus);
+                } catch (Exception e) {
+                    // 远程服务调用失败,静默处理,不影响设备保存成功
+                }
+            });
         }
-
-        remoteIotTaskService.addDeviceInfo("502_USKY", egDevice.getDeviceUuid(),egDevice.getDeviceId()+egDevice.getEgNumber(),egDevice.getDeviceName(),egDevice.getInstallAddress(),egDevice.getServiceStatus());
     }
 
     @Override
     public void update(EgDevice egDevice) {
-
-        String[] fids = new String[0];
-        if(Objects.nonNull(egDevice.getBindFace()) || StringUtils.isNotBlank(egDevice.getBindFace())){
-            fids = egDevice.getBindFace().split(",");
-
-            egDeviceMapper.deleteMeetingFaceDevice(egDevice.getId());
-        }else{
-            EgDevice one = this.getById(egDevice.getId());
-            egDevice.setBindFace(one.getBindFace());
+        if(checkDeviceNameUnique(egDevice)){
+            throw new BusinessException("修改门禁设备'"+egDevice.getDeviceName()+"'失败,设备名称已存在");
         }
-        if(fids.length > 0){
-            for (int i = 0; i < fids.length; i++) {
-                egDeviceMapper.insertMeetingFaceDevice(Integer.parseInt(fids[i]),egDevice.getId());
+
+        // 校验设备IP是否已被其他设备绑定(排除当前设备)
+        if (StringUtils.isNotBlank(egDevice.getDeviceIp())) {
+            LambdaQueryWrapper<EgDevice> ipCheckWrapper = Wrappers.lambdaQuery();
+            ipCheckWrapper.eq(EgDevice::getDeviceIp, egDevice.getDeviceIp())
+                    .eq(EgDevice::getTenantId, SecurityUtils.getTenantId())
+                    .ne(EgDevice::getId, egDevice.getId());
+            EgDevice existingDeviceByIp = this.getOne(ipCheckWrapper);
+            if (existingDeviceByIp != null) {
+                throw new BusinessException("设备IP'" + egDevice.getDeviceIp() + "'已被设备'" 
+                        + existingDeviceByIp.getDeviceName() + "'绑定,请更换IP或检查设备配置!");
             }
         }
 
-
-        if(checkNameUnique(egDevice)){
-            throw new BusinessException("修改门禁门号设备'"+egDevice.getDeviceId()+","+egDevice.getEgNumber()+"'失败,设备已存在");
+        // 若未传deviceCode但传了deviceIp,尝试从心跳表中获取deviceCode
+        if (StringUtils.isBlank(egDevice.getDeviceCode()) && StringUtils.isNotBlank(egDevice.getDeviceIp())) {
+            LambdaQueryWrapper<EgDeviceHeartbeat> heartbeatCheckWrapper = Wrappers.lambdaQuery();
+            heartbeatCheckWrapper.eq(EgDeviceHeartbeat::getIpAddr, egDevice.getDeviceIp())
+                    .eq(EgDeviceHeartbeat::getDeviceStatus, 1);
+            EgDeviceHeartbeat heartbeat = egDeviceHeartbeatMapper.selectOne(heartbeatCheckWrapper);
+            if (heartbeat != null && StringUtils.isNotBlank(heartbeat.getDeviceCode())) {
+                egDevice.setDeviceCode(heartbeat.getDeviceCode());
+            }
         }
-        if(checkDeviceNameUnique(egDevice)){
-            throw new BusinessException("新增门禁设备'"+egDevice.getDeviceName()+"'失败,设备已存在");
+
+        // 校验设备编码是否已被其他设备绑定(排除当前设备)
+        if (StringUtils.isNotBlank(egDevice.getDeviceCode())) {
+            LambdaQueryWrapper<EgDevice> codeCheckWrapper = Wrappers.lambdaQuery();
+            codeCheckWrapper.eq(EgDevice::getDeviceCode, egDevice.getDeviceCode())
+                    .eq(EgDevice::getTenantId, SecurityUtils.getTenantId())
+                    .ne(EgDevice::getId, egDevice.getId());
+            EgDevice existingDeviceByCode = this.getOne(codeCheckWrapper);
+            if (existingDeviceByCode != null) {
+                throw new BusinessException("设备编码'" + egDevice.getDeviceCode() + "'已被设备'" 
+                        + existingDeviceByCode.getDeviceName() + "'绑定,请更换设备编码或检查设备配置!");
+            }
         }
 
         egDevice.setUpdateBy(SecurityUtils.getUsername());
@@ -199,9 +315,7 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
         EgDevice one = this.getById(egDevice.getId());
         egDevice.setBindFace(one.getBindFace());
 
-        if(checkNameUnique(egDevice)){
-            throw new BusinessException("更新门禁设备附加功能'"+egDevice.getDeviceId()+"'失败,设备已存在");
-        }
+
         if(checkDeviceNameUnique(egDevice)){
             throw new BusinessException("新增门禁设备'"+egDevice.getDeviceName()+"'失败,设备已存在");
         }
@@ -217,16 +331,72 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
         EgDevice egDevice = this.getById(id);
         Optional.ofNullable(egDevice).orElseThrow(() -> new BusinessException("门禁设备信息不存在"));
 
-        LambdaQueryWrapper<MeetingFaceDevice> queryWrapper = Wrappers.lambdaQuery();
-        queryWrapper.eq(MeetingFaceDevice::getDeviceId,id);
-        Integer count = meetingFaceDeviceMapper.selectCount(queryWrapper);
-        if(count > 0){
+        // 校验 eg_device_person_bind 是否存在绑定人员
+        Integer personBindCount = egDevicePersonBindMapper.countByDeviceId(id);
+        if (personBindCount != null && personBindCount > 0) {
             throw new BusinessException("已绑定人员不能删除");
         }
 
         this.removeById(id);
 
-        remoteIotTaskService.deleteDeviceInfo(egDevice.getDeviceUuid());
+        // 异步调用远程服务删除设备信息,静默处理异常,不影响设备删除
+        String deviceUuid = egDevice.getDeviceUuid();
+        if (StringUtils.isNotBlank(deviceUuid)) {
+            CompletableFuture.runAsync(() -> {
+                try {
+                    remoteIotTaskService.deleteDeviceInfo(deviceUuid);
+                } catch (Exception e) {
+                    // 远程服务调用失败,静默处理,不影响设备删除
+                }
+            });
+        }
+    }
+
+    /**
+     * 根据设备ID集合查询 eg_device_person_bind,将 person_id 按逗号拼接后写入设备的 bindPerson 字段
+     */
+    private void fillBindPerson(List<EgDevice> devices) {
+        if (CollectionUtils.isEmpty(devices)) {
+            return;
+        }
+
+        List<Integer> deviceIds = new ArrayList<>();
+        for (EgDevice device : devices) {
+            if (device != null && device.getId() != null) {
+                deviceIds.add(device.getId());
+            }
+        }
+
+        if (deviceIds.isEmpty()) {
+            return;
+        }
+
+        List<EgDevicePersonBind> bindList = egDevicePersonBindMapper.selectByDeviceIds(deviceIds);
+        if (CollectionUtils.isEmpty(bindList)) {
+            return;
+        }
+
+        Map<Integer, List<String>> devicePersonMap = new HashMap<>();
+        for (EgDevicePersonBind bind : bindList) {
+            if (bind.getDeviceId() == null || bind.getPersonId() == null) {
+                continue;
+            }
+            devicePersonMap
+                    .computeIfAbsent(bind.getDeviceId(), k -> new ArrayList<>())
+                    .add(String.valueOf(bind.getPersonId()));
+        }
+
+        for (EgDevice device : devices) {
+            if (device == null || device.getId() == null) {
+                continue;
+            }
+            List<String> personIds = devicePersonMap.get(device.getId());
+            if (personIds != null && !personIds.isEmpty()) {
+                device.setBindPerson(String.join(",", personIds));
+            } else {
+                device.setBindPerson(null);
+            }
+        }
     }
 
     @Override
@@ -234,7 +404,6 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
         Integer id = null == egDevice.getId() ? -1 : egDevice.getId();
         LambdaQueryWrapper<EgDevice> queryWrapper = Wrappers.lambdaQuery();
         queryWrapper.eq(EgDevice::getDeviceId,egDevice.getDeviceId())
-                .eq(EgDevice::getEgNumber,egDevice.getEgNumber())
                 .eq(EgDevice::getTenantId, SecurityUtils.getTenantId());
         EgDevice one = this.getOne(queryWrapper);
         return null != one && !Objects.equals(one.getId(),id);
@@ -251,56 +420,179 @@ public class EgDeviceServiceImpl extends AbstractCrudService<EgDeviceMapper, EgD
     }
 
     @Override
-    public Map<String,Object> control(String productCode, String deviceUuid, String commandCode, String commandValue, String domain, Long userId, String userName, Integer categoryType, String gatewayUuid){
-        Integer tenantId;
-        long commandUserId;
-        String commandUserName;
-        if(StringUtils.isNotBlank(domain)){
-            tenantId = baseMapper.sysTenantId(domain);
-            commandUserId = userId;
-            commandUserName = userName;
-        }else{
-            tenantId = SecurityUtils.getTenantId();
-            commandUserId = SecurityUtils.getUserId();
-            commandUserName = SecurityUtils.getUsername();
+    public Map<String,Object> control(String productCode, String deviceUuid, String commandCode, String commandValue, Long userId, String userName, Integer categoryType, String gatewayUuid, Boolean skipCheck, Integer passType){
+        // 1. 查询设备
+        EgDevice device = this.getOne(Wrappers.<EgDevice>lambdaQuery()
+                .select(EgDevice::getId, EgDevice::getTenantId)
+                .eq(EgDevice::getDeviceUuid, deviceUuid));
+        if (device == null) {
+            throw new BusinessException("设备未注册,请先注册");
         }
 
-        //人员设备权限校验,校验通过,可以下发命令控制设备
-        Integer fid = baseMapper.getMeetingFaceData(commandUserId);
-        if(fid == null){
-            throw new BusinessException("人脸卡号信息未注册");
-        }
-        Integer[] deviceFid = baseMapper.getMeetingFaceDeviceList(fid);
-        if(deviceFid.length == 0){
-            throw new BusinessException("人员未绑定设备,请检查");
-        }
+        Integer tenantId = device.getTenantId();
+        long commandUserId = userId != null ? userId : SecurityUtils.getUserId();
+        String commandUserName = userName != null ? userName : SecurityUtils.getUsername();
 
-        LambdaQueryWrapper<EgDevice> queryWrapper = Wrappers.lambdaQuery();
-        queryWrapper.select(EgDevice::getId)
-                .eq(EgDevice::getDeviceUuid,deviceUuid);
-        EgDevice one = this.getOne(queryWrapper);
-        if(one != null){
-            boolean exist = Arrays.asList(deviceFid).contains(one.getId());
-            if(!exist){
+        // 2. 权限校验
+        if (!Boolean.TRUE.equals(skipCheck)) {
+            Integer bindCount = egDevicePersonBindMapper.countByUserIdAndDeviceId(commandUserId, device.getId());
+            if (bindCount == null || bindCount == 0) {
+                saveRecord(device.getId(), tenantId, commandUserName, passType, "失败:暂无权限");
                 throw new BusinessException("暂无权限");
             }
-        }else{
-            throw new BusinessException("设备未注册,请先注册");
         }
 
-
+        // 3. 构建下发命令参数
+        Map<String,Object> params = new HashMap<>();
+        params.put("commandCode", commandCode);
+        params.put("commandValue", commandValue);
         Map<String,Object> map = new HashMap<>();
-        map.put("method","control");
+        map.put("method", "control");
         map.put("deviceUuid", deviceUuid);
-        Map<String,Object> map1 = new HashMap<>();
-        map1.put("commandCode",commandCode);
-        map1.put("commandValue",commandValue);
-        map.put("params",map1);
+        map.put("params", params);
+
+        // 4. 下发命令并记录结果
+        String targetUuid = categoryType == 3 ? gatewayUuid : deviceUuid;
+        Map<String,Object> result = null;
+        String passResult = "下发命令失败";
+        try {
+            result = remoteTransferService.deviceControl(productCode, targetUuid, JSON.toJSONString(map), tenantId, commandUserId, commandUserName);
+            if (result == null || !Integer.valueOf(200).equals(result.get("code"))) {
+                passResult = result != null && result.get("message") != null ? result.get("message").toString() : "下发命令失败";
+                throw new BusinessException(passResult);
+            }
+            passResult = result.get("message") != null ? result.get("message").toString() : "下发命令成功";
+            return result;
+        } finally {
+            saveRecord(device.getId(), tenantId, commandUserName, passType, passResult);
+        }
+    }
 
-        if(categoryType == 3){
-            return remoteTransferService.deviceControl(productCode, gatewayUuid, JSON.toJSONString(map), tenantId, commandUserId, commandUserName);
-        }else{
-            return remoteTransferService.deviceControl(productCode, deviceUuid, JSON.toJSONString(map), tenantId, commandUserId, commandUserName);
+    /**
+     * 插入通行记录,失败不影响主流程
+     */
+    private void saveRecord(Integer deviceId, Integer tenantId, String userName, Integer passType, String passResult) {
+        try {
+            EgRecord record = new EgRecord();
+            record.setEgDeviceId(deviceId);
+            record.setTenantId(tenantId);
+            record.setPassTime(LocalDateTime.now());
+            record.setCreateTime(LocalDateTime.now());
+            record.setPassResult(passResult);
+            record.setUserName(userName);
+            record.setPassType(passType);
+            egRecordMapper.insert(record);
+        } catch (Exception ignored) {}
+    }
+
+    @Override
+    public CommonPage<EgDeviceBindFacePersonVO> pageBindFacePersons(EgDeviceBindFacePersonQueryVO queryVO) {
+        Integer cur = queryVO.getCurrent();
+        Integer sz = queryVO.getSize();
+        IPage<SysPerson> page = new Page<>(cur, sz);
+
+        List<Integer> deviceIds = new ArrayList<>();
+        if (StringUtils.isNotBlank(queryVO.getDeviceCode())) {
+            EgDevice tempDevice = egDeviceMapper.selectOne(Wrappers.<EgDevice>lambdaQuery()
+                    .eq(EgDevice::getDeviceCode, queryVO.getDeviceCode()));
+            if (tempDevice == null) {
+                return new CommonPage<>(new ArrayList<>(), 0L, sz, cur);
+            }
+            EgDevice device = egDeviceMapper.selectOne(Wrappers.<EgDevice>lambdaQuery()
+                    .eq(EgDevice::getDeviceCode, queryVO.getDeviceCode())
+                    .eq(EgDevice::getTenantId, tempDevice.getTenantId()));
+            if (device == null || device.getId() == null) {
+                return new CommonPage<>(new ArrayList<>(), 0L, sz, cur);
+            }
+            deviceIds.add(device.getId());
+        } else {
+            List<EgDevice> devices = egDeviceMapper.selectList(Wrappers.<EgDevice>lambdaQuery()
+                    .eq(EgDevice::getTenantId, SecurityUtils.getTenantId()));
+            if (!CollectionUtils.isEmpty(devices)) {
+                for (EgDevice d : devices) {
+                    if (d != null && d.getId() != null) {
+                        deviceIds.add(d.getId());
+                    }
+                }
+            }
+        }
+
+        if (deviceIds.isEmpty()) {
+            return new CommonPage<>(new ArrayList<>(), 0L, sz, cur);
+        }
+
+        List<EgDevicePersonBind> bindList = egDevicePersonBindMapper.selectByDeviceIds(deviceIds);
+        LinkedHashSet<Integer> personIdSet = new LinkedHashSet<>();
+        if (!CollectionUtils.isEmpty(bindList)) {
+            for (EgDevicePersonBind b : bindList) {
+                if (b.getPersonId() != null) {
+                    personIdSet.add(b.getPersonId());
+                }
+            }
+        }
+        if (personIdSet.isEmpty()) {
+            return new CommonPage<>(new ArrayList<>(), 0L, sz, cur);
+        }
+        List<Integer> personIds = new ArrayList<>(personIdSet);
+
+        LambdaQueryWrapper<SysPerson> personWrapper = Wrappers.lambdaQuery();
+        personWrapper.in(SysPerson::getId, personIds);
+        if (StringUtils.isNotBlank(queryVO.getFullName())) {
+            personWrapper.like(SysPerson::getFullName, queryVO.getFullName());
+        }
+        Integer faceStatusFilter = queryVO.getFaceStatus();
+        if (faceStatusFilter != null) {
+            personWrapper.eq(SysPerson::getFaceStatus, faceStatusFilter.byteValue());
+        }
+        personWrapper.orderByAsc(SysPerson::getId);
+        page = sysPersonMapper.selectPage(page, personWrapper);
+
+        List<SysPerson> records = page.getRecords();
+        List<EgDeviceBindFacePersonVO> rows = new ArrayList<>(records.size());
+        for (SysPerson p : records) {
+            EgDeviceBindFacePersonVO vo = new EgDeviceBindFacePersonVO();
+            vo.setFullName(p.getFullName());
+            vo.setAge(p.getAge());
+            vo.setGender(p.getGender());
+            vo.setLinkPhone(p.getLinkPhone());
+            vo.setFaceBase(p.getFaceBase());
+            vo.setVefNum(p.getVefNum());
+            vo.setFaceStatus(p.getFaceStatus());
+            vo.setCardNum(p.getCardNum());
+            rows.add(vo);
+        }
+        return new CommonPage<>(rows, page.getTotal(), sz, cur);
+    }
+
+    @Override
+    public void bindPerson(Integer deviceId, String personIds, Integer isLoginNotify) {
+        if (deviceId == null) {
+            throw new BusinessException("设备ID不能为空");
+        }
+
+        // 先校验设备是否存在
+        EgDevice device = this.getById(deviceId);
+        Optional.ofNullable(device).orElseThrow(() -> new BusinessException("门禁设备信息不存在"));
+
+        // 先清空原有绑定关系
+        egDevicePersonBindMapper.deleteByDeviceId(deviceId);
+
+        // 为空则视为解绑所有人员
+        if (!StringUtils.isNotBlank(personIds)) {
+            return;
+        }
+
+        String[] personIdArr = personIds.split(",");
+        int loginNotify = (isLoginNotify == null) ? 0 : isLoginNotify;
+        for (String personIdStr : personIdArr) {
+            if (!StringUtils.isNotBlank(personIdStr)) {
+                continue;
+            }
+            EgDevicePersonBind bind = new EgDevicePersonBind();
+            bind.setDeviceId(deviceId);
+            bind.setPersonId(Integer.parseInt(personIdStr.trim()));
+            bind.setIsLoginNotify(loginNotify);
+            egDevicePersonBindMapper.insert(bind);
         }
     }
 }

+ 17 - 10
service-eg/service-eg-biz/src/main/java/com/usky/eg/service/impl/EgRecordServiceImpl.java

@@ -47,23 +47,31 @@ public class EgRecordServiceImpl extends AbstractCrudService<EgRecordMapper, EgR
     @Override
     public void add(EgRecord egRecord){
         Integer tenantId;
-        if(StringUtils.isNotBlank(egRecord.getDomain())){
-            tenantId = egDeviceMapper.sysTenantId(egRecord.getDomain());
-        }else{
-            tenantId = SecurityUtils.getTenantId();
-        }
-
+        
+        // 通过deviceUuid查询设备获取tenantId
         LambdaQueryWrapper<EgDevice> queryWrapper = Wrappers.lambdaQuery();
         if(StringUtils.isBlank(egRecord.getDeviceUuid())){
             throw new BusinessException("设备Uuid不能为空");
         }
-        queryWrapper.eq(EgDevice::getDeviceUuid,egRecord.getDeviceUuid());
+        queryWrapper.select(EgDevice::getId, EgDevice::getTenantId)
+                .eq(EgDevice::getDeviceUuid,egRecord.getDeviceUuid());
         EgDevice one = egDeviceService.getOne(queryWrapper);
+        if(one == null){
+            throw new BusinessException("设备未注册,请先注册");
+        }
+        tenantId = one.getTenantId();
         egRecord.setEgDeviceId(one.getId());
-
         egRecord.setCreateTime(LocalDateTime.now());
         egRecord.setTenantId(tenantId);
-        egRecord.setDeptId(SecurityUtils.getLoginUser().getSysUser().getDeptId().intValue());
+        
+        // 安全获取部门ID
+        Integer deptId = null;
+        if(SecurityUtils.getLoginUser() != null 
+                && SecurityUtils.getLoginUser().getSysUser() != null 
+                && SecurityUtils.getLoginUser().getSysUser().getDeptId() != null){
+            deptId = SecurityUtils.getLoginUser().getSysUser().getDeptId().intValue();
+        }
+        egRecord.setDeptId(deptId);
 
         this.save(egRecord);
     }
@@ -114,7 +122,6 @@ public class EgRecordServiceImpl extends AbstractCrudService<EgRecordMapper, EgR
             }
         }
 
-
         return new CommonPage<>(page.getRecords(),page.getTotal(),requestVO.getSize(),requestVO.getCurrent());
     }
 }

+ 21 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/service/vo/EgDeviceBindFacePersonQueryVO.java

@@ -0,0 +1,21 @@
+package com.usky.eg.service.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/** 设备绑定人员分页查询参数 */
+@Data
+public class EgDeviceBindFacePersonQueryVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private Integer current;
+    private Integer size;
+    /** 设备编码,空则当前租户全部设备 */
+    private String deviceCode;
+    /** 姓名模糊,可空 */
+    private String fullName;
+    /** 人脸状态 0/1,可空 */
+    private Integer faceStatus;
+}

+ 21 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/service/vo/EgDeviceBindFacePersonVO.java

@@ -0,0 +1,21 @@
+package com.usky.eg.service.vo;
+
+import lombok.Data;
+
+import java.io.Serializable;
+
+/** 绑定人员列表项 */
+@Data
+public class EgDeviceBindFacePersonVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    private String fullName;
+    private Integer age;
+    private Integer gender;
+    private String linkPhone;
+    private String faceBase;
+    private Integer vefNum;
+    private Byte faceStatus;
+    private String cardNum;
+}

+ 9 - 0
service-eg/service-eg-biz/src/main/java/com/usky/eg/service/vo/EgDeviceRequestVO.java

@@ -45,4 +45,13 @@ public class EgDeviceRequestVO implements Serializable {
      * 设备uuid
      */
     private String deviceUuid;
+
+    /**
+     * 设备编码
+     */
+    private String deviceCode;
+    /**
+     * 设备状态
+     */
+    private Integer deviceStatus;
 }

+ 20 - 0
service-eg/service-eg-biz/src/main/resources/mapper/eg/EgDeviceHeartbeatMapper.xml

@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.eg.mapper.EgDeviceHeartbeatMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.eg.domain.EgDeviceHeartbeat">
+        <id column="id" property="id"/>
+        <result column="device_code" property="deviceCode"/>
+        <result column="ip_addr" property="ipAddr"/>
+        <result column="mac_addr" property="macAddr"/>
+        <result column="device_type" property="deviceType"/>
+        <result column="create_time" property="createTime"/>
+        <result column="model" property="model"/>
+        <result column="manu_facturer"  property="manuFacturer"/>
+        <result column="version" property="version"/>
+        <result column="sdk" property="sdk"/>
+        <result column="device_status" property="deviceStatus"/>
+    </resultMap>
+
+</mapper>

+ 1 - 2
service-eg/service-eg-biz/src/main/resources/mapper/eg/EgDeviceMapper.xml

@@ -11,9 +11,8 @@
         <result column="install_address" property="installAddress" />
         <result column="service_status" property="serviceStatus" />
         <result column="device_ip" property="deviceIp" />
-        <result column="device_port" property="devicePort" />
-        <result column="eg_number" property="egNumber" />
         <result column="bind_face" property="bindFace" />
+        <result column="bind_person" property="bindPerson" />
         <result column="create_by" property="createBy" />
         <result column="update_by" property="updateBy" />
         <result column="create_time" property="createTime" />

+ 62 - 0
service-eg/service-eg-biz/src/main/resources/mapper/eg/EgDevicePersonBindMapper.xml

@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.eg.mapper.EgDevicePersonBindMapper">
+
+    <resultMap id="BaseResultMap" type="com.usky.eg.domain.EgDevicePersonBind">
+        <result column="device_id" property="deviceId" />
+        <result column="person_id" property="personId" />
+        <result column="is_login_notify" property="isLoginNotify" />
+    </resultMap>
+
+    <select id="selectByDeviceIds" resultMap="BaseResultMap">
+        select device_id, person_id, is_login_notify
+        from eg_device_person_bind
+        <where>
+            <if test="deviceIds != null and deviceIds.size() > 0">
+                and device_id in
+                <foreach collection="deviceIds" item="id" open="(" separator="," close=")">
+                    #{id}
+                </foreach>
+            </if>
+        </where>
+    </select>
+
+    <select id="countByDeviceId" resultType="java.lang.Integer">
+        select count(1)
+        from eg_device_person_bind
+        where device_id = #{deviceId}
+    </select>
+
+    <!--
+        通过 sys_user_person 关联 sys_person,最终关联到 eg_device_person_bind,判断某个用户是否对某设备有绑定关系
+        假定字段:sys_user_person(user_id, person_id),sys_person(id),eg_device_person_bind(person_id, device_id)
+    -->
+    <select id="countByUserIdAndDeviceId" resultType="java.lang.Integer">
+        select count(1)
+        from eg_device_person_bind b
+                 join sys_person p on b.person_id = p.id
+                 join sys_user_person up on up.person_id = p.id
+        where up.user_id = #{userId}
+          and b.device_id = #{deviceId}
+    </select>
+
+    <!-- 根据用户ID查询其绑定的设备ID列表 -->
+    <select id="selectDeviceIdsByUserId" resultType="java.lang.Integer">
+        select distinct b.device_id
+        from eg_device_person_bind b
+                 join sys_person p on b.person_id = p.id
+                 join sys_user_person up on up.person_id = p.id
+        where up.user_id = #{userId}
+    </select>
+
+    <delete id="deleteByDeviceId">
+        delete from eg_device_person_bind
+        where device_id = #{deviceId}
+    </delete>
+
+    <insert id="insert" parameterType="com.usky.eg.domain.EgDevicePersonBind">
+        insert into eg_device_person_bind (device_id, person_id, is_login_notify)
+        values (#{deviceId}, #{personId}, #{isLoginNotify})
+    </insert>
+
+</mapper>

+ 32 - 0
service-eg/service-eg-biz/src/main/resources/mapper/eg/SysPersonMapper.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.eg.mapper.SysPersonMapper">
+
+    <resultMap id="BaseResultMap" type="com.usky.eg.domain.SysPerson">
+        <id column="id" property="id"/>
+        <result column="full_name" property="fullName"/>
+        <result column="age" property="age"/>
+        <result column="gender" property="gender"/>
+        <result column="address" property="address"/>
+        <result column="education_degree" property="educationDegree"/>
+        <result column="id_number" property="idNumber"/>
+        <result column="link_phone" property="linkPhone"/>
+        <result column="post_id" property="postId"/>
+        <result column="dept_id" property="deptId"/>
+        <result column="entry_time" property="entryTime"/>
+        <result column="certificate_url1" property="certificateUrl1"/>
+        <result column="certificate_url2" property="certificateUrl2"/>
+        <result column="certificate_url3" property="certificateUrl3"/>
+        <result column="creator" property="creator"/>
+        <result column="create_time" property="createTime"/>
+        <result column="update_person" property="updatePerson"/>
+        <result column="update_time" property="updateTime"/>
+        <result column="face_base" property="faceBase"/>
+        <result column="vef_num" property="vefNum"/>
+        <result column="face_name" property="faceName"/>
+        <result column="remark" property="remark"/>
+        <result column="face_status" property="faceStatus"/>
+        <result column="card_num" property="cardNum"/>
+    </resultMap>
+
+</mapper>

+ 6 - 0
service-job/pom.xml

@@ -88,6 +88,12 @@
             <version>0.0.1</version>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>com.usky</groupId>
+            <artifactId>service-eg-api</artifactId>
+            <version>0.0.1</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 
     <build>

+ 9 - 0
service-job/src/main/java/com/ruoyi/job/task/RyTask.java

@@ -3,6 +3,7 @@ package com.ruoyi.job.task;
 import com.usky.cdi.AlarmDataSyncTaskService;
 import com.usky.cdi.RemotecdiTaskService;
 import com.usky.common.core.utils.StringUtils;
+import com.usky.eg.RemoteEgService;
 import com.usky.meeting.RemoteMeetingService;
 import com.usky.ems.RemoteEmsTaskService;
 import com.usky.fire.RemoteFireService;
@@ -39,6 +40,8 @@ public class RyTask {
     @Autowired
     private RemoteEmsTaskService remoteEmsTaskService;
 
+    @Autowired
+    private RemoteEgService remoteEgService;
 
     public void ryMultipleParams(String s, Boolean b, Long l, Double d, Integer i) {
         System.out.println(StringUtils.format("执行多参方法: 字符串类型{},布尔类型{},长整型{},浮点型{},整形{}", s, b, l, d, i));
@@ -121,4 +124,10 @@ public class RyTask {
         remoteEmsTaskService.sendEnergyHeartbeat();
     }
 
+    // 门禁设备心跳状态
+    public void egDeviceStatus() {
+        System.out.println("egDeviceStatus start......");
+        remoteEgService.egDeviceStatus();
+    }
+
 }

+ 0 - 10
service-meeting/service-meeting-biz/src/main/java/com/usky/meeting/domain/EgDevice.java

@@ -57,16 +57,6 @@ public class EgDevice implements Serializable {
      */
     private String deviceIp;
 
-    /**
-     * 端口
-     */
-    private Integer devicePort;
-
-    /**
-     * 门禁号
-     */
-    private String egNumber;
-
     /**
      * 创建者
      */

+ 0 - 2
service-meeting/service-meeting-biz/src/main/resources/mapper/meeting/EgDeviceMapper.xml

@@ -11,8 +11,6 @@
         <result column="install_address" property="installAddress" />
         <result column="service_status" property="serviceStatus" />
         <result column="device_ip" property="deviceIp" />
-        <result column="device_port" property="devicePort" />
-        <result column="eg_number" property="egNumber" />
         <result column="create_by" property="createBy" />
         <result column="update_by" property="updateBy" />
         <result column="create_time" property="createTime" />

+ 5 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/controller/web/SasAgboxConfigController.java

@@ -51,6 +51,11 @@ public class SasAgboxConfigController {
     @PutMapping("/config")
     public ApiResult<Void> updateConfig(@RequestBody SasConfig config) {
         sasConfigService.updateConfig(config);
+        if (this.mqttService.getConnectionStatus()) {
+            this.mqttService.disconnect();
+        }
+
+        this.mqttService.reconnect();
         return ApiResult.success();
     }
 

+ 21 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/controller/web/SasPatrolUserParamController.java

@@ -0,0 +1,21 @@
+package com.usky.sas.controller.web;
+
+
+import org.springframework.web.bind.annotation.RequestMapping;
+
+import org.springframework.stereotype.Controller;
+
+/**
+ * <p>
+ * 实时电子巡检用户参数表 前端控制器
+ * </p>
+ *
+ * @author fu
+ * @since 2026-03-16
+ */
+@Controller
+@RequestMapping("/sasPatrolUserParam")
+public class SasPatrolUserParamController {
+
+}
+

+ 53 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/domain/SasPatrolUserParam.java

@@ -0,0 +1,53 @@
+package com.usky.sas.domain;
+
+import java.time.LocalDateTime;
+import java.io.Serializable;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * <p>
+ * 实时电子巡检用户参数表
+ * </p>
+ *
+ * @author fu
+ * @since 2026-03-16
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class SasPatrolUserParam implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 主键id
+     */
+    private String id;
+
+    /**
+     * 事件备注
+     */
+    private String status;
+
+    /**
+     * 事件编号
+     */
+    private Integer statusCode;
+
+    /**
+     * 终端编号
+     */
+    private String handleCode;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+
+}

+ 16 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/mapper/SasPatrolUserParamMapper.java

@@ -0,0 +1,16 @@
+package com.usky.sas.mapper;
+
+import com.usky.sas.domain.SasPatrolUserParam;
+import com.usky.common.mybatis.core.CrudMapper;
+
+/**
+ * <p>
+ * 实时电子巡检用户参数表 Mapper 接口
+ * </p>
+ *
+ * @author fu
+ * @since 2026-03-16
+ */
+public interface SasPatrolUserParamMapper extends CrudMapper<SasPatrolUserParam> {
+
+}

+ 191 - 149
service-sas/service-sas-biz/src/main/java/com/usky/sas/mqtt/MqttService.java

@@ -4,6 +4,8 @@ import cn.hutool.core.bean.BeanUtil;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.IdUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpUtil;
+import cn.hutool.json.JSONUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.fasterxml.jackson.databind.DeserializationFeature;
 import com.fasterxml.jackson.databind.JsonNode;
@@ -14,6 +16,8 @@ import com.usky.sas.domain.*;
 import com.usky.sas.mapper.*;
 import com.usky.sas.mqtt.dto.*;
 import com.usky.sas.service.*;
+import com.usky.sas.service.dto.agbox.JsonRpcRequest;
+import com.usky.sas.service.vo.*;
 import lombok.extern.slf4j.Slf4j;
 import org.eclipse.paho.client.mqttv3.*;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -26,7 +30,9 @@ import javax.annotation.PreDestroy;
 import java.time.LocalDateTime;
 import java.time.format.DateTimeFormatter;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -75,6 +81,8 @@ public class MqttService {
     @Autowired
     private SasEventTypeGroupService eventTypeGroupService;
     @Autowired
+    private SasPatrolUserParamService sasPatrolUserParamService;
+    @Autowired
     private SasGisMapper gisMapper;
     
     @Autowired
@@ -378,66 +386,68 @@ public class MqttService {
             String method = root.path("method").asText();
 
             if ("event".equals(method)) {
-                SnapEventMessage message = mapper.readValue(payload, SnapEventMessage.class);
-                if(message.getEventCode() == null && root.get("code") != null) {
-                    message.setEventCode(root.get("code").asInt());
+                SnapEventResult eventResult = (SnapEventResult)this.mapper.readValue(payload, SnapEventResult.class);
+                log.info("实时智能分析");
+                Map<String, Object> map = new HashMap();
+                map.put("eventId", eventResult.getId());
+                JsonRpcRequest getEventInfoJson = new JsonRpcRequest("getEvent", map, (Long)null);
+                Map<String, Object> getEventInfo = new HashMap();
+                getEventInfo.put("key", this.sasConfigService.getConfig().getKeyds());
+                getEventInfo.put("json", getEventInfoJson.toString());
+                log.info("请求AG报文:{}", JSONUtil.toJsonStr(getEventInfo));
+                String resultEvent = HttpUtil.post(this.sasConfigService.getConfig().getHost() + "/agbox/device/snap", getEventInfo);
+                log.info("请求AG响应:{}", resultEvent);
+                SnapEventInfoVo eventInfoVo = (SnapEventInfoVo)this.mapper.readValue(resultEvent, SnapEventInfoVo.class);
+                SnapEventInfoResult result = eventInfoVo.getResult();
+                SnapEventInfo eventInfo = result.getEventInfo();
+                SasSnapEvent bean = (SasSnapEvent)BeanUtil.toBean(eventInfo, SasSnapEvent.class);
+                BrieflyEventInfo info = (BrieflyEventInfo)BeanUtil.toBeanIgnoreError(bean, BrieflyEventInfo.class);
+                if (eventInfo.getEventPic() != null) {
+                    SasPic pic = (SasPic)BeanUtil.toBean(eventInfo.getEventPic(), SasPic.class);
+                    pic.setId(IdUtil.randomUUID());
+                    this.sasPicMapper.insert(pic);
+                    bean.setEventPicId(pic.getId());
+                    info.setEventUrl(pic.getUrl() + pic.getPath());
+                }
+
+                if (eventInfo.getScenePic() != null) {
+                    SasPic pic = (SasPic)BeanUtil.toBean(eventInfo.getScenePic(), SasPic.class);
+                    pic.setId(IdUtil.randomUUID());
+                    this.sasPicMapper.insert(pic);
+                    bean.setScenePicId(pic.getId());
+                    info.setSceneUrl(pic.getUrl() + pic.getPath());
+                }
+                if(eventInfo.getEventCode() == null && root.get("code") != null) {
+                    eventInfo.setEventCode(root.get("code").asInt());
                 }
 
                 // 1. 检查设备是否存在
-                SasDevice device = findDevice(message.getDeviceId(), message.getChannel(), SystemTypeCodeEnum.snap.getCode());
+                SasDevice device = findDevice(eventInfo.getDeviceId(), eventInfo.getChannel(), SystemTypeCodeEnum.snap.getCode());
                 if (device == null) {
-                    log.warn("设备不存在,丢弃抓拍事件: deviceId={}, channel={}", message.getDeviceId(), message.getChannel());
+                    log.warn("设备不存在,丢弃抓拍事件: deviceId={}, channel={}", eventInfo.getDeviceId(), eventInfo.getChannel());
                     return;
                 }
 
-                SasSnapEvent event = new SasSnapEvent();
-
-                String eventId = message.getId() != null && !message.getId().isEmpty()
-                        ? message.getId()
+                String eventId = eventResult.getId() != null && !eventResult.getId().isEmpty()
+                        ? eventResult.getId()
                         : IdUtil.randomUUID();
                 // 如果该事件已存在(设备重发/重复消息),避免主键冲突,直接跳过
                 if (snapEventService.getById(eventId) != null) {
                     log.info("抓拍事件已存在, 跳过保存, eventId={}", eventId);
                     return;
                 }
-                event.setEventId(eventId);
-                BeanUtil.copyProperties(message, event, "eventId");
-                event.setTriggerTime(parseTime(message.getTriggerTime()));
-                event.setCreateTime(LocalDateTime.now());
-                event.setUpdateTime(LocalDateTime.now());
+                bean.setEventId(eventId);
+                BeanUtil.copyProperties(eventInfo, bean, "eventId");
+                bean.setTriggerTime(parseTime(eventInfo.getTriggerTime()));
+                bean.setCreateTime(LocalDateTime.now());
+                bean.setUpdateTime(LocalDateTime.now());
 
-                // 处理图片信息
-                if (message.getSnapPic() != null) {
-                    SnapEventMessage.PicInfo picInfo = message.getSnapPic();
-                    if (picInfo != null && StrUtil.isNotBlank(picInfo.getUrl()) && StrUtil.isNotBlank(picInfo.getPath())) {
-                        SasPic pic = new SasPic();
-                        pic.setId(IdUtil.randomUUID());
-                        pic.setUrl(picInfo.getUrl());
-                        pic.setPath(picInfo.getPath());
-                        pic.setCreateTime(LocalDateTime.now());
-                        pic.setUpdateTime(LocalDateTime.now());
-                        sasPicMapper.insert(pic);
-                        
-                        // 设置事件图片ID
-                        event.setEventPicId(pic.getId());
-                    }
-                }
-
-                snapEventService.save(event);
+                snapEventService.save(bean);
 
-                BrieflyEventInfo info = BeanUtil.toBean(event, BrieflyEventInfo.class);
-                info.setTriggerTime(message.getTriggerTime());
+                info.setTriggerTime(eventInfo.getTriggerTime());
                 info.setCreateTime(LocalDateTime.now().format(formatter));
                 
-                // 如果前面已经设置了eventUrl,这里需要保持
-                if (info.getEventUrl() == null && event.getEventPicId() != null) {
-                    SasPic pic = sasPicMapper.selectById(event.getEventPicId());
-                    if (pic != null && StrUtil.isNotBlank(pic.getUrl()) && StrUtil.isNotBlank(pic.getPath())) {
-                        info.setEventUrl(pic.getUrl() + pic.getPath());
-                    }
-                }
-                
-                SasSnapTypeCode code = snapTypeCodeMapper.selectById(event.getEventCode());
+                SasSnapTypeCode code = snapTypeCodeMapper.selectById(bean.getEventCode());
                 if (code != null) {
                     info.setEventTypeName(code.getName());
                 }
@@ -450,7 +460,7 @@ public class MqttService {
                 info.setDeviceType(SystemTypeCodeEnum.snap.getCode());
                 SasWebSocket.sendAll(info);
 
-                log.info("抓拍事件保存并推送成功, eventId={}", event.getEventId());
+                log.info("抓拍事件保存并推送成功, eventId={}", bean.getEventId());
             } else if ("heart".equals(method)) {
                 handleHeartbeat(payload, SystemTypeCodeEnum.snap.getCode());
             }
@@ -554,21 +564,53 @@ public class MqttService {
             String method = root.path("method").asText();
 
             if ("event".equals(method)) {
-                ParkingEventMessage message = mapper.readValue(payload, ParkingEventMessage.class);
-                if(message.getEventCode() == null && root.get("code") != null) {
-                    message.setEventCode(root.get("code").asInt());
+                ParkingEventResult eventResult = (ParkingEventResult)this.mapper.readValue(payload, ParkingEventResult.class);
+                Map<String, Object> map = new HashMap();
+                map.put("eventId", eventResult.getId());
+                JsonRpcRequest getParkingEventInfoJson = new JsonRpcRequest("getEvent", map, (Long)null);
+                Map<String, Object> getParkingEventInfo = new HashMap();
+                getParkingEventInfo.put("key", this.sasConfigService.getConfig().getKeyds());
+                getParkingEventInfo.put("json", getParkingEventInfoJson.toString());
+                log.info("请求AG报文:{}", JSONUtil.toJsonStr(getParkingEventInfo));
+                String resultEvent = HttpUtil.post(this.sasConfigService.getConfig().getHost() + "/agbox/device/parking", getParkingEventInfo);
+                log.info("请求AG响应:{}", resultEvent);
+                ParkingEventInfoVo parkingEventInfoVo = (ParkingEventInfoVo)this.mapper.readValue(resultEvent, ParkingEventInfoVo.class);
+                ParkingEventInfoResult result = parkingEventInfoVo.getResult();
+                ParkingEventInfo eventInfo = result.getEventInfo();
+                SasParkingEvent bean = (SasParkingEvent)BeanUtil.toBean(eventInfo, SasParkingEvent.class);
+                bean.setCreateTime(LocalDateTime.parse(eventInfo.getReceivingTime(), this.formatter));
+                bean.setTriggerTime(LocalDateTime.parse(eventInfo.getTriggerTime(), this.formatter));
+                bean.setEventId(eventResult.getId());
+                bean.setAccessType(eventInfo.getIo());
+                bean.setCarType(eventInfo.getCarCode());
+                bean.setPlateType(eventInfo.getPlateCode());
+                if (eventInfo.getEventPic() != null) {
+                    SasPic pic = (SasPic)BeanUtil.toBean(eventInfo.getEventPic(), SasPic.class);
+                    pic.setId(IdUtil.randomUUID());
+                    this.sasPicMapper.insert(pic);
+                    bean.setEventPicId(pic.getId());
+                }
+
+                if (eventInfo.getPlacePic() != null) {
+                    SasPic pic = (SasPic)BeanUtil.toBean(eventInfo.getPlacePic(), SasPic.class);
+                    pic.setId(IdUtil.randomUUID());
+                    this.sasPicMapper.insert(pic);
+                    bean.setPlatePicId(pic.getId());
+                }
+                if(eventInfo.getEventCode() == null && root.get("code") != null) {
+                    eventInfo.setEventCode(root.get("code").asInt());
                 }
 
                 // 1. 检查设备是否存在
-                SasDevice device = findDevice(message.getDeviceId(), message.getChannel(), SystemTypeCodeEnum.parking.getCode());
+                SasDevice device = findDevice(eventInfo.getDeviceId(), eventInfo.getChannel(), SystemTypeCodeEnum.parking.getCode());
                 if (device == null) {
-                    log.warn("设备不存在,丢弃停车场事件: deviceId={}, channel={}", message.getDeviceId(), message.getChannel());
+                    log.warn("设备不存在,丢弃停车场事件: deviceId={}, channel={}", eventInfo.getDeviceId(), eventInfo.getChannel());
                     return;
                 }
 
                 SasParkingEvent event = new SasParkingEvent();
-                String eventId = message.getId() != null && !message.getId().isEmpty()
-                        ? message.getId()
+                String eventId = eventResult.getId() != null && !eventResult.getId().isEmpty()
+                        ? eventResult.getId()
                         : IdUtil.randomUUID();
                 // 如果该事件已存在(设备重发/重复消息),避免主键冲突,直接跳过
                 if (parkingEventService.getById(eventId) != null) {
@@ -576,40 +618,24 @@ public class MqttService {
                     return;
                 }
                 event.setEventId(eventId);
-                BeanUtil.copyProperties(message, event, "eventId");
-                event.setTriggerTime(parseTime(message.getTriggerTime()));
+                BeanUtil.copyProperties(eventInfo, event, "eventId");
+                event.setTriggerTime(parseTime(eventInfo.getTriggerTime()));
                 event.setCreateTime(LocalDateTime.now());
                 event.setUpdateTime(LocalDateTime.now());
 
-                // 处理图片信息
-                if (message.getPlatePic() != null) {
-                    ParkingEventMessage.PicInfo picInfo = message.getPlatePic();
-                    if (picInfo != null && StrUtil.isNotBlank(picInfo.getUrl()) && StrUtil.isNotBlank(picInfo.getPath())) {
-                        SasPic pic = new SasPic();
-                        pic.setId(IdUtil.randomUUID());
-                        pic.setUrl(picInfo.getUrl());
-                        pic.setPath(picInfo.getPath());
-                        pic.setCreateTime(LocalDateTime.now());
-                        pic.setUpdateTime(LocalDateTime.now());
-                        sasPicMapper.insert(pic);
-                        
-                        // 设置车牌图片ID
-                        event.setPlatePicId(pic.getId());
-                    }
-                }
-
                 parkingEventService.save(event);
 
                 BrieflyEventInfo info = BeanUtil.toBean(event, BrieflyEventInfo.class);
-                info.setTriggerTime(message.getTriggerTime());
+                info.setTriggerTime(eventInfo.getTriggerTime());
                 info.setCreateTime(LocalDateTime.now().format(formatter));
                 
                 // 设置图片URL(如果有)
-                if (event.getPlatePicId() != null) {
-                    SasPic pic = sasPicMapper.selectById(event.getPlatePicId());
-                    if (pic != null && StrUtil.isNotBlank(pic.getUrl()) && StrUtil.isNotBlank(pic.getPath())) {
-                        info.setEventUrl(pic.getUrl() + pic.getPath());
-                    }
+                if (eventInfo.getEventPic() != null) {
+                    info.setEventUrl(eventInfo.getEventPic().getUrl() + eventInfo.getEventPic().getPath());
+                }
+
+                if (eventInfo.getPlacePic() != null) {
+                    info.setEventUrl(eventInfo.getPlacePic().getUrl() + eventInfo.getPlacePic().getPath());
                 }
                 
                 SasParkingEventCode code = parkingEventCodeMapper.selectById(event.getEventCode());
@@ -728,21 +754,46 @@ public class MqttService {
             String method = root.path("method").asText();
 
             if ("event".equals(method)) {
-                AlarsasEventMessage message = mapper.readValue(payload, AlarsasEventMessage.class);
-                if(message.getEventCode() == null && root.get("code") != null) {
-                    message.setEventCode(root.get("code").asInt());
+                AlarmEventResult eventResult = (AlarmEventResult)this.mapper.readValue(payload, AlarmEventResult.class);
+                Map<String, Object> map = new HashMap();
+                map.put("eventId", eventResult.getId());
+                JsonRpcRequest getAlarmEventInfoJson = new JsonRpcRequest("getEvent", map, (Long)null);
+                Map<String, Object> getAlarmEventInfo = new HashMap();
+                getAlarmEventInfo.put("key", this.sasConfigService.getConfig().getKeyds());
+                getAlarmEventInfo.put("json", getAlarmEventInfoJson.toString());
+                log.info("请求AG报文:{}", JSONUtil.toJsonStr(getAlarmEventInfo));
+                String resultEvent = HttpUtil.post(this.sasConfigService.getConfig().getHost() + "/agbox/device/alarm", getAlarmEventInfo);
+                log.info("请求AG响应:{}", resultEvent);
+                AlarmEventInfoVo alarmEventInfoVo = (AlarmEventInfoVo)this.mapper.readValue(resultEvent, AlarmEventInfoVo.class);
+                AlarmEventInfoResult result = alarmEventInfoVo.getResult();
+                AlarmEventInfo eventInfo = result.getEventInfo();
+
+                SasAlarsasEvent event = new SasAlarsasEvent();
+                if (eventInfo.getEventPic() != null) {
+                    SasPic pic = (SasPic)BeanUtil.toBean(eventInfo.getEventPic(), SasPic.class);
+                    pic.setId(IdUtil.randomUUID());
+                    this.sasPicMapper.insert(pic);
+                    event.setPicId(pic.getId());
+                }
+
+                if (eventInfo.getGis() != null) {
+                    SasGis gis = (SasGis)BeanUtil.toBean(eventInfo.getGis(), SasGis.class);
+                    gis.setId(IdUtil.randomUUID());
+                    this.gisMapper.insert(gis);
+                    event.setGisId(gis.getId());
+                }
+                if(eventInfo.getEventCode() == null && root.get("code") != null) {
+                    eventInfo.setEventCode(root.get("code").asInt());
                 }
 
                 // 1. 检查设备是否存在
-                SasDevice device = findDevice(message.getDeviceId(), message.getChannel(), SystemTypeCodeEnum.alarm.getCode());
+                SasDevice device = findDevice(eventInfo.getDeviceId(), eventInfo.getChannel(), SystemTypeCodeEnum.alarm.getCode());
                 if (device == null) {
-                    log.warn("设备不存在,丢弃入侵报警事件: deviceId={}, channel={}", message.getDeviceId(), message.getChannel());
+                    log.warn("设备不存在,丢弃入侵报警事件: deviceId={}, channel={}", eventInfo.getDeviceId(), eventInfo.getChannel());
                     return;
                 }
-
-                SasAlarsasEvent event = new SasAlarsasEvent();
-                String eventId = message.getId() != null && !message.getId().isEmpty()
-                        ? message.getId()
+                String eventId = eventResult.getId() != null && !eventResult.getId().isEmpty()
+                        ? eventResult.getId()
                         : IdUtil.randomUUID();
                 // 如果该事件已存在(设备重发/重复消息),避免主键冲突,直接跳过
 //                if (alarsasEventService.getById(eventId) != null) {
@@ -750,32 +801,15 @@ public class MqttService {
 //                    return;
 //                }
                 event.setEventId(eventId);
-                BeanUtil.copyProperties(message, event, "eventId");
-                event.setTriggerTime(parseTime(message.getTriggerTime()));
+                BeanUtil.copyProperties(eventResult, event, "eventId");
+                event.setTriggerTime(parseTime(eventInfo.getTriggerTime()));
                 event.setCreateTime(LocalDateTime.now());
                 event.setUpdateTime(LocalDateTime.now());
 
-                // 处理图片信息
-                if (message.getAlarmPic() != null) {
-                    AlarsasEventMessage.PicInfo picInfo = message.getAlarmPic();
-                    if (picInfo != null && StrUtil.isNotBlank(picInfo.getUrl()) && StrUtil.isNotBlank(picInfo.getPath())) {
-                        SasPic pic = new SasPic();
-                        pic.setId(IdUtil.randomUUID());
-                        pic.setUrl(picInfo.getUrl());
-                        pic.setPath(picInfo.getPath());
-                        pic.setCreateTime(LocalDateTime.now());
-                        pic.setUpdateTime(LocalDateTime.now());
-                        sasPicMapper.insert(pic);
-                        
-                        // 设置事件图片ID
-                        event.setPicId(pic.getId());
-                    }
-                }
-
                 alarsasEventService.saveOrUpdate(event);
 
                 BrieflyEventInfo info = BeanUtil.toBean(event, BrieflyEventInfo.class);
-                info.setTriggerTime(message.getTriggerTime());
+                info.setTriggerTime(eventInfo.getTriggerTime());
                 info.setCreateTime(LocalDateTime.now().format(formatter));
                 
                 // 设置图片URL(如果有)
@@ -815,21 +849,30 @@ public class MqttService {
             String method = root.path("method").asText();
 
             if ("event".equals(method)) {
-                PatrolEventMessage message = mapper.readValue(payload, PatrolEventMessage.class);
-                if(message.getEventCode() == null && root.get("code") != null) {
-                    message.setEventCode(root.get("code").asInt());
-                }
+                PatrolEventResult eventResult = (PatrolEventResult)this.mapper.readValue(payload, PatrolEventResult.class);
+                Map<String, Object> map = new HashMap();
+                map.put("eventId", eventResult.getId());
+                JsonRpcRequest getPatrolEventInfoJson = new JsonRpcRequest("getEvent", map, (Long)null);
+                Map<String, Object> getPatrolEventInfo = new HashMap();
+                getPatrolEventInfo.put("key", this.sasConfigService.getConfig().getKeyds());
+                getPatrolEventInfo.put("json", getPatrolEventInfoJson.toString());
+                log.info("请求AG报文:{}", JSONUtil.toJsonStr(getPatrolEventInfo));
+                String resultEvent = HttpUtil.post(this.sasConfigService.getConfig().getHost() + "/agbox/device/patrol", getPatrolEventInfo);
+                log.info("请求AG响应:{}", resultEvent);
+                PatrolEventInfoVo patrolEventInfoVo = (PatrolEventInfoVo)this.mapper.readValue(resultEvent, PatrolEventInfoVo.class);
+                PatrolEventInfoResult result = patrolEventInfoVo.getResult();
+                PatrolEventInfo eventInfo = result.getEventInfo();
 
                 // 1. 检查设备是否存在
-                SasDevice device = findDevice(message.getDeviceId(), 0, SystemTypeCodeEnum.patrol.getCode());
+                SasDevice device = findDevice(eventInfo.getDeviceId(), 0, SystemTypeCodeEnum.patrol.getCode());
                 if (device == null) {
-                    log.warn("设备不存在,丢弃巡检事件: deviceId={}, channel={}", message.getDeviceId(), message.getChannel());
+                    log.warn("设备不存在,丢弃巡检事件: deviceId={}, channel={}", eventInfo.getDeviceId(), eventInfo.getChannel());
                     return;
                 }
 
                 SasPatrolEvent event = new SasPatrolEvent();
-                String eventId = message.getId() != null && !message.getId().isEmpty()
-                        ? message.getId()
+                String eventId = patrolEventInfoVo.getId() != null && !patrolEventInfoVo.getId().isEmpty()
+                        ? patrolEventInfoVo.getId()
                         : IdUtil.randomUUID();
                 // 如果该事件已存在(设备重发/重复消息),避免主键冲突,直接跳过
                 if (patrolEventService.getById(eventId) != null) {
@@ -837,32 +880,30 @@ public class MqttService {
                     return;
                 }
                 event.setEventId(eventId);
-                BeanUtil.copyProperties(message, event, "eventId");
-                event.setTriggerTime(parseTime(message.getTriggerTime()));
+                BeanUtil.copyProperties(eventInfo, event, "eventId");
+                event.setTriggerTime(LocalDateTime.parse(eventInfo.getTriggerTime()));
                 event.setCreateTime(LocalDateTime.now());
                 event.setUpdateTime(LocalDateTime.now());
 
                 // 处理图片信息
-                if (message.getPatrolPic() != null) {
-                    PatrolEventMessage.PicInfo picInfo = message.getPatrolPic();
-                    if (picInfo != null && StrUtil.isNotBlank(picInfo.getUrl()) && StrUtil.isNotBlank(picInfo.getPath())) {
-                        SasPic pic = new SasPic();
-                        pic.setId(IdUtil.randomUUID());
-                        pic.setUrl(picInfo.getUrl());
-                        pic.setPath(picInfo.getPath());
-                        pic.setCreateTime(LocalDateTime.now());
-                        pic.setUpdateTime(LocalDateTime.now());
-                        sasPicMapper.insert(pic);
-                        
-                        // 设置事件图片ID
-                        event.setPicId(pic.getId());
-                    }
+                if (eventInfo.getEventPic() != null) {
+                    SasPic pic = (SasPic) BeanUtil.toBean(eventInfo.getEventPic(), SasPic.class);
+                    pic.setId(IdUtil.randomUUID());
+                    this.sasPicMapper.insert(pic);
+                    event.setPicId(pic.getId());
+                }
+
+                if (eventInfo.getUserParams() != null) {
+                    SasPatrolUserParam userParam = (SasPatrolUserParam)BeanUtil.toBean(eventInfo.getUserParams(), SasPatrolUserParam.class);
+                    userParam.setId(IdUtil.randomUUID());
+                    this.sasPatrolUserParamService.save(userParam);
+                    event.setUserParasasId(userParam.getId());
                 }
 
                 patrolEventService.save(event);
 
                 BrieflyEventInfo info = BeanUtil.toBean(event, BrieflyEventInfo.class);
-                info.setTriggerTime(message.getTriggerTime());
+                info.setTriggerTime(eventInfo.getTriggerTime());
                 info.setCreateTime(LocalDateTime.now().format(formatter));
                 
                 // 设置图片URL(如果有)
@@ -1104,20 +1145,18 @@ public class MqttService {
                 event.setUpdateTime(LocalDateTime.now());
 
                 // 处理图片信息
-                if (message.getCollectionPic() != null) {
-                    CollectionEventMessage.PicInfo picInfo = message.getCollectionPic();
-                    if (picInfo != null && StrUtil.isNotBlank(picInfo.getUrl()) && StrUtil.isNotBlank(picInfo.getPath())) {
-                        SasPic pic = new SasPic();
-                        pic.setId(IdUtil.randomUUID());
-                        pic.setUrl(picInfo.getUrl());
-                        pic.setPath(picInfo.getPath());
-                        pic.setCreateTime(LocalDateTime.now());
-                        pic.setUpdateTime(LocalDateTime.now());
-                        sasPicMapper.insert(pic);
-                        
-                        // 设置事件图片ID
-                        event.setPicId(pic.getId());
-                    }
+                if (message.getScenePicInfo() != null) {
+                    SasPic bean = (SasPic)BeanUtil.toBean(message.getScenePicInfo(), SasPic.class);
+                    bean.setId(IdUtil.randomUUID());
+                    this.sasPicMapper.insert(bean);
+                    event.setScenePicId(bean.getId());
+                }
+
+                if (message.getEventPicInfo() != null) {
+                    SasPic bean = (SasPic)BeanUtil.toBean(message.getEventPicInfo(), SasPic.class);
+                    bean.setId(IdUtil.randomUUID());
+                    this.sasPicMapper.insert(bean);
+                    event.setPicId(bean.getId());
                 }
 
                 collectionEventService.save(event);
@@ -1127,11 +1166,12 @@ public class MqttService {
                 info.setCreateTime(LocalDateTime.now().format(formatter));
                 
                 // 设置图片URL(如果有)
-                if (event.getPicId() != null) {
-                    SasPic pic = sasPicMapper.selectById(event.getPicId());
-                    if (pic != null && StrUtil.isNotBlank(pic.getUrl()) && StrUtil.isNotBlank(pic.getPath())) {
-                        info.setEventUrl(pic.getUrl() + pic.getPath());
-                    }
+                if (message.getEventPicInfo() != null) {
+                    info.setEventUrl(message.getEventPicInfo().getUrl() + message.getEventPicInfo().getPath());
+                }
+
+                if (message.getScenePicInfo() != null) {
+                    info.setEventUrl(message.getScenePicInfo().getUrl() + message.getScenePicInfo().getPath());
                 }
                 
                 SasCollectionEventCode code = collectionEventCodeMapper.selectById(event.getEventCode());
@@ -1294,4 +1334,6 @@ public class MqttService {
             log.error("断开MQTT连接失败", e);
         }
     }
+
+
 }

+ 2 - 1
service-sas/service-sas-biz/src/main/java/com/usky/sas/mqtt/dto/CollectionEventMessage.java

@@ -23,7 +23,8 @@ public class CollectionEventMessage {
     private BigDecimal total;
 
     // 图片信息
-    private PicInfo collectionPic;
+    private PicInfo eventPicInfo;
+    private PicInfo scenePicInfo;
 
     @Data
     public static class PicInfo {

+ 16 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/SasPatrolUserParamService.java

@@ -0,0 +1,16 @@
+package com.usky.sas.service;
+
+import com.usky.sas.domain.SasPatrolUserParam;
+import com.usky.common.mybatis.core.CrudService;
+
+/**
+ * <p>
+ * 实时电子巡检用户参数表 服务类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-03-16
+ */
+public interface SasPatrolUserParamService extends CrudService<SasPatrolUserParam> {
+
+}

+ 127 - 13
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/dto/agbox/JsonRpcRequest.java

@@ -1,30 +1,144 @@
 package com.usky.sas.service.dto.agbox;
 
 import com.fasterxml.jackson.annotation.JsonInclude;
-import lombok.Data;
-
+import com.fasterxml.jackson.annotation.JsonInclude.Include;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.HashMap;
 import java.util.Map;
 
-/**
- * AG 接口 JsonRpc 请求体
- */
-@Data
-@JsonInclude(JsonInclude.Include.NON_NULL)
+@JsonInclude(Include.NON_NULL)
 public class JsonRpcRequest {
+    private String jsonrpc = "2.0";
+    private String method;
+    private Object params;
+    private Long id;
 
-    private static final String JSONRPC = "2.0";
+    public JsonRpcRequest(String method, Object params, Long id) {
+        this.method = method;
+        this.params = params;
+        this.id = id;
+    }
 
-    private String jsonrpc = JSONRPC;
+    public String toString() {
+        try {
+            return (new ObjectMapper()).writeValueAsString(this);
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
 
-    private String method;
+    public static void main(String[] args) {
+        int a = 5;
+        int b = 3;
+        Map<String, Object> map = new HashMap();
+        map.put("a", a);
+        map.put("b", b);
+        JsonRpcRequest request = new JsonRpcRequest("add", map, 1L);
+        System.out.println(request);
+    }
 
-    private Map<String, Object> params;
+    public String getJsonrpc() {
+        return this.jsonrpc;
+    }
 
-    private Long id;
+    public String getMethod() {
+        return this.method;
+    }
+
+    public Object getParams() {
+        return this.params;
+    }
+
+    public Long getId() {
+        return this.id;
+    }
 
-    public JsonRpcRequest(String method, Map<String, Object> params, Long id) {
+    public void setJsonrpc(String jsonrpc) {
+        this.jsonrpc = jsonrpc;
+    }
+
+    public void setMethod(String method) {
         this.method = method;
+    }
+
+    public void setParams(Object params) {
         this.params = params;
+    }
+
+    public void setId(Long id) {
         this.id = id;
     }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof JsonRpcRequest)) {
+            return false;
+        } else {
+            JsonRpcRequest other = (JsonRpcRequest)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$jsonrpc = this.getJsonrpc();
+                Object other$jsonrpc = other.getJsonrpc();
+                if (this$jsonrpc == null) {
+                    if (other$jsonrpc != null) {
+                        return false;
+                    }
+                } else if (!this$jsonrpc.equals(other$jsonrpc)) {
+                    return false;
+                }
+
+                Object this$method = this.getMethod();
+                Object other$method = other.getMethod();
+                if (this$method == null) {
+                    if (other$method != null) {
+                        return false;
+                    }
+                } else if (!this$method.equals(other$method)) {
+                    return false;
+                }
+
+                Object this$params = this.getParams();
+                Object other$params = other.getParams();
+                if (this$params == null) {
+                    if (other$params != null) {
+                        return false;
+                    }
+                } else if (!this$params.equals(other$params)) {
+                    return false;
+                }
+
+                Object this$id = this.getId();
+                Object other$id = other.getId();
+                if (this$id == null) {
+                    if (other$id != null) {
+                        return false;
+                    }
+                } else if (!this$id.equals(other$id)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof JsonRpcRequest;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $jsonrpc = this.getJsonrpc();
+        result = result * 59 + ($jsonrpc == null ? 43 : $jsonrpc.hashCode());
+        Object $method = this.getMethod();
+        result = result * 59 + ($method == null ? 43 : $method.hashCode());
+        Object $params = this.getParams();
+        result = result * 59 + ($params == null ? 43 : $params.hashCode());
+        Object $id = this.getId();
+        result = result * 59 + ($id == null ? 43 : $id.hashCode());
+        return result;
+    }
 }

+ 12 - 9
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasDeviceServiceImpl.java

@@ -367,15 +367,18 @@ public class SasDeviceServiceImpl extends AbstractCrudService<SasDeviceMapper, S
             return;
         }
         String ipcId = device.getBindingIpc();
-        device.setBindingIpc(null);
-        device.setUpdateTime(LocalDateTime.now());
-        this.updateById(device);
-        SasDevice ipc = this.getById(ipcId);
-        if (ipc != null) {
-            ipc.setIsBinding(false);
-            ipc.setUpdateTime(LocalDateTime.now());
-            this.updateById(ipc);
-        }
+        // 显式更新绑定关系,避免依赖通用 updateById 未更新 binding_ipc 的问题
+        this.lambdaUpdate()
+                .set(SasDevice::getBindingIpc, null)
+                .set(SasDevice::getUpdateTime, LocalDateTime.now())
+                .eq(SasDevice::getId, id)
+                .update();
+
+        this.lambdaUpdate()
+                .set(SasDevice::getIsBinding, false)
+                .set(SasDevice::getUpdateTime, LocalDateTime.now())
+                .eq(SasDevice::getId, ipcId)
+                .update();
     }
 
     @Override

+ 20 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasPatrolUserParamServiceImpl.java

@@ -0,0 +1,20 @@
+package com.usky.sas.service.impl;
+
+import com.usky.sas.domain.SasPatrolUserParam;
+import com.usky.sas.mapper.SasPatrolUserParamMapper;
+import com.usky.sas.service.SasPatrolUserParamService;
+import com.usky.common.mybatis.core.AbstractCrudService;
+import org.springframework.stereotype.Service;
+
+/**
+ * <p>
+ * 实时电子巡检用户参数表 服务实现类
+ * </p>
+ *
+ * @author fu
+ * @since 2026-03-16
+ */
+@Service
+public class SasPatrolUserParamServiceImpl extends AbstractCrudService<SasPatrolUserParamMapper, SasPatrolUserParam> implements SasPatrolUserParamService {
+
+}

+ 2 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/impl/SasSystemConfigServiceImpl.java

@@ -4,7 +4,9 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.usky.common.mybatis.core.AbstractCrudService;
 import com.usky.sas.domain.SasSystemConfig;
 import com.usky.sas.mapper.SasSystemConfigMapper;
+import com.usky.sas.mqtt.MqttService;
 import com.usky.sas.service.SasSystemConfigService;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;

+ 285 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/AlarmEventInfo.java

@@ -0,0 +1,285 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.usky.sas.domain.SasGis;
+import com.usky.sas.domain.SasPic;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class AlarmEventInfo {
+    private String deviceId;
+    private Integer channel;
+    private Integer eventCode;
+    private String triggerTime;
+    private String receivingTime;
+    private String note;
+    private SasGis gis;
+    private SasPic eventPic;
+    private Integer systemCode;
+    private String relPerson;
+    private String dealPerson;
+
+    public AlarmEventInfo() {
+    }
+
+    public String getDeviceId() {
+        return this.deviceId;
+    }
+
+    public Integer getChannel() {
+        return this.channel;
+    }
+
+    public Integer getEventCode() {
+        return this.eventCode;
+    }
+
+    public String getTriggerTime() {
+        return this.triggerTime;
+    }
+
+    public String getReceivingTime() {
+        return this.receivingTime;
+    }
+
+    public String getNote() {
+        return this.note;
+    }
+
+    public SasGis getGis() {
+        return this.gis;
+    }
+
+    public SasPic getEventPic() {
+        return this.eventPic;
+    }
+
+    public Integer getSystemCode() {
+        return this.systemCode;
+    }
+
+    public String getRelPerson() {
+        return this.relPerson;
+    }
+
+    public String getDealPerson() {
+        return this.dealPerson;
+    }
+
+    public AlarmEventInfo setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+        return this;
+    }
+
+    public AlarmEventInfo setChannel(Integer channel) {
+        this.channel = channel;
+        return this;
+    }
+
+    public AlarmEventInfo setEventCode(Integer eventCode) {
+        this.eventCode = eventCode;
+        return this;
+    }
+
+    public AlarmEventInfo setTriggerTime(String triggerTime) {
+        this.triggerTime = triggerTime;
+        return this;
+    }
+
+    public AlarmEventInfo setReceivingTime(String receivingTime) {
+        this.receivingTime = receivingTime;
+        return this;
+    }
+
+    public AlarmEventInfo setNote(String note) {
+        this.note = note;
+        return this;
+    }
+
+    public AlarmEventInfo setGis(SasGis gis) {
+        this.gis = gis;
+        return this;
+    }
+
+    public AlarmEventInfo setEventPic(SasPic eventPic) {
+        this.eventPic = eventPic;
+        return this;
+    }
+
+    public AlarmEventInfo setSystemCode(Integer systemCode) {
+        this.systemCode = systemCode;
+        return this;
+    }
+
+    public AlarmEventInfo setRelPerson(String relPerson) {
+        this.relPerson = relPerson;
+        return this;
+    }
+
+    public AlarmEventInfo setDealPerson(String dealPerson) {
+        this.dealPerson = dealPerson;
+        return this;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof AlarmEventInfo)) {
+            return false;
+        } else {
+            AlarmEventInfo other = (AlarmEventInfo)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$deviceId = this.getDeviceId();
+                Object other$deviceId = other.getDeviceId();
+                if (this$deviceId == null) {
+                    if (other$deviceId != null) {
+                        return false;
+                    }
+                } else if (!this$deviceId.equals(other$deviceId)) {
+                    return false;
+                }
+
+                Object this$channel = this.getChannel();
+                Object other$channel = other.getChannel();
+                if (this$channel == null) {
+                    if (other$channel != null) {
+                        return false;
+                    }
+                } else if (!this$channel.equals(other$channel)) {
+                    return false;
+                }
+
+                Object this$eventCode = this.getEventCode();
+                Object other$eventCode = other.getEventCode();
+                if (this$eventCode == null) {
+                    if (other$eventCode != null) {
+                        return false;
+                    }
+                } else if (!this$eventCode.equals(other$eventCode)) {
+                    return false;
+                }
+
+                Object this$triggerTime = this.getTriggerTime();
+                Object other$triggerTime = other.getTriggerTime();
+                if (this$triggerTime == null) {
+                    if (other$triggerTime != null) {
+                        return false;
+                    }
+                } else if (!this$triggerTime.equals(other$triggerTime)) {
+                    return false;
+                }
+
+                Object this$receivingTime = this.getReceivingTime();
+                Object other$receivingTime = other.getReceivingTime();
+                if (this$receivingTime == null) {
+                    if (other$receivingTime != null) {
+                        return false;
+                    }
+                } else if (!this$receivingTime.equals(other$receivingTime)) {
+                    return false;
+                }
+
+                Object this$note = this.getNote();
+                Object other$note = other.getNote();
+                if (this$note == null) {
+                    if (other$note != null) {
+                        return false;
+                    }
+                } else if (!this$note.equals(other$note)) {
+                    return false;
+                }
+
+                Object this$gis = this.getGis();
+                Object other$gis = other.getGis();
+                if (this$gis == null) {
+                    if (other$gis != null) {
+                        return false;
+                    }
+                } else if (!this$gis.equals(other$gis)) {
+                    return false;
+                }
+
+                Object this$eventPic = this.getEventPic();
+                Object other$eventPic = other.getEventPic();
+                if (this$eventPic == null) {
+                    if (other$eventPic != null) {
+                        return false;
+                    }
+                } else if (!this$eventPic.equals(other$eventPic)) {
+                    return false;
+                }
+
+                Object this$systemCode = this.getSystemCode();
+                Object other$systemCode = other.getSystemCode();
+                if (this$systemCode == null) {
+                    if (other$systemCode != null) {
+                        return false;
+                    }
+                } else if (!this$systemCode.equals(other$systemCode)) {
+                    return false;
+                }
+
+                Object this$relPerson = this.getRelPerson();
+                Object other$relPerson = other.getRelPerson();
+                if (this$relPerson == null) {
+                    if (other$relPerson != null) {
+                        return false;
+                    }
+                } else if (!this$relPerson.equals(other$relPerson)) {
+                    return false;
+                }
+
+                Object this$dealPerson = this.getDealPerson();
+                Object other$dealPerson = other.getDealPerson();
+                if (this$dealPerson == null) {
+                    if (other$dealPerson != null) {
+                        return false;
+                    }
+                } else if (!this$dealPerson.equals(other$dealPerson)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof AlarmEventInfo;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $deviceId = this.getDeviceId();
+        result = result * 59 + ($deviceId == null ? 43 : $deviceId.hashCode());
+        Object $channel = this.getChannel();
+        result = result * 59 + ($channel == null ? 43 : $channel.hashCode());
+        Object $eventCode = this.getEventCode();
+        result = result * 59 + ($eventCode == null ? 43 : $eventCode.hashCode());
+        Object $triggerTime = this.getTriggerTime();
+        result = result * 59 + ($triggerTime == null ? 43 : $triggerTime.hashCode());
+        Object $receivingTime = this.getReceivingTime();
+        result = result * 59 + ($receivingTime == null ? 43 : $receivingTime.hashCode());
+        Object $note = this.getNote();
+        result = result * 59 + ($note == null ? 43 : $note.hashCode());
+        Object $gis = this.getGis();
+        result = result * 59 + ($gis == null ? 43 : $gis.hashCode());
+        Object $eventPic = this.getEventPic();
+        result = result * 59 + ($eventPic == null ? 43 : $eventPic.hashCode());
+        Object $systemCode = this.getSystemCode();
+        result = result * 59 + ($systemCode == null ? 43 : $systemCode.hashCode());
+        Object $relPerson = this.getRelPerson();
+        result = result * 59 + ($relPerson == null ? 43 : $relPerson.hashCode());
+        Object $dealPerson = this.getDealPerson();
+        result = result * 59 + ($dealPerson == null ? 43 : $dealPerson.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "AlarmEventInfo(deviceId=" + this.getDeviceId() + ", channel=" + this.getChannel() + ", eventCode=" + this.getEventCode() + ", triggerTime=" + this.getTriggerTime() + ", receivingTime=" + this.getReceivingTime() + ", note=" + this.getNote() + ", gis=" + this.getGis() + ", eventPic=" + this.getEventPic() + ", systemCode=" + this.getSystemCode() + ", relPerson=" + this.getRelPerson() + ", dealPerson=" + this.getDealPerson() + ")";
+    }
+}

+ 63 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/AlarmEventInfoResult.java

@@ -0,0 +1,63 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class AlarmEventInfoResult {
+    private AlarmEventInfo eventInfo;
+
+    public AlarmEventInfoResult() {
+    }
+
+    public AlarmEventInfo getEventInfo() {
+        return this.eventInfo;
+    }
+
+    public AlarmEventInfoResult setEventInfo(AlarmEventInfo eventInfo) {
+        this.eventInfo = eventInfo;
+        return this;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof AlarmEventInfoResult)) {
+            return false;
+        } else {
+            AlarmEventInfoResult other = (AlarmEventInfoResult)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$eventInfo = this.getEventInfo();
+                Object other$eventInfo = other.getEventInfo();
+                if (this$eventInfo == null) {
+                    if (other$eventInfo != null) {
+                        return false;
+                    }
+                } else if (!this$eventInfo.equals(other$eventInfo)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof AlarmEventInfoResult;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $eventInfo = this.getEventInfo();
+        result = result * 59 + ($eventInfo == null ? 43 : $eventInfo.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "AlarmEventInfoResult(eventInfo=" + this.getEventInfo() + ")";
+    }
+}

+ 107 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/AlarmEventInfoVo.java

@@ -0,0 +1,107 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class AlarmEventInfoVo {
+    private String jsonrpc;
+    private AlarmEventInfoResult result;
+    private String id;
+
+    public AlarmEventInfoVo() {
+    }
+
+    public String getJsonrpc() {
+        return this.jsonrpc;
+    }
+
+    public AlarmEventInfoResult getResult() {
+        return this.result;
+    }
+
+    public String getId() {
+        return this.id;
+    }
+
+    public AlarmEventInfoVo setJsonrpc(String jsonrpc) {
+        this.jsonrpc = jsonrpc;
+        return this;
+    }
+
+    public AlarmEventInfoVo setResult(AlarmEventInfoResult result) {
+        this.result = result;
+        return this;
+    }
+
+    public AlarmEventInfoVo setId(String id) {
+        this.id = id;
+        return this;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof AlarmEventInfoVo)) {
+            return false;
+        } else {
+            AlarmEventInfoVo other = (AlarmEventInfoVo)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$jsonrpc = this.getJsonrpc();
+                Object other$jsonrpc = other.getJsonrpc();
+                if (this$jsonrpc == null) {
+                    if (other$jsonrpc != null) {
+                        return false;
+                    }
+                } else if (!this$jsonrpc.equals(other$jsonrpc)) {
+                    return false;
+                }
+
+                Object this$result = this.getResult();
+                Object other$result = other.getResult();
+                if (this$result == null) {
+                    if (other$result != null) {
+                        return false;
+                    }
+                } else if (!this$result.equals(other$result)) {
+                    return false;
+                }
+
+                Object this$id = this.getId();
+                Object other$id = other.getId();
+                if (this$id == null) {
+                    if (other$id != null) {
+                        return false;
+                    }
+                } else if (!this$id.equals(other$id)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof AlarmEventInfoVo;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $jsonrpc = this.getJsonrpc();
+        result = result * 59 + ($jsonrpc == null ? 43 : $jsonrpc.hashCode());
+        Object $result = this.getResult();
+        result = result * 59 + ($result == null ? 43 : $result.hashCode());
+        Object $id = this.getId();
+        result = result * 59 + ($id == null ? 43 : $id.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "AlarmEventInfoVo(jsonrpc=" + this.getJsonrpc() + ", result=" + this.getResult() + ", id=" + this.getId() + ")";
+    }
+}

+ 125 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/AlarmEventResult.java

@@ -0,0 +1,125 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class AlarmEventResult {
+    private String method;
+    private String id;
+    private Integer code;
+    private Integer channel;
+
+    public AlarmEventResult() {
+    }
+
+    public String getMethod() {
+        return this.method;
+    }
+
+    public String getId() {
+        return this.id;
+    }
+
+    public Integer getCode() {
+        return this.code;
+    }
+
+    public Integer getChannel() {
+        return this.channel;
+    }
+
+    public void setMethod(String method) {
+        this.method = method;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public void setCode(Integer code) {
+        this.code = code;
+    }
+
+    public void setChannel(Integer channel) {
+        this.channel = channel;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof AlarmEventResult)) {
+            return false;
+        } else {
+            AlarmEventResult other = (AlarmEventResult)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$method = this.getMethod();
+                Object other$method = other.getMethod();
+                if (this$method == null) {
+                    if (other$method != null) {
+                        return false;
+                    }
+                } else if (!this$method.equals(other$method)) {
+                    return false;
+                }
+
+                Object this$id = this.getId();
+                Object other$id = other.getId();
+                if (this$id == null) {
+                    if (other$id != null) {
+                        return false;
+                    }
+                } else if (!this$id.equals(other$id)) {
+                    return false;
+                }
+
+                Object this$code = this.getCode();
+                Object other$code = other.getCode();
+                if (this$code == null) {
+                    if (other$code != null) {
+                        return false;
+                    }
+                } else if (!this$code.equals(other$code)) {
+                    return false;
+                }
+
+                Object this$channel = this.getChannel();
+                Object other$channel = other.getChannel();
+                if (this$channel == null) {
+                    if (other$channel != null) {
+                        return false;
+                    }
+                } else if (!this$channel.equals(other$channel)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof AlarmEventResult;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $method = this.getMethod();
+        result = result * 59 + ($method == null ? 43 : $method.hashCode());
+        Object $id = this.getId();
+        result = result * 59 + ($id == null ? 43 : $id.hashCode());
+        Object $code = this.getCode();
+        result = result * 59 + ($code == null ? 43 : $code.hashCode());
+        Object $channel = this.getChannel();
+        result = result * 59 + ($channel == null ? 43 : $channel.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "AlarmEventResult(method=" + this.getMethod() + ", id=" + this.getId() + ", code=" + this.getCode() + ", channel=" + this.getChannel() + ")";
+    }
+}

+ 372 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/ParkingEventInfo.java

@@ -0,0 +1,372 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.usky.sas.domain.SasPic;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class ParkingEventInfo {
+    private String deviceId;
+    private Integer channel;
+    private Integer eventCode;
+    private String triggerTime;
+    private String receivingTime;
+    private String note;
+    private SasPic eventPic;
+    private SasPic placePic;
+    private Integer similarity;
+    private String entranceCode;
+    private Integer io;
+    private String plateNo;
+    private Integer plateCode;
+    private Integer carCode;
+    private String plateColor;
+
+    public ParkingEventInfo() {
+    }
+
+    public String getDeviceId() {
+        return this.deviceId;
+    }
+
+    public Integer getChannel() {
+        return this.channel;
+    }
+
+    public Integer getEventCode() {
+        return this.eventCode;
+    }
+
+    public String getTriggerTime() {
+        return this.triggerTime;
+    }
+
+    public String getReceivingTime() {
+        return this.receivingTime;
+    }
+
+    public String getNote() {
+        return this.note;
+    }
+
+    public SasPic getEventPic() {
+        return this.eventPic;
+    }
+
+    public SasPic getPlacePic() {
+        return this.placePic;
+    }
+
+    public Integer getSimilarity() {
+        return this.similarity;
+    }
+
+    public String getEntranceCode() {
+        return this.entranceCode;
+    }
+
+    public Integer getIo() {
+        return this.io;
+    }
+
+    public String getPlateNo() {
+        return this.plateNo;
+    }
+
+    public Integer getPlateCode() {
+        return this.plateCode;
+    }
+
+    public Integer getCarCode() {
+        return this.carCode;
+    }
+
+    public String getPlateColor() {
+        return this.plateColor;
+    }
+
+    public ParkingEventInfo setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+        return this;
+    }
+
+    public ParkingEventInfo setChannel(Integer channel) {
+        this.channel = channel;
+        return this;
+    }
+
+    public ParkingEventInfo setEventCode(Integer eventCode) {
+        this.eventCode = eventCode;
+        return this;
+    }
+
+    public ParkingEventInfo setTriggerTime(String triggerTime) {
+        this.triggerTime = triggerTime;
+        return this;
+    }
+
+    public ParkingEventInfo setReceivingTime(String receivingTime) {
+        this.receivingTime = receivingTime;
+        return this;
+    }
+
+    public ParkingEventInfo setNote(String note) {
+        this.note = note;
+        return this;
+    }
+
+    public ParkingEventInfo setEventPic(SasPic eventPic) {
+        this.eventPic = eventPic;
+        return this;
+    }
+
+    public ParkingEventInfo setPlacePic(SasPic placePic) {
+        this.placePic = placePic;
+        return this;
+    }
+
+    public ParkingEventInfo setSimilarity(Integer similarity) {
+        this.similarity = similarity;
+        return this;
+    }
+
+    public ParkingEventInfo setEntranceCode(String entranceCode) {
+        this.entranceCode = entranceCode;
+        return this;
+    }
+
+    public ParkingEventInfo setIo(Integer io) {
+        this.io = io;
+        return this;
+    }
+
+    public ParkingEventInfo setPlateNo(String plateNo) {
+        this.plateNo = plateNo;
+        return this;
+    }
+
+    public ParkingEventInfo setPlateCode(Integer plateCode) {
+        this.plateCode = plateCode;
+        return this;
+    }
+
+    public ParkingEventInfo setCarCode(Integer carCode) {
+        this.carCode = carCode;
+        return this;
+    }
+
+    public ParkingEventInfo setPlateColor(String plateColor) {
+        this.plateColor = plateColor;
+        return this;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof ParkingEventInfo)) {
+            return false;
+        } else {
+            ParkingEventInfo other = (ParkingEventInfo)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$deviceId = this.getDeviceId();
+                Object other$deviceId = other.getDeviceId();
+                if (this$deviceId == null) {
+                    if (other$deviceId != null) {
+                        return false;
+                    }
+                } else if (!this$deviceId.equals(other$deviceId)) {
+                    return false;
+                }
+
+                Object this$channel = this.getChannel();
+                Object other$channel = other.getChannel();
+                if (this$channel == null) {
+                    if (other$channel != null) {
+                        return false;
+                    }
+                } else if (!this$channel.equals(other$channel)) {
+                    return false;
+                }
+
+                Object this$eventCode = this.getEventCode();
+                Object other$eventCode = other.getEventCode();
+                if (this$eventCode == null) {
+                    if (other$eventCode != null) {
+                        return false;
+                    }
+                } else if (!this$eventCode.equals(other$eventCode)) {
+                    return false;
+                }
+
+                Object this$triggerTime = this.getTriggerTime();
+                Object other$triggerTime = other.getTriggerTime();
+                if (this$triggerTime == null) {
+                    if (other$triggerTime != null) {
+                        return false;
+                    }
+                } else if (!this$triggerTime.equals(other$triggerTime)) {
+                    return false;
+                }
+
+                Object this$receivingTime = this.getReceivingTime();
+                Object other$receivingTime = other.getReceivingTime();
+                if (this$receivingTime == null) {
+                    if (other$receivingTime != null) {
+                        return false;
+                    }
+                } else if (!this$receivingTime.equals(other$receivingTime)) {
+                    return false;
+                }
+
+                Object this$note = this.getNote();
+                Object other$note = other.getNote();
+                if (this$note == null) {
+                    if (other$note != null) {
+                        return false;
+                    }
+                } else if (!this$note.equals(other$note)) {
+                    return false;
+                }
+
+                Object this$eventPic = this.getEventPic();
+                Object other$eventPic = other.getEventPic();
+                if (this$eventPic == null) {
+                    if (other$eventPic != null) {
+                        return false;
+                    }
+                } else if (!this$eventPic.equals(other$eventPic)) {
+                    return false;
+                }
+
+                Object this$placePic = this.getPlacePic();
+                Object other$placePic = other.getPlacePic();
+                if (this$placePic == null) {
+                    if (other$placePic != null) {
+                        return false;
+                    }
+                } else if (!this$placePic.equals(other$placePic)) {
+                    return false;
+                }
+
+                Object this$similarity = this.getSimilarity();
+                Object other$similarity = other.getSimilarity();
+                if (this$similarity == null) {
+                    if (other$similarity != null) {
+                        return false;
+                    }
+                } else if (!this$similarity.equals(other$similarity)) {
+                    return false;
+                }
+
+                Object this$entranceCode = this.getEntranceCode();
+                Object other$entranceCode = other.getEntranceCode();
+                if (this$entranceCode == null) {
+                    if (other$entranceCode != null) {
+                        return false;
+                    }
+                } else if (!this$entranceCode.equals(other$entranceCode)) {
+                    return false;
+                }
+
+                Object this$io = this.getIo();
+                Object other$io = other.getIo();
+                if (this$io == null) {
+                    if (other$io != null) {
+                        return false;
+                    }
+                } else if (!this$io.equals(other$io)) {
+                    return false;
+                }
+
+                Object this$plateNo = this.getPlateNo();
+                Object other$plateNo = other.getPlateNo();
+                if (this$plateNo == null) {
+                    if (other$plateNo != null) {
+                        return false;
+                    }
+                } else if (!this$plateNo.equals(other$plateNo)) {
+                    return false;
+                }
+
+                Object this$plateCode = this.getPlateCode();
+                Object other$plateCode = other.getPlateCode();
+                if (this$plateCode == null) {
+                    if (other$plateCode != null) {
+                        return false;
+                    }
+                } else if (!this$plateCode.equals(other$plateCode)) {
+                    return false;
+                }
+
+                Object this$carCode = this.getCarCode();
+                Object other$carCode = other.getCarCode();
+                if (this$carCode == null) {
+                    if (other$carCode != null) {
+                        return false;
+                    }
+                } else if (!this$carCode.equals(other$carCode)) {
+                    return false;
+                }
+
+                Object this$plateColor = this.getPlateColor();
+                Object other$plateColor = other.getPlateColor();
+                if (this$plateColor == null) {
+                    if (other$plateColor != null) {
+                        return false;
+                    }
+                } else if (!this$plateColor.equals(other$plateColor)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof ParkingEventInfo;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $deviceId = this.getDeviceId();
+        result = result * 59 + ($deviceId == null ? 43 : $deviceId.hashCode());
+        Object $channel = this.getChannel();
+        result = result * 59 + ($channel == null ? 43 : $channel.hashCode());
+        Object $eventCode = this.getEventCode();
+        result = result * 59 + ($eventCode == null ? 43 : $eventCode.hashCode());
+        Object $triggerTime = this.getTriggerTime();
+        result = result * 59 + ($triggerTime == null ? 43 : $triggerTime.hashCode());
+        Object $receivingTime = this.getReceivingTime();
+        result = result * 59 + ($receivingTime == null ? 43 : $receivingTime.hashCode());
+        Object $note = this.getNote();
+        result = result * 59 + ($note == null ? 43 : $note.hashCode());
+        Object $eventPic = this.getEventPic();
+        result = result * 59 + ($eventPic == null ? 43 : $eventPic.hashCode());
+        Object $placePic = this.getPlacePic();
+        result = result * 59 + ($placePic == null ? 43 : $placePic.hashCode());
+        Object $similarity = this.getSimilarity();
+        result = result * 59 + ($similarity == null ? 43 : $similarity.hashCode());
+        Object $entranceCode = this.getEntranceCode();
+        result = result * 59 + ($entranceCode == null ? 43 : $entranceCode.hashCode());
+        Object $io = this.getIo();
+        result = result * 59 + ($io == null ? 43 : $io.hashCode());
+        Object $plateNo = this.getPlateNo();
+        result = result * 59 + ($plateNo == null ? 43 : $plateNo.hashCode());
+        Object $plateCode = this.getPlateCode();
+        result = result * 59 + ($plateCode == null ? 43 : $plateCode.hashCode());
+        Object $carCode = this.getCarCode();
+        result = result * 59 + ($carCode == null ? 43 : $carCode.hashCode());
+        Object $plateColor = this.getPlateColor();
+        result = result * 59 + ($plateColor == null ? 43 : $plateColor.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "ParkingEventInfo(deviceId=" + this.getDeviceId() + ", channel=" + this.getChannel() + ", eventCode=" + this.getEventCode() + ", triggerTime=" + this.getTriggerTime() + ", receivingTime=" + this.getReceivingTime() + ", note=" + this.getNote() + ", eventPic=" + this.getEventPic() + ", placePic=" + this.getPlacePic() + ", similarity=" + this.getSimilarity() + ", entranceCode=" + this.getEntranceCode() + ", io=" + this.getIo() + ", plateNo=" + this.getPlateNo() + ", plateCode=" + this.getPlateCode() + ", carCode=" + this.getCarCode() + ", plateColor=" + this.getPlateColor() + ")";
+    }
+}

+ 63 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/ParkingEventInfoResult.java

@@ -0,0 +1,63 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class ParkingEventInfoResult {
+    private ParkingEventInfo eventInfo;
+
+    public ParkingEventInfoResult() {
+    }
+
+    public ParkingEventInfo getEventInfo() {
+        return this.eventInfo;
+    }
+
+    public ParkingEventInfoResult setEventInfo(ParkingEventInfo eventInfo) {
+        this.eventInfo = eventInfo;
+        return this;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof ParkingEventInfoResult)) {
+            return false;
+        } else {
+            ParkingEventInfoResult other = (ParkingEventInfoResult)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$eventInfo = this.getEventInfo();
+                Object other$eventInfo = other.getEventInfo();
+                if (this$eventInfo == null) {
+                    if (other$eventInfo != null) {
+                        return false;
+                    }
+                } else if (!this$eventInfo.equals(other$eventInfo)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof ParkingEventInfoResult;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $eventInfo = this.getEventInfo();
+        result = result * 59 + ($eventInfo == null ? 43 : $eventInfo.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "ParkingEventInfoResult(eventInfo=" + this.getEventInfo() + ")";
+    }
+}

+ 107 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/ParkingEventInfoVo.java

@@ -0,0 +1,107 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class ParkingEventInfoVo {
+    private String jsonrpc;
+    private ParkingEventInfoResult result;
+    private String id;
+
+    public ParkingEventInfoVo() {
+    }
+
+    public String getJsonrpc() {
+        return this.jsonrpc;
+    }
+
+    public ParkingEventInfoResult getResult() {
+        return this.result;
+    }
+
+    public String getId() {
+        return this.id;
+    }
+
+    public ParkingEventInfoVo setJsonrpc(String jsonrpc) {
+        this.jsonrpc = jsonrpc;
+        return this;
+    }
+
+    public ParkingEventInfoVo setResult(ParkingEventInfoResult result) {
+        this.result = result;
+        return this;
+    }
+
+    public ParkingEventInfoVo setId(String id) {
+        this.id = id;
+        return this;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof ParkingEventInfoVo)) {
+            return false;
+        } else {
+            ParkingEventInfoVo other = (ParkingEventInfoVo)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$jsonrpc = this.getJsonrpc();
+                Object other$jsonrpc = other.getJsonrpc();
+                if (this$jsonrpc == null) {
+                    if (other$jsonrpc != null) {
+                        return false;
+                    }
+                } else if (!this$jsonrpc.equals(other$jsonrpc)) {
+                    return false;
+                }
+
+                Object this$result = this.getResult();
+                Object other$result = other.getResult();
+                if (this$result == null) {
+                    if (other$result != null) {
+                        return false;
+                    }
+                } else if (!this$result.equals(other$result)) {
+                    return false;
+                }
+
+                Object this$id = this.getId();
+                Object other$id = other.getId();
+                if (this$id == null) {
+                    if (other$id != null) {
+                        return false;
+                    }
+                } else if (!this$id.equals(other$id)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof ParkingEventInfoVo;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $jsonrpc = this.getJsonrpc();
+        result = result * 59 + ($jsonrpc == null ? 43 : $jsonrpc.hashCode());
+        Object $result = this.getResult();
+        result = result * 59 + ($result == null ? 43 : $result.hashCode());
+        Object $id = this.getId();
+        result = result * 59 + ($id == null ? 43 : $id.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "ParkingEventInfoVo(jsonrpc=" + this.getJsonrpc() + ", result=" + this.getResult() + ", id=" + this.getId() + ")";
+    }
+}

+ 125 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/ParkingEventResult.java

@@ -0,0 +1,125 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class ParkingEventResult {
+    private String method;
+    private String id;
+    private Integer code;
+    private Integer channel;
+
+    public ParkingEventResult() {
+    }
+
+    public String getMethod() {
+        return this.method;
+    }
+
+    public String getId() {
+        return this.id;
+    }
+
+    public Integer getCode() {
+        return this.code;
+    }
+
+    public Integer getChannel() {
+        return this.channel;
+    }
+
+    public void setMethod(String method) {
+        this.method = method;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public void setCode(Integer code) {
+        this.code = code;
+    }
+
+    public void setChannel(Integer channel) {
+        this.channel = channel;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof ParkingEventResult)) {
+            return false;
+        } else {
+            ParkingEventResult other = (ParkingEventResult)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$method = this.getMethod();
+                Object other$method = other.getMethod();
+                if (this$method == null) {
+                    if (other$method != null) {
+                        return false;
+                    }
+                } else if (!this$method.equals(other$method)) {
+                    return false;
+                }
+
+                Object this$id = this.getId();
+                Object other$id = other.getId();
+                if (this$id == null) {
+                    if (other$id != null) {
+                        return false;
+                    }
+                } else if (!this$id.equals(other$id)) {
+                    return false;
+                }
+
+                Object this$code = this.getCode();
+                Object other$code = other.getCode();
+                if (this$code == null) {
+                    if (other$code != null) {
+                        return false;
+                    }
+                } else if (!this$code.equals(other$code)) {
+                    return false;
+                }
+
+                Object this$channel = this.getChannel();
+                Object other$channel = other.getChannel();
+                if (this$channel == null) {
+                    if (other$channel != null) {
+                        return false;
+                    }
+                } else if (!this$channel.equals(other$channel)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof ParkingEventResult;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $method = this.getMethod();
+        result = result * 59 + ($method == null ? 43 : $method.hashCode());
+        Object $id = this.getId();
+        result = result * 59 + ($id == null ? 43 : $id.hashCode());
+        Object $code = this.getCode();
+        result = result * 59 + ($code == null ? 43 : $code.hashCode());
+        Object $channel = this.getChannel();
+        result = result * 59 + ($channel == null ? 43 : $channel.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "ParkingEventResult(method=" + this.getMethod() + ", id=" + this.getId() + ", code=" + this.getCode() + ", channel=" + this.getChannel() + ")";
+    }
+}

+ 319 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/PatrolEventInfo.java

@@ -0,0 +1,319 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.usky.sas.domain.SasPic;
+import io.swagger.annotations.ApiModelProperty;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class PatrolEventInfo {
+    @ApiModelProperty("设备编号")
+    private String deviceId;
+    @ApiModelProperty("通道号")
+    private Integer channel;
+    @ApiModelProperty("事件编码")
+    private Integer eventCode;
+    @ApiModelProperty("触发时间")
+    private String triggerTime;
+    @ApiModelProperty("接收时间")
+    private String receivingTime;
+    @ApiModelProperty("备注")
+    private String note;
+    @ApiModelProperty("用户参数对象")
+    private UserParams userParams;
+    @ApiModelProperty("事件图片")
+    private SasPic eventPic;
+    @ApiModelProperty("证件类型编码")
+    private Integer credentialType;
+    @ApiModelProperty("证件号")
+    private String credentialNo;
+    @ApiModelProperty("巡检人名")
+    private String name;
+    @ApiModelProperty("事件类型名称")
+    private String typeName;
+
+    public PatrolEventInfo() {
+    }
+
+    public String getDeviceId() {
+        return this.deviceId;
+    }
+
+    public Integer getChannel() {
+        return this.channel;
+    }
+
+    public Integer getEventCode() {
+        return this.eventCode;
+    }
+
+    public String getTriggerTime() {
+        return this.triggerTime;
+    }
+
+    public String getReceivingTime() {
+        return this.receivingTime;
+    }
+
+    public String getNote() {
+        return this.note;
+    }
+
+    public UserParams getUserParams() {
+        return this.userParams;
+    }
+
+    public SasPic getEventPic() {
+        return this.eventPic;
+    }
+
+    public Integer getCredentialType() {
+        return this.credentialType;
+    }
+
+    public String getCredentialNo() {
+        return this.credentialNo;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getTypeName() {
+        return this.typeName;
+    }
+
+    public PatrolEventInfo setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+        return this;
+    }
+
+    public PatrolEventInfo setChannel(Integer channel) {
+        this.channel = channel;
+        return this;
+    }
+
+    public PatrolEventInfo setEventCode(Integer eventCode) {
+        this.eventCode = eventCode;
+        return this;
+    }
+
+    public PatrolEventInfo setTriggerTime(String triggerTime) {
+        this.triggerTime = triggerTime;
+        return this;
+    }
+
+    public PatrolEventInfo setReceivingTime(String receivingTime) {
+        this.receivingTime = receivingTime;
+        return this;
+    }
+
+    public PatrolEventInfo setNote(String note) {
+        this.note = note;
+        return this;
+    }
+
+    public PatrolEventInfo setUserParams(UserParams userParams) {
+        this.userParams = userParams;
+        return this;
+    }
+
+    public PatrolEventInfo setEventPic(SasPic eventPic) {
+        this.eventPic = eventPic;
+        return this;
+    }
+
+    public PatrolEventInfo setCredentialType(Integer credentialType) {
+        this.credentialType = credentialType;
+        return this;
+    }
+
+    public PatrolEventInfo setCredentialNo(String credentialNo) {
+        this.credentialNo = credentialNo;
+        return this;
+    }
+
+    public PatrolEventInfo setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public PatrolEventInfo setTypeName(String typeName) {
+        this.typeName = typeName;
+        return this;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof PatrolEventInfo)) {
+            return false;
+        } else {
+            PatrolEventInfo other = (PatrolEventInfo)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$deviceId = this.getDeviceId();
+                Object other$deviceId = other.getDeviceId();
+                if (this$deviceId == null) {
+                    if (other$deviceId != null) {
+                        return false;
+                    }
+                } else if (!this$deviceId.equals(other$deviceId)) {
+                    return false;
+                }
+
+                Object this$channel = this.getChannel();
+                Object other$channel = other.getChannel();
+                if (this$channel == null) {
+                    if (other$channel != null) {
+                        return false;
+                    }
+                } else if (!this$channel.equals(other$channel)) {
+                    return false;
+                }
+
+                Object this$eventCode = this.getEventCode();
+                Object other$eventCode = other.getEventCode();
+                if (this$eventCode == null) {
+                    if (other$eventCode != null) {
+                        return false;
+                    }
+                } else if (!this$eventCode.equals(other$eventCode)) {
+                    return false;
+                }
+
+                Object this$triggerTime = this.getTriggerTime();
+                Object other$triggerTime = other.getTriggerTime();
+                if (this$triggerTime == null) {
+                    if (other$triggerTime != null) {
+                        return false;
+                    }
+                } else if (!this$triggerTime.equals(other$triggerTime)) {
+                    return false;
+                }
+
+                Object this$receivingTime = this.getReceivingTime();
+                Object other$receivingTime = other.getReceivingTime();
+                if (this$receivingTime == null) {
+                    if (other$receivingTime != null) {
+                        return false;
+                    }
+                } else if (!this$receivingTime.equals(other$receivingTime)) {
+                    return false;
+                }
+
+                Object this$note = this.getNote();
+                Object other$note = other.getNote();
+                if (this$note == null) {
+                    if (other$note != null) {
+                        return false;
+                    }
+                } else if (!this$note.equals(other$note)) {
+                    return false;
+                }
+
+                Object this$userParams = this.getUserParams();
+                Object other$userParams = other.getUserParams();
+                if (this$userParams == null) {
+                    if (other$userParams != null) {
+                        return false;
+                    }
+                } else if (!this$userParams.equals(other$userParams)) {
+                    return false;
+                }
+
+                Object this$eventPic = this.getEventPic();
+                Object other$eventPic = other.getEventPic();
+                if (this$eventPic == null) {
+                    if (other$eventPic != null) {
+                        return false;
+                    }
+                } else if (!this$eventPic.equals(other$eventPic)) {
+                    return false;
+                }
+
+                Object this$credentialType = this.getCredentialType();
+                Object other$credentialType = other.getCredentialType();
+                if (this$credentialType == null) {
+                    if (other$credentialType != null) {
+                        return false;
+                    }
+                } else if (!this$credentialType.equals(other$credentialType)) {
+                    return false;
+                }
+
+                Object this$credentialNo = this.getCredentialNo();
+                Object other$credentialNo = other.getCredentialNo();
+                if (this$credentialNo == null) {
+                    if (other$credentialNo != null) {
+                        return false;
+                    }
+                } else if (!this$credentialNo.equals(other$credentialNo)) {
+                    return false;
+                }
+
+                Object this$name = this.getName();
+                Object other$name = other.getName();
+                if (this$name == null) {
+                    if (other$name != null) {
+                        return false;
+                    }
+                } else if (!this$name.equals(other$name)) {
+                    return false;
+                }
+
+                Object this$typeName = this.getTypeName();
+                Object other$typeName = other.getTypeName();
+                if (this$typeName == null) {
+                    if (other$typeName != null) {
+                        return false;
+                    }
+                } else if (!this$typeName.equals(other$typeName)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof PatrolEventInfo;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $deviceId = this.getDeviceId();
+        result = result * 59 + ($deviceId == null ? 43 : $deviceId.hashCode());
+        Object $channel = this.getChannel();
+        result = result * 59 + ($channel == null ? 43 : $channel.hashCode());
+        Object $eventCode = this.getEventCode();
+        result = result * 59 + ($eventCode == null ? 43 : $eventCode.hashCode());
+        Object $triggerTime = this.getTriggerTime();
+        result = result * 59 + ($triggerTime == null ? 43 : $triggerTime.hashCode());
+        Object $receivingTime = this.getReceivingTime();
+        result = result * 59 + ($receivingTime == null ? 43 : $receivingTime.hashCode());
+        Object $note = this.getNote();
+        result = result * 59 + ($note == null ? 43 : $note.hashCode());
+        Object $userParams = this.getUserParams();
+        result = result * 59 + ($userParams == null ? 43 : $userParams.hashCode());
+        Object $eventPic = this.getEventPic();
+        result = result * 59 + ($eventPic == null ? 43 : $eventPic.hashCode());
+        Object $credentialType = this.getCredentialType();
+        result = result * 59 + ($credentialType == null ? 43 : $credentialType.hashCode());
+        Object $credentialNo = this.getCredentialNo();
+        result = result * 59 + ($credentialNo == null ? 43 : $credentialNo.hashCode());
+        Object $name = this.getName();
+        result = result * 59 + ($name == null ? 43 : $name.hashCode());
+        Object $typeName = this.getTypeName();
+        result = result * 59 + ($typeName == null ? 43 : $typeName.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "PatrolEventInfo(deviceId=" + this.getDeviceId() + ", channel=" + this.getChannel() + ", eventCode=" + this.getEventCode() + ", triggerTime=" + this.getTriggerTime() + ", receivingTime=" + this.getReceivingTime() + ", note=" + this.getNote() + ", userParams=" + this.getUserParams() + ", eventPic=" + this.getEventPic() + ", credentialType=" + this.getCredentialType() + ", credentialNo=" + this.getCredentialNo() + ", name=" + this.getName() + ", typeName=" + this.getTypeName() + ")";
+    }
+}

+ 64 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/PatrolEventInfoResult.java

@@ -0,0 +1,64 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.usky.sas.domain.SasPatrolEvent;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class PatrolEventInfoResult {
+    private PatrolEventInfo eventInfo;
+
+    public PatrolEventInfoResult() {
+    }
+
+    public PatrolEventInfo getEventInfo() {
+        return this.eventInfo;
+    }
+
+    public PatrolEventInfoResult setEventInfo(PatrolEventInfo eventInfo) {
+        this.eventInfo = eventInfo;
+        return this;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof PatrolEventInfoResult)) {
+            return false;
+        } else {
+            PatrolEventInfoResult other = (PatrolEventInfoResult)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$eventInfo = this.getEventInfo();
+                Object other$eventInfo = other.getEventInfo();
+                if (this$eventInfo == null) {
+                    if (other$eventInfo != null) {
+                        return false;
+                    }
+                } else if (!this$eventInfo.equals(other$eventInfo)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof PatrolEventInfoResult;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $eventInfo = this.getEventInfo();
+        result = result * 59 + ($eventInfo == null ? 43 : $eventInfo.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "PatrolEventInfoResult(eventInfo=" + this.getEventInfo() + ")";
+    }
+}

+ 107 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/PatrolEventInfoVo.java

@@ -0,0 +1,107 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class PatrolEventInfoVo {
+    private String jsonrpc;
+    private PatrolEventInfoResult result;
+    private String id;
+
+    public PatrolEventInfoVo() {
+    }
+
+    public String getJsonrpc() {
+        return this.jsonrpc;
+    }
+
+    public PatrolEventInfoResult getResult() {
+        return this.result;
+    }
+
+    public String getId() {
+        return this.id;
+    }
+
+    public PatrolEventInfoVo setJsonrpc(String jsonrpc) {
+        this.jsonrpc = jsonrpc;
+        return this;
+    }
+
+    public PatrolEventInfoVo setResult(PatrolEventInfoResult result) {
+        this.result = result;
+        return this;
+    }
+
+    public PatrolEventInfoVo setId(String id) {
+        this.id = id;
+        return this;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof PatrolEventInfoVo)) {
+            return false;
+        } else {
+            PatrolEventInfoVo other = (PatrolEventInfoVo)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$jsonrpc = this.getJsonrpc();
+                Object other$jsonrpc = other.getJsonrpc();
+                if (this$jsonrpc == null) {
+                    if (other$jsonrpc != null) {
+                        return false;
+                    }
+                } else if (!this$jsonrpc.equals(other$jsonrpc)) {
+                    return false;
+                }
+
+                Object this$result = this.getResult();
+                Object other$result = other.getResult();
+                if (this$result == null) {
+                    if (other$result != null) {
+                        return false;
+                    }
+                } else if (!this$result.equals(other$result)) {
+                    return false;
+                }
+
+                Object this$id = this.getId();
+                Object other$id = other.getId();
+                if (this$id == null) {
+                    if (other$id != null) {
+                        return false;
+                    }
+                } else if (!this$id.equals(other$id)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof PatrolEventInfoVo;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $jsonrpc = this.getJsonrpc();
+        result = result * 59 + ($jsonrpc == null ? 43 : $jsonrpc.hashCode());
+        Object $result = this.getResult();
+        result = result * 59 + ($result == null ? 43 : $result.hashCode());
+        Object $id = this.getId();
+        result = result * 59 + ($id == null ? 43 : $id.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "PatrolEventInfoVo(jsonrpc=" + this.getJsonrpc() + ", result=" + this.getResult() + ", id=" + this.getId() + ")";
+    }
+}

+ 104 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/PatrolEventResult.java

@@ -0,0 +1,104 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class PatrolEventResult {
+    private String method;
+    private String id;
+    private Integer eventCode;
+
+    public PatrolEventResult() {
+    }
+
+    public String getMethod() {
+        return this.method;
+    }
+
+    public String getId() {
+        return this.id;
+    }
+
+    public Integer getEventCode() {
+        return this.eventCode;
+    }
+
+    public void setMethod(String method) {
+        this.method = method;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public void setEventCode(Integer eventCode) {
+        this.eventCode = eventCode;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof PatrolEventResult)) {
+            return false;
+        } else {
+            PatrolEventResult other = (PatrolEventResult)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$method = this.getMethod();
+                Object other$method = other.getMethod();
+                if (this$method == null) {
+                    if (other$method != null) {
+                        return false;
+                    }
+                } else if (!this$method.equals(other$method)) {
+                    return false;
+                }
+
+                Object this$id = this.getId();
+                Object other$id = other.getId();
+                if (this$id == null) {
+                    if (other$id != null) {
+                        return false;
+                    }
+                } else if (!this$id.equals(other$id)) {
+                    return false;
+                }
+
+                Object this$eventCode = this.getEventCode();
+                Object other$eventCode = other.getEventCode();
+                if (this$eventCode == null) {
+                    if (other$eventCode != null) {
+                        return false;
+                    }
+                } else if (!this$eventCode.equals(other$eventCode)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof PatrolEventResult;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $method = this.getMethod();
+        result = result * 59 + ($method == null ? 43 : $method.hashCode());
+        Object $id = this.getId();
+        result = result * 59 + ($id == null ? 43 : $id.hashCode());
+        Object $eventCode = this.getEventCode();
+        result = result * 59 + ($eventCode == null ? 43 : $eventCode.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "PatrolEventResult(method=" + this.getMethod() + ", id=" + this.getId() + ", eventCode=" + this.getEventCode() + ")";
+    }
+}

+ 411 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/SnapEventInfo.java

@@ -0,0 +1,411 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.usky.sas.domain.SasPic;
+import io.swagger.annotations.ApiModelProperty;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class SnapEventInfo {
+    @ApiModelProperty("设备编号")
+    private String deviceId;
+    @ApiModelProperty("通道号")
+    private Integer channel;
+    @ApiModelProperty("事件编码")
+    private Integer eventCode;
+    @ApiModelProperty("触发时间")
+    private String triggerTime;
+    @ApiModelProperty("接收时间")
+    private String receivingTime;
+    @ApiModelProperty("备注")
+    private String note;
+    @ApiModelProperty("用户参数对象")
+    private UserParams userParams;
+    @ApiModelProperty("事件图片")
+    private SasPic eventPic;
+    @ApiModelProperty("场景图片")
+    private SasPic scenePic;
+    @ApiModelProperty("相似度")
+    private Integer similarity;
+    @ApiModelProperty("图片质量")
+    private Integer quality;
+    @ApiModelProperty("人员计数数量")
+    private Integer count;
+    @ApiModelProperty("证件类型编码")
+    private Integer credentialType;
+    @ApiModelProperty("证件号")
+    private String credentialNo;
+    @ApiModelProperty("人名")
+    private String name;
+    @ApiModelProperty("人员编码")
+    private String personCode;
+
+    public SnapEventInfo() {
+    }
+
+    public String getDeviceId() {
+        return this.deviceId;
+    }
+
+    public Integer getChannel() {
+        return this.channel;
+    }
+
+    public Integer getEventCode() {
+        return this.eventCode;
+    }
+
+    public String getTriggerTime() {
+        return this.triggerTime;
+    }
+
+    public String getReceivingTime() {
+        return this.receivingTime;
+    }
+
+    public String getNote() {
+        return this.note;
+    }
+
+    public UserParams getUserParams() {
+        return this.userParams;
+    }
+
+    public SasPic getEventPic() {
+        return this.eventPic;
+    }
+
+    public SasPic getScenePic() {
+        return this.scenePic;
+    }
+
+    public Integer getSimilarity() {
+        return this.similarity;
+    }
+
+    public Integer getQuality() {
+        return this.quality;
+    }
+
+    public Integer getCount() {
+        return this.count;
+    }
+
+    public Integer getCredentialType() {
+        return this.credentialType;
+    }
+
+    public String getCredentialNo() {
+        return this.credentialNo;
+    }
+
+    public String getName() {
+        return this.name;
+    }
+
+    public String getPersonCode() {
+        return this.personCode;
+    }
+
+    public SnapEventInfo setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+        return this;
+    }
+
+    public SnapEventInfo setChannel(Integer channel) {
+        this.channel = channel;
+        return this;
+    }
+
+    public SnapEventInfo setEventCode(Integer eventCode) {
+        this.eventCode = eventCode;
+        return this;
+    }
+
+    public SnapEventInfo setTriggerTime(String triggerTime) {
+        this.triggerTime = triggerTime;
+        return this;
+    }
+
+    public SnapEventInfo setReceivingTime(String receivingTime) {
+        this.receivingTime = receivingTime;
+        return this;
+    }
+
+    public SnapEventInfo setNote(String note) {
+        this.note = note;
+        return this;
+    }
+
+    public SnapEventInfo setUserParams(UserParams userParams) {
+        this.userParams = userParams;
+        return this;
+    }
+
+    public SnapEventInfo setEventPic(SasPic eventPic) {
+        this.eventPic = eventPic;
+        return this;
+    }
+
+    public SnapEventInfo setScenePic(SasPic scenePic) {
+        this.scenePic = scenePic;
+        return this;
+    }
+
+    public SnapEventInfo setSimilarity(Integer similarity) {
+        this.similarity = similarity;
+        return this;
+    }
+
+    public SnapEventInfo setQuality(Integer quality) {
+        this.quality = quality;
+        return this;
+    }
+
+    public SnapEventInfo setCount(Integer count) {
+        this.count = count;
+        return this;
+    }
+
+    public SnapEventInfo setCredentialType(Integer credentialType) {
+        this.credentialType = credentialType;
+        return this;
+    }
+
+    public SnapEventInfo setCredentialNo(String credentialNo) {
+        this.credentialNo = credentialNo;
+        return this;
+    }
+
+    public SnapEventInfo setName(String name) {
+        this.name = name;
+        return this;
+    }
+
+    public SnapEventInfo setPersonCode(String personCode) {
+        this.personCode = personCode;
+        return this;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof SnapEventInfo)) {
+            return false;
+        } else {
+            SnapEventInfo other = (SnapEventInfo)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$deviceId = this.getDeviceId();
+                Object other$deviceId = other.getDeviceId();
+                if (this$deviceId == null) {
+                    if (other$deviceId != null) {
+                        return false;
+                    }
+                } else if (!this$deviceId.equals(other$deviceId)) {
+                    return false;
+                }
+
+                Object this$channel = this.getChannel();
+                Object other$channel = other.getChannel();
+                if (this$channel == null) {
+                    if (other$channel != null) {
+                        return false;
+                    }
+                } else if (!this$channel.equals(other$channel)) {
+                    return false;
+                }
+
+                Object this$eventCode = this.getEventCode();
+                Object other$eventCode = other.getEventCode();
+                if (this$eventCode == null) {
+                    if (other$eventCode != null) {
+                        return false;
+                    }
+                } else if (!this$eventCode.equals(other$eventCode)) {
+                    return false;
+                }
+
+                Object this$triggerTime = this.getTriggerTime();
+                Object other$triggerTime = other.getTriggerTime();
+                if (this$triggerTime == null) {
+                    if (other$triggerTime != null) {
+                        return false;
+                    }
+                } else if (!this$triggerTime.equals(other$triggerTime)) {
+                    return false;
+                }
+
+                Object this$receivingTime = this.getReceivingTime();
+                Object other$receivingTime = other.getReceivingTime();
+                if (this$receivingTime == null) {
+                    if (other$receivingTime != null) {
+                        return false;
+                    }
+                } else if (!this$receivingTime.equals(other$receivingTime)) {
+                    return false;
+                }
+
+                Object this$note = this.getNote();
+                Object other$note = other.getNote();
+                if (this$note == null) {
+                    if (other$note != null) {
+                        return false;
+                    }
+                } else if (!this$note.equals(other$note)) {
+                    return false;
+                }
+
+                Object this$userParams = this.getUserParams();
+                Object other$userParams = other.getUserParams();
+                if (this$userParams == null) {
+                    if (other$userParams != null) {
+                        return false;
+                    }
+                } else if (!this$userParams.equals(other$userParams)) {
+                    return false;
+                }
+
+                Object this$eventPic = this.getEventPic();
+                Object other$eventPic = other.getEventPic();
+                if (this$eventPic == null) {
+                    if (other$eventPic != null) {
+                        return false;
+                    }
+                } else if (!this$eventPic.equals(other$eventPic)) {
+                    return false;
+                }
+
+                Object this$scenePic = this.getScenePic();
+                Object other$scenePic = other.getScenePic();
+                if (this$scenePic == null) {
+                    if (other$scenePic != null) {
+                        return false;
+                    }
+                } else if (!this$scenePic.equals(other$scenePic)) {
+                    return false;
+                }
+
+                Object this$similarity = this.getSimilarity();
+                Object other$similarity = other.getSimilarity();
+                if (this$similarity == null) {
+                    if (other$similarity != null) {
+                        return false;
+                    }
+                } else if (!this$similarity.equals(other$similarity)) {
+                    return false;
+                }
+
+                Object this$quality = this.getQuality();
+                Object other$quality = other.getQuality();
+                if (this$quality == null) {
+                    if (other$quality != null) {
+                        return false;
+                    }
+                } else if (!this$quality.equals(other$quality)) {
+                    return false;
+                }
+
+                Object this$count = this.getCount();
+                Object other$count = other.getCount();
+                if (this$count == null) {
+                    if (other$count != null) {
+                        return false;
+                    }
+                } else if (!this$count.equals(other$count)) {
+                    return false;
+                }
+
+                Object this$credentialType = this.getCredentialType();
+                Object other$credentialType = other.getCredentialType();
+                if (this$credentialType == null) {
+                    if (other$credentialType != null) {
+                        return false;
+                    }
+                } else if (!this$credentialType.equals(other$credentialType)) {
+                    return false;
+                }
+
+                Object this$credentialNo = this.getCredentialNo();
+                Object other$credentialNo = other.getCredentialNo();
+                if (this$credentialNo == null) {
+                    if (other$credentialNo != null) {
+                        return false;
+                    }
+                } else if (!this$credentialNo.equals(other$credentialNo)) {
+                    return false;
+                }
+
+                Object this$name = this.getName();
+                Object other$name = other.getName();
+                if (this$name == null) {
+                    if (other$name != null) {
+                        return false;
+                    }
+                } else if (!this$name.equals(other$name)) {
+                    return false;
+                }
+
+                Object this$personCode = this.getPersonCode();
+                Object other$personCode = other.getPersonCode();
+                if (this$personCode == null) {
+                    if (other$personCode != null) {
+                        return false;
+                    }
+                } else if (!this$personCode.equals(other$personCode)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof SnapEventInfo;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $deviceId = this.getDeviceId();
+        result = result * 59 + ($deviceId == null ? 43 : $deviceId.hashCode());
+        Object $channel = this.getChannel();
+        result = result * 59 + ($channel == null ? 43 : $channel.hashCode());
+        Object $eventCode = this.getEventCode();
+        result = result * 59 + ($eventCode == null ? 43 : $eventCode.hashCode());
+        Object $triggerTime = this.getTriggerTime();
+        result = result * 59 + ($triggerTime == null ? 43 : $triggerTime.hashCode());
+        Object $receivingTime = this.getReceivingTime();
+        result = result * 59 + ($receivingTime == null ? 43 : $receivingTime.hashCode());
+        Object $note = this.getNote();
+        result = result * 59 + ($note == null ? 43 : $note.hashCode());
+        Object $userParams = this.getUserParams();
+        result = result * 59 + ($userParams == null ? 43 : $userParams.hashCode());
+        Object $eventPic = this.getEventPic();
+        result = result * 59 + ($eventPic == null ? 43 : $eventPic.hashCode());
+        Object $scenePic = this.getScenePic();
+        result = result * 59 + ($scenePic == null ? 43 : $scenePic.hashCode());
+        Object $similarity = this.getSimilarity();
+        result = result * 59 + ($similarity == null ? 43 : $similarity.hashCode());
+        Object $quality = this.getQuality();
+        result = result * 59 + ($quality == null ? 43 : $quality.hashCode());
+        Object $count = this.getCount();
+        result = result * 59 + ($count == null ? 43 : $count.hashCode());
+        Object $credentialType = this.getCredentialType();
+        result = result * 59 + ($credentialType == null ? 43 : $credentialType.hashCode());
+        Object $credentialNo = this.getCredentialNo();
+        result = result * 59 + ($credentialNo == null ? 43 : $credentialNo.hashCode());
+        Object $name = this.getName();
+        result = result * 59 + ($name == null ? 43 : $name.hashCode());
+        Object $personCode = this.getPersonCode();
+        result = result * 59 + ($personCode == null ? 43 : $personCode.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "SnapEventInfo(deviceId=" + this.getDeviceId() + ", channel=" + this.getChannel() + ", eventCode=" + this.getEventCode() + ", triggerTime=" + this.getTriggerTime() + ", receivingTime=" + this.getReceivingTime() + ", note=" + this.getNote() + ", userParams=" + this.getUserParams() + ", eventPic=" + this.getEventPic() + ", scenePic=" + this.getScenePic() + ", similarity=" + this.getSimilarity() + ", quality=" + this.getQuality() + ", count=" + this.getCount() + ", credentialType=" + this.getCredentialType() + ", credentialNo=" + this.getCredentialNo() + ", name=" + this.getName() + ", personCode=" + this.getPersonCode() + ")";
+    }
+}

+ 63 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/SnapEventInfoResult.java

@@ -0,0 +1,63 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class SnapEventInfoResult {
+    private SnapEventInfo eventInfo;
+
+    public SnapEventInfoResult() {
+    }
+
+    public SnapEventInfo getEventInfo() {
+        return this.eventInfo;
+    }
+
+    public SnapEventInfoResult setEventInfo(SnapEventInfo eventInfo) {
+        this.eventInfo = eventInfo;
+        return this;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof SnapEventInfoResult)) {
+            return false;
+        } else {
+            SnapEventInfoResult other = (SnapEventInfoResult)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$eventInfo = this.getEventInfo();
+                Object other$eventInfo = other.getEventInfo();
+                if (this$eventInfo == null) {
+                    if (other$eventInfo != null) {
+                        return false;
+                    }
+                } else if (!this$eventInfo.equals(other$eventInfo)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof SnapEventInfoResult;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $eventInfo = this.getEventInfo();
+        result = result * 59 + ($eventInfo == null ? 43 : $eventInfo.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "SnapEventInfoResult(eventInfo=" + this.getEventInfo() + ")";
+    }
+}

+ 107 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/SnapEventInfoVo.java

@@ -0,0 +1,107 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class SnapEventInfoVo {
+    private String jsonrpc;
+    private SnapEventInfoResult result;
+    private String id;
+
+    public SnapEventInfoVo() {
+    }
+
+    public String getJsonrpc() {
+        return this.jsonrpc;
+    }
+
+    public SnapEventInfoResult getResult() {
+        return this.result;
+    }
+
+    public String getId() {
+        return this.id;
+    }
+
+    public SnapEventInfoVo setJsonrpc(String jsonrpc) {
+        this.jsonrpc = jsonrpc;
+        return this;
+    }
+
+    public SnapEventInfoVo setResult(SnapEventInfoResult result) {
+        this.result = result;
+        return this;
+    }
+
+    public SnapEventInfoVo setId(String id) {
+        this.id = id;
+        return this;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof SnapEventInfoVo)) {
+            return false;
+        } else {
+            SnapEventInfoVo other = (SnapEventInfoVo)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$jsonrpc = this.getJsonrpc();
+                Object other$jsonrpc = other.getJsonrpc();
+                if (this$jsonrpc == null) {
+                    if (other$jsonrpc != null) {
+                        return false;
+                    }
+                } else if (!this$jsonrpc.equals(other$jsonrpc)) {
+                    return false;
+                }
+
+                Object this$result = this.getResult();
+                Object other$result = other.getResult();
+                if (this$result == null) {
+                    if (other$result != null) {
+                        return false;
+                    }
+                } else if (!this$result.equals(other$result)) {
+                    return false;
+                }
+
+                Object this$id = this.getId();
+                Object other$id = other.getId();
+                if (this$id == null) {
+                    if (other$id != null) {
+                        return false;
+                    }
+                } else if (!this$id.equals(other$id)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof SnapEventInfoVo;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $jsonrpc = this.getJsonrpc();
+        result = result * 59 + ($jsonrpc == null ? 43 : $jsonrpc.hashCode());
+        Object $result = this.getResult();
+        result = result * 59 + ($result == null ? 43 : $result.hashCode());
+        Object $id = this.getId();
+        result = result * 59 + ($id == null ? 43 : $id.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "SnapEventInfoVo(jsonrpc=" + this.getJsonrpc() + ", result=" + this.getResult() + ", id=" + this.getId() + ")";
+    }
+}

+ 168 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/SnapEventResult.java

@@ -0,0 +1,168 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.usky.sas.domain.SasPic;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class SnapEventResult {
+    private String method;
+    private String id;
+    private Integer code;
+    private Integer channel;
+    private String personCode;
+    private SasPic picUrl;
+
+    public SnapEventResult() {
+    }
+
+    public String getMethod() {
+        return this.method;
+    }
+
+    public String getId() {
+        return this.id;
+    }
+
+    public Integer getCode() {
+        return this.code;
+    }
+
+    public Integer getChannel() {
+        return this.channel;
+    }
+
+    public String getPersonCode() {
+        return this.personCode;
+    }
+
+    public SasPic getPicUrl() {
+        return this.picUrl;
+    }
+
+    public void setMethod(String method) {
+        this.method = method;
+    }
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public void setCode(Integer code) {
+        this.code = code;
+    }
+
+    public void setChannel(Integer channel) {
+        this.channel = channel;
+    }
+
+    public void setPersonCode(String personCode) {
+        this.personCode = personCode;
+    }
+
+    public void setPicUrl(SasPic picUrl) {
+        this.picUrl = picUrl;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof SnapEventResult)) {
+            return false;
+        } else {
+            SnapEventResult other = (SnapEventResult)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$method = this.getMethod();
+                Object other$method = other.getMethod();
+                if (this$method == null) {
+                    if (other$method != null) {
+                        return false;
+                    }
+                } else if (!this$method.equals(other$method)) {
+                    return false;
+                }
+
+                Object this$id = this.getId();
+                Object other$id = other.getId();
+                if (this$id == null) {
+                    if (other$id != null) {
+                        return false;
+                    }
+                } else if (!this$id.equals(other$id)) {
+                    return false;
+                }
+
+                Object this$code = this.getCode();
+                Object other$code = other.getCode();
+                if (this$code == null) {
+                    if (other$code != null) {
+                        return false;
+                    }
+                } else if (!this$code.equals(other$code)) {
+                    return false;
+                }
+
+                Object this$channel = this.getChannel();
+                Object other$channel = other.getChannel();
+                if (this$channel == null) {
+                    if (other$channel != null) {
+                        return false;
+                    }
+                } else if (!this$channel.equals(other$channel)) {
+                    return false;
+                }
+
+                Object this$personCode = this.getPersonCode();
+                Object other$personCode = other.getPersonCode();
+                if (this$personCode == null) {
+                    if (other$personCode != null) {
+                        return false;
+                    }
+                } else if (!this$personCode.equals(other$personCode)) {
+                    return false;
+                }
+
+                Object this$picUrl = this.getPicUrl();
+                Object other$picUrl = other.getPicUrl();
+                if (this$picUrl == null) {
+                    if (other$picUrl != null) {
+                        return false;
+                    }
+                } else if (!this$picUrl.equals(other$picUrl)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof SnapEventResult;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $method = this.getMethod();
+        result = result * 59 + ($method == null ? 43 : $method.hashCode());
+        Object $id = this.getId();
+        result = result * 59 + ($id == null ? 43 : $id.hashCode());
+        Object $code = this.getCode();
+        result = result * 59 + ($code == null ? 43 : $code.hashCode());
+        Object $channel = this.getChannel();
+        result = result * 59 + ($channel == null ? 43 : $channel.hashCode());
+        Object $personCode = this.getPersonCode();
+        result = result * 59 + ($personCode == null ? 43 : $personCode.hashCode());
+        Object $picUrl = this.getPicUrl();
+        result = result * 59 + ($picUrl == null ? 43 : $picUrl.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "SnapEventResult(method=" + this.getMethod() + ", id=" + this.getId() + ", code=" + this.getCode() + ", channel=" + this.getChannel() + ", personCode=" + this.getPersonCode() + ", picUrl=" + this.getPicUrl() + ")";
+    }
+}

+ 111 - 0
service-sas/service-sas-biz/src/main/java/com/usky/sas/service/vo/UserParams.java

@@ -0,0 +1,111 @@
+package com.usky.sas.service.vo;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import io.swagger.annotations.ApiModelProperty;
+
+@JsonIgnoreProperties(
+        ignoreUnknown = true
+)
+public class UserParams {
+    @ApiModelProperty("事件备注")
+    private String status;
+    @ApiModelProperty("事件编号")
+    private Integer statusCode;
+    @ApiModelProperty("终端编号")
+    private String handleCode;
+
+    public UserParams() {
+    }
+
+    public String getStatus() {
+        return this.status;
+    }
+
+    public Integer getStatusCode() {
+        return this.statusCode;
+    }
+
+    public String getHandleCode() {
+        return this.handleCode;
+    }
+
+    public UserParams setStatus(String status) {
+        this.status = status;
+        return this;
+    }
+
+    public UserParams setStatusCode(Integer statusCode) {
+        this.statusCode = statusCode;
+        return this;
+    }
+
+    public UserParams setHandleCode(String handleCode) {
+        this.handleCode = handleCode;
+        return this;
+    }
+
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        } else if (!(o instanceof UserParams)) {
+            return false;
+        } else {
+            UserParams other = (UserParams)o;
+            if (!other.canEqual(this)) {
+                return false;
+            } else {
+                Object this$status = this.getStatus();
+                Object other$status = other.getStatus();
+                if (this$status == null) {
+                    if (other$status != null) {
+                        return false;
+                    }
+                } else if (!this$status.equals(other$status)) {
+                    return false;
+                }
+
+                Object this$statusCode = this.getStatusCode();
+                Object other$statusCode = other.getStatusCode();
+                if (this$statusCode == null) {
+                    if (other$statusCode != null) {
+                        return false;
+                    }
+                } else if (!this$statusCode.equals(other$statusCode)) {
+                    return false;
+                }
+
+                Object this$handleCode = this.getHandleCode();
+                Object other$handleCode = other.getHandleCode();
+                if (this$handleCode == null) {
+                    if (other$handleCode != null) {
+                        return false;
+                    }
+                } else if (!this$handleCode.equals(other$handleCode)) {
+                    return false;
+                }
+
+                return true;
+            }
+        }
+    }
+
+    protected boolean canEqual(Object other) {
+        return other instanceof UserParams;
+    }
+
+    public int hashCode() {
+        int PRIME = 59;
+        int result = 1;
+        Object $status = this.getStatus();
+        result = result * 59 + ($status == null ? 43 : $status.hashCode());
+        Object $statusCode = this.getStatusCode();
+        result = result * 59 + ($statusCode == null ? 43 : $statusCode.hashCode());
+        Object $handleCode = this.getHandleCode();
+        result = result * 59 + ($handleCode == null ? 43 : $handleCode.hashCode());
+        return result;
+    }
+
+    public String toString() {
+        return "UserParams(status=" + this.getStatus() + ", statusCode=" + this.getStatusCode() + ", handleCode=" + this.getHandleCode() + ")";
+    }
+}

+ 15 - 0
service-sas/service-sas-biz/src/main/resources/mapper/pm/SasPatrolUserParamMapper.xml

@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.sas.mapper.SasPatrolUserParamMapper">
+
+    <!-- 通用查询映射结果 -->
+    <resultMap id="BaseResultMap" type="com.usky.sas.domain.SasPatrolUserParam">
+        <id column="id" property="id" />
+        <result column="status" property="status" />
+        <result column="status_code" property="statusCode" />
+        <result column="handle_code" property="handleCode" />
+        <result column="create_time" property="createTime" />
+        <result column="update_time" property="updateTime" />
+    </resultMap>
+
+</mapper>