Quellcode durchsuchen

人防监测数据推送

fuyuchuan vor 2 Tagen
Ursprung
Commit
c25e67ef05

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

@@ -13,7 +13,7 @@ import java.util.List;
 /**
  * 基础类数据传输控制器
  * 提供基础类数据上报的接口
- * 
+ *
  * @author han
  * @date 2025/03/20
  */

+ 100 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/IotDataController.java

@@ -0,0 +1,100 @@
+package com.usky.cdi.controller;
+
+import com.usky.cdi.service.impl.IotDataTransferService;
+import com.usky.cdi.service.vo.base.*;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+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;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/11/20
+ */
+@Slf4j
+@RestController
+@RequestMapping("/api/iotInfo")
+@ConditionalOnProperty(prefix = "mqtt", value = {"enabled"}, havingValue = "true")
+public class IotDataController {
+
+    @Autowired
+    private IotDataTransferService iotDataTransferService;
+
+    /**
+     * 上报水浸状态
+     */
+    @PostMapping("/flooded")
+    public String sendWaterLeak(@RequestBody WaterLeakVO vo) {
+        boolean success = iotDataTransferService.sendWaterLeak(vo);
+        return success ? "上报成功" : "上报失败";
+    }
+
+    /**
+     * 上报温度
+     */
+    @PostMapping("/temp")
+    public String sendTemp(@RequestBody TempVO vo) {
+        boolean success = iotDataTransferService.sendTemp(vo);
+        return success ? "上报成功" : "上报失败";
+    }
+
+    /**
+     * 上报湿度
+     */
+    @PostMapping("/rh")
+    public String batchSendHumidity(@RequestBody HumidityVO vo) {
+        boolean success = iotDataTransferService.batchSendHumidity(vo);
+        return success ? "上报成功" : "上报失败";
+    }
+
+    /**
+     * 上报氧气
+     */
+    @PostMapping("/o2")
+    public String sendOxygen(@RequestBody OxygenVO vo) {
+        boolean success = iotDataTransferService.sendOxygen(vo);
+        return success ? "上报成功" : "上报失败";
+    }
+
+    /**
+     * 上报二氧化碳
+     */
+    @PostMapping("/co2")
+    public String sendCarbonDioxide(@RequestBody Co2VO vo) {
+        boolean success = iotDataTransferService.sendCarbonDioxide(vo);
+        return success ? "上报成功" : "上报失败";
+    }
+
+    /**
+     * 上报一氧化碳
+     */
+    @PostMapping("/co")
+    public String sendCarbonMonoxide(@RequestBody CoVO vo) {
+        boolean success = iotDataTransferService.sendCarbonMonoxide(vo);
+        return success ? "上报成功" : "上报失败";
+    }
+
+    /**
+     * 上报人员闯入
+     */
+    @PostMapping("/personPresence")
+    public String sendPerson(@RequestBody PersonPresenceVO vo) {
+        boolean success = iotDataTransferService.sendPersonPresence(vo);
+        return success ? "上报成功" : "上报失败";
+    }
+
+    /**
+     * 上报用电负荷
+     */
+    @PostMapping("/electricityLoad")
+    public String sendElectricityLoad(@RequestBody ElectricityLoadVO vo) {
+        boolean success = iotDataTransferService.sendElectricityLoad(vo);
+        return success ? "上报成功" : "上报失败";
+    }
+
+}

+ 45 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/EnvMonitorMqttTopic.java

@@ -0,0 +1,45 @@
+package com.usky.cdi.service.enums;
+
+import lombok.Data;
+
+/**
+ * 环境监测MQTT Topic枚举(统一管理)
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/11/20
+ */
+public enum EnvMonitorMqttTopic {
+    // 水浸状态
+    WATER_LEAK("iotInfo/flooded"),
+
+    // 温度
+    TEMP("iotInfo/temp"),
+
+    // 湿度
+    HUMIDITY("iotInfo/rh"),
+
+    // 氧气
+    OXYGEN("iotInfo/o2"),
+
+    // 二氧化碳
+    CO2("iotInfo/co2"),
+
+    // 一氧化碳
+    CO("iotInfo/co"),
+
+    // 人员闯入情况
+    PERSON_PRESENCE("iotInfo/personPresence"),
+
+    // 用电负荷(后续用电负荷用)
+    ELECTRICITY_LOAD("iotInfo/electricityLoad");
+
+    private final String topic;
+
+    EnvMonitorMqttTopic(String topic) {
+        this.topic = topic;
+    }
+
+    public String getTopic() {
+        return topic;
+    }
+}

+ 303 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java

@@ -0,0 +1,303 @@
+package com.usky.cdi.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.usky.cdi.service.config.mqtt.MqttGateway;
+import com.usky.cdi.service.enums.EnvMonitorMqttTopic;
+import com.usky.cdi.service.util.SnowflakeIdGenerator;
+import com.usky.cdi.service.vo.base.*;
+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 java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.util.Date;
+
+/**
+ *
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/11/20
+ */
+@Slf4j
+@Service
+@ConditionalOnProperty(prefix = "mqtt", value = {"enabled"}, havingValue = "true")
+public class IotDataTransferService {
+
+    @Autowired(required = false)
+    private MqttGateway mqttGateway;
+
+    private final SnowflakeIdGenerator idGenerator;
+
+    public IotDataTransferService() {
+        // 使用默认的workerId和datacenterId,实际项目中可以从配置读取
+        this.idGenerator = new SnowflakeIdGenerator(1L, 1L);
+    }
+
+    /**
+     * 获取当前时间
+     */
+    private LocalDateTime getCurrentTime() {
+        return LocalDateTime.now();
+    }
+
+    /**
+     * 生成数据包ID
+     */
+    private Long generateDataPacketID() {
+        return idGenerator.nextPacketId();
+    }
+
+    /**
+     * 发送水浸状态
+     * Topic: iotInfo/flooded
+     *
+     * @param vo 水浸状态
+     * @return 是否发送成功
+     */
+    public boolean sendWaterLeak(WaterLeakVO vo) {
+        if (mqttGateway == null) {
+            log.warn("MQTT Gateway未初始化,无法发送消息");
+            return false;
+        }
+        try {
+            if (vo.getDataPacketID() == null) {
+                vo.setDataPacketID(generateDataPacketID());
+            }
+            if (vo.getPublishTime() == null) {
+                vo.setPublishTime(getCurrentTime());
+            }
+
+            String json = JSON.toJSONString(vo);
+            String topic = EnvMonitorMqttTopic.WATER_LEAK.getTopic();
+
+            log.info("发送水浸状态信息,Topic: {}, Data: {}", topic, json);
+            mqttGateway.sendToMqtt(topic, json);
+            return true;
+        } catch (Exception e) {
+            log.error("发送水浸状态信息失败", e);
+            return false;
+        }
+    }
+
+    /**
+     * 发送温度
+     * Topic: iotInfo/temp
+     *
+     * @param vo 温度信息
+     * @return 是否发送成功
+     */
+    public boolean sendTemp(TempVO vo) {
+        if (mqttGateway == null) {
+            log.warn("MQTT Gateway未初始化,无法发送消息");
+            return false;
+        }
+        try {
+            if (vo.getDataPacketID() == null) {
+                vo.setDataPacketID(generateDataPacketID());
+            }
+            if (vo.getPublishTime() == null) {
+                vo.setPublishTime(getCurrentTime());
+            }
+
+            String json = JSON.toJSONString(vo);
+            String topic = EnvMonitorMqttTopic.TEMP.getTopic();
+
+            log.info("发送温度信息,Topic: {}, Data: {}", topic, json);
+            mqttGateway.sendToMqtt(topic, json);
+            return true;
+        } catch (Exception e) {
+            log.error("发送温度信息失败", e);
+            return false;
+        }
+    }
+
+    /**
+     * 发送湿度
+     * Topic: iotInfo/rh
+     *
+     * @param vo 湿度信息
+     * @return 是否发送成功
+     */
+    public boolean batchSendHumidity(HumidityVO vo) {
+        if (mqttGateway == null) {
+            log.warn("MQTT Gateway未初始化,无法发送消息");
+            return false;
+        }
+        try {
+            if (vo.getDataPacketID() == null) {
+                vo.setDataPacketID(generateDataPacketID());
+            }
+            if (vo.getPublishTime() == null) {
+                vo.setPublishTime(getCurrentTime());
+            }
+
+            String json = JSON.toJSONString(vo);
+            String topic = EnvMonitorMqttTopic.HUMIDITY.getTopic();
+            log.info("发送湿度信息,Topic: {}, Data: {}", topic, json);
+            mqttGateway.sendToMqtt(topic, json);
+            return true;
+        } catch (Exception e) {
+            log.error("发送湿度信息失败", e);
+            return false;
+        }
+    }
+
+    /**
+     * 发送氧气浓度信息
+     * Topic: iotInfo/o2
+     *
+     * @param vo 氧气浓度
+     * @return 是否发送成功
+     */
+    public boolean sendOxygen(OxygenVO vo) {
+        if (mqttGateway == null) {
+            log.warn("MQTT Gateway未初始化,无法发送消息");
+            return false;
+        }
+        try {
+            if (vo.getDataPacketID() == null) {
+                vo.setDataPacketID(generateDataPacketID());
+            }
+            if (vo.getPublishTime() == null) {
+                vo.setPublishTime(getCurrentTime());
+            }
+
+            String json = JSON.toJSONString(vo);
+            String topic = EnvMonitorMqttTopic.OXYGEN.getTopic();
+            log.info("发送氧气浓度信息,Topic: {}, Data: {}", topic, json);
+            mqttGateway.sendToMqtt(topic, json);
+            return true;
+        } catch (Exception e) {
+            log.error("发送氧气浓度信息失败", e);
+            return false;
+        }
+    }
+
+    /**
+     * 发送二氧化碳浓度信息
+     * Topic: iotInfo/co2
+     *
+     * @param vo 二氧化碳浓度
+     * @return 是否发送成功
+     */
+    public boolean sendCarbonDioxide(Co2VO vo) {
+        if (mqttGateway == null) {
+            log.warn("MQTT Gateway未初始化,无法发送消息");
+            return false;
+        }
+        try {
+            if (vo.getDataPacketID() == null) {
+                vo.setDataPacketID(generateDataPacketID());
+            }
+            if (vo.getPublishTime() == null) {
+                vo.setPublishTime(getCurrentTime());
+            }
+
+            String json = JSON.toJSONString(vo);
+            String topic = EnvMonitorMqttTopic.CO2.getTopic();
+            log.info("发送二氧化碳浓度信息,Topic: {}, Data: {}", topic, json);
+
+            mqttGateway.sendToMqtt(topic, json);
+            return true;
+        } catch (Exception e) {
+            log.error("发送二氧化碳浓度信息失败", e);
+            return false;
+        }
+    }
+
+    /**
+     * 发送一氧化碳浓度信息
+     * Topic: iotInfo/co
+     *
+     * @param vo 一氧化碳浓度
+     * @return 是否发送成功
+     */
+    public boolean sendCarbonMonoxide(CoVO vo) {
+        if (mqttGateway == null) {
+            log.warn("MQTT Gateway未初始化,无法发送消息");
+            return false;
+        }
+        try {
+            if (vo.getDataPacketID() == null) {
+                vo.setDataPacketID(generateDataPacketID());
+            }
+            if (vo.getPublishTime() == null) {
+                vo.setPublishTime(getCurrentTime());
+            }
+
+            String json = JSON.toJSONString(vo);
+            String topic = EnvMonitorMqttTopic.CO.getTopic();
+            log.info("发送一氧化碳浓度信息,Topic: {}, Data: {}", topic, json);
+            mqttGateway.sendToMqtt(topic, json);
+            return true;
+        } catch (Exception e) {
+            log.error("发送一氧化碳浓度信息失败", e);
+            return false;
+        }
+    }
+
+    /**
+     * 发送人员闯入情况
+     *
+     * @param vo 人员闯入
+     * @return 是否发送成功
+     **/
+    public boolean sendPersonPresence(PersonPresenceVO vo) {
+        if (mqttGateway == null) {
+            log.warn("MQTT Gateway未初始化,无法发送消息");
+            return false;
+        }
+        try {
+            if (vo.getDataPacketID() == null) {
+                vo.setDataPacketID(generateDataPacketID());
+            }
+            if (vo.getPublishTime() == null) {
+                vo.setPublishTime(getCurrentTime());
+            }
+
+            String json = JSON.toJSONString(vo);
+            String topic = EnvMonitorMqttTopic.PERSON_PRESENCE.getTopic();
+            log.info("发送人员闯入情况,Topic: {}, Data: {}", topic, json);
+            mqttGateway.sendToMqtt(topic, json);
+            return true;
+        } catch (Exception e) {
+            log.error("发送人员闯入情况失败", e);
+            return false;
+        }
+    }
+
+    /**
+     * 发送人防用电负荷情况
+     *
+     * @param vo 防人电用负荷
+     * @return 是否发送成功
+     **/
+    public boolean sendElectricityLoad(ElectricityLoadVO vo) {
+        if (mqttGateway == null) {
+            log.warn("MQTT Gateway未初始化,无法发送消息");
+            return false;
+        }
+        try {
+            if (vo.getDataPacketID() == null) {
+                vo.setDataPacketID(generateDataPacketID());
+            }
+            if (vo.getPublishTime() == null) {
+                vo.setPublishTime(getCurrentTime());
+            }
+
+            String json = JSON.toJSONString(vo);
+            String topic = EnvMonitorMqttTopic.ELECTRICITY_LOAD.getTopic();
+            log.info("发送人防用电负荷情况,Topic: {}, Data: {}", topic, json);
+            mqttGateway.sendToMqtt(topic, json);
+            return true;
+
+        } catch (Exception e) {
+            log.error("发送人防用电负荷情况失败", e);
+            return false;
+        }
+    }
+
+
+}

+ 92 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/BaseEnvMonitorPushVO.java

@@ -0,0 +1,92 @@
+package com.usky.cdi.service.vo.base;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * 环境监测数据推送基类(兼容2021版)
+ * 子类:温度、湿度、氧气、二氧化碳、一氧化碳
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/11/20
+ */
+@Data
+public abstract class BaseEnvMonitorPushVO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 数据包ID(必填)
+     * 类型:Int,长度15(0 ~ 999999999999999)
+     */
+    private Long dataPacketID;
+
+    /**
+     * 人防工程ID(必填)
+     * 类型:Int,长度10(0 ~ 9999999999)
+     */
+    private Long engineeringID;
+
+    /**
+     * 物联设施ID(必填)
+     * 类型:Int,长度8(0 ~ 99999999)
+     */
+    private Long sensorID;
+
+    /**
+     * 监测时间(必填)
+     * 类型:Time(yyyy-MM-dd HH:mm:ss.SSS)
+     * 说明:1小时平均周期的末点时间(如10:00表示9:00-10:00的平均数据)
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
+    private LocalDateTime dataEndTime;
+
+    /**
+     * 上报时间(必填)
+     * 类型:Time(yyyy-MM-dd HH:mm:ss.SSS)
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss.SSS")
+    private LocalDateTime publishTime;
+
+    /**
+     * 获取监测值(子类实现,返回具体数值)
+     */
+    public abstract Number getSensorValue();
+
+    /**
+     * 校验公共字段(子类需重写校验监测值)
+     */
+    public void validateCommon() {
+        // 1. 必填字段非空校验
+        if (dataPacketID == null) throw new IllegalArgumentException("数据包ID(dataPacketID)为必填项");
+        if (engineeringID == null) throw new IllegalArgumentException("人防工程ID(engineeringID)为必填项");
+        if (sensorID == null) throw new IllegalArgumentException("物联设施ID(sensorID)为必填项");
+        if (dataEndTime == null) throw new IllegalArgumentException("监测时间(dataEndTime)为必填项");
+        if (publishTime == null) throw new IllegalArgumentException("上报时间(publishTime)为必填项");
+
+        // 2. 字段长度校验
+        if (String.valueOf(dataPacketID).length() > 15) throw new IllegalArgumentException("数据包ID长度不能超过15位");
+        if (String.valueOf(engineeringID).length() > 10)
+            throw new IllegalArgumentException("人防工程ID长度不能超过10位");
+        if (String.valueOf(sensorID).length() > 8) throw new IllegalArgumentException("物联设施ID长度不能超过8位");
+
+        // 3. 时间逻辑校验(监测时间不能晚于上报时间)
+        if (dataEndTime.isAfter(publishTime)) throw new IllegalArgumentException("监测时间不能晚于上报时间");
+    }
+
+    /**
+     * 完整校验(公共字段+监测值)
+     */
+    public final void validate() {
+        validateCommon();
+        validateSensorValue();
+    }
+
+    /**
+     * 校验监测值(子类重写,实现具体规则)
+     */
+    protected abstract void validateSensorValue();
+}

+ 57 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/Co2VO.java

@@ -0,0 +1,57 @@
+package com.usky.cdi.service.vo.base;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 二氧化碳浓度推送VO(兼容2021版)
+ * MQTT Topic:iotInfo/co2
+ * 说明:Float(0,3) → 无整数位,3位小数(如 0.123mg/m³,范围:0.000 ~ 0.999)
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/11/20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class Co2VO extends BaseEnvMonitorPushVO {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 1小时平均浓度(必填)
+     * 类型:Float(0,3),单位:mg/m³
+     */
+    private Float sensorValue;
+
+    @Override
+    public Number getSensorValue() {
+        return sensorValue;
+    }
+
+    @Override
+    protected void validateSensorValue() {
+        if (sensorValue == null) throw new IllegalArgumentException("二氧化碳浓度(sensorValue)为必填项");
+
+        // 校验范围:0.000 ~ 0.999mg/m³(符合Float(0,3)约束)
+        if (sensorValue < 0.000f || sensorValue > 0.999f) {
+            throw new IllegalArgumentException("二氧化碳浓度超出范围(0.000 ~ 0.999mg/m³),当前值:" + sensorValue);
+        }
+
+        // 校验小数位:最多3位
+        if (getDecimalDigits(sensorValue) > 3) {
+            throw new IllegalArgumentException("二氧化碳浓度小数位不能超过3位,当前值:" + sensorValue);
+        }
+    }
+
+    private int getDecimalDigits(float value) {
+        String str = String.valueOf(value);
+        if (!str.contains(".")) return 0;
+        String decimalPart = str.split("\\.")[1];
+        // 处理科学计数法(如 1.234E-4 → 0.0001234)
+        if (decimalPart.contains("E")) {
+            return 6;
+            // 科学计数法默认按超3位处理
+        }
+        return decimalPart.length();
+    }
+}

+ 51 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/CoVO.java

@@ -0,0 +1,51 @@
+package com.usky.cdi.service.vo.base;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 一氧化碳浓度推送VO(兼容2021版)
+ * MQTT Topic:iotInfo/co
+ * 说明:Float(2,2) → 0.00 ~ 99.99mg/m³
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/11/20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class CoVO extends BaseEnvMonitorPushVO {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 1小时平均浓度(必填)
+     * 类型:Float(2,2),单位:mg/m³
+     */
+    private Float sensorValue;
+
+    @Override
+    public Number getSensorValue() {
+        return sensorValue;
+    }
+
+    @Override
+    protected void validateSensorValue() {
+        if (sensorValue == null) throw new IllegalArgumentException("一氧化碳浓度(sensorValue)为必填项");
+
+        // 校验范围:0.00 ~ 99.99mg/m³(符合Float(2,2)约束)
+        if (sensorValue < 0.00f || sensorValue > 99.99f) {
+            throw new IllegalArgumentException("一氧化碳浓度超出范围(0.00 ~ 99.99mg/m³),当前值:" + sensorValue);
+        }
+
+        // 校验小数位:最多2位
+        if (getDecimalDigits(sensorValue) > 2) {
+            throw new IllegalArgumentException("一氧化碳浓度小数位不能超过2位,当前值:" + sensorValue);
+        }
+    }
+
+    private int getDecimalDigits(float value) {
+        String str = String.valueOf(value);
+        if (!str.contains(".")) return 0;
+        return str.split("\\.")[1].length();
+    }
+}

+ 170 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/ElectricityLoadVO.java

@@ -0,0 +1,170 @@
+package com.usky.cdi.service.vo.base;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+
+/**
+ * 人防用电负荷推送VO(兼容2021版)
+ * 用途:每半小时上报配电箱监测数据(电压、电流、功率等)
+ * MQTT Topic:iotInfo/electricityLoad
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/11/20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class ElectricityLoadVO extends BaseEnvMonitorPushVO {
+
+    private static final long serialVersionUID = 1L;
+
+    // ====================== 专属字段(严格映射数据包定义)======================
+    /**
+     * 物联设施ID(必填)
+     * 类型:Int,长度8(0 ~ 99999999)
+     */
+    private Long sensorID;
+
+    /**
+     * A相电压(必填)
+     * 类型:Float(3,2),单位:伏(V)(范围:0.00 ~ 999.99V)
+     */
+    private Float aVoltage;
+
+    /**
+     * B相电压(必填)
+     * 类型:Float(3,2),单位:伏(V)
+     */
+    private Float bVoltage;
+
+    /**
+     * C相电压(必填)
+     * 类型:Float(3,2),单位:伏(V)
+     */
+    private Float cVoltage;
+
+    /**
+     * A相电流(必填)
+     * 类型:Float(3,2),单位:安(A)
+     */
+    private Float aElectricity;
+
+    /**
+     * B相电流(必填)
+     * 类型:Float(3,2),单位:安(A)
+     */
+    private Float bElectricity;
+
+    /**
+     * C相电流(必填)
+     * 类型:Float(3,2),单位:安(A)
+     */
+    private Float cElectricity;
+
+    /**
+     * 总功率(必填)
+     * 类型:Float(4,2),单位:千瓦(kW)(范围:0.00 ~ 9999.99kW)
+     */
+    private Float totalPower;
+
+    /**
+     * 线温1(非必填)
+     * 类型:Float(2,2),单位:℃(范围:-99.99 ~ 99.99℃)
+     */
+    private Float line1TEMP;
+
+    /**
+     * 线温2(非必填)
+     * 类型:Float(2,2),单位:℃
+     */
+    private Float line2TEMP;
+
+    /**
+     * 线温3(非必填)
+     * 类型:Float(2,2),单位:℃
+     */
+    private Float line3TEMP;
+
+    /**
+     * 线温4(非必填)
+     * 类型:Float(2,2),单位:℃
+     */
+    private Float line4TEMP;
+
+    /**
+     * 剩余电流(非必填)
+     * 类型:Float(4,2),单位:mA(范围:0.00 ~ 9999.99mA)
+     */
+    private Float leakageCurrent;
+
+    // ====================== 重写父类方法(适配多字段校验)======================
+
+    /**
+     * 父类抽象方法:返回核心监测值(取总功率作为代表)
+     */
+    @Override
+    public Number getSensorValue() {
+        return totalPower;
+    }
+
+    /**
+     * 个性化校验:校验所有用电负荷字段的合法性
+     */
+    @Override
+    protected void validateSensorValue() {
+        // 1. 必填字段非空校验(父类已校验公共字段,此处校验专属必填字段)
+        if (sensorID == null) throw new IllegalArgumentException("物联设施ID(sensorID)为必填项");
+        if (aVoltage == null) throw new IllegalArgumentException("A相电压(aVoltage)为必填项");
+        if (bVoltage == null) throw new IllegalArgumentException("B相电压(bVoltage)为必填项");
+        if (cVoltage == null) throw new IllegalArgumentException("C相电压(cVoltage)为必填项");
+        if (aElectricity == null) throw new IllegalArgumentException("A相电流(aElectricity)为必填项");
+        if (bElectricity == null) throw new IllegalArgumentException("B相电流(bElectricity)为必填项");
+        if (cElectricity == null) throw new IllegalArgumentException("C相电流(cElectricity)为必填项");
+        if (totalPower == null) throw new IllegalArgumentException("总功率(totalPower)为必填项");
+
+        // 2. 字段长度/范围校验(Float(3,2):总3位+2位小数;Float(4,2):总4位+2位小数)
+        validateFloatRange(aVoltage, 0.00f, 999.99f, "A相电压", 2);
+        validateFloatRange(bVoltage, 0.00f, 999.99f, "B相电压", 2);
+        validateFloatRange(cVoltage, 0.00f, 999.99f, "C相电压", 2);
+        validateFloatRange(aElectricity, 0.00f, 999.99f, "A相电流", 2);
+        validateFloatRange(bElectricity, 0.00f, 999.99f, "B相电流", 2);
+        validateFloatRange(cElectricity, 0.00f, 999.99f, "C相电流", 2);
+        validateFloatRange(totalPower, 0.00f, 9999.99f, "总功率", 2);
+
+        // 3. 非必填字段校验(存在时才校验)
+        if (line1TEMP != null) validateFloatRange(line1TEMP, -99.99f, 99.99f, "线温1", 2);
+        if (line2TEMP != null) validateFloatRange(line2TEMP, -99.99f, 99.99f, "线温2", 2);
+        if (line3TEMP != null) validateFloatRange(line3TEMP, -99.99f, 99.99f, "线温3", 2);
+        if (line4TEMP != null) validateFloatRange(line4TEMP, -99.99f, 99.99f, "线温4", 2);
+        if (leakageCurrent != null) validateFloatRange(leakageCurrent, 0.00f, 9999.99f, "剩余电流", 2);
+
+        // 4. 物联设施ID长度校验
+        if (String.valueOf(sensorID).length() > 8) {
+            throw new IllegalArgumentException("物联设施ID(sensorID)长度不能超过8位");
+        }
+    }
+
+    /**
+     * 辅助方法:校验Float类型的范围和小数位
+     * @param value 字段值
+     * @param min 最小值
+     * @param max 最大值
+     * @param fieldName 字段名称
+     * @param maxDecimal 最大小数位数
+     */
+    private void validateFloatRange(Float value, float min, float max, String fieldName, int maxDecimal) {
+        // 范围校验
+        if (value < min || value > max) {
+            throw new IllegalArgumentException(fieldName + "(" + fieldName.toLowerCase() + ")超出范围[" + min + "~" + max + "],当前值:" + value);
+        }
+        // 小数位校验
+        String str = String.valueOf(value);
+        if (str.contains(".")) {
+            int decimalLen = str.split("\\.")[1].length();
+            if (decimalLen > maxDecimal) {
+                throw new IllegalArgumentException(fieldName + "(" + fieldName.toLowerCase() + ")小数位不能超过" + maxDecimal + "位,当前值:" + value);
+            }
+        }
+    }
+}

+ 51 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/HumidityVO.java

@@ -0,0 +1,51 @@
+package com.usky.cdi.service.vo.base;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 空气湿度推送VO(兼容2021版)
+ * MQTT Topic:iotInfo/rh
+ * 说明:Float(2,2) → 0.00 ~ 100.00%
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/11/20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class HumidityVO extends BaseEnvMonitorPushVO {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 湿度(必填)
+     * 类型:Float(2,2),单位:%
+     */
+    private Float sensorValue;
+
+    @Override
+    public Number getSensorValue() {
+        return sensorValue;
+    }
+
+    @Override
+    protected void validateSensorValue() {
+        if (sensorValue == null) throw new IllegalArgumentException("湿度(sensorValue)为必填项");
+
+        // 校验范围:0.00 ~ 100.00%(湿度不可能为负或超过100%)
+        if (sensorValue < 0.00f || sensorValue > 100.00f) {
+            throw new IllegalArgumentException("湿度值超出范围(0.00 ~ 100.00%),当前值:" + sensorValue);
+        }
+
+        // 校验小数位:最多2位
+        if (getDecimalDigits(sensorValue) > 2) {
+            throw new IllegalArgumentException("湿度值小数位不能超过2位,当前值:" + sensorValue);
+        }
+    }
+
+    private int getDecimalDigits(float value) {
+        String str = String.valueOf(value);
+        if (!str.contains(".")) return 0;
+        return str.split("\\.")[1].length();
+    }
+}

+ 51 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/OxygenVO.java

@@ -0,0 +1,51 @@
+package com.usky.cdi.service.vo.base;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 氧气浓度推送VO(兼容2021版)
+ * MQTT Topic:iotInfo/o2
+ * 说明:Float(2,2) → 0.00 ~ 100.00%
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/11/20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class OxygenVO extends BaseEnvMonitorPushVO {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 1小时平均浓度(必填)
+     * 类型:Float(2,2),单位:%
+     */
+    private Float sensorValue;
+
+    @Override
+    public Number getSensorValue() {
+        return sensorValue;
+    }
+
+    @Override
+    protected void validateSensorValue() {
+        if (sensorValue == null) throw new IllegalArgumentException("氧气浓度(sensorValue)为必填项");
+
+        // 校验范围:0.00 ~ 100.00%(浓度不可能为负或超过100%)
+        if (sensorValue < 0.00f || sensorValue > 100.00f) {
+            throw new IllegalArgumentException("氧气浓度超出范围(0.00 ~ 100.00%),当前值:" + sensorValue);
+        }
+
+        // 校验小数位:最多2位
+        if (getDecimalDigits(sensorValue) > 2) {
+            throw new IllegalArgumentException("氧气浓度小数位不能超过2位,当前值:" + sensorValue);
+        }
+    }
+
+    private int getDecimalDigits(float value) {
+        String str = String.valueOf(value);
+        if (!str.contains(".")) return 0;
+        return str.split("\\.")[1].length();
+    }
+}

+ 60 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/PersonPresenceVO.java

@@ -0,0 +1,60 @@
+package com.usky.cdi.service.vo.base;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+
+/**
+ * 人员闯入情况推送VO(兼容2021版)
+ * 用途:每半小时上报最新人员闯入状态,设防时段闯入需同步作为告警事件上传
+ * MQTT Topic:iotInfo/personPresence
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/11/20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class PersonPresenceVO extends BaseEnvMonitorPushVO {
+
+    private static final long serialVersionUID = 1L;
+
+    // ====================== 人员闯入状态专属枚举(对应附录表19)======================
+    public static final class SensorValueEnum {
+        public static final int NO_INTRUSION = 0; // 无人员闯入
+        public static final int INTRUSION = 1;    // 有人员闯入
+
+        /**
+         * 校验状态编码合法性
+         */
+        public static boolean isValid(int value) {
+            return value == NO_INTRUSION || value == INTRUSION;
+        }
+    }
+
+    // ====================== 专属字段(父类已包含公共字段)======================
+    /**
+     * 人员闯入状态(必填)
+     * 类型:Int,长度1(取值参考 SensorValueEnum)
+     */
+    private Integer sensorValue;
+
+    // ====================== 实现父类抽象方法 ======================
+    @Override
+    public Number getSensorValue() {
+        return sensorValue;
+    }
+
+    @Override
+    protected void validateSensorValue() {
+        // 1. 非空校验
+        if (sensorValue == null) {
+            throw new IllegalArgumentException("人员闯入状态(sensorValue)为必填项");
+        }
+
+        // 2. 状态合法性校验
+        if (!SensorValueEnum.isValid(sensorValue)) {
+            throw new IllegalArgumentException("人员闯入状态(sensorValue)无效,允许值:0(无闯入)、1(有闯入)");
+        }
+    }
+}

+ 54 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/TempVO.java

@@ -0,0 +1,54 @@
+package com.usky.cdi.service.vo.base;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 空气温度推送VO(兼容2021版)
+ * MQTT Topic:iotInfo/temp
+ * 说明:Float(2,2) → 总长度2位,小数位2位(如 18.50℃,范围:-99.99 ~ 99.99)
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/11/20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class TempVO extends BaseEnvMonitorPushVO {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 温度(必填)
+     * 类型:Float(2,2),单位:℃
+     */
+    private Float sensorValue;
+
+    @Override
+    public Number getSensorValue() {
+        return sensorValue;
+    }
+
+    @Override
+    protected void validateSensorValue() {
+        if (sensorValue == null) throw new IllegalArgumentException("温度(sensorValue)为必填项");
+
+        // 校验范围:-99.99 ~ 99.99(符合Float(2,2)长度约束)
+        if (sensorValue < -99.99f || sensorValue > 99.99f) {
+            throw new IllegalArgumentException("温度值超出范围(-99.99 ~ 99.99℃),当前值:" + sensorValue);
+        }
+
+        // 校验小数位:最多2位
+        if (getDecimalDigits(sensorValue) > 2) {
+            throw new IllegalArgumentException("温度值小数位不能超过2位,当前值:" + sensorValue);
+        }
+    }
+
+    /**
+     * 辅助方法:获取小数位数
+     */
+    private int getDecimalDigits(float value) {
+        String str = String.valueOf(value);
+        if (!str.contains(".")) return 0;
+        return str.split("\\.")[1].length();
+    }
+}

+ 68 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/base/WaterLeakVO.java

@@ -0,0 +1,68 @@
+package com.usky.cdi.service.vo.base;
+
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+
+/**
+ * 水浸状态定时上报 VO(兼容2021版)
+ * 用途:每半小时上报传感器最新水浸状态
+ * MQTT Topic:iotInfo/flooded
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2025/11/20
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class WaterLeakVO extends BaseEnvMonitorPushVO {
+
+    private static final long serialVersionUID = 1L;
+
+    // ====================== 水浸状态专属枚举(保留个性化定义)======================
+
+    /**
+     * 水浸状态定义(对应附录表12)
+     */
+    public static final class SensorValueEnum {
+        public static final int NO_WATER = 0; // 无水
+        public static final int HAS_WATER = 1; // 有水
+
+        /**
+         * 校验状态编码合法性
+         */
+        public static boolean isValid(int value) {
+            return value == NO_WATER || value == HAS_WATER;
+        }
+    }
+
+    // ====================== 水浸状态专属字段(仅保留个性化字段)======================
+    /**
+     * 最新水浸状态(必填)
+     * 类型:Int,长度1(取值参考 SensorValueEnum)
+     * 说明:0=无水,1=有水
+     */
+    private Integer sensorValue;
+
+    // ====================== 实现父类抽象方法(个性化逻辑)======================
+    @Override
+    public Number getSensorValue() {
+        return sensorValue;
+    }
+
+    /**
+     * 个性化校验:仅校验水浸状态的合法性(公共字段校验由父类完成)
+     */
+    @Override
+    protected void validateSensorValue() {
+        // 1. 校验水浸状态非空
+        if (sensorValue == null) {
+            throw new IllegalArgumentException("最新水浸状态(sensorValue)为必填项");
+        }
+
+        // 2. 校验水浸状态编码合法性(对应附录表12)
+        if (!SensorValueEnum.isValid(sensorValue)) {
+            throw new IllegalArgumentException("最新水浸状态(sensorValue)无效,允许值:0(无水)、1(有水)");
+        }
+    }
+}