Jelajahi Sumber

提交隐患排查及mqtt连接替代websocket

Zimo 2 hari lalu
induk
melakukan
85507a93b8
28 mengubah file dengan 1480 tambahan dan 360 penghapusan
  1. 1 1
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java
  2. 2 2
      yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java
  3. 1 0
      yudao-module-pms/pom.xml
  4. 53 0
      yudao-module-pms/yudao-module-pms-abc/pom.xml
  5. 26 0
      yudao-module-pms/yudao-module-pms-abc/src/main/java/cn/iocoder/yudao/module/pms/abc/config/DeviceMqttService.java
  6. 476 0
      yudao-module-pms/yudao-module-pms-abc/src/main/java/cn/iocoder/yudao/module/pms/abc/config/MqttClientManager.java
  7. 19 0
      yudao-module-pms/yudao-module-pms-abc/src/main/java/cn/iocoder/yudao/module/pms/abc/config/MqttProperties.java
  8. 128 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/hazard/IotHazardController.java
  9. 46 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/hazard/vo/IotHazardPageReqVO.java
  10. 17 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/hazard/vo/IotHazardRectifyVO.java
  11. 56 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/hazard/vo/IotHazardRespVO.java
  12. 42 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/hazard/vo/IotHazardSaveReqVO.java
  13. 68 68
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/config/MqttClientConfig.java
  14. 7 11
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/core/StartBoot.java
  15. 246 246
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/mqtt/PubMqttClient.java
  16. 62 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/dataobject/qhse/hazard/IotHazardDO.java
  17. 34 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/qhse/hazard/IotHazardMapper.java
  18. 4 5
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/ZhbdJob.java
  19. 1 6
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/alarm/AlarmJob.java
  20. 3 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/inspect/IotInspectOrderServiceImpl.java
  21. 56 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/qhse/hazard/IotHazardService.java
  22. 102 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/qhse/hazard/IotHazardServiceImpl.java
  23. 5 8
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/yanfan/sip/VideoMqttService.java
  24. 4 5
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/websocket/WsClientManager.java
  25. 2 2
      yudao-server/pom.xml
  26. 3 3
      yudao-server/src/main/resources/application-dev.yaml
  27. 15 2
      yudao-server/src/main/resources/application.yaml
  28. 1 1
      yudao-server/src/main/resources/logback-spring.xml

+ 1 - 1
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/controller/admin/file/FileController.java

@@ -25,7 +25,6 @@ import javax.annotation.security.PermitAll;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.validation.Valid;
-
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -80,6 +79,7 @@ public class FileController {
     }
 
     @GetMapping("/{configId}/get/**")
+//    @PreAuthorize("@ss.hasPermission('infra:file:query')")
     @PermitAll
     @Operation(summary = "下载文件")
     @Parameter(name = "configId", description = "配置编号", required = true)

+ 2 - 2
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/framework/security/config/SecurityConfiguration.java

@@ -35,8 +35,8 @@ public class SecurityConfiguration {
                 // Spring Boot Admin Server 的安全配置
                 registry.requestMatchers(adminSeverContextPath).permitAll()
                         .requestMatchers(adminSeverContextPath + "/**").permitAll();
-                // 文件读取
-                registry.requestMatchers(buildAdminApi("/infra/file/*/get/**")).permitAll();
+                // 文件读取 todo phli
+//                registry.requestMatchers(buildAdminApi("/infra/file/*/get/**")).permitAll();
             }
 
         };

+ 1 - 0
yudao-module-pms/pom.xml

@@ -10,6 +10,7 @@
     <modules>
         <module>yudao-module-pms-api</module>
         <module>yudao-module-pms-biz</module>
+        <module>yudao-module-pms-abc</module>
     </modules>
     <modelVersion>4.0.0</modelVersion>
 

+ 53 - 0
yudao-module-pms/yudao-module-pms-abc/pom.xml

@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>cn.iocoder.boot</groupId>
+        <artifactId>yudao-module-pms</artifactId>
+        <version>2.4.2-jdk8-SNAPSHOT</version>
+    </parent>
+
+    <artifactId>yudao-module-pms-abc</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+    <dependencies>
+        <dependency>
+            <groupId>org.eclipse.paho</groupId>
+            <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
+            <version>1.2.5</version>
+        </dependency>
+
+        <!-- Spring 集成 MQTT -->
+        <dependency>
+            <groupId>org.springframework.integration</groupId>
+            <artifactId>spring-integration-mqtt</artifactId>
+            <version>5.5.20</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-common</artifactId>
+            <version>2.4.2-jdk8-SNAPSHOT</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot</artifactId>
+            <version>2.7.18</version>
+        </dependency>
+        <!--        <dependency>-->
+<!--            <groupId>org.springframework.boot</groupId>-->
+<!--            <artifactId>spring-boot</artifactId>-->
+<!--        </dependency>-->
+        <dependency>
+            <groupId>cn.iocoder.boot</groupId>
+            <artifactId>yudao-module-pms-biz</artifactId>
+            <version>2.4.2-jdk8-SNAPSHOT</version>
+        </dependency>
+    </dependencies>
+
+</project>

+ 26 - 0
yudao-module-pms/yudao-module-pms-abc/src/main/java/cn/iocoder/yudao/module/pms/abc/config/DeviceMqttService.java

@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.pms.abc.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+
+@Slf4j
+@Service
+public class DeviceMqttService {
+    @Autowired
+    private MqttClientManager mqttClientManager;
+
+    @PostConstruct
+    public void initDeviceSubscription() {
+        // 批量订阅多个设备 Topic(即使启动时设备未在线,后续上线也能接收消息)
+        String[] deviceTopics = {
+                "/649/YF6660355/property/post"
+        };
+        mqttClientManager.batchSubscribe(deviceTopics);
+
+        // 动态新增单个设备订阅
+//        mqttClientManager.subscribe("/636/YF652/property/post");
+    }
+}

+ 476 - 0
yudao-module-pms/yudao-module-pms-abc/src/main/java/cn/iocoder/yudao/module/pms/abc/config/MqttClientManager.java

@@ -0,0 +1,476 @@
+package cn.iocoder.yudao.module.pms.abc.config;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.date.LocalDateTimeUtil;
+import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
+import cn.iocoder.yudao.module.pms.constant.PmsConstants;
+import cn.iocoder.yudao.module.pms.controller.admin.alarm.vo.IotAlarmRecordPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.alarm.vo.WsTdPropertyVO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotdeviceperson.vo.IotDevicePersonPageReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.IotDeviceDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.alarm.IotAlarmRecordDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.alarm.IotAlarmSettingDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.iotdeviceperson.IotDevicePersonDO;
+import cn.iocoder.yudao.module.pms.dal.mysql.IotDeviceMapper;
+import cn.iocoder.yudao.module.pms.dal.mysql.alarm.IotAlarmRecordMapper;
+import cn.iocoder.yudao.module.pms.dal.mysql.alarm.IotAlarmSettingMapper;
+import cn.iocoder.yudao.module.pms.message.PmsMessage;
+import cn.iocoder.yudao.module.pms.service.iotdeviceperson.IotDevicePersonService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import com.alibaba.fastjson.JSON;
+import com.google.common.collect.ImmutableList;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.*;
+import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import javax.annotation.Resource;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+@Slf4j
+@Component
+public class MqttClientManager {
+    @Autowired
+    private MqttProperties mqttProperties;
+
+    private MqttClient mqttClient;
+    // 存储「需要订阅的 Topic + QoS」(核心:重连时重新订阅)
+    private final ConcurrentHashMap<String, Integer> needSubscribeTopics = new ConcurrentHashMap<>();
+    // 连接重试线程池(避免重复创建)
+    private final ScheduledExecutorService connectRetryExecutor = Executors.newSingleThreadScheduledExecutor();
+
+    /**
+     * 初始化 MQTT 连接
+     */
+    @PostConstruct
+    public void init() {
+        // 初始化连接
+        connectMqtt();
+    }
+
+    /**
+     * 建立 MQTT 连接(抽离独立方法,方便重试/重连调用)
+     */
+    private void connectMqtt() {
+        // 若已连接,直接返回
+        if (mqttClient != null && mqttClient.isConnected()) {
+            log.warn("MQTT 客户端已处于连接状态,跳过连接");
+            return;
+        }
+
+        try {
+            // 1. 生成客户端ID(避免重复)
+            String clientId = mqttProperties.getClientIdPrefix() + UUID.randomUUID().toString().replace("-", "");
+            MemoryPersistence persistence = new MemoryPersistence();
+
+            // 2. 创建 MQTT 客户端
+            mqttClient = new MqttClient(mqttProperties.getHost(), clientId, persistence);
+
+            // 3. 配置连接参数
+            MqttConnectOptions connectOptions = new MqttConnectOptions();
+            connectOptions.setCleanSession(mqttProperties.isCleanSession());
+            connectOptions.setUserName(mqttProperties.getUsername());
+            connectOptions.setPassword(mqttProperties.getPassword().toCharArray());
+            connectOptions.setConnectionTimeout(mqttProperties.getConnectTimeout() / 1000); // 单位:秒
+            connectOptions.setAutomaticReconnect(true); // 开启自动重连
+            connectOptions.setMaxReconnectDelay(mqttProperties.getReconnectPeriod()); // 最大重连延迟
+
+            // 4. 设置回调(核心:重连成功后重新订阅)
+            mqttClient.setCallback(new MqttCallbackExtended() {
+                // 重连/首次连接成功回调(关键:MqttCallbackExtended 才有的方法)
+                @Override
+                public void connectComplete(boolean reconnect, String serverURI) {
+                    if (reconnect) {
+                        log.info("MQTT 重连成功,服务器地址:{}", serverURI);
+                    } else {
+                        log.info("MQTT 首次连接成功,服务器地址:{}", serverURI);
+                    }
+                    // 无论首次连接还是重连,都强制重新订阅所有 Topic(避免丢失)
+                    subscribeAllNeededTopics();
+                }
+
+                // 连接断开回调
+                @Override
+                public void connectionLost(Throwable cause) {
+                    log.error("MQTT 连接断开,原因:{}", cause.getMessage(), cause);
+                }
+
+                // 消息到达回调
+                @Override
+                public void messageArrived(String topic, MqttMessage message) throws Exception {
+                    handleReceivedMessage(topic, message);
+                }
+
+                // 消息发布完成回调
+                @Override
+                public void deliveryComplete(IMqttDeliveryToken token) {
+                    // 无需处理发布回调(当前仅订阅)
+                }
+            });
+
+            // 5. 建立连接
+            mqttClient.connect(connectOptions);
+            log.info("MQTT 客户端连接成功,客户端ID:{}", clientId);
+
+        } catch (MqttException e) {
+            log.error("MQTT 连接失败,错误码:{},5秒后重试", e.getReasonCode(), e);
+            // 连接失败,5秒后重试
+            connectRetryExecutor.schedule(this::connectMqtt, 5, TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * 批量订阅 Topic(核心:先记录,再订阅)
+     * @param topics 要订阅的 Topic 数组
+     */
+    public void batchSubscribe(String[] topics) {
+        batchSubscribe(topics, mqttProperties.getQos());
+    }
+
+    /**
+     * 批量订阅 Topic(指定 QoS)
+     * @param topics 要订阅的 Topic 数组
+     * @param qos QoS 级别(0/1/2)
+     */
+    public void batchSubscribe(String[] topics, int qos) {
+        if (topics == null || topics.length == 0) {
+            log.warn("批量订阅的 Topic 列表为空,跳过");
+            return;
+        }
+
+        // 1. 先记录需要订阅的 Topic(持久化,重连时使用)
+        for (String topic : topics) {
+            if (topic == null || topic.trim().isEmpty()) {
+                log.warn("跳过空的 Topic 订阅");
+                continue;
+            }
+            needSubscribeTopics.put(topic.trim(), qos);
+            log.debug("已记录需要订阅的 Topic:{},QoS:{}", topic, qos);
+        }
+
+        // 2. 立即订阅(如果当前已连接)
+        if (mqttClient != null && mqttClient.isConnected()) {
+            subscribeTopics(topics, qos);
+        } else {
+            log.warn("MQTT 客户端未连接,已记录 Topic 待连接后自动订阅:{}", Arrays.toString(topics));
+        }
+    }
+
+    /**
+     * 单个订阅 Topic
+     * @param topic 要订阅的 Topic
+     */
+    public void subscribe(String topic) {
+        subscribe(topic, mqttProperties.getQos());
+    }
+
+    /**
+     * 单个订阅 Topic(指定 QoS)
+     * @param topic 要订阅的 Topic
+     * @param qos QoS 级别
+     */
+    public void subscribe(String topic, int qos) {
+        if (topic == null || topic.trim().isEmpty()) {
+            log.warn("订阅的 Topic 为空,跳过");
+            return;
+        }
+        topic = topic.trim();
+
+        // 1. 记录需要订阅的 Topic
+        needSubscribeTopics.put(topic, qos);
+
+        // 2. 立即订阅(如果已连接)
+        if (mqttClient != null && mqttClient.isConnected()) {
+            try {
+                mqttClient.subscribe(topic, qos);
+                log.info("MQTT 订阅 Topic 成功:{},QoS:{}", topic, qos);
+            } catch (MqttException e) {
+                log.error("MQTT 订阅 Topic 失败:{},QoS:{}", topic, qos, e);
+            }
+        } else {
+            log.warn("MQTT 客户端未连接,已记录 Topic 待连接后自动订阅:{}", topic);
+        }
+    }
+
+    /**
+     * 取消单个 Topic 订阅
+     * @param topic 要取消的 Topic
+     */
+    public void unsubscribe(String topic) {
+        if (topic == null || topic.trim().isEmpty()) {
+            log.warn("取消订阅的 Topic 为空,跳过");
+            return;
+        }
+        topic = topic.trim();
+
+        // 1. 移除需要订阅的记录
+        needSubscribeTopics.remove(topic);
+
+        // 2. 立即取消订阅(如果已连接)
+        if (mqttClient != null && mqttClient.isConnected()) {
+            try {
+                mqttClient.unsubscribe(topic);
+                log.info("MQTT 取消订阅 Topic 成功:{}", topic);
+            } catch (MqttException e) {
+                log.error("MQTT 取消订阅 Topic 失败:{}", topic, e);
+            }
+        } else {
+            log.warn("MQTT 客户端未连接,已移除 Topic 订阅记录:{}", topic);
+        }
+    }
+
+    /**
+     * 订阅所有已记录的 Topic(核心:重连/首次连接时调用)
+     */
+    private void subscribeAllNeededTopics() {
+        if (needSubscribeTopics.isEmpty()) {
+            log.debug("无需要订阅的 Topic,跳过重订阅");
+            return;
+        }
+        if (mqttClient == null || !mqttClient.isConnected()) {
+            log.warn("MQTT 未连接,无法订阅 Topic");
+            return;
+        }
+
+        // 拆分 Topic 和 QoS 数组
+        String[] topics = needSubscribeTopics.keySet().toArray(new String[0]);
+        int[] qosArr = needSubscribeTopics.values().stream().mapToInt(Integer::intValue).toArray();
+
+        try {
+            // 强制订阅(即使已订阅,重复调用也不会报错)
+            mqttClient.subscribe(topics, qosArr);
+            log.info("MQTT 批量订阅/重订阅成功,共 {} 个 Topic:{}", topics.length, Arrays.toString(topics));
+        } catch (MqttException e) {
+            log.error("MQTT 重订阅 Topic 失败,5秒后重试", e);
+            // 订阅失败,5秒后重试
+            connectRetryExecutor.schedule(this::subscribeAllNeededTopics, 5, TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * 直接订阅指定 Topic 数组(底层调用)
+     */
+    private void subscribeTopics(String[] topics, int qos) {
+        int[] qosArr = new int[topics.length];
+        Arrays.fill(qosArr, qos);
+        try {
+            mqttClient.subscribe(topics, qosArr);
+            log.info("MQTT 批量订阅成功,Topic 列表:{},QoS:{}", Arrays.toString(topics), qos);
+        } catch (MqttException e) {
+            log.error("MQTT 批量订阅失败,Topic 列表:{}", Arrays.toString(topics), e);
+        }
+    }
+
+    /**
+     * 处理收到的 MQTT 消息(核心业务逻辑)
+     */
+    private void handleReceivedMessage(String topic, MqttMessage message) {
+        try {
+            // 1. 基础校验:是否是需要处理的 Topic
+            if (!needSubscribeTopics.containsKey(topic)) {
+                log.warn("收到未订阅的 Topic 消息,忽略处理:{}", topic);
+                return;
+            }
+
+            // 2. 解析消息体(指定 UTF-8 编码,避免乱码)
+            String payload = new String(message.getPayload(), StandardCharsets.UTF_8);
+            log.debug("收到 MQTT 消息,Topic:{},内容:{}", topic, payload);
+
+            // 3. 结构化解析(根据实际消息格式调整)
+            String message1 = JSON.parseObject(payload, String.class);
+
+            //分发逻辑处理
+            String[] split = topic.split("/");
+            handleReceivedMessagePms(split[2], message1);
+
+        } catch (Exception e) {
+            log.error("处理 MQTT 消息失败,Topic:{}", topic, e);
+        }
+    }
+
+    @Resource
+    private IotDeviceMapper iotDeviceMapper;
+    @Resource
+    private IotAlarmSettingMapper iotAlarmSettingMapper;
+    @Resource
+    private IotAlarmRecordMapper iotAlarmRecordMapper;
+    @Resource
+    private IotDevicePersonService iotDevicePersonService;
+    @Resource
+    private AdminUserApi adminUserApi;
+    @Resource
+    private PmsMessage pmsMessage;
+
+    @TenantIgnore
+    private void handleReceivedMessagePms(String clientId, String message) {
+        try {
+            if (!message.startsWith("【连接成功】")){
+                List<WsTdPropertyVO> wsTdPropertyVOS = JSON.parseArray(message, WsTdPropertyVO.class);
+                //todo websocket
+                TenantUtils.executeIgnore(() ->{
+                    List<IotDeviceDO> iotDeviceDOS = iotDeviceMapper.selectByCodeIn(ImmutableList.of(clientId));
+                    if (CollUtil.isNotEmpty(iotDeviceDOS)) {
+                        IotDeviceDO iotDeviceDO = iotDeviceDOS.get(0);
+                        List<IotAlarmSettingDO> alarms = iotAlarmSettingMapper.selectList("device_id", iotDeviceDO.getId());
+                        if (CollUtil.isNotEmpty(alarms)) {//设备赋值了
+                            wsTdPropertyVOS.forEach(wsTdPropertyVO -> {
+                                alarms.stream().filter(e -> e.getPropertyCode().equals(wsTdPropertyVO.getIdentity())).findFirst().ifPresent(alarm -> {
+                                    if (NumberUtils.isConvertibleToDouble(wsTdPropertyVO.getLogValue())&&NumberUtils.isConvertibleToDouble(alarm.getMaxValue())&&
+                                            NumberUtils.isConvertibleToDouble(alarm.getMinValue())){//如果可以被转为数值
+                                        if (Double.parseDouble(wsTdPropertyVO.getLogValue())>Double.parseDouble(alarm.getMaxValue())||
+                                                Double.parseDouble(wsTdPropertyVO.getLogValue())<Double.parseDouble(alarm.getMinValue())) {//大于最大值,小于最小值
+                                            //查找近半小时的该设备该属性是否有过告警
+                                            IotAlarmRecordPageReqVO pageReqVO = new IotAlarmRecordPageReqVO();
+                                            pageReqVO.setDeviceCode(clientId);
+                                            pageReqVO.setDeviceProperty(wsTdPropertyVO.getIdentity());
+                                            LocalDateTime offset = LocalDateTimeUtil.offset(LocalDateTime.now(), -30L, ChronoUnit.MINUTES);
+                                            LocalDateTime[] createTime = new LocalDateTime[]{offset, LocalDateTime.now()};
+                                            pageReqVO.setCreateTime(createTime);
+                                            List<IotAlarmRecordDO> iotAlarmRecordDOS = iotAlarmRecordMapper.selectListAlarm(pageReqVO);
+                                            if (CollUtil.isEmpty(iotAlarmRecordDOS)) {//如果空的话报警并且插入
+                                                IotAlarmRecordDO iotAlarmRecordDO = new IotAlarmRecordDO();
+                                                iotAlarmRecordDO.setDeviceCode(clientId);
+                                                iotAlarmRecordDO.setDeviceProperty(wsTdPropertyVO.getIdentity());
+                                                iotAlarmRecordDO.setAlarmTime(DateUtil.now());
+                                                iotAlarmRecordDO.setAlarmValue(wsTdPropertyVO.getLogValue());
+                                                iotAlarmRecordDO.setDeleted(false);
+                                                iotAlarmRecordMapper.insert(iotAlarmRecordDO);
+                                                //发送告警消息通知
+                                                IotDevicePersonPageReqVO personPageReqVO = new IotDevicePersonPageReqVO();
+                                                personPageReqVO.setDeviceIds(ImmutableList.of(iotDeviceDO.getId()));
+                                                List<IotDevicePersonDO> persons = iotDevicePersonService.getPersonsByDeviceIds(personPageReqVO);
+                                                if (CollUtil.isNotEmpty(persons)) {
+                                                    AdminUserRespDTO user = adminUserApi.getUser(persons.get(0).getPersonId());
+                                                    if (Objects.nonNull(user)) {
+                                                        pmsMessage.sendMessage(iotDeviceDO.getId(), iotDeviceDO.getDeviceCode()+iotDeviceDO.getDeviceName()+","+wsTdPropertyVO.getModelName(), PmsConstants.ALARM_MESSAGE, user.getId(), user.getMobile());
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    }
+                                });
+                            });
+                        } else {//设备没赋值,就去找分类的max及min值
+                            List<IotAlarmSettingDO> flAlarms = iotAlarmSettingMapper.selectList("classify_id", iotDeviceDO.getAssetClass());
+                            if (CollUtil.isNotEmpty(flAlarms)) {
+                                wsTdPropertyVOS.forEach(wsTdPropertyVO -> {
+                                    flAlarms.stream().filter(e -> e.getPropertyCode().equals(wsTdPropertyVO.getIdentity())).findFirst().ifPresent(alarm -> {
+                                        if (NumberUtils.isConvertibleToDouble(wsTdPropertyVO.getLogValue())&&NumberUtils.isConvertibleToDouble(alarm.getMaxValue())&&
+                                                NumberUtils.isConvertibleToDouble(alarm.getMinValue())){//如果可以被转为数值
+                                            if (Double.parseDouble(wsTdPropertyVO.getLogValue())>Double.parseDouble(alarm.getMaxValue())||
+                                                    Double.parseDouble(wsTdPropertyVO.getLogValue())<Double.parseDouble(alarm.getMinValue())) {//大于最大值,小于最小值
+                                                //查找近半小时的该设备该属性是否有过告警
+                                                IotAlarmRecordPageReqVO pageReqVO = new IotAlarmRecordPageReqVO();
+                                                pageReqVO.setDeviceCode(clientId);
+                                                pageReqVO.setDeviceProperty(wsTdPropertyVO.getIdentity());
+                                                LocalDateTime offset = LocalDateTimeUtil.offset(LocalDateTime.now(), 30L, ChronoUnit.MINUTES);
+                                                LocalDateTime[] createTime = new LocalDateTime[]{offset, LocalDateTime.now()};
+                                                pageReqVO.setCreateTime(createTime);
+                                                List<IotAlarmRecordDO> iotAlarmRecordDOS = iotAlarmRecordMapper.selectListAlarm(pageReqVO);
+                                                if (CollUtil.isEmpty(iotAlarmRecordDOS)) {//如果空的话报警并且插入
+                                                    IotAlarmRecordDO iotAlarmRecordDO = new IotAlarmRecordDO();
+                                                    iotAlarmRecordDO.setDeviceCode(clientId);
+                                                    iotAlarmRecordDO.setDeviceProperty(wsTdPropertyVO.getIdentity());
+                                                    iotAlarmRecordDO.setAlarmTime(DateUtil.now());
+                                                    iotAlarmRecordDO.setAlarmValue(wsTdPropertyVO.getLogValue());
+                                                    iotAlarmRecordDO.setDeleted(false);
+                                                    iotAlarmRecordMapper.insert(iotAlarmRecordDO);
+                                                    //发送告警消息通知
+                                                    IotDevicePersonPageReqVO personPageReqVO = new IotDevicePersonPageReqVO();
+                                                    personPageReqVO.setDeviceIds(ImmutableList.of(iotDeviceDO.getId()));
+                                                    List<IotDevicePersonDO> persons = iotDevicePersonService.getPersonsByDeviceIds(personPageReqVO);
+                                                    if (CollUtil.isNotEmpty(persons)) {
+                                                        AdminUserRespDTO user = adminUserApi.getUser(persons.get(0).getPersonId());
+                                                        if (Objects.nonNull(user)) {
+                                                            pmsMessage.sendMessage(iotDeviceDO.getId(), iotDeviceDO.getDeviceCode()+iotDeviceDO.getDeviceName()+","+wsTdPropertyVO.getModelName(), PmsConstants.ALARM_MESSAGE, user.getId(), user.getMobile());
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    });
+                                });
+                            }
+                        }
+                    }});
+            }
+        } catch (Exception e) {
+            log.error("❌ [{}] 处理消息失败", clientId, e);
+        }
+    }
+
+    // ---------------------- 内部类:消息结构 ----------------------
+    private static class MqttDeviceMessage {
+        private String deviceCode; // 设备编码
+        private double value;      // 设备数值
+        private long timestamp;    // 时间戳
+
+        // Getter & Setter
+        public String getDeviceCode() { return deviceCode; }
+        public void setDeviceCode(String deviceCode) { this.deviceCode = deviceCode; }
+        public double getValue() { return value; }
+        public void setValue(double value) { this.value = value; }
+        public long getTimestamp() { return timestamp; }
+        public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
+
+        @Override
+        public String toString() {
+            return "MqttDeviceMessage{" +
+                    "deviceCode='" + deviceCode + '\'' +
+                    ", value=" + value +
+                    ", timestamp=" + timestamp +
+                    '}';
+        }
+    }
+
+    // ---------------------- 资源销毁 ----------------------
+    @PreDestroy
+    public void destroy() {
+        // 关闭重试线程池
+        connectRetryExecutor.shutdown();
+        try {
+            if (!connectRetryExecutor.awaitTermination(3, TimeUnit.SECONDS)) {
+                connectRetryExecutor.shutdownNow();
+            }
+        } catch (InterruptedException e) {
+            connectRetryExecutor.shutdownNow();
+        }
+
+        // 断开 MQTT 连接
+        if (mqttClient != null && mqttClient.isConnected()) {
+            try {
+                mqttClient.disconnect();
+                mqttClient.close();
+                needSubscribeTopics.clear();
+                log.info("MQTT 客户端已断开连接,资源已清理");
+            } catch (MqttException e) {
+                log.error("MQTT 断开连接失败", e);
+            }
+        }
+    }
+
+    // ---------------------- 辅助方法 ----------------------
+    /**
+     * 获取 MQTT 连接状态
+     */
+    public boolean isConnected() {
+        return mqttClient != null && mqttClient.isConnected();
+    }
+}

+ 19 - 0
yudao-module-pms/yudao-module-pms-abc/src/main/java/cn/iocoder/yudao/module/pms/abc/config/MqttProperties.java

@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.pms.abc.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "spring.mqtt")
+public class MqttProperties {
+    private String host;
+    private String username;
+    private String password;
+    private String clientIdPrefix = "spring-boot-mqtt-";
+    private boolean cleanSession = true;
+    private int reconnectPeriod = 5000; // 重连延迟(毫秒)
+    private int connectTimeout = 10000; // 连接超时(毫秒)
+    private int qos = 0; // 默认 QoS 级别
+}

+ 128 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/hazard/IotHazardController.java

@@ -0,0 +1,128 @@
+package cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard;
+
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo.IotHazardPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo.IotHazardRectifyVO;
+import cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo.IotHazardRespVO;
+import cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo.IotHazardSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.qhse.hazard.IotHazardDO;
+import cn.iocoder.yudao.module.pms.service.qhse.hazard.IotHazardService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+
+@Tag(name = "管理后台 - QHSE隐患排查及整改")
+@RestController
+@RequestMapping("/rq/iot-hazard")
+@Validated
+public class IotHazardController {
+
+    @Resource
+    private IotHazardService iotHazardService;
+    @Autowired
+    private DeptApi deptApi;
+
+    @PostMapping("/create")
+    @Operation(summary = "创建QHSE隐患排查及整改")
+    @PreAuthorize("@ss.hasPermission('rq:iot-hazard:create')")
+    public CommonResult<Long> createIotHazard(@Valid @RequestBody IotHazardSaveReqVO createReqVO) {
+        return success(iotHazardService.createIotHazard(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "更新QHSE隐患排查及整改")
+    @PreAuthorize("@ss.hasPermission('rq:iot-hazard:update')")
+    public CommonResult<Boolean> updateIotHazard(@Valid @RequestBody IotHazardSaveReqVO updateReqVO) {
+        iotHazardService.updateIotHazard(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "删除QHSE隐患排查及整改")
+    @Parameter(name = "id", description = "编号", required = true)
+    @PreAuthorize("@ss.hasPermission('rq:iot-hazard:delete')")
+    public CommonResult<Boolean> deleteIotHazard(@RequestParam("id") Long id) {
+        iotHazardService.deleteIotHazard(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "获得QHSE隐患排查及整改")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('rq:iot-hazard:query')")
+    public CommonResult<IotHazardRespVO> getIotHazard(@RequestParam("id") Long id) {
+        IotHazardDO iotHazard = iotHazardService.getIotHazard(id);
+        return success(BeanUtils.toBean(iotHazard, IotHazardRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "获得QHSE隐患排查及整改分页")
+    @PreAuthorize("@ss.hasPermission('rq:iot-hazard:query')")
+    public CommonResult<PageResult<IotHazardRespVO>> getIotHazardPage(@Valid IotHazardPageReqVO pageReqVO) {
+        PageResult<IotHazardDO> pageResult = iotHazardService.getIotHazardPage(pageReqVO);
+        List<IotHazardRespVO> collect = pageResult.getList().stream().map(e -> {
+            IotHazardRespVO iotHazardRespVO = new IotHazardRespVO();
+            BeanUtils.copyProperties(e, iotHazardRespVO);
+            if (Objects.nonNull(iotHazardRespVO.getDeptId())) {
+                DeptRespDTO dept = deptApi.getDept(iotHazardRespVO.getDeptId());
+                if(Objects.nonNull(dept)){
+                    iotHazardRespVO.setDeptName(dept.getName());
+                }
+            }
+            return iotHazardRespVO;
+        }).collect(Collectors.toList());
+        return success(new PageResult<>(collect, pageResult.getTotal()));
+    }
+
+    @PutMapping("/rectify")
+    @Operation(summary = "隐患排查整改")
+    @PreAuthorize("@ss.hasPermission('rq:iot-hazard:update')")
+    public CommonResult<String> rectifyHazard(@Valid @RequestBody IotHazardRectifyVO iotHazardRectifyVO) {
+        if (Objects.isNull(iotHazardRectifyVO.getRectifyDesc())|| StringUtils.isBlank(iotHazardRectifyVO.getRectifyDesc())
+            ||Objects.isNull(iotHazardRectifyVO.getRectifyFile())|| StringUtils.isBlank(iotHazardRectifyVO.getRectifyFile())) {
+            throw new ServiceException(new ErrorCode(1,"整改信息缺失"));
+        }
+        iotHazardService.rectifyHazard(iotHazardRectifyVO);
+        return success("整改完成");
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "导出QHSE隐患排查及整改 Excel")
+    @PreAuthorize("@ss.hasPermission('rq:iot-hazard:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportIotHazardExcel(@Valid IotHazardPageReqVO pageReqVO,
+              HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<IotHazardDO> list = iotHazardService.getIotHazardPage(pageReqVO).getList();
+        // 导出 Excel
+        ExcelUtils.write(response, "QHSE隐患排查及整改.xls", "数据", IotHazardRespVO.class,
+                        BeanUtils.toBean(list, IotHazardRespVO.class));
+    }
+
+}

+ 46 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/hazard/vo/IotHazardPageReqVO.java

@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo;
+
+import lombok.*;
+import java.util.*;
+import io.swagger.v3.oas.annotations.media.Schema;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import org.springframework.format.annotation.DateTimeFormat;
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "管理后台 - QHSE隐患排查及整改分页 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class IotHazardPageReqVO extends PageParam {
+
+    @Schema(description = "状态", example = "1")
+    private String status;
+
+    @Schema(description = "地点")
+    private String address;
+
+    @Schema(description = "问题")
+    private String problem;
+
+    @Schema(description = "隐患照片/附件")
+    private String hazardFile;
+
+    @Schema(description = "整改描述")
+    private String rectifyDesc;
+
+    @Schema(description = "整改附件")
+    private String rectifyFile;
+
+    @Schema(description = "部门id", example = "8662")
+    private Long deptId;
+
+    @Schema(description = "创建时间")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+    @Schema(description = "备注", example = "你说的对")
+    private String remark;
+
+}

+ 17 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/hazard/vo/IotHazardRectifyVO.java

@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+@Schema(description = "管理后台 - QHSE隐患排查及整改新增/修改 Request VO")
+@Data
+public class IotHazardRectifyVO {
+
+    private Long id;
+
+    @Schema(description = "整改描述")
+    private String rectifyDesc;
+
+    @Schema(description = "整改附件")
+    private String rectifyFile;
+}

+ 56 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/hazard/vo/IotHazardRespVO.java

@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo;
+
+import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
+import com.alibaba.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "管理后台 - QHSE隐患排查及整改 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class IotHazardRespVO {
+
+    @Schema(description = "主键id", requiredMode = Schema.RequiredMode.REQUIRED, example = "8504")
+    @ExcelProperty("主键id")
+    private Long id;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty("状态")
+    private String status;
+
+    @Schema(description = "地点", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("地点")
+    private String address;
+
+    @Schema(description = "问题", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("问题")
+    private String problem;
+
+    @Schema(description = "隐患照片/附件")
+    @ExcelProperty("隐患照片/附件")
+    private String hazardFile;
+
+    @Schema(description = "整改描述")
+    @ExcelProperty("整改描述")
+    private String rectifyDesc;
+
+    @Schema(description = "整改附件")
+    @ExcelProperty("整改附件")
+    private String rectifyFile;
+
+    @Schema(description = "部门id", example = "8662")
+    @ExcelProperty("部门id")
+    private Long deptId;
+
+    @Schema(description = "创建时间")
+    @ExcelProperty("创建时间")
+    private LocalDateTime createTime;
+
+    @Schema(description = "备注", example = "你说的对")
+    @ExcelProperty("备注")
+    private String remark;
+
+    private String deptName;
+}

+ 42 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/qhse/hazard/vo/IotHazardSaveReqVO.java

@@ -0,0 +1,42 @@
+package cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+
+@Schema(description = "管理后台 - QHSE隐患排查及整改新增/修改 Request VO")
+@Data
+public class IotHazardSaveReqVO {
+
+    @Schema(description = "主键id", requiredMode = Schema.RequiredMode.REQUIRED, example = "8504")
+    private Long id;
+
+    @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+//    @NotEmpty(message = "状态不能为空")
+    private String status;
+
+    @Schema(description = "地点", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "地点不能为空")
+    private String address;
+
+    @Schema(description = "问题", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "问题不能为空")
+    private String problem;
+
+    @Schema(description = "隐患照片/附件")
+    private String hazardFile;
+
+    @Schema(description = "整改描述")
+    private String rectifyDesc;
+
+    @Schema(description = "整改附件")
+    private String rectifyFile;
+
+    @Schema(description = "部门id", example = "8662")
+    private Long deptId;
+
+    @Schema(description = "备注", example = "你说的对")
+    private String remark;
+
+}

+ 68 - 68
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/config/MqttClientConfig.java

@@ -1,68 +1,68 @@
-package cn.iocoder.yudao.module.pms.controller.admin.yanfan.config;
-
-import lombok.Data;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-
-/**
- * mqtt配置信息
- */
-
-@Data
-@Component
-@ConfigurationProperties(prefix = "spring.mqtt")
-public class MqttClientConfig {
-
-    /**
-     * 用户名
-     */
-    private String username;
-    /**
-     * 密码
-     */
-    private String password;
-    /**
-     * 连接地址
-     */
-    private String hostUrl;
-    /**
-     * 客户Id
-     */
-    private String clientId;
-    /**
-     * 默认连接话题
-     */
-    private String defaultTopic;
-    /**
-     * 超时时间
-     */
-    private int timeout;
-    /**
-     * 保持连接数
-     */
-    private int keepalive;
-
-    /**
-     * 是否清除session
-     */
-    private boolean clearSession;
-    /**
-     * 是否共享订阅
-     */
-    private boolean isShared;
-    /**
-     * 分组共享订阅
-     */
-    private boolean isSharedGroup;
-
-    /**
-     * true: 使用netty搭建的mqttBroker  false: 使用emq
-     */
-    @Value("${server.broker.enabled}")
-    private Boolean enabled;
-
-    private int maxInflight;
-
-}
-
+//package cn.iocoder.yudao.module.pms.controller.admin.yanfan.config;
+//
+//import lombok.Data;
+//import org.springframework.beans.factory.annotation.Value;
+//import org.springframework.boot.context.properties.ConfigurationProperties;
+//import org.springframework.stereotype.Component;
+//
+///**
+// * mqtt配置信息
+// */
+//
+//@Data
+//@Component
+//@ConfigurationProperties(prefix = "spring.mqtt")
+//public class MqttClientConfig {
+//
+//    /**
+//     * 用户名
+//     */
+//    private String username;
+//    /**
+//     * 密码
+//     */
+//    private String password;
+//    /**
+//     * 连接地址
+//     */
+//    private String hostUrl;
+//    /**
+//     * 客户Id
+//     */
+//    private String clientId;
+//    /**
+//     * 默认连接话题
+//     */
+//    private String defaultTopic;
+//    /**
+//     * 超时时间
+//     */
+//    private int timeout;
+//    /**
+//     * 保持连接数
+//     */
+//    private int keepalive;
+//
+//    /**
+//     * 是否清除session
+//     */
+//    private boolean clearSession;
+//    /**
+//     * 是否共享订阅
+//     */
+//    private boolean isShared;
+//    /**
+//     * 分组共享订阅
+//     */
+//    private boolean isSharedGroup;
+//
+//    /**
+//     * true: 使用netty搭建的mqttBroker  false: 使用emq
+//     */
+//    @Value("${server.broker.enabled}")
+//    private Boolean enabled;
+//
+//    private int maxInflight;
+//
+//}
+//

+ 7 - 11
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/core/StartBoot.java

@@ -1,17 +1,13 @@
 package cn.iocoder.yudao.module.pms.controller.admin.yanfan.core;
 
-import cn.iocoder.yudao.module.pms.controller.admin.yanfan.mqtt.PubMqttClient;
-import lombok.Setter;
+//import cn.iocoder.yudao.module.pms.controller.admin.yanfan.mqtt.PubMqttClient;
+
 import lombok.extern.slf4j.Slf4j;
-import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.ApplicationArguments;
 import org.springframework.boot.ApplicationRunner;
-import org.springframework.core.annotation.Order;
 import org.springframework.stereotype.Component;
 
-import javax.annotation.Resource;
-
 /**
  * 启动类
  *
@@ -23,9 +19,9 @@ public class StartBoot implements ApplicationRunner {
 
 
     @Autowired
-    private PubMqttClient mqttClient;
-    @Setter
-    private IMqttMessageListener subscribeCallback;
+//    private PubMqttClient mqttClient;
+//    @Setter
+//    private IMqttMessageListener subscribeCallback;
 
     @Override
     public void run(ApplicationArguments args) throws Exception {
@@ -39,8 +35,8 @@ public class StartBoot implements ApplicationRunner {
 //            otherListen.listen();
 //            testListen.listen();
             /*启动内部客户端,用来下发客户端服务*/
-            mqttClient.setListener(subscribeCallback);
-            mqttClient.initialize();
+//            mqttClient.setListener(subscribeCallback);
+//            mqttClient.initialize();
 //            protocolManagerService.getAllProtocols();
             log.info("=>设备监听队列启动成功");
         } catch (Exception e) {

+ 246 - 246
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/yanfan/mqtt/PubMqttClient.java

@@ -1,246 +1,246 @@
-package cn.iocoder.yudao.module.pms.controller.admin.yanfan.mqtt;
-
-import cn.iocoder.yudao.framework.common.exception.ErrorCode;
-import cn.iocoder.yudao.framework.common.exception.ServiceException;
-import cn.iocoder.yudao.module.pms.controller.admin.yanfan.config.MqttClientConfig;
-import cn.iocoder.yudao.module.pms.controller.admin.yanfan.constant.YanfanConstant;
-import cn.iocoder.yudao.module.pms.controller.admin.yanfan.redis.RedisCache;
-import lombok.Setter;
-import lombok.extern.slf4j.Slf4j;
-import org.eclipse.paho.client.mqttv3.*;
-import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
-import org.springframework.scheduling.annotation.Async;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-
-/**
- * 发布服务mqtt客户端
- */
-@Component
-@Slf4j
-public class PubMqttClient {
-
-    @Resource
-    private MqttClientConfig mqttConfig;
-    @Resource(name = "pubMqttCallBack")
-    private PubMqttCallBack mqttCallBack;
-    /**
-     * 连接配置
-     */
-    private MqttConnectOptions options;
-    /**
-     * MQTT异步客户端
-     */
-    private MqttAsyncClient client;
-    /**
-     * 是否连接标记
-     */
-    private boolean isConnected = false;
-    @Resource
-    private RedisCache redisCache;
-    @Setter
-    private IMqttMessageListener listener;
-
-    /**
-     * 启动MQTT客户端
-     */
-    @Async(YanfanConstant.TASK.INNER_MQTT_TASK)
-    public synchronized void initialize() {
-
-        try {
-            setOptions();
-            createClient();
-            while (!client.isConnected()) {
-                IMqttToken token = client.connect(options);
-                if (token != null && token.isComplete()) {
-                    log.debug("=>内部MQTT客户端启动成功");
-                    this.isConnected = true;
-                    break;
-                }
-                log.debug("=>内部mqtt客户端连接中...");
-                Thread.sleep(20000);
-            }
-        } catch (MqttException ex) {
-            log.error("=>MQTT客户端初始化异常", ex);
-        } catch (Exception e) {
-            log.error("=>连接MQTT服务器异常", e);
-            this.isConnected = false;
-        }
-
-    }
-
-    public boolean isConnected() {
-        return this.isConnected;
-    }
-
-    private void createClient() {
-        try {
-            if (client == null) {
-                /*host为主机名,clientId是连接MQTT的客户端ID*/
-                client = new MqttAsyncClient(mqttConfig.getHostUrl(), getClientId(), new MemoryPersistence());
-                //设置回调函数
-                client.setCallback(mqttCallBack);
-                mqttCallBack.setClient(client);
-                mqttCallBack.setOptions(this.options);
-                mqttCallBack.setEnabled(mqttConfig.getEnabled());
-                mqttCallBack.setListener(this.listener);
-            }
-        } catch (Exception e) {
-            log.error("=>mqtt客户端创建错误");
-        }
-    }
-
-    /**
-     * 设置连接属性
-     */
-    private void setOptions() {
-
-        if (options != null) {
-            options = null;
-        }
-        options = new MqttConnectOptions();
-        options.setConnectionTimeout(mqttConfig.getTimeout());
-        options.setKeepAliveInterval(mqttConfig.getKeepalive());
-        options.setUserName(mqttConfig.getUsername());
-        options.setPassword(mqttConfig.getPassword().toCharArray());
-        options.setMaxInflight(mqttConfig.getMaxInflight());
-        //设置自动重新连接
-        options.setAutomaticReconnect(true);
-            /*设置为false,断开连接,不清除session,重连后还是原来的session
-              保留订阅的主题,能接收离线期间的消息*/
-        options.setCleanSession(true);
-    }
-
-    /**
-     * 断开与mqtt的连接
-     */
-    public synchronized void disconnect() {
-        //判断客户端是否null 是否连接
-        if (client != null && client.isConnected()) {
-            try {
-                IMqttToken token = client.disconnect();
-                token.waitForCompletion();
-            } catch (MqttException e) {
-                log.error("=>断开mqtt连接发生错误 message={}", e.getMessage());
-                throw new ServiceException(new ErrorCode(1,"断开mqtt连接发生错误" + e.getMessage()));
-            }
-        }
-        client = null;
-    }
-
-    /**
-     * 重新连接MQTT
-     */
-    public synchronized void refresh() {
-        disconnect();
-        initialize();
-    }
-
-    /**
-     * 拼接客户端id
-     */
-    public final String getClientId() {
-        return YanfanConstant.SERVER.WM_PREFIX + System.currentTimeMillis();
-    }
-
-
-    /**
-     * 发布主题
-     *
-     * @param message  payload消息体
-     * @param topic    主题
-     * @param retained 是否保留消息
-     * @param qos      消息质量
-     *                 Qos1:消息发送一次,不确保
-     *                 Qos2:至少分发一次,服务器确保接收消息进行确认
-     *                 Qos3:只分发一次,确保消息送达和只传递一次
-     */
-    public void publish(byte[] message, String topic, boolean retained, int qos) {
-        //设置mqtt消息
-        MqttMessage mqttMessage = new MqttMessage();
-        mqttMessage.setQos(qos);
-        mqttMessage.setRetained(retained);
-        mqttMessage.setPayload(message);
-
-        IMqttDeliveryToken token;
-        try {
-            token = client.publish(topic, mqttMessage);
-            token.waitForCompletion();
-        } catch (MqttPersistenceException e) {
-            log.error("=>发布主题时发生错误 topic={},message={}", topic, e.getMessage());
-            throw new ServiceException(new ErrorCode(1,e.getMessage()));
-        } catch (MqttException ex) {
-            throw new ServiceException(new ErrorCode(1,ex.getMessage()));
-        }
-    }
-
-
-    /**
-     * 发布
-     *
-     * @param qos         连接方式
-     * @param retained    是否保留
-     * @param topic       主题
-     * @param pushMessage 消息体
-     */
-    public void publish(int qos, boolean retained, String topic, String pushMessage) {
-        redisCache.incr2(YanfanConstant.REDIS.MESSAGE_SEND_TOTAL, -1L);
-        redisCache.incr2(YanfanConstant.REDIS.MESSAGE_SEND_TODAY, 60 * 60 * 24);
-        MqttMessage message = new MqttMessage();
-        message.setQos(qos);
-        message.setRetained(retained);
-        message.setPayload(pushMessage.getBytes());
-
-        try {
-            IMqttDeliveryToken token = client.publish(topic, message);
-            token.waitForCompletion();
-            log.info("发布主题[{}],发布消息{}", topic, pushMessage);
-        } catch (MqttPersistenceException e) {
-            e.printStackTrace();
-        } catch (MqttException e) {
-            log.error("=>发布主题时发生错误 topic={},message={}", topic, e.getMessage());
-        }
-    }
-
-    /**
-     * 订阅主题
-     *
-     * @param topic qos默认为1
-     */
-    public void subTopic(String topic) {
-        try {
-            client.subscribe(topic, 1);
-        } catch (MqttException e) {
-            // TODO Auto-generated catch block
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * 订阅某个主题
-     *
-     * @param topic
-     * @param gos
-     */
-    public void subTopic(String topic, int gos) {
-        try {
-            client.subscribe(topic, gos);
-        } catch (MqttException e) {
-            e.printStackTrace();
-        }
-    }
-
-    /**
-     * 清空主题
-     *
-     * @param topic
-     */
-    public void cleanTopic(String topic) {
-        try {
-            client.unsubscribe(topic);
-        } catch (MqttException e) {
-            e.printStackTrace();
-        }
-    }
-}
+//package cn.iocoder.yudao.module.pms.controller.admin.yanfan.mqtt;
+//
+//import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+//import cn.iocoder.yudao.framework.common.exception.ServiceException;
+//import cn.iocoder.yudao.module.pms.controller.admin.yanfan.config.MqttClientConfig;
+//import cn.iocoder.yudao.module.pms.controller.admin.yanfan.constant.YanfanConstant;
+//import cn.iocoder.yudao.module.pms.controller.admin.yanfan.redis.RedisCache;
+//import lombok.Setter;
+//import lombok.extern.slf4j.Slf4j;
+//import org.eclipse.paho.client.mqttv3.*;
+//import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
+//import org.springframework.scheduling.annotation.Async;
+//import org.springframework.stereotype.Component;
+//
+//import javax.annotation.Resource;
+//
+///**
+// * 发布服务mqtt客户端
+// */
+//@Component
+//@Slf4j
+//public class PubMqttClient {
+//
+//    @Resource
+//    private MqttClientConfig mqttConfig;
+//    @Resource(name = "pubMqttCallBack")
+//    private PubMqttCallBack mqttCallBack;
+//    /**
+//     * 连接配置
+//     */
+//    private MqttConnectOptions options;
+//    /**
+//     * MQTT异步客户端
+//     */
+//    private MqttAsyncClient client;
+//    /**
+//     * 是否连接标记
+//     */
+//    private boolean isConnected = false;
+//    @Resource
+//    private RedisCache redisCache;
+//    @Setter
+//    private IMqttMessageListener listener;
+//
+//    /**
+//     * 启动MQTT客户端
+//     */
+//    @Async(YanfanConstant.TASK.INNER_MQTT_TASK)
+//    public synchronized void initialize() {
+//
+//        try {
+//            setOptions();
+//            createClient();
+//            while (!client.isConnected()) {
+//                IMqttToken token = client.connect(options);
+//                if (token != null && token.isComplete()) {
+//                    log.debug("=>内部MQTT客户端启动成功");
+//                    this.isConnected = true;
+//                    break;
+//                }
+//                log.debug("=>内部mqtt客户端连接中...");
+//                Thread.sleep(20000);
+//            }
+//        } catch (MqttException ex) {
+//            log.error("=>MQTT客户端初始化异常", ex);
+//        } catch (Exception e) {
+//            log.error("=>连接MQTT服务器异常", e);
+//            this.isConnected = false;
+//        }
+//
+//    }
+//
+//    public boolean isConnected() {
+//        return this.isConnected;
+//    }
+//
+//    private void createClient() {
+//        try {
+//            if (client == null) {
+//                /*host为主机名,clientId是连接MQTT的客户端ID*/
+//                client = new MqttAsyncClient(mqttConfig.getHostUrl(), getClientId(), new MemoryPersistence());
+//                //设置回调函数
+//                client.setCallback(mqttCallBack);
+//                mqttCallBack.setClient(client);
+//                mqttCallBack.setOptions(this.options);
+//                mqttCallBack.setEnabled(mqttConfig.getEnabled());
+//                mqttCallBack.setListener(this.listener);
+//            }
+//        } catch (Exception e) {
+//            log.error("=>mqtt客户端创建错误");
+//        }
+//    }
+//
+//    /**
+//     * 设置连接属性
+//     */
+//    private void setOptions() {
+//
+//        if (options != null) {
+//            options = null;
+//        }
+//        options = new MqttConnectOptions();
+//        options.setConnectionTimeout(mqttConfig.getTimeout());
+//        options.setKeepAliveInterval(mqttConfig.getKeepalive());
+//        options.setUserName(mqttConfig.getUsername());
+//        options.setPassword(mqttConfig.getPassword().toCharArray());
+//        options.setMaxInflight(mqttConfig.getMaxInflight());
+//        //设置自动重新连接
+//        options.setAutomaticReconnect(true);
+//            /*设置为false,断开连接,不清除session,重连后还是原来的session
+//              保留订阅的主题,能接收离线期间的消息*/
+//        options.setCleanSession(true);
+//    }
+//
+//    /**
+//     * 断开与mqtt的连接
+//     */
+//    public synchronized void disconnect() {
+//        //判断客户端是否null 是否连接
+//        if (client != null && client.isConnected()) {
+//            try {
+//                IMqttToken token = client.disconnect();
+//                token.waitForCompletion();
+//            } catch (MqttException e) {
+//                log.error("=>断开mqtt连接发生错误 message={}", e.getMessage());
+//                throw new ServiceException(new ErrorCode(1,"断开mqtt连接发生错误" + e.getMessage()));
+//            }
+//        }
+//        client = null;
+//    }
+//
+//    /**
+//     * 重新连接MQTT
+//     */
+//    public synchronized void refresh() {
+//        disconnect();
+//        initialize();
+//    }
+//
+//    /**
+//     * 拼接客户端id
+//     */
+//    public final String getClientId() {
+//        return YanfanConstant.SERVER.WM_PREFIX + System.currentTimeMillis();
+//    }
+//
+//
+//    /**
+//     * 发布主题
+//     *
+//     * @param message  payload消息体
+//     * @param topic    主题
+//     * @param retained 是否保留消息
+//     * @param qos      消息质量
+//     *                 Qos1:消息发送一次,不确保
+//     *                 Qos2:至少分发一次,服务器确保接收消息进行确认
+//     *                 Qos3:只分发一次,确保消息送达和只传递一次
+//     */
+//    public void publish(byte[] message, String topic, boolean retained, int qos) {
+//        //设置mqtt消息
+//        MqttMessage mqttMessage = new MqttMessage();
+//        mqttMessage.setQos(qos);
+//        mqttMessage.setRetained(retained);
+//        mqttMessage.setPayload(message);
+//
+//        IMqttDeliveryToken token;
+//        try {
+//            token = client.publish(topic, mqttMessage);
+//            token.waitForCompletion();
+//        } catch (MqttPersistenceException e) {
+//            log.error("=>发布主题时发生错误 topic={},message={}", topic, e.getMessage());
+//            throw new ServiceException(new ErrorCode(1,e.getMessage()));
+//        } catch (MqttException ex) {
+//            throw new ServiceException(new ErrorCode(1,ex.getMessage()));
+//        }
+//    }
+//
+//
+//    /**
+//     * 发布
+//     *
+//     * @param qos         连接方式
+//     * @param retained    是否保留
+//     * @param topic       主题
+//     * @param pushMessage 消息体
+//     */
+//    public void publish(int qos, boolean retained, String topic, String pushMessage) {
+//        redisCache.incr2(YanfanConstant.REDIS.MESSAGE_SEND_TOTAL, -1L);
+//        redisCache.incr2(YanfanConstant.REDIS.MESSAGE_SEND_TODAY, 60 * 60 * 24);
+//        MqttMessage message = new MqttMessage();
+//        message.setQos(qos);
+//        message.setRetained(retained);
+//        message.setPayload(pushMessage.getBytes());
+//
+//        try {
+//            IMqttDeliveryToken token = client.publish(topic, message);
+//            token.waitForCompletion();
+//            log.info("发布主题[{}],发布消息{}", topic, pushMessage);
+//        } catch (MqttPersistenceException e) {
+//            e.printStackTrace();
+//        } catch (MqttException e) {
+//            log.error("=>发布主题时发生错误 topic={},message={}", topic, e.getMessage());
+//        }
+//    }
+//
+//    /**
+//     * 订阅主题
+//     *
+//     * @param topic qos默认为1
+//     */
+//    public void subTopic(String topic) {
+//        try {
+//            client.subscribe(topic, 1);
+//        } catch (MqttException e) {
+//            // TODO Auto-generated catch block
+//            e.printStackTrace();
+//        }
+//    }
+//
+//    /**
+//     * 订阅某个主题
+//     *
+//     * @param topic
+//     * @param gos
+//     */
+//    public void subTopic(String topic, int gos) {
+//        try {
+//            client.subscribe(topic, gos);
+//        } catch (MqttException e) {
+//            e.printStackTrace();
+//        }
+//    }
+//
+//    /**
+//     * 清空主题
+//     *
+//     * @param topic
+//     */
+//    public void cleanTopic(String topic) {
+//        try {
+//            client.unsubscribe(topic);
+//        } catch (MqttException e) {
+//            e.printStackTrace();
+//        }
+//    }
+//}

+ 62 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/dataobject/qhse/hazard/IotHazardDO.java

@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.pms.dal.dataobject.qhse.hazard;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+/**
+ * QHSE隐患排查及整改 DO
+ *
+ * @author 超级管理员
+ */
+@TableName("rq_iot_hazard")
+@KeySequence("rq_iot_hazard_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class IotHazardDO extends BaseDO {
+
+    /**
+     * 主键id
+     */
+    @TableId
+    private Long id;
+    /**
+     * 状态
+     */
+    private String status;
+    /**
+     * 地点
+     */
+    private String address;
+    /**
+     * 问题
+     */
+    private String problem;
+    /**
+     * 隐患照片/附件
+     */
+    private String hazardFile;
+    /**
+     * 整改描述
+     */
+    private String rectifyDesc;
+    /**
+     * 整改附件
+     */
+    private String rectifyFile;
+    /**
+     * 部门id
+     */
+    private Long deptId;
+    /**
+     * 备注
+     */
+    private String remark;
+
+}

+ 34 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/qhse/hazard/IotHazardMapper.java

@@ -0,0 +1,34 @@
+package cn.iocoder.yudao.module.pms.dal.mysql.qhse.hazard;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo.IotHazardPageReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.qhse.hazard.IotHazardDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Set;
+
+/**
+ * QHSE隐患排查及整改 Mapper
+ *
+ * @author 超级管理员
+ */
+@Mapper
+public interface IotHazardMapper extends BaseMapperX<IotHazardDO> {
+
+    default PageResult<IotHazardDO> selectPage(IotHazardPageReqVO reqVO, Set<Long> ids) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<IotHazardDO>()
+                .eqIfPresent(IotHazardDO::getStatus, reqVO.getStatus())
+                .eqIfPresent(IotHazardDO::getAddress, reqVO.getAddress())
+                .eqIfPresent(IotHazardDO::getProblem, reqVO.getProblem())
+                .eqIfPresent(IotHazardDO::getHazardFile, reqVO.getHazardFile())
+                .eqIfPresent(IotHazardDO::getRectifyDesc, reqVO.getRectifyDesc())
+                .eqIfPresent(IotHazardDO::getRectifyFile, reqVO.getRectifyFile())
+                .inIfPresent(IotHazardDO::getDeptId, ids)
+                .betweenIfPresent(IotHazardDO::getCreateTime, reqVO.getCreateTime())
+                .eqIfPresent(IotHazardDO::getRemark, reqVO.getRemark())
+                .orderByDesc(IotHazardDO::getId));
+    }
+
+}

+ 4 - 5
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/ZhbdJob.java

@@ -19,15 +19,11 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.http.HttpEntity;
 import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
 import org.springframework.stereotype.Component;
 import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import org.springframework.web.client.RestTemplate;
 
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -125,7 +121,10 @@ public class ZhbdJob implements JobHandler {
                         });
                         redisTemplate.opsForHash().putAll("TSLV:"+e.getDeviceCode(),abcMap);
                         //插入td时序库
-                        deviceMapper.batchInsert(logDO);
+                        Integer i = deviceMapper.tableIfExist(logDO.getTableName().toLowerCase());
+                        if (i==1) {
+                            deviceMapper.batchInsert(logDO);
+                        }
                         List<IotDeviceDO> iotDeviceDOS = iotDeviceMapper.selectByCodeIn(ImmutableList.of(e.getDeviceCode()));
                         if (CollUtil.isNotEmpty(iotDeviceDOS)) {
                             IotDeviceDO iotDeviceDO = iotDeviceDOS.get(0);

+ 1 - 6
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/alarm/AlarmJob.java

@@ -7,16 +7,13 @@ import cn.hutool.core.date.DateUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
 import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
-import cn.iocoder.yudao.module.pms.ThingsModelDTO;
 import cn.iocoder.yudao.module.pms.constant.PmsConstants;
 import cn.iocoder.yudao.module.pms.controller.admin.iotdeviceperson.vo.IotDevicePersonPageReqVO;
-import cn.iocoder.yudao.module.pms.controller.admin.vo.DeviceVO;
 import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDevicePageReqVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.IotDeviceDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.alarm.IotAlarmSettingDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotdeviceperson.IotDevicePersonDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.tdparams.IotTdParamsDO;
-import cn.iocoder.yudao.module.pms.dal.mysql.IotDeviceMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.TDDeviceMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.alarm.IotAlarmSettingMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.tdparams.IotTdParamsMapper;
@@ -26,14 +23,12 @@ import cn.iocoder.yudao.module.pms.service.iotdeviceperson.IotDevicePersonServic
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import com.google.common.collect.ImmutableList;
-import liquibase.pro.packaged.F;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;
 import java.sql.Timestamp;
-import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.Objects;
@@ -84,7 +79,7 @@ public class AlarmJob implements JobHandler {
                     String maxValue = t.getMaxValue();
                     String minValue = t.getMinValue();
                     Integer count = tdDeviceMapper.selectRangeCount(f.getDeviceCode(), t.getPropertyCode(), start, end, maxValue, minValue);
-                    if (count == 0) {
+                    if (Objects.nonNull(count)&&count == 0) {
                         //准备告警,设置该设备该属性为其他颜色
                         model.setIfAlarm("alarm");
                         iotTdParamsMapper.updateById(model);

+ 3 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/inspect/IotInspectOrderServiceImpl.java

@@ -291,6 +291,9 @@ public class IotInspectOrderServiceImpl implements IotInspectOrderService {
                             if (Objects.nonNull(iotDeviceDO)){
                                 detailDO.setDeptId(iotDeviceDO.getDeptId());
                             }
+                            if (Objects.isNull(detailDO.getItemId())) {
+                                System.out.println("1111");
+                            }
                             results.add(detailDO);
                         }
                     }

+ 56 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/qhse/hazard/IotHazardService.java

@@ -0,0 +1,56 @@
+package cn.iocoder.yudao.module.pms.service.qhse.hazard;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo.IotHazardPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo.IotHazardRectifyVO;
+import cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo.IotHazardSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.qhse.hazard.IotHazardDO;
+
+import javax.validation.Valid;
+
+/**
+ * QHSE隐患排查及整改 Service 接口
+ *
+ * @author 超级管理员
+ */
+public interface IotHazardService {
+
+    /**
+     * 创建QHSE隐患排查及整改
+     *
+     * @param createReqVO 创建信息
+     * @return 编号
+     */
+    Long createIotHazard(@Valid IotHazardSaveReqVO createReqVO);
+
+    /**
+     * 更新QHSE隐患排查及整改
+     *
+     * @param updateReqVO 更新信息
+     */
+    void updateIotHazard(@Valid IotHazardSaveReqVO updateReqVO);
+    void rectifyHazard(@Valid IotHazardRectifyVO updateReqVO);
+    /**
+     * 删除QHSE隐患排查及整改
+     *
+     * @param id 编号
+     */
+    void deleteIotHazard(Long id);
+
+    /**
+     * 获得QHSE隐患排查及整改
+     *
+     * @param id 编号
+     * @return QHSE隐患排查及整改
+     */
+    IotHazardDO getIotHazard(Long id);
+
+    /**
+     * 获得QHSE隐患排查及整改分页
+     *
+     * @param pageReqVO 分页查询
+     * @return QHSE隐患排查及整改分页
+     */
+    PageResult<IotHazardDO> getIotHazardPage(IotHazardPageReqVO pageReqVO);
+
+}

+ 102 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/qhse/hazard/IotHazardServiceImpl.java

@@ -0,0 +1,102 @@
+package cn.iocoder.yudao.module.pms.service.qhse.hazard;
+
+import cn.iocoder.yudao.framework.common.exception.ErrorCode;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo.IotHazardPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo.IotHazardRectifyVO;
+import cn.iocoder.yudao.module.pms.controller.admin.qhse.hazard.vo.IotHazardSaveReqVO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.qhse.hazard.IotHazardDO;
+import cn.iocoder.yudao.module.pms.dal.mysql.qhse.hazard.IotHazardMapper;
+import cn.iocoder.yudao.module.system.service.dept.DeptService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import javax.annotation.Resource;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+
+/**
+ * QHSE隐患排查及整改 Service 实现类
+ *
+ * @author 超级管理员
+ */
+@Service
+@Validated
+public class IotHazardServiceImpl implements IotHazardService {
+
+    @Resource
+    private IotHazardMapper iotHazardMapper;
+    @Autowired
+    private DeptService deptService;
+
+    @Override
+    public Long createIotHazard(IotHazardSaveReqVO createReqVO) {
+        // 插入
+        IotHazardDO iotHazard = BeanUtils.toBean(createReqVO, IotHazardDO.class);
+        iotHazard.setDeleted(false);
+        //未整改
+        iotHazard.setStatus("todo");
+        Long deptId = SecurityFrameworkUtils.getLoginUserDeptId();
+        iotHazard.setDeptId(deptId);
+        iotHazardMapper.insert(iotHazard);
+        // 返回
+        return iotHazard.getId();
+    }
+
+    @Override
+    public void updateIotHazard(IotHazardSaveReqVO updateReqVO) {
+        // 校验存在
+        validateIotHazardExists(updateReqVO.getId());
+        // 更新
+        IotHazardDO updateObj = BeanUtils.toBean(updateReqVO, IotHazardDO.class);
+        iotHazardMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void rectifyHazard(IotHazardRectifyVO updateReqVO) {
+        if (Objects.isNull(updateReqVO.getId())) throw new ServiceException(new ErrorCode(2,"不存在"));
+        IotHazardDO iotHazardDO = iotHazardMapper.selectById(updateReqVO.getId());
+        iotHazardDO.setStatus("finished");
+        iotHazardDO.setRectifyDesc(updateReqVO.getRectifyDesc());
+        iotHazardDO.setRectifyFile(updateReqVO.getRectifyFile());
+        iotHazardMapper.updateById(iotHazardDO);
+    }
+
+    @Override
+    public void deleteIotHazard(Long id) {
+        // 校验存在
+        validateIotHazardExists(id);
+        // 删除
+        iotHazardMapper.deleteById(id);
+    }
+
+    private void validateIotHazardExists(Long id) {
+        if (iotHazardMapper.selectById(id) == null) {
+            throw exception(new ErrorCode(22,"不存在"));
+        }
+    }
+
+    @Override
+    public IotHazardDO getIotHazard(Long id) {
+        return iotHazardMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<IotHazardDO> getIotHazardPage(IotHazardPageReqVO pageReqVO) {
+        Set<Long> ids = new HashSet<>();
+        if (Objects.nonNull(pageReqVO.getDeptId())) {
+            ids = deptService.getChildDeptIdListFromCache(pageReqVO.getDeptId());
+            ids.add(pageReqVO.getDeptId());
+        }
+        return iotHazardMapper.selectPage(pageReqVO, ids);
+    }
+
+}

+ 5 - 8
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/yanfan/sip/VideoMqttService.java

@@ -1,17 +1,14 @@
 package cn.iocoder.yudao.module.pms.service.yanfan.sip;
 
 import cn.iocoder.yudao.module.pms.controller.admin.yanfan.enums.TopicType;
-import cn.iocoder.yudao.module.pms.controller.admin.yanfan.mqtt.PubMqttClient;
 import cn.iocoder.yudao.module.pms.controller.admin.yanfan.sip.vo.SipDeviceSummary;
 import cn.iocoder.yudao.module.pms.controller.admin.yanfan.thingsmodel.vo.ThingsModelSimpleItem;
 import cn.iocoder.yudao.module.pms.controller.admin.yanfan.utils.TopicsUtils;
 import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.device.YfIotDeviceDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.config.IotSipConfigDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.yanfan.sip.device.YfSipDeviceDO;
-import cn.iocoder.yudao.module.pms.service.IDeviceService;
 import cn.iocoder.yudao.module.pms.service.yanfan.device.YfIotDeviceService;
 import cn.iocoder.yudao.module.pms.service.yanfan.sip.config.IotSipConfigService;
-import cn.iocoder.yudao.module.pms.service.yanfan.sip.model.RecordList;
 import cn.iocoder.yudao.module.pms.service.yanfan.sip.msg.Alarm;
 import com.alibaba.fastjson.JSON;
 import lombok.extern.slf4j.Slf4j;
@@ -27,8 +24,8 @@ import java.util.List;
 @Slf4j
 @Service
 public class VideoMqttService implements IMqttService {
-    @Resource
-    private PubMqttClient mqttClient;
+//    @Resource
+//    private PubMqttClient mqttClient;
 
     @Autowired
     private IotSipConfigService sipConfigService;
@@ -61,7 +58,7 @@ public class VideoMqttService implements IMqttService {
             Long productId = sipConfig.getProductId();
             if (null != productId && productId != -1L && productId != 0L) {
                 String topic = topicsUtils.buildTopic(productId, device.getDeviceSipId(), TopicType.DEV_INFO_POST);
-                mqttClient.publish(1, false, topic, JSON.toJSONString(iotdevice));
+//                mqttClient.publish(1, false, topic, JSON.toJSONString(iotdevice));
             }
         }
     }
@@ -76,7 +73,7 @@ public class VideoMqttService implements IMqttService {
                 int rssi = 0;
                 String message = "{\"status\":" + deviceStatus + ",\"isShadow\":" + isShadow + ",\"rssi\":" + rssi + "}";
                 String topic = topicsUtils.buildTopic(sipConfig.getProductId(), device.getDeviceSipId(), TopicType.STATUS_POST);
-                mqttClient.publish(1, false, topic, message);
+//                mqttClient.publish(1, false, topic, message);
             }
         }
     }
@@ -133,7 +130,7 @@ public class VideoMqttService implements IMqttService {
             Long productId = sipConfig.getProductId();
             if (null != productId && productId != -1L && productId != 0L) {
                 String topic = topicsUtils.buildTopic(sipConfig.getProductId(), alarm.getDeviceId(), TopicType.DEV_EVENT_POST);
-                mqttClient.publish(1, false, topic, JSON.toJSONString(events));
+//                mqttClient.publish(1, false, topic, JSON.toJSONString(events));
             }
         }
     }

+ 4 - 5
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/websocket/WsClientManager.java

@@ -5,7 +5,6 @@ import org.springframework.stereotype.Component;
 import org.springframework.web.socket.WebSocketSession;
 import org.springframework.web.socket.client.standard.StandardWebSocketClient;
 
-import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import javax.annotation.Resource;
 import java.net.URI;
@@ -34,10 +33,10 @@ public class WsClientManager {
     /**
      * 项目启动时初始化所有动态连接(自动开始接收消息)
      */
-    @PostConstruct
-    public void initAllConnections() {
-        executor.submit(this::connectAll);
-    }
+//    @PostConstruct
+//    public void initAllConnections() {
+//        executor.submit(this::connectAll);
+//    }
 
     /**
      * 批量连接所有动态获取的 WSS 地址(Nginx 代理)

+ 2 - 2
yudao-server/pom.xml

@@ -28,8 +28,8 @@
         </dependency>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-module-pms-biz</artifactId>
-            <version>${revision}</version>
+            <artifactId>yudao-module-pms-abc</artifactId>
+            <version>2.4.2-jdk8-SNAPSHOT</version>
         </dependency>
         <dependency>
             <groupId>cn.iocoder.boot</groupId>

+ 3 - 3
yudao-server/src/main/resources/application-dev.yaml

@@ -54,7 +54,7 @@ spring:
       primary: master
       datasource:
         master:
-          url: jdbc:mysql://172.21.20.20:3306/rqiot?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
+          url: jdbc:mysql://172.21.20.20:3306/rqiot-test?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 连接的示例
           username: ruiqi
           password: .N_Mdq!BR1W4
         slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改
@@ -306,7 +306,7 @@ file:
   upload-path: D:\wenjian
 
 sip:
-  enabled: true                            # 是否启用视频监控SIP,true为启用
+  enabled: false                           # 是否启用视频监控SIP,true为启用
   ## 本地调试时,绑定网卡局域网IP,设备在同一局域网,设备接入IP填写绑定IP
   ## 部署服务端时,默认绑定容器IP,设备接入IP填写服务器公网IP
   ip: 172.26.0.3
@@ -371,4 +371,4 @@ hikvision:
         level: "HIGH"
       - name: "设备离线"
         code: "DeviceOffline"
-        level: "CRITICAL"
+        level: "CRITICAL"

+ 15 - 2
yudao-server/src/main/resources/application.yaml

@@ -7,7 +7,19 @@ spring:
 
   main:
     allow-circular-references: true # 允许循环依赖,因为项目是三层架构,无法避免这个情况。
-
+  mqtt:
+    # MQTT 服务端地址(对应前端 wss://aims.deepoil.cc/mqtt)
+    host: wss://aims.deepoil.cc/mqtt
+    # 前端配置的用户名/密码
+    username: yanfan
+    password: eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjY0YmM2NjJlLWZhMjQtNGY1Ny1hOTk1LWZiMGM2YjNhYzI4OCJ9.9nxoDUNGTk1szRlZHHG0AcWZctLrzJ16UA5rsBagHNcD10PC-LIMTgAr2CK1Ppafa6cW5XPdn7RqBF6iZjHtww
+    # 客户端ID(前端为随机生成)
+    client-id-prefix: web-
+    # 连接配置
+    clean-session: true
+    reconnect-period: 5000
+    connect-timeout: 10000
+    qos: 0
   # Servlet 配置
   servlet:
     # 文件上传相关配置项
@@ -67,7 +79,7 @@ flowable:
 mybatis-plus:
   configuration:
     map-underscore-to-camel-case: true # 虽然默认为 true ,但是还是显示去指定下。
-    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+#    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
   global-config:
     db-config:
       id-type: NONE # “智能”模式,基于 IdTypeEnvironmentPostProcessor + 数据源的类型,自动适配成 AUTO、INPUT 模式。
@@ -247,6 +259,7 @@ yudao:
       - /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,不需要登录
       - /admin-api/rq/iot-app/**
       - /admin-api/hikvision/**
+      - /admin-api/rq/stat/**
   websocket:
     enable: true # websocket的开关
     path: /infra/ws # 路径

+ 1 - 1
yudao-server/src/main/resources/logback-spring.xml

@@ -66,7 +66,7 @@
     </springProfile>
     <!-- 其它环境 -->
     <springProfile name="dev,test,stage,prod,default">
-        <root level="WARN">
+        <root level="INFO">
             <appender-ref ref="STDOUT"/>
             <appender-ref ref="ASYNC"/>
             <appender-ref ref="GRPC"/>