Parcourir la source

Merge branch 'master' of http://60.171.161.56:20000/tjf/deviceManage

wangmengwei il y a 1 semaine
Parent
commit
ecf122a543
40 fichiers modifiés avec 2482 ajouts et 200 suppressions
  1. 11 3
      pom.xml
  2. 7 1
      ruoyi-admin/pom.xml
  3. 2 2
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/manage/ChannelNumberController.java
  4. 25 21
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/manage/EquipmentManageController.java
  5. 91 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/manage/ParameterSetController.java
  6. 227 0
      ruoyi-admin/src/main/java/com/ruoyi/web/controller/mqtt/MqttController.java
  7. 16 1
      ruoyi-admin/src/main/resources/application.yml
  8. 37 3
      ruoyi-common/pom.xml
  9. 84 0
      ruoyi-common/src/main/java/com/ruoyi/common/config/MqttCallbackHandler.java
  10. 106 0
      ruoyi-common/src/main/java/com/ruoyi/common/config/MqttConfig.java
  11. 76 0
      ruoyi-common/src/main/java/com/ruoyi/common/config/MqttProperties.java
  12. 23 8
      ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
  13. 83 0
      ruoyi-common/src/main/java/com/ruoyi/common/model/MqttMessage.java
  14. 1 1
      ruoyi-system/pom.xml
  15. 116 16
      ruoyi-system/src/main/java/com/ruoyi/manage/domain/ChannelNumber.java
  16. 2 0
      ruoyi-system/src/main/java/com/ruoyi/manage/domain/EquipmentManage.java
  17. 206 0
      ruoyi-system/src/main/java/com/ruoyi/manage/domain/ParameterSet.java
  18. 1 0
      ruoyi-system/src/main/java/com/ruoyi/manage/mapper/ChannelNumberMapper.java
  19. 1 0
      ruoyi-system/src/main/java/com/ruoyi/manage/mapper/EquipmentManageMapper.java
  20. 63 0
      ruoyi-system/src/main/java/com/ruoyi/manage/mapper/ParameterSetMapper.java
  21. 3 2
      ruoyi-system/src/main/java/com/ruoyi/manage/service/IChannelNumberService.java
  22. 3 2
      ruoyi-system/src/main/java/com/ruoyi/manage/service/IEquipmentManageService.java
  23. 63 0
      ruoyi-system/src/main/java/com/ruoyi/manage/service/IParameterSetService.java
  24. 71 24
      ruoyi-system/src/main/java/com/ruoyi/manage/service/impl/ChannelNumberServiceImpl.java
  25. 29 23
      ruoyi-system/src/main/java/com/ruoyi/manage/service/impl/EquipmentManageServiceImpl.java
  26. 163 0
      ruoyi-system/src/main/java/com/ruoyi/manage/service/impl/ParameterSetServiceImpl.java
  27. 61 0
      ruoyi-system/src/main/java/com/ruoyi/mqtt/service/MqttMessageListener.java
  28. 271 0
      ruoyi-system/src/main/java/com/ruoyi/mqtt/service/MqttService.java
  29. 37 4
      ruoyi-system/src/main/resources/mapper/manage/ChannelNumberMapper.xml
  30. 8 2
      ruoyi-system/src/main/resources/mapper/manage/EquipmentManageMapper.xml
  31. 128 0
      ruoyi-system/src/main/resources/mapper/manage/ParameterSetMapper.xml
  32. BIN
      ruoyi-ui/src/assets/images/search/bficoa.png
  33. BIN
      ruoyi-ui/src/assets/images/search/bficob.png
  34. BIN
      ruoyi-ui/src/assets/images/search/bficoc.png
  35. BIN
      ruoyi-ui/src/assets/images/search/cir.png
  36. BIN
      ruoyi-ui/src/assets/images/search/pic_jsjg_slt_sxt.png
  37. 391 0
      ruoyi-ui/src/components/jiansuo/VideoPlayer.vue
  38. 4 2
      ruoyi-ui/src/components/jiansuo/index.vue
  39. 32 61
      ruoyi-ui/src/views/shipinggaoj/jiansuo/detail.vue
  40. 40 24
      ruoyi-ui/src/views/shipinggaoj/jiansuo/index.vue

+ 11 - 3
pom.xml

@@ -3,7 +3,7 @@
          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>
-	
+
     <groupId>com.ruoyi</groupId>
     <artifactId>ruoyi</artifactId>
     <version>3.8.9</version>
@@ -11,7 +11,7 @@
     <name>boman</name>
     <url>https://www.baidu.com/</url>
     <description>物业管理系统</description>
-    
+
     <properties>
         <ruoyi.version>3.8.9</ruoyi.version>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -35,6 +35,7 @@
         <logback.version>1.2.13</logback.version>
         <spring-security.version>5.7.12</spring-security.version>
         <spring-framework.version>5.3.39</spring-framework.version>
+        <lombok.version>1.18.30</lombok.version>
     </properties>
 
     <!-- 依赖声明 -->
@@ -169,6 +170,13 @@
                 <version>${fastjson.version}</version>
             </dependency>
 
+            <!-- Lombok -->
+            <dependency>
+                <groupId>org.projectlombok</groupId>
+                <artifactId>lombok</artifactId>
+                <version>${lombok.version}</version>
+            </dependency>
+
             <!-- Token生成与解析-->
             <dependency>
                 <groupId>io.jsonwebtoken</groupId>
@@ -271,4 +279,4 @@
         </pluginRepository>
     </pluginRepositories>
 
-</project>
+</project>

+ 7 - 1
ruoyi-admin/pom.xml

@@ -77,6 +77,12 @@
             <artifactId>ruoyi-generator</artifactId>
         </dependency>
 
+        <!-- lombok-->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
     </dependencies>
 
     <build>
@@ -109,4 +115,4 @@
         <finalName>${project.artifactId}</finalName>
     </build>
 
-</project>
+</project>

+ 2 - 2
ruoyi-admin/src/main/java/com/ruoyi/web/controller/manage/ChannelNumberController.java

@@ -76,7 +76,7 @@ public class ChannelNumberController extends BaseController {
     @Log(title = "通道管理", businessType = BusinessType.INSERT)
     @PostMapping
     public AjaxResult add(@RequestBody ChannelNumber channelNumber) {
-        return toAjax(channelNumberService.insertChannelNumber(channelNumber));
+        return channelNumberService.insertChannelNumber(channelNumber);
     }
 
     /**
@@ -86,7 +86,7 @@ public class ChannelNumberController extends BaseController {
     @Log(title = "通道管理", businessType = BusinessType.UPDATE)
     @PostMapping("/put")
     public AjaxResult edit(@RequestBody ChannelNumber channelNumber) {
-        return toAjax(channelNumberService.updateChannelNumber(channelNumber));
+        return channelNumberService.updateChannelNumber(channelNumber);
     }
 
     /**

+ 25 - 21
ruoyi-admin/src/main/java/com/ruoyi/web/controller/manage/EquipmentManageController.java

@@ -11,6 +11,7 @@ import com.ruoyi.manage.service.IEquipmentManageService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.web.bind.annotation.*;
+
 import javax.servlet.http.HttpServletResponse;
 import java.util.List;
 
@@ -22,31 +23,38 @@ import java.util.List;
  */
 @RestController
 @RequestMapping("/manage/equipmentManage")
-public class EquipmentManageController extends BaseController
-{
+public class EquipmentManageController extends BaseController {
     @Autowired
     private IEquipmentManageService equipmentManageService;
 
-/**
- * 查询设备管理列表
- */
-@PreAuthorize("@ss.hasPermi('manage:equipmentManage:list')")
-@GetMapping("/list")
-    public TableDataInfo list(EquipmentManage equipmentManage)
-    {
+    /**
+     * 查询设备管理列表
+     */
+    @PreAuthorize("@ss.hasPermi('manage:equipmentManage:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(EquipmentManage equipmentManage) {
         startPage();
         List<EquipmentManage> list = equipmentManageService.selectEquipmentManageList(equipmentManage);
         return getDataTable(list);
     }
 
+    /**
+     * 查询设备管理列表
+     */
+    @PreAuthorize("@ss.hasPermi('manage:equipmentManage:listNoPage')")
+    @GetMapping("/listNoPage")
+    public TableDataInfo listNoPage(EquipmentManage equipmentManage) {
+        List<EquipmentManage> list = equipmentManageService.selectEquipmentManageList(equipmentManage);
+        return getDataTable(list);
+    }
+
     /**
      * 导出设备管理列表
      */
     @PreAuthorize("@ss.hasPermi('manage:equipmentManage:export')")
     @Log(title = "设备管理", businessType = BusinessType.EXPORT)
     @PostMapping("/export")
-    public void export(HttpServletResponse response, EquipmentManage equipmentManage)
-    {
+    public void export(HttpServletResponse response, EquipmentManage equipmentManage) {
         List<EquipmentManage> list = equipmentManageService.selectEquipmentManageList(equipmentManage);
         ExcelUtil<EquipmentManage> util = new ExcelUtil<EquipmentManage>(EquipmentManage.class);
         util.exportExcel(response, list, "设备管理数据");
@@ -57,8 +65,7 @@ public class EquipmentManageController extends BaseController
      */
     @PreAuthorize("@ss.hasPermi('manage:equipmentManage:query')")
     @GetMapping(value = "/{equipmentId}")
-    public AjaxResult getInfo(@PathVariable("equipmentId") Long equipmentId)
-    {
+    public AjaxResult getInfo(@PathVariable("equipmentId") Long equipmentId) {
         return success(equipmentManageService.selectEquipmentManageByEquipmentId(equipmentId));
     }
 
@@ -68,9 +75,8 @@ public class EquipmentManageController extends BaseController
     @PreAuthorize("@ss.hasPermi('manage:equipmentManage:add')")
     @Log(title = "设备管理", businessType = BusinessType.INSERT)
     @PostMapping
-    public AjaxResult add(@RequestBody EquipmentManage equipmentManage)
-    {
-        return toAjax(equipmentManageService.insertEquipmentManage(equipmentManage));
+    public AjaxResult add(@RequestBody EquipmentManage equipmentManage) {
+        return equipmentManageService.insertEquipmentManage(equipmentManage);
     }
 
     /**
@@ -79,9 +85,8 @@ public class EquipmentManageController extends BaseController
     @PreAuthorize("@ss.hasPermi('manage:equipmentManage:edit')")
     @Log(title = "设备管理", businessType = BusinessType.UPDATE)
     @PostMapping("/put")
-    public AjaxResult edit(@RequestBody EquipmentManage equipmentManage)
-    {
-        return toAjax(equipmentManageService.updateEquipmentManage(equipmentManage));
+    public AjaxResult edit(@RequestBody EquipmentManage equipmentManage) {
+        return equipmentManageService.updateEquipmentManage(equipmentManage);
     }
 
     /**
@@ -90,8 +95,7 @@ public class EquipmentManageController extends BaseController
     @PreAuthorize("@ss.hasPermi('manage:equipmentManage:remove')")
     @Log(title = "设备管理", businessType = BusinessType.DELETE)
     @GetMapping("/delete/{equipmentIds}")
-    public AjaxResult remove(@PathVariable Long[] equipmentIds)
-    {
+    public AjaxResult remove(@PathVariable Long[] equipmentIds) {
         return toAjax(equipmentManageService.deleteEquipmentManageByEquipmentIds(equipmentIds));
     }
 }

+ 91 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/manage/ParameterSetController.java

@@ -0,0 +1,91 @@
+package com.ruoyi.web.controller.manage;
+
+import com.ruoyi.common.annotation.Log;
+import com.ruoyi.common.core.controller.BaseController;
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.core.page.TableDataInfo;
+import com.ruoyi.common.enums.BusinessType;
+import com.ruoyi.common.utils.poi.ExcelUtil;
+import com.ruoyi.manage.domain.ParameterSet;
+import com.ruoyi.manage.service.IParameterSetService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletResponse;
+import java.util.List;
+
+/**
+ * 参数设置Controller
+ *
+ * @author boman
+ * @date 2025-06-18
+ */
+@RestController
+@RequestMapping("/manage/parameterSet")
+public class ParameterSetController extends BaseController {
+    @Autowired
+    private IParameterSetService parameterSetService;
+
+    /**
+     * 查询参数设置列表
+     */
+    @PreAuthorize("@ss.hasPermi('manage:parameterSet:list')")
+    @GetMapping("/list")
+    public TableDataInfo list(ParameterSet parameterSet) {
+        startPage();
+        List<ParameterSet> list = parameterSetService.selectParameterSetList(parameterSet);
+        return getDataTable(list);
+    }
+
+    /**
+     * 导出参数设置列表
+     */
+    @PreAuthorize("@ss.hasPermi('manage:parameterSet:export')")
+    @Log(title = "参数设置", businessType = BusinessType.EXPORT)
+    @PostMapping("/export")
+    public void export(HttpServletResponse response, ParameterSet parameterSet) {
+        List<ParameterSet> list = parameterSetService.selectParameterSetList(parameterSet);
+        ExcelUtil<ParameterSet> util = new ExcelUtil<ParameterSet>(ParameterSet.class);
+        util.exportExcel(response, list, "参数设置数据");
+    }
+
+    /**
+     * 获取参数设置详细信息
+     */
+    @PreAuthorize("@ss.hasPermi('manage:parameterSet:query')")
+    @GetMapping(value = "/{parameterId}")
+    public AjaxResult getInfo(@PathVariable("parameterId") Long parameterId) {
+        return success(parameterSetService.selectParameterSetByParameterId(parameterId));
+    }
+
+    /**
+     * 新增参数设置
+     */
+    @PreAuthorize("@ss.hasPermi('manage:parameterSet:add')")
+    @Log(title = "参数设置", businessType = BusinessType.INSERT)
+    @PostMapping
+    public AjaxResult add(@RequestBody ParameterSet parameterSet) {
+        return parameterSetService.insertParameterSet(parameterSet);
+    }
+
+    /**
+     * 修改参数设置
+     */
+    @PreAuthorize("@ss.hasPermi('manage:parameterSet:edit')")
+    @Log(title = "参数设置", businessType = BusinessType.UPDATE)
+    @PostMapping("/put")
+    public AjaxResult edit(@RequestBody ParameterSet parameterSet) {
+        return parameterSetService.updateParameterSet(parameterSet);
+    }
+
+    /**
+     * 删除参数设置
+     */
+    @PreAuthorize("@ss.hasPermi('manage:parameterSet:remove')")
+    @Log(title = "参数设置", businessType = BusinessType.DELETE)
+    @GetMapping("/delete/{parameterIds}")
+    public AjaxResult remove(@PathVariable Long[] parameterIds) {
+        return toAjax(parameterSetService.deleteParameterSetByParameterIds(parameterIds));
+    }
+}

+ 227 - 0
ruoyi-admin/src/main/java/com/ruoyi/web/controller/mqtt/MqttController.java

@@ -0,0 +1,227 @@
+package com.ruoyi.web.controller.mqtt;
+
+import com.ruoyi.common.model.MqttMessage;
+import com.ruoyi.mqtt.service.MqttService;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * MQTT控制器
+ *
+ * <p>提供MQTT相关操作的REST API接口,包括消息发布、主题订阅和连接管理等,
+ * 方便通过HTTP请求操作MQTT客户端。</p>
+ *
+ * @author andy
+ * @version 1.0.0
+ * @since 2025-06-17
+ */
+@Slf4j
+@RestController
+@RequiredArgsConstructor
+@RequestMapping("/mqtt")
+public class MqttController {
+
+    private final MqttService mqttService;
+
+    /**
+     * 获取MQTT连接状态
+     *
+     * @return 连接状态信息
+     */
+    @GetMapping("/status")
+    public ResponseEntity<Map<String, Object>> getStatus() {
+        Map<String, Object> status = new HashMap<>();
+        status.put("connected", mqttService.isConnected());
+        status.put("timestamp", System.currentTimeMillis());
+
+        return ResponseEntity.ok(status);
+    }
+
+    /**
+     * 重新连接MQTT服务器
+     *
+     * @return 重连结果
+     */
+    @RequestMapping("/reconnect")
+    public ResponseEntity<Map<String, Object>> reconnect() {
+        Map<String, Object> result = new HashMap<>();
+
+        try {
+            CompletableFuture<Void> future = mqttService.reconnect();
+            future.get(10, TimeUnit.SECONDS);
+
+            result.put("success", true);
+            result.put("message", "已成功重新连接到MQTT服务器");
+            return ResponseEntity.ok(result);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            log.error("重连MQTT服务器失败: {}", e.getMessage(), e);
+
+            result.put("success", false);
+            result.put("message", "重连MQTT服务器失败: " + e.getMessage());
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
+        }
+    }
+
+    /**
+     * 发布MQTT消息
+     *
+     * @param message MQTT消息对象
+     * @return 发布结果
+     */
+    @PostMapping("/publish")
+    public ResponseEntity<Map<String, Object>> publishMessage(@RequestBody MqttMessage message) {
+        Map<String, Object> result = new HashMap<>();
+
+        if (message.getTopic() == null || message.getTopic().isEmpty()) {
+            result.put("success", false);
+            result.put("message", "消息主题不能为空");
+            return ResponseEntity.badRequest().body(result);
+        }
+
+        if (message.getPayload() == null) {
+            result.put("success", false);
+            result.put("message", "消息内容不能为空");
+            return ResponseEntity.badRequest().body(result);
+        }
+
+        try {
+            CompletableFuture<Void> future = mqttService.publish(message);
+            future.get(10, TimeUnit.SECONDS);
+
+            result.put("success", true);
+            result.put("message", "消息发布成功");
+            result.put("topic", message.getTopic());
+            return ResponseEntity.ok(result);
+        } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            log.error("发布消息失败: {}", e.getMessage(), e);
+
+            result.put("success", false);
+            result.put("message", "发布消息失败: " + e.getMessage());
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
+        }
+    }
+
+    /**
+     * 简化版发布消息接口
+     *
+     * @param topic 消息主题
+     * @param payload 消息内容
+     * @return 发布结果
+     */
+    @RequestMapping("/publish/simple")
+    public ResponseEntity<Map<String, Object>> publishSimpleMessage(
+            @RequestParam String topic,
+            @RequestParam String payload) {
+
+        MqttMessage message = new MqttMessage(topic, payload);
+        return publishMessage(message);
+    }
+
+    /**
+     * 订阅主题
+     *
+     * @param topic 要订阅的主题
+     * @return 订阅结果
+     */
+    @RequestMapping("/subscribe")
+    public ResponseEntity<Map<String, Object>> subscribeTopic(@RequestParam String topic) {
+        Map<String, Object> result = new HashMap<>();
+
+        if (topic == null || topic.isEmpty()) {
+            result.put("success", false);
+            result.put("message", "订阅主题不能为空");
+            return ResponseEntity.badRequest().body(result);
+        }
+
+        try {
+            String subscribedTopic = mqttService.subscribe(topic);
+
+            result.put("success", true);
+            result.put("message", "主题订阅成功");
+            result.put("topic", subscribedTopic);
+            return ResponseEntity.ok(result);
+        } catch (MqttException e) {
+            log.error("订阅主题失败: {}", e.getMessage(), e);
+
+            result.put("success", false);
+            result.put("message", "订阅主题失败: " + e.getMessage());
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
+        }
+    }
+
+    /**
+     * 批量订阅主题
+     *
+     * @param topics 要订阅的主题列表
+     * @return 订阅结果
+     */
+    @PostMapping("/subscribe/batch")
+    public ResponseEntity<Map<String, Object>> subscribeTopics(@RequestBody List<String> topics) {
+        Map<String, Object> result = new HashMap<>();
+
+        if (topics == null || topics.isEmpty()) {
+            result.put("success", false);
+            result.put("message", "订阅主题列表不能为空");
+            return ResponseEntity.badRequest().body(result);
+        }
+
+        try {
+            List<String> subscribedTopics = mqttService.subscribe(topics);
+
+            result.put("success", true);
+            result.put("message", "批量主题订阅成功");
+            result.put("topics", subscribedTopics);
+            return ResponseEntity.ok(result);
+        } catch (MqttException e) {
+            log.error("批量订阅主题失败: {}", e.getMessage(), e);
+
+            result.put("success", false);
+            result.put("message", "批量订阅主题失败: " + e.getMessage());
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
+        }
+    }
+
+    /**
+     * 取消订阅主题
+     *
+     * @param topic 要取消订阅的主题
+     * @return 取消订阅结果
+     */
+    @PostMapping("/unsubscribe")
+    public ResponseEntity<Map<String, Object>> unsubscribeTopic(@RequestParam String topic) {
+        Map<String, Object> result = new HashMap<>();
+
+        if (topic == null || topic.isEmpty()) {
+            result.put("success", false);
+            result.put("message", "取消订阅的主题不能为空");
+            return ResponseEntity.badRequest().body(result);
+        }
+
+        try {
+            mqttService.unsubscribe(topic);
+
+            result.put("success", true);
+            result.put("message", "主题取消订阅成功");
+            result.put("topic", topic);
+            return ResponseEntity.ok(result);
+        } catch (MqttException e) {
+            log.error("取消订阅主题失败: {}", e.getMessage(), e);
+
+            result.put("success", false);
+            result.put("message", "取消订阅主题失败: " + e.getMessage());
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
+        }
+    }
+}

+ 16 - 1
ruoyi-admin/src/main/resources/application.yml

@@ -72,4 +72,19 @@ xss:
   # 排除链接(多个用逗号分隔)
   excludes: /system/notice,/new/news,/new/news/put
   # 匹配链接
-  urlPatterns: /system/*,/monitor/*,/tool/*
+  urlPatterns: /system/*,/monitor/*,/tool/*
+
+# MQTT配置
+mqtt:
+  server-uri: tcp://13.229.167.76:1883
+  #  server-uri: tcp://127.0.0.1:1883
+  client-id: JavaClient
+  username:
+  password:
+  default-topic: test/topic
+  default-qos: 1
+  timeout: 30
+  keep-alive: 60
+  clean-session: true
+  auto-reconnect: true
+  command-timeout: 10

+ 37 - 3
ruoyi-common/pom.xml

@@ -15,6 +15,12 @@
         common通用工具
     </description>
 
+    <properties>
+        <paho.mqtt.version>1.2.5</paho.mqtt.version>
+        <moquette.version>0.16</moquette.version>
+        <lombok.version>1.18.30</lombok.version>
+    </properties>
+
     <dependencies>
 
         <!-- Spring框架基本的核心工具 -->
@@ -52,19 +58,25 @@
             <groupId>org.apache.commons</groupId>
             <artifactId>commons-lang3</artifactId>
         </dependency>
-  
+
         <!-- JSON工具类 -->
         <dependency>
             <groupId>com.fasterxml.jackson.core</groupId>
             <artifactId>jackson-databind</artifactId>
         </dependency>
-        
+
         <!-- 阿里JSON解析器 -->
         <dependency>
             <groupId>com.alibaba.fastjson2</groupId>
             <artifactId>fastjson2</artifactId>
         </dependency>
 
+        <!-- Lombok -->
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
         <!-- io常用工具类 -->
         <dependency>
             <groupId>commons-io</groupId>
@@ -119,6 +131,28 @@
             <artifactId>javax.servlet-api</artifactId>
         </dependency>
 
+        <!-- === MQTT 相关依赖 === -->
+        <!-- Moquette MQTT broker -->
+        <dependency>
+            <groupId>io.moquette</groupId>
+            <artifactId>moquette-broker</artifactId>
+            <version>${moquette.version}</version>
+        </dependency>
+
+        <!-- Eclipse Paho MQTT Client (MQTT 3.x) -->
+        <dependency>
+            <groupId>org.eclipse.paho</groupId>
+            <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
+            <version>${paho.mqtt.version}</version>
+        </dependency>
+
+        <!-- Eclipse Paho MQTT Client (MQTT 5.0) -->
+        <dependency>
+            <groupId>org.eclipse.paho</groupId>
+            <artifactId>org.eclipse.paho.mqttv5.client</artifactId>
+            <version>${paho.mqtt.version}</version>
+        </dependency>
+
     </dependencies>
 
-</project>
+</project>

+ 84 - 0
ruoyi-common/src/main/java/com/ruoyi/common/config/MqttCallbackHandler.java

@@ -0,0 +1,84 @@
+package com.ruoyi.common.config;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
+import org.eclipse.paho.client.mqttv3.MqttCallback;
+import org.eclipse.paho.client.mqttv3.MqttMessage;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Component;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * MQTT回调处理器
+ *
+ * <p>处理MQTT客户端的各种回调事件,包括连接丢失、消息到达和消息发送完成。
+ * 通过Spring事件机制将消息分发到其他组件进行处理。</p>
+ *
+ * @author andy
+ * @version 1.0.0
+ * @since 2025-06-17
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class MqttCallbackHandler implements MqttCallback {
+
+    private final ApplicationEventPublisher eventPublisher;
+
+    /**
+     * 连接丢失回调
+     * 当与MQTT服务器的连接意外断开时调用此方法
+     *
+     * @param cause 连接丢失原因
+     */
+    @Override
+    public void connectionLost(Throwable cause) {
+        log.warn("MQTT连接丢失: {}", cause.getMessage());
+        // 连接丢失后的处理逻辑
+        // 可以根据需要添加重连机制,通常由MqttConnectOptions.setAutomaticReconnect处理
+    }
+
+    /**
+     * 消息到达回调
+     * 当从订阅的主题接收到新消息时调用此方法
+     *
+     * @param topic 消息的主题
+     * @param message 接收到的消息
+     */
+    @Override
+    public void messageArrived(String topic, MqttMessage message) throws Exception {
+        String payload = new String(message.getPayload(), StandardCharsets.UTF_8);
+        log.info("收到MQTT消息: 主题={}, 消息内容={}, QoS={}", topic, payload, message.getQos());
+
+        // 创建事件对象
+        com.ruoyi.common.model.MqttMessage mqttMessage = new com.ruoyi.common.model.MqttMessage();
+        mqttMessage.setTopic(topic);
+        mqttMessage.setPayload(payload);
+        mqttMessage.setQos(message.getQos());
+        mqttMessage.setRetained(message.isRetained());
+
+        // 发布事件,通知其他组件处理此消息
+        eventPublisher.publishEvent(mqttMessage);
+    }
+
+    /**
+     * 消息发送完成回调
+     * 当消息成功发送到服务器后调用此方法
+     *
+     * @param token 消息传递令牌
+     */
+    @Override
+    public void deliveryComplete(IMqttDeliveryToken token) {
+        try {
+            MqttMessage message = token.getMessage();
+            if (message != null) {
+                String payload = new String(message.getPayload(), StandardCharsets.UTF_8);
+                log.debug("MQTT消息发送完成: {}", payload);
+            }
+        } catch (Exception e) {
+            log.error("处理消息发送完成回调时出错: {}", e.getMessage(), e);
+        }
+    }
+}

+ 106 - 0
ruoyi-common/src/main/java/com/ruoyi/common/config/MqttConfig.java

@@ -0,0 +1,106 @@
+package com.ruoyi.common.config;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.eclipse.paho.client.mqttv3.MqttException;
+import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.UUID;
+
+/**
+ * MQTT客户端配置类
+ *
+ * <p>配置MQTT客户端的连接和回调处理,包括自动重连、证书配置、回调处理等,
+ * 创建MQTT连接并初始化相关组件。</p>
+ *
+ * @author andy
+ * @version 1.0.0
+ * @since 2025-06-17
+ */
+@Slf4j
+@Configuration
+@RequiredArgsConstructor
+public class MqttConfig {
+
+    private final MqttProperties mqttProperties;
+    private final MqttCallbackHandler mqttCallbackHandler;
+
+    /**
+     * 创建MQTT连接选项
+     *
+     * @return MQTT连接选项
+     */
+    @Bean
+    public MqttConnectOptions mqttConnectOptions() {
+        MqttConnectOptions options = new MqttConnectOptions();
+
+        // 设置是否清除会话
+        options.setCleanSession(mqttProperties.isCleanSession());
+
+        // 设置连接超时
+        options.setConnectionTimeout(mqttProperties.getTimeout());
+
+        // 设置心跳间隔
+        options.setKeepAliveInterval(mqttProperties.getKeepAlive());
+
+        // 设置自动重连
+        options.setAutomaticReconnect(mqttProperties.isAutoReconnect());
+
+        // 设置用户名和密码
+        if (mqttProperties.getUsername() != null && !mqttProperties.getUsername().isEmpty()) {
+            options.setUserName(mqttProperties.getUsername());
+
+            if (mqttProperties.getPassword() != null && !mqttProperties.getPassword().isEmpty()) {
+                options.setPassword(mqttProperties.getPassword().toCharArray());
+            }
+        }
+
+        log.info("MQTT连接选项已配置,服务器地址: {}", mqttProperties.getServerUri());
+        return options;
+    }
+
+    /**
+     * 创建MQTT客户端
+     *
+     * @return MQTT客户端
+     * @throws MqttException MQTT异常
+     */
+    @Bean
+    public MqttAsyncClient mqttClient() throws MqttException {
+        // 检查服务器URI
+        if (mqttProperties.getServerUri() == null || mqttProperties.getServerUri().isEmpty()) {
+            throw new IllegalArgumentException("MQTT服务器URI不能为空");
+        }
+
+        // 生成客户端ID
+        String clientId = mqttProperties.getClientId();
+        if (clientId == null || clientId.isEmpty()) {
+            clientId = "spring-mqtt-" + UUID.randomUUID().toString().substring(0, 8);
+            log.info("未配置客户端ID,已自动生成: {}", clientId);
+        }
+
+        // 创建客户端实例
+        MqttAsyncClient client = new MqttAsyncClient(
+                mqttProperties.getServerUri(),
+                clientId,
+                new MemoryPersistence());
+
+        // 设置回调
+        client.setCallback(mqttCallbackHandler);
+
+        // 初始化连接
+        try {
+            client.connect(mqttConnectOptions()).waitForCompletion(mqttProperties.getCommandTimeout() * 1000L);
+            log.info("MQTT客户端已连接到服务器: {}", mqttProperties.getServerUri());
+        } catch (MqttException e) {
+            log.error("MQTT客户端连接失败: {}", e.getMessage(), e);
+            throw e;
+        }
+
+        return client;
+    }
+}

+ 76 - 0
ruoyi-common/src/main/java/com/ruoyi/common/config/MqttProperties.java

@@ -0,0 +1,76 @@
+package com.ruoyi.common.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * MQTT连接属性配置类
+ *
+ * <p>用于从application.yml或application.properties文件中加载MQTT相关的配置项,
+ * 支持自定义MQTT服务器地址、客户端ID、用户名密码等各种连接参数。</p>
+ *
+ * @author andy
+ * @version 1.0.0
+ * @since 2025-06-17
+ */
+@Data
+@Configuration
+@ConfigurationProperties(prefix = "mqtt")
+public class MqttProperties {
+
+    /**
+     * 服务器地址URI,例如: tcp://localhost:1883
+     */
+    private String serverUri;
+
+    /**
+     * 客户端ID
+     */
+    private String clientId;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 密码
+     */
+    private String password;
+
+    /**
+     * 超时时间,单位秒
+     */
+    private int timeout = 30;
+
+    /**
+     * 保活时间,单位秒
+     */
+    private int keepAlive = 60;
+
+    /**
+     * 是否自动重连
+     */
+    private boolean autoReconnect = true;
+
+    /**
+     * 是否清除会话
+     */
+    private boolean cleanSession = true;
+
+    /**
+     * 默认QoS级别
+     */
+    private int defaultQos = 1;
+
+    /**
+     * 默认主题
+     */
+    private String defaultTopic = "test/topic";
+
+    /**
+     * 消息发布/订阅超时,单位秒
+     */
+    private int commandTimeout = 10;
+}

+ 23 - 8
ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java

@@ -1,15 +1,15 @@
 package com.ruoyi.common.constant;
 
-import java.util.Locale;
 import io.jsonwebtoken.Claims;
 
+import java.util.Locale;
+
 /**
  * 通用常量信息
- * 
+ *
  * @author ruoyi
  */
-public class Constants
-{
+public class Constants {
     /**
      * UTF-8 字符集
      */
@@ -158,16 +158,31 @@ public class Constants
     /**
      * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
      */
-    public static final String[] JSON_WHITELIST_STR = { "org.springframework", "com.ruoyi" };
+    public static final String[] JSON_WHITELIST_STR = {"org.springframework", "com.ruoyi"};
 
     /**
      * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
      */
-    public static final String[] JOB_WHITELIST_STR = { "com.ruoyi.quartz.task" };
+    public static final String[] JOB_WHITELIST_STR = {"com.ruoyi.quartz.task"};
 
     /**
      * 定时任务违规的字符
      */
-    public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
-            "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator" };
+    public static final String[] JOB_ERROR_STR = {"java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
+            "org.springframework", "org.apache", "com.ruoyi.common.utils.file", "com.ruoyi.common.config", "com.ruoyi.generator"};
+
+    public static final String Y = "Y";
+    public static final String N = "N";
+    //坐标
+    public static final String DETECTION_RECT = "detection/rect";
+    //离岗时间
+    public static final String DETECTION_LEAVETIME = "detection/leavetime";
+    //玩手机时间
+    public static final String DETECTION_PLAYTIME = "detection/playtime";
+    //离岗相似度
+    public static final String DETECTION_LEAVERATE = "detection/leaverate";
+    //玩手机相似度
+    public static final String DETECTION_PLAYRATE = "detection/playrate";
+    //rtsp地址
+    public static final String DETECTION_RTSP = "detection/rtsp";
 }

+ 83 - 0
ruoyi-common/src/main/java/com/ruoyi/common/model/MqttMessage.java

@@ -0,0 +1,83 @@
+package com.ruoyi.common.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDateTime;
+import java.util.Map;
+
+/**
+ * MQTT消息模型类
+ *
+ * <p>封装MQTT消息的各种属性,包括主题、内容、QoS等,
+ * 可用于消息发布和接收场景。</p>
+ *
+ * @author andy
+ * @version 1.0.0
+ * @since 2025-06-17
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class MqttMessage {
+
+    /**
+     * 消息主题
+     */
+    private String topic;
+
+    /**
+     * 消息内容
+     */
+    private String payload;
+
+    /**
+     * 服务质量
+     * <p>0 - 最多发送一次,不保证送达</p>
+     * <p>1 - 至少发送一次,确保送达但可能重复</p>
+     * <p>2 - 确保仅送达一次</p>
+     */
+    private int qos = 1;
+
+    /**
+     * 是否为保留消息
+     */
+    private boolean retained = false;
+
+    /**
+     * 消息发送/接收时间
+     */
+    private LocalDateTime timestamp = LocalDateTime.now();
+
+    /**
+     * 消息自定义属性
+     */
+    private Map<String, Object> properties;
+
+    /**
+     * 带主题和内容的构造函数
+     *
+     * @param topic 消息主题
+     * @param payload 消息内容
+     */
+    public MqttMessage(String topic, String payload) {
+        this.topic = topic;
+        this.payload = payload;
+    }
+
+    /**
+     * 带主题、内容、QoS和保留标志的构造函数
+     *
+     * @param topic 消息主题
+     * @param payload 消息内容
+     * @param qos 服务质量
+     * @param retained 是否保留
+     */
+    public MqttMessage(String topic, String payload, int qos, boolean retained) {
+        this.topic = topic;
+        this.payload = payload;
+        this.qos = qos;
+        this.retained = retained;
+    }
+}

+ 1 - 1
ruoyi-system/pom.xml

@@ -25,4 +25,4 @@
 
     </dependencies>
 
-</project>
+</project>

+ 116 - 16
ruoyi-system/src/main/java/com/ruoyi/manage/domain/ChannelNumber.java

@@ -1,10 +1,10 @@
 package com.ruoyi.manage.domain;
 
-import org.apache.commons.lang3.builder.ToStringBuilder;
-import org.apache.commons.lang3.builder.ToStringStyle;
 import com.ruoyi.common.annotation.Excel;
 import com.ruoyi.common.core.domain.BaseEntity;
 
+import javax.validation.constraints.NotNull;
+
 /**
  * 通道管理对象 channel_number
  * 
@@ -18,13 +18,37 @@ public class ChannelNumber extends BaseEntity
     /** 通道ID */
     private Long channelId;
 
+    /** 设备ID */
+    @NotNull
+    private Long equipmentId;
+    /** 设备编号 */
+    @Excel(name = "设备编号")
+    private String equipmentNum;
+
+    /** 设备名称 */
+    @Excel(name = "设备名称")
+    private String equipmentName;
     /** 通道编号 */
     @Excel(name = "通道编号")
+    @NotNull
     private String channelNum;
 
+    /**
+     * 预览端口
+     */
+    private String port;
+
     /** 视频地址 */
     @Excel(name = "视频地址")
     private String videoAddress;
+    /**
+     * 账号
+     */
+    private String account;
+    /**
+     * 密码
+     */
+    private String password;
 
     /** 探测协议类型 */
     @Excel(name = "探测协议类型")
@@ -36,8 +60,81 @@ public class ChannelNumber extends BaseEntity
 
     /** 删除标志(0代表存在 1代表删除) */
     private String delFlag;
+    /**
+     * 是否设置过参数 N Y 不在数据库中
+     */
+    private String isChannel;
+
+    /**
+     * 参数设置
+     */
+    private ParameterSet parameterSet;
+
+    public ParameterSet getParameterSet() {
+        return parameterSet;
+    }
+
+    public void setParameterSet(ParameterSet parameterSet) {
+        this.parameterSet = parameterSet;
+    }
+
+    public String getPort() {
+        return port;
+    }
+
+    public void setPort(String port) {
+        this.port = port;
+    }
+
+    public String getIsChannel() {
+        return isChannel;
+    }
+
+    public void setIsChannel(String isChannel) {
+        this.isChannel = isChannel;
+    }
+
+    public String getEquipmentNum() {
+        return equipmentNum;
+    }
+
+    public void setEquipmentNum(String equipmentNum) {
+        this.equipmentNum = equipmentNum;
+    }
+
+    public String getEquipmentName() {
+        return equipmentName;
+    }
+
+    public void setEquipmentName(String equipmentName) {
+        this.equipmentName = equipmentName;
+    }
+
+    public Long getEquipmentId() {
+        return equipmentId;
+    }
+
+    public void setEquipmentId(Long equipmentId) {
+        this.equipmentId = equipmentId;
+    }
+
+    public String getAccount() {
+        return account;
+    }
+
+    public void setAccount(String account) {
+        this.account = account;
+    }
+
+    public String getPassword() {
+        return password;
+    }
+
+    public void setPassword(String password) {
+        this.password = password;
+    }
 
-    public void setChannelId(Long channelId) 
+    public void setChannelId(Long channelId)
     {
         this.channelId = channelId;
     }
@@ -99,18 +196,21 @@ public class ChannelNumber extends BaseEntity
 
     @Override
     public String toString() {
-        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
-            .append("channelId", getChannelId())
-            .append("channelNum", getChannelNum())
-            .append("videoAddress", getVideoAddress())
-            .append("protocolType", getProtocolType())
-            .append("channelDetails", getChannelDetails())
-            .append("delFlag", getDelFlag())
-            .append("createBy", getCreateBy())
-            .append("createTime", getCreateTime())
-            .append("updateBy", getUpdateBy())
-            .append("updateTime", getUpdateTime())
-            .append("remark", getRemark())
-            .toString();
+        return "ChannelNumber{" +
+                "channelId=" + channelId +
+                ", equipmentId=" + equipmentId +
+                ", equipmentNum='" + equipmentNum + '\'' +
+                ", equipmentName='" + equipmentName + '\'' +
+                ", channelNum='" + channelNum + '\'' +
+                ", port='" + port + '\'' +
+                ", videoAddress='" + videoAddress + '\'' +
+                ", account='" + account + '\'' +
+                ", password='" + password + '\'' +
+                ", protocolType='" + protocolType + '\'' +
+                ", channelDetails='" + channelDetails + '\'' +
+                ", delFlag='" + delFlag + '\'' +
+                ", isChannel='" + isChannel + '\'' +
+                ", parameterSet=" + parameterSet +
+                '}';
     }
 }

+ 2 - 0
ruoyi-system/src/main/java/com/ruoyi/manage/domain/EquipmentManage.java

@@ -6,6 +6,7 @@ import com.ruoyi.common.core.domain.BaseEntity;
 import org.apache.commons.lang3.builder.ToStringBuilder;
 import org.apache.commons.lang3.builder.ToStringStyle;
 
+import javax.validation.constraints.NotNull;
 import java.util.Date;
 
 /**
@@ -23,6 +24,7 @@ public class EquipmentManage extends BaseEntity
 
     /** 设备编号 */
     @Excel(name = "设备编号")
+    @NotNull
     private String equipmentNum;
 
     /** 设备名称 */

+ 206 - 0
ruoyi-system/src/main/java/com/ruoyi/manage/domain/ParameterSet.java

@@ -0,0 +1,206 @@
+package com.ruoyi.manage.domain;
+
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import com.ruoyi.common.annotation.Excel;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+/**
+ * 参数设置对象 parameter_set
+ * 
+ * @author boman
+ * @date 2025-06-18
+ */
+public class ParameterSet extends BaseEntity
+{
+    private static final long serialVersionUID = 1L;
+
+    /** 参数ID */
+    private Long parameterId;
+
+    /** 通道ID */
+    @Excel(name = "通道ID")
+    private Long channelId;
+
+    /** 设备id */
+    @Excel(name = "设备id")
+    private Long equipmentId;
+
+    /** 设备编号 */
+    @Excel(name = "设备编号")
+    private String equipmentNum;
+
+    /** 设备名称 */
+    @Excel(name = "设备名称")
+    private String equipmentName;
+
+    /** 通道编号 */
+    @Excel(name = "通道编号")
+    private String channelNum;
+
+    /** 坐标范围 */
+    @Excel(name = "坐标范围")
+    private String channelRange;
+
+    /** 离岗时间设置 默认:10 分钟 */
+    @Excel(name = "离岗时间设置 默认:10 分钟")
+    private String leaveTime;
+
+    /** 玩手机时间设置 默认:10 分钟 */
+    @Excel(name = "玩手机时间设置 默认:10 分钟")
+    private String playTime;
+
+    /** 离岗时间相似度设置 默认:0.5 */
+    @Excel(name = "离岗时间相似度设置 默认:0.5")
+    private String leaveRate;
+
+    /** 玩手机相似度设置 默认:0.5 */
+    @Excel(name = "玩手机相似度设置 默认:0.5")
+    private String playRate;
+
+    /** 删除标志(0代表存在 1代表删除) */
+    private String delFlag;
+
+    public void setParameterId(Long parameterId) 
+    {
+        this.parameterId = parameterId;
+    }
+
+    public Long getParameterId() 
+    {
+        return parameterId;
+    }
+
+    public void setChannelId(Long channelId) 
+    {
+        this.channelId = channelId;
+    }
+
+    public Long getChannelId() 
+    {
+        return channelId;
+    }
+
+    public void setEquipmentId(Long equipmentId) 
+    {
+        this.equipmentId = equipmentId;
+    }
+
+    public Long getEquipmentId() 
+    {
+        return equipmentId;
+    }
+
+    public void setEquipmentNum(String equipmentNum) 
+    {
+        this.equipmentNum = equipmentNum;
+    }
+
+    public String getEquipmentNum() 
+    {
+        return equipmentNum;
+    }
+
+    public void setEquipmentName(String equipmentName) 
+    {
+        this.equipmentName = equipmentName;
+    }
+
+    public String getEquipmentName() 
+    {
+        return equipmentName;
+    }
+
+    public void setChannelNum(String channelNum) 
+    {
+        this.channelNum = channelNum;
+    }
+
+    public String getChannelNum() 
+    {
+        return channelNum;
+    }
+
+    public void setChannelRange(String channelRange) 
+    {
+        this.channelRange = channelRange;
+    }
+
+    public String getChannelRange() 
+    {
+        return channelRange;
+    }
+
+    public void setLeaveTime(String leaveTime) 
+    {
+        this.leaveTime = leaveTime;
+    }
+
+    public String getLeaveTime() 
+    {
+        return leaveTime;
+    }
+
+    public void setPlayTime(String playTime) 
+    {
+        this.playTime = playTime;
+    }
+
+    public String getPlayTime() 
+    {
+        return playTime;
+    }
+
+    public void setLeaveRate(String leaveRate) 
+    {
+        this.leaveRate = leaveRate;
+    }
+
+    public String getLeaveRate() 
+    {
+        return leaveRate;
+    }
+
+    public void setPlayRate(String playRate) 
+    {
+        this.playRate = playRate;
+    }
+
+    public String getPlayRate() 
+    {
+        return playRate;
+    }
+
+    public void setDelFlag(String delFlag) 
+    {
+        this.delFlag = delFlag;
+    }
+
+    public String getDelFlag() 
+    {
+        return delFlag;
+    }
+
+    @Override
+    public String toString() {
+        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
+            .append("parameterId", getParameterId())
+            .append("channelId", getChannelId())
+            .append("equipmentId", getEquipmentId())
+            .append("equipmentNum", getEquipmentNum())
+            .append("equipmentName", getEquipmentName())
+            .append("channelNum", getChannelNum())
+            .append("channelRange", getChannelRange())
+            .append("leaveTime", getLeaveTime())
+            .append("playTime", getPlayTime())
+            .append("leaveRate", getLeaveRate())
+            .append("playRate", getPlayRate())
+            .append("delFlag", getDelFlag())
+            .append("createBy", getCreateBy())
+            .append("createTime", getCreateTime())
+            .append("updateBy", getUpdateBy())
+            .append("updateTime", getUpdateTime())
+            .append("remark", getRemark())
+            .toString();
+    }
+}

+ 1 - 0
ruoyi-system/src/main/java/com/ruoyi/manage/mapper/ChannelNumberMapper.java

@@ -18,6 +18,7 @@ public interface ChannelNumberMapper
      * @return 通道管理
      */
     public ChannelNumber selectChannelNumberByChannelId(Long channelId);
+    public ChannelNumber selectChannelNumberByChannelNum(ChannelNumber channelNumber);
 
     /**
      * 查询通道管理列表

+ 1 - 0
ruoyi-system/src/main/java/com/ruoyi/manage/mapper/EquipmentManageMapper.java

@@ -27,6 +27,7 @@ public interface EquipmentManageMapper
      * @return 设备管理集合
      */
     public List<EquipmentManage> selectEquipmentManageList(EquipmentManage equipmentManage);
+    public EquipmentManage selectEquipmentManageByEquipmentNum(EquipmentManage equipmentManage);
 
     /**
      * 新增设备管理

+ 63 - 0
ruoyi-system/src/main/java/com/ruoyi/manage/mapper/ParameterSetMapper.java

@@ -0,0 +1,63 @@
+package com.ruoyi.manage.mapper;
+
+import com.ruoyi.manage.domain.ParameterSet;
+
+import java.util.List;
+
+/**
+ * 参数设置Mapper接口
+ * 
+ * @author boman
+ * @date 2025-06-18
+ */
+public interface ParameterSetMapper 
+{
+    /**
+     * 查询参数设置
+     * 
+     * @param parameterId 参数设置主键
+     * @return 参数设置
+     */
+    public ParameterSet selectParameterSetByParameterId(Long parameterId);
+    public ParameterSet selectParameterSetByChannelId(Long channelId);
+
+    /**
+     * 查询参数设置列表
+     * 
+     * @param parameterSet 参数设置
+     * @return 参数设置集合
+     */
+    public List<ParameterSet> selectParameterSetList(ParameterSet parameterSet);
+
+    /**
+     * 新增参数设置
+     * 
+     * @param parameterSet 参数设置
+     * @return 结果
+     */
+    public int insertParameterSet(ParameterSet parameterSet);
+
+    /**
+     * 修改参数设置
+     * 
+     * @param parameterSet 参数设置
+     * @return 结果
+     */
+    public int updateParameterSet(ParameterSet parameterSet);
+
+    /**
+     * 删除参数设置
+     * 
+     * @param parameterId 参数设置主键
+     * @return 结果
+     */
+    public int deleteParameterSetByParameterId(Long parameterId);
+
+    /**
+     * 批量删除参数设置
+     * 
+     * @param parameterIds 需要删除的数据主键集合
+     * @return 结果
+     */
+    public int deleteParameterSetByParameterIds(Long[] parameterIds);
+}

+ 3 - 2
ruoyi-system/src/main/java/com/ruoyi/manage/service/IChannelNumberService.java

@@ -1,5 +1,6 @@
 package com.ruoyi.manage.service;
 
+import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.manage.domain.ChannelNumber;
 
 import java.util.List;
@@ -35,7 +36,7 @@ public interface IChannelNumberService
      * @param channelNumber 通道管理
      * @return 结果
      */
-    public int insertChannelNumber(ChannelNumber channelNumber);
+    public AjaxResult insertChannelNumber(ChannelNumber channelNumber);
 
     /**
      * 修改通道管理
@@ -43,7 +44,7 @@ public interface IChannelNumberService
      * @param channelNumber 通道管理
      * @return 结果
      */
-    public int updateChannelNumber(ChannelNumber channelNumber);
+    public AjaxResult updateChannelNumber(ChannelNumber channelNumber);
 
     /**
      * 批量删除通道管理

+ 3 - 2
ruoyi-system/src/main/java/com/ruoyi/manage/service/IEquipmentManageService.java

@@ -1,5 +1,6 @@
 package com.ruoyi.manage.service;
 
+import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.manage.domain.EquipmentManage;
 
 import java.util.List;
@@ -34,7 +35,7 @@ public interface IEquipmentManageService
      * @param equipmentManage 设备管理
      * @return 结果
      */
-    public int insertEquipmentManage(EquipmentManage equipmentManage);
+    public AjaxResult insertEquipmentManage(EquipmentManage equipmentManage);
 
     /**
      * 修改设备管理
@@ -42,7 +43,7 @@ public interface IEquipmentManageService
      * @param equipmentManage 设备管理
      * @return 结果
      */
-    public int updateEquipmentManage(EquipmentManage equipmentManage);
+    public AjaxResult updateEquipmentManage(EquipmentManage equipmentManage);
 
     /**
      * 批量删除设备管理

+ 63 - 0
ruoyi-system/src/main/java/com/ruoyi/manage/service/IParameterSetService.java

@@ -0,0 +1,63 @@
+package com.ruoyi.manage.service;
+
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.manage.domain.ParameterSet;
+
+import java.util.List;
+
+/**
+ * 参数设置Service接口
+ * 
+ * @author boman
+ * @date 2025-06-18
+ */
+public interface IParameterSetService 
+{
+    /**
+     * 查询参数设置
+     * 
+     * @param parameterId 参数设置主键
+     * @return 参数设置
+     */
+    public ParameterSet selectParameterSetByParameterId(Long parameterId);
+
+    /**
+     * 查询参数设置列表
+     * 
+     * @param parameterSet 参数设置
+     * @return 参数设置集合
+     */
+    public List<ParameterSet> selectParameterSetList(ParameterSet parameterSet);
+
+    /**
+     * 新增参数设置
+     * 
+     * @param parameterSet 参数设置
+     * @return 结果
+     */
+    public AjaxResult insertParameterSet(ParameterSet parameterSet);
+
+    /**
+     * 修改参数设置
+     * 
+     * @param parameterSet 参数设置
+     * @return 结果
+     */
+    public AjaxResult updateParameterSet(ParameterSet parameterSet);
+
+    /**
+     * 批量删除参数设置
+     * 
+     * @param parameterIds 需要删除的参数设置主键集合
+     * @return 结果
+     */
+    public int deleteParameterSetByParameterIds(Long[] parameterIds);
+
+    /**
+     * 删除参数设置信息
+     * 
+     * @param parameterId 参数设置主键
+     * @return 结果
+     */
+    public int deleteParameterSetByParameterId(Long parameterId);
+}

+ 71 - 24
ruoyi-system/src/main/java/com/ruoyi/manage/service/impl/ChannelNumberServiceImpl.java

@@ -1,97 +1,144 @@
 package com.ruoyi.manage.service.impl;
 
+import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.manage.domain.ChannelNumber;
+import com.ruoyi.manage.domain.ParameterSet;
 import com.ruoyi.manage.mapper.ChannelNumberMapper;
+import com.ruoyi.manage.mapper.ParameterSetMapper;
 import com.ruoyi.manage.service.IChannelNumberService;
+import com.ruoyi.mqtt.service.MqttService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static com.ruoyi.common.constant.Constants.*;
 
 
 /**
  * 通道管理Service业务层处理
- * 
+ *
  * @author boman
  * @date 2025-05-07
  */
 @Service
-public class ChannelNumberServiceImpl implements IChannelNumberService
-{
+public class ChannelNumberServiceImpl implements IChannelNumberService {
     @Autowired
     private ChannelNumberMapper channelNumberMapper;
 
+    @Autowired
+    private ParameterSetMapper parameterSetMapper;
+
+    @Autowired
+    private MqttService mqttService;
+
     /**
      * 查询通道管理
-     * 
+     *
      * @param channelId 通道管理主键
      * @return 通道管理
      */
     @Override
-    public ChannelNumber selectChannelNumberByChannelId(Long channelId)
-    {
-        return channelNumberMapper.selectChannelNumberByChannelId(channelId);
+    public ChannelNumber selectChannelNumberByChannelId(Long channelId) {
+        ChannelNumber channelNumber = channelNumberMapper.selectChannelNumberByChannelId(channelId);
+        channelNumber.setIsChannel(N);
+        //判断是否设置过参数
+        ParameterSet parameterSet = parameterSetMapper.selectParameterSetByChannelId(channelId);
+        if (parameterSet != null) {
+            channelNumber.setIsChannel(Y);
+            channelNumber.setParameterSet(parameterSet);
+        }
+        return channelNumber;
     }
 
     /**
      * 查询通道管理列表
-     * 
+     *
      * @param channelNumber 通道管理
      * @return 通道管理
      */
     @Override
-    public List<ChannelNumber> selectChannelNumberList(ChannelNumber channelNumber)
-    {
+    public List<ChannelNumber> selectChannelNumberList(ChannelNumber channelNumber) {
         return channelNumberMapper.selectChannelNumberList(channelNumber);
     }
 
     /**
      * 新增通道管理
-     * 
+     *
      * @param channelNumber 通道管理
      * @return 结果
      */
     @Override
-    public int insertChannelNumber(ChannelNumber channelNumber)
-    {
+    public AjaxResult insertChannelNumber(ChannelNumber channelNumber) {
+        ChannelNumber channelNumberOld = channelNumberMapper.selectChannelNumberByChannelNum(channelNumber);
+        if (channelNumberOld != null) {
+            return AjaxResult.error("设备已存在当前通道编号");
+        }
+        String videoAddress = channelNumber.getVideoAddress();
+        if (StringUtils.isNotEmpty(videoAddress)) {
+            try {
+                CompletableFuture<Void> future = mqttService.publish(DETECTION_RTSP, videoAddress);
+                future.get(10, TimeUnit.SECONDS);
+            } catch (InterruptedException | ExecutionException | TimeoutException e) {
+                return AjaxResult.error("发布消息失败:" + DETECTION_RTSP);
+            }
+        }
         channelNumber.setCreateTime(DateUtils.getNowDate());
-        return channelNumberMapper.insertChannelNumber(channelNumber);
+        channelNumberMapper.insertChannelNumber(channelNumber);
+        return AjaxResult.success();
     }
 
     /**
      * 修改通道管理
-     * 
+     *
      * @param channelNumber 通道管理
      * @return 结果
      */
     @Override
-    public int updateChannelNumber(ChannelNumber channelNumber)
-    {
+    public AjaxResult updateChannelNumber(ChannelNumber channelNumber) {
+        ChannelNumber channelNumberOld = channelNumberMapper.selectChannelNumberByChannelNum(channelNumber);
+        if (channelNumberOld != null && !channelNumber.getChannelId().equals(channelNumberOld.getChannelId())) {
+            return AjaxResult.error("设备已存在当前通道编号");
+        }
+        String videoAddress = channelNumber.getVideoAddress();
+        if (StringUtils.isNotEmpty(videoAddress)) {
+            try {
+                CompletableFuture<Void> future = mqttService.publish(DETECTION_RTSP, videoAddress);
+                future.get(10, TimeUnit.SECONDS);
+            } catch (InterruptedException | ExecutionException | TimeoutException e) {
+                return AjaxResult.error("发布消息失败:" + DETECTION_RTSP);
+            }
+        }
         channelNumber.setUpdateTime(DateUtils.getNowDate());
-        return channelNumberMapper.updateChannelNumber(channelNumber);
+        channelNumberMapper.updateChannelNumber(channelNumber);
+        return AjaxResult.success();
     }
 
     /**
      * 批量删除通道管理
-     * 
+     *
      * @param channelIds 需要删除的通道管理主键
      * @return 结果
      */
     @Override
-    public int deleteChannelNumberByChannelIds(Long[] channelIds)
-    {
+    public int deleteChannelNumberByChannelIds(Long[] channelIds) {
         return channelNumberMapper.deleteChannelNumberByChannelIds(channelIds);
     }
 
     /**
      * 删除通道管理信息
-     * 
+     *
      * @param channelId 通道管理主键
      * @return 结果
      */
     @Override
-    public int deleteChannelNumberByChannelId(Long channelId)
-    {
+    public int deleteChannelNumberByChannelId(Long channelId) {
         return channelNumberMapper.deleteChannelNumberByChannelId(channelId);
     }
 }

+ 29 - 23
ruoyi-system/src/main/java/com/ruoyi/manage/service/impl/EquipmentManageServiceImpl.java

@@ -1,5 +1,6 @@
 package com.ruoyi.manage.service.impl;
 
+import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.utils.DateUtils;
 import com.ruoyi.manage.domain.EquipmentManage;
 import com.ruoyi.manage.mapper.EquipmentManageMapper;
@@ -11,87 +12,92 @@ import java.util.List;
 
 /**
  * 设备管理Service业务层处理
- * 
+ *
  * @author boman
  * @date 2025-05-07
  */
 @Service
-public class EquipmentManageServiceImpl implements IEquipmentManageService
-{
+public class EquipmentManageServiceImpl implements IEquipmentManageService {
     @Autowired
     private EquipmentManageMapper equipmentManageMapper;
 
     /**
      * 查询设备管理
-     * 
+     *
      * @param equipmentId 设备管理主键
      * @return 设备管理
      */
     @Override
-    public EquipmentManage selectEquipmentManageByEquipmentId(Long equipmentId)
-    {
+    public EquipmentManage selectEquipmentManageByEquipmentId(Long equipmentId) {
         return equipmentManageMapper.selectEquipmentManageByEquipmentId(equipmentId);
     }
 
     /**
      * 查询设备管理列表
-     * 
+     *
      * @param equipmentManage 设备管理
      * @return 设备管理
      */
     @Override
-    public List<EquipmentManage> selectEquipmentManageList(EquipmentManage equipmentManage)
-    {
+    public List<EquipmentManage> selectEquipmentManageList(EquipmentManage equipmentManage) {
         return equipmentManageMapper.selectEquipmentManageList(equipmentManage);
     }
 
     /**
      * 新增设备管理
-     * 
+     *
      * @param equipmentManage 设备管理
      * @return 结果
      */
     @Override
-    public int insertEquipmentManage(EquipmentManage equipmentManage)
-    {
+    public AjaxResult insertEquipmentManage(EquipmentManage equipmentManage) {
+        EquipmentManage equipmentManageOld = equipmentManageMapper.selectEquipmentManageByEquipmentNum(equipmentManage);
+        if (equipmentManageOld != null) {
+            return AjaxResult.error("当前设备编号已存在");
+        }
         equipmentManage.setCreateTime(DateUtils.getNowDate());
-        return equipmentManageMapper.insertEquipmentManage(equipmentManage);
+        equipmentManageMapper.insertEquipmentManage(equipmentManage);
+        return AjaxResult.success();
     }
 
     /**
      * 修改设备管理
-     * 
+     *
      * @param equipmentManage 设备管理
      * @return 结果
      */
     @Override
-    public int updateEquipmentManage(EquipmentManage equipmentManage)
-    {
+    public AjaxResult updateEquipmentManage(EquipmentManage equipmentManage) {
+        EquipmentManage equipmentManageOld = equipmentManageMapper.selectEquipmentManageByEquipmentNum(equipmentManage);
+        if (equipmentManageOld != null && !equipmentManage.getEquipmentId().equals(equipmentManageOld.getEquipmentId())) {
+            return AjaxResult.error("当前设备编号已存在");
+        }
         equipmentManage.setUpdateTime(DateUtils.getNowDate());
-        return equipmentManageMapper.updateEquipmentManage(equipmentManage);
+        equipmentManageMapper.updateEquipmentManage(equipmentManage);
+        return AjaxResult.success();
     }
 
+
     /**
      * 批量删除设备管理
-     * 
+     *
      * @param equipmentIds 需要删除的设备管理主键
      * @return 结果
      */
     @Override
-    public int deleteEquipmentManageByEquipmentIds(Long[] equipmentIds)
-    {
+    public int deleteEquipmentManageByEquipmentIds(Long[] equipmentIds) {
         return equipmentManageMapper.deleteEquipmentManageByEquipmentIds(equipmentIds);
     }
 
     /**
      * 删除设备管理信息
-     * 
+     *
      * @param equipmentId 设备管理主键
      * @return 结果
      */
     @Override
-    public int deleteEquipmentManageByEquipmentId(Long equipmentId)
-    {
+    public int deleteEquipmentManageByEquipmentId(Long equipmentId) {
         return equipmentManageMapper.deleteEquipmentManageByEquipmentId(equipmentId);
     }
 }
+

+ 163 - 0
ruoyi-system/src/main/java/com/ruoyi/manage/service/impl/ParameterSetServiceImpl.java

@@ -0,0 +1,163 @@
+package com.ruoyi.manage.service.impl;
+
+import com.ruoyi.common.core.domain.AjaxResult;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.manage.domain.ParameterSet;
+import com.ruoyi.manage.mapper.ParameterSetMapper;
+import com.ruoyi.manage.service.IParameterSetService;
+import com.ruoyi.mqtt.service.MqttService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import static com.ruoyi.common.constant.Constants.*;
+
+/**
+ * 参数设置Service业务层处理
+ *
+ * @author boman
+ * @date 2025-06-18
+ */
+@Service
+public class ParameterSetServiceImpl implements IParameterSetService {
+    @Autowired
+    private ParameterSetMapper parameterSetMapper;
+    @Autowired
+    private MqttService mqttService;
+
+    /**
+     * 查询参数设置
+     *
+     * @param parameterId 参数设置主键
+     * @return 参数设置
+     */
+    @Override
+    public ParameterSet selectParameterSetByParameterId(Long parameterId) {
+        return parameterSetMapper.selectParameterSetByParameterId(parameterId);
+    }
+
+    /**
+     * 查询参数设置列表
+     *
+     * @param parameterSet 参数设置
+     * @return 参数设置
+     */
+    @Override
+    public List<ParameterSet> selectParameterSetList(ParameterSet parameterSet) {
+        return parameterSetMapper.selectParameterSetList(parameterSet);
+    }
+
+    /**
+     * 新增参数设置
+     *
+     * @param parameterSet 参数设置
+     * @return 结果
+     */
+    @Override
+    public AjaxResult insertParameterSet(ParameterSet parameterSet) {
+        Long channelId = parameterSet.getChannelId();
+        ParameterSet parameterSetOld = parameterSetMapper.selectParameterSetByChannelId(channelId);
+        if (parameterSetOld != null) {
+            return AjaxResult.error("当前通道已设置参数,请勿重复新增");
+        }
+        AjaxResult ajaxResult = checkParameter(parameterSet);
+        if (ajaxResult.isError()) {
+            return ajaxResult;
+        }
+        parameterSet.setCreateTime(DateUtils.getNowDate());
+        parameterSetMapper.insertParameterSet(parameterSet);
+        return AjaxResult.success();
+    }
+
+    //校验是否需要下发参数
+    public AjaxResult checkParameter(ParameterSet parameterSet) {
+        //下发指令
+        String channelRange = parameterSet.getChannelRange();
+        String leaveTime = parameterSet.getLeaveTime();
+        String playTime = parameterSet.getPlayTime();
+        String leaveRate = parameterSet.getLeaveRate();
+        String playRate = parameterSet.getPlayRate();
+        if (StringUtils.isNotEmpty(channelRange)) {
+            try {
+                CompletableFuture<Void> future = mqttService.publish(DETECTION_RECT, channelRange);
+                future.get(10, TimeUnit.SECONDS);
+            } catch (InterruptedException | ExecutionException | TimeoutException e) {
+                return AjaxResult.error("发布消息失败:" + DETECTION_RECT);
+            }
+        } else if (StringUtils.isNotEmpty(leaveTime)) {
+            try {
+                CompletableFuture<Void> future = mqttService.publish(DETECTION_LEAVETIME, leaveTime);
+                future.get(10, TimeUnit.SECONDS);
+            } catch (InterruptedException | ExecutionException | TimeoutException e) {
+                return AjaxResult.error("发布消息失败:" + DETECTION_LEAVETIME);
+            }
+        } else if (StringUtils.isNotEmpty(playTime)) {
+            try {
+                CompletableFuture<Void> future = mqttService.publish(DETECTION_PLAYTIME, playTime);
+                future.get(10, TimeUnit.SECONDS);
+            } catch (InterruptedException | ExecutionException | TimeoutException e) {
+                return AjaxResult.error("发布消息失败:" + DETECTION_PLAYTIME);
+            }
+        } else if (StringUtils.isNotEmpty(leaveRate)) {
+            try {
+                CompletableFuture<Void> future = mqttService.publish(DETECTION_LEAVERATE, leaveRate);
+                future.get(10, TimeUnit.SECONDS);
+            } catch (InterruptedException | ExecutionException | TimeoutException e) {
+                return AjaxResult.error("发布消息失败:" + DETECTION_LEAVERATE);
+            }
+        } else if (StringUtils.isNotEmpty(playRate)) {
+            try {
+                CompletableFuture<Void> future = mqttService.publish(DETECTION_PLAYRATE, playRate);
+                future.get(10, TimeUnit.SECONDS);
+            } catch (InterruptedException | ExecutionException | TimeoutException e) {
+                return AjaxResult.error("发布消息失败:" + DETECTION_PLAYRATE);
+            }
+        }
+        return AjaxResult.success();
+    }
+
+    /**
+     * 修改参数设置
+     *
+     * @param parameterSet 参数设置
+     * @return 结果
+     */
+    @Override
+    public AjaxResult updateParameterSet(ParameterSet parameterSet) {
+        AjaxResult ajaxResult = checkParameter(parameterSet);
+        if (ajaxResult.isError()) {
+            return ajaxResult;
+        }
+        parameterSet.setUpdateTime(DateUtils.getNowDate());
+        parameterSetMapper.updateParameterSet(parameterSet);
+        return AjaxResult.success();
+    }
+
+    /**
+     * 批量删除参数设置
+     *
+     * @param parameterIds 需要删除的参数设置主键
+     * @return 结果
+     */
+    @Override
+    public int deleteParameterSetByParameterIds(Long[] parameterIds) {
+        return parameterSetMapper.deleteParameterSetByParameterIds(parameterIds);
+    }
+
+    /**
+     * 删除参数设置信息
+     *
+     * @param parameterId 参数设置主键
+     * @return 结果
+     */
+    @Override
+    public int deleteParameterSetByParameterId(Long parameterId) {
+        return parameterSetMapper.deleteParameterSetByParameterId(parameterId);
+    }
+}

+ 61 - 0
ruoyi-system/src/main/java/com/ruoyi/mqtt/service/MqttMessageListener.java

@@ -0,0 +1,61 @@
+package com.ruoyi.mqtt.service;
+
+import com.ruoyi.common.model.MqttMessage;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+/**
+ * MQTT消息监听器
+ *
+ * <p>通过Spring事件机制接收MQTT消息,可以添加自定义的业务逻辑来处理接收到的消息。
+ * 可以创建多个不同的监听器来处理不同主题或类型的消息。</p>
+ *
+ * @author andy
+ * @version 1.0.0
+ * @since 2025-06-17
+ */
+@Slf4j
+@Component
+public class MqttMessageListener {
+
+    /**
+     * 处理接收到的MQTT消息
+     *
+     * @param message MQTT消息对象
+     */
+    @EventListener
+    public void handleMqttMessage(MqttMessage message) {
+        log.info("收到MQTT消息事件: 主题={}, 消息内容={}", message.getTopic(), message.getPayload());
+
+        // TODO: 添加自定义的业务逻辑来处理消息
+        // 例如:解析JSON消息内容,更新数据库,触发其他操作等
+
+        // 可以根据主题进行不同的处理
+        if (message.getTopic().startsWith("device/")) {
+            handleDeviceMessage(message);
+        } else if (message.getTopic().startsWith("alert/")) {
+            handleAlertMessage(message);
+        }
+    }
+
+    /**
+     * 处理设备相关消息
+     *
+     * @param message MQTT消息
+     */
+    private void handleDeviceMessage(MqttMessage message) {
+        // 设备消息处理逻辑
+        log.debug("处理设备消息: {}", message.getPayload());
+    }
+
+    /**
+     * 处理告警相关消息
+     *
+     * @param message MQTT消息
+     */
+    private void handleAlertMessage(MqttMessage message) {
+        // 告警消息处理逻辑
+        log.debug("处理告警消息: {}", message.getPayload());
+    }
+}

+ 271 - 0
ruoyi-system/src/main/java/com/ruoyi/mqtt/service/MqttService.java

@@ -0,0 +1,271 @@
+package com.ruoyi.mqtt.service;
+
+
+import com.ruoyi.common.config.MqttProperties;
+import com.ruoyi.common.model.MqttMessage;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.*;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * MQTT服务类
+ *
+ * <p>提供MQTT消息发布和订阅功能的核心服务,封装了底层MQTT客户端操作,
+ * 提供简单易用的API供应用程序使用。</p>
+ *
+ * @author andy
+ * @version 1.0.0
+ * @since 2025-06-17
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class MqttService {
+
+    private final MqttAsyncClient mqttClient;
+    private final MqttProperties mqttProperties;
+
+    /**
+     * 初始化方法
+     * 在服务启动时订阅默认主题
+     */
+    @PostConstruct
+    public void init() {
+        // 如果配置了默认主题,则自动订阅
+        if (mqttProperties.getDefaultTopic() != null && !mqttProperties.getDefaultTopic().isEmpty()) {
+            try {
+                subscribe(mqttProperties.getDefaultTopic());
+                log.info("已自动订阅默认主题: {}", mqttProperties.getDefaultTopic());
+            } catch (MqttException e) {
+                log.error("自动订阅默认主题失败: {}", e.getMessage(), e);
+            }
+        }
+    }
+
+    /**
+     * 销毁方法
+     * 在服务关闭时断开MQTT连接
+     */
+    @PreDestroy
+    public void destroy() {
+        try {
+            if (mqttClient != null && mqttClient.isConnected()) {
+                mqttClient.disconnect().waitForCompletion(mqttProperties.getCommandTimeout() * 1000L);
+                log.info("MQTT客户端已断开连接");
+            }
+        } catch (MqttException e) {
+            log.error("断开MQTT连接时出错: {}", e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 检查客户端连接状态
+     *
+     * @return 是否已连接
+     */
+    public boolean isConnected() {
+        return mqttClient != null && mqttClient.isConnected();
+    }
+
+    /**
+     * 重新连接到MQTT服务器
+     *
+     * @return 重连操作的CompletableFuture
+     */
+    public CompletableFuture<Void> reconnect() {
+        CompletableFuture<Void> future = new CompletableFuture<>();
+
+        try {
+            if (mqttClient == null) {
+                future.completeExceptionally(new IllegalStateException("MQTT客户端未初始化"));
+                return future;
+            }
+
+            if (mqttClient.isConnected()) {
+                log.info("MQTT客户端已经连接,无需重连");
+                future.complete(null);
+                return future;
+            }
+
+            mqttClient.connect(new MqttConnectOptions())
+                    .setActionCallback(new IMqttActionListener() {
+                        @Override
+                        public void onSuccess(IMqttToken token) {
+                            log.info("MQTT客户端重连成功");
+                            future.complete(null);
+                        }
+
+                        @Override
+                        public void onFailure(IMqttToken token, Throwable exception) {
+                            log.error("MQTT客户端重连失败: {}", exception.getMessage(), exception);
+                            future.completeExceptionally(exception);
+                        }
+                    });
+        } catch (MqttException e) {
+            log.error("MQTT重连过程中出错: {}", e.getMessage(), e);
+            future.completeExceptionally(e);
+        }
+
+        return future;
+    }
+
+    /**
+     * 发布消息
+     *
+     * @param message MQTT消息对象
+     * @return 发布操作的CompletableFuture
+     */
+    public CompletableFuture<Void> publish(MqttMessage message) {
+        CompletableFuture<Void> future = new CompletableFuture<>();
+
+        try {
+            if (!isConnected()) {
+                throw new MqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED);
+            }
+
+            // 创建Paho消息对象
+            org.eclipse.paho.client.mqttv3.MqttMessage mqttMessage =
+                    new org.eclipse.paho.client.mqttv3.MqttMessage(message.getPayload().getBytes(StandardCharsets.UTF_8));
+            mqttMessage.setQos(message.getQos());
+            mqttMessage.setRetained(message.isRetained());
+
+            // 发布消息
+            mqttClient.publish(message.getTopic(), mqttMessage)
+                    .setActionCallback(new IMqttActionListener() {
+                        @Override
+                        public void onSuccess(IMqttToken token) {
+                            log.debug("消息发布成功: 主题={}, 内容={}", message.getTopic(), message.getPayload());
+                            future.complete(null);
+                        }
+
+                        @Override
+                        public void onFailure(IMqttToken token, Throwable exception) {
+                            log.error("消息发布失败: {}", exception.getMessage(), exception);
+                            future.completeExceptionally(exception);
+                        }
+                    });
+        } catch (Exception e) {
+            log.error("发布消息时出错: {}", e.getMessage(), e);
+            future.completeExceptionally(e);
+        }
+
+        return future;
+    }
+
+    /**
+     * 使用单独参数发布消息
+     *
+     * @param topic 消息主题
+     * @param payload 消息内容
+     * @return 发布操作的CompletableFuture
+     */
+    public CompletableFuture<Void> publish(String topic, String payload) {
+        MqttMessage message = new MqttMessage(topic, payload, mqttProperties.getDefaultQos(), false);
+        return publish(message);
+    }
+
+    /**
+     * 使用完整参数发布消息
+     *
+     * @param topic 消息主题
+     * @param payload 消息内容
+     * @param qos 服务质量
+     * @param retained 是否保留
+     * @return 发布操作的CompletableFuture
+     */
+    public CompletableFuture<Void> publish(String topic, String payload, int qos, boolean retained) {
+        MqttMessage message = new MqttMessage(topic, payload, qos, retained);
+        return publish(message);
+    }
+
+    /**
+     * 订阅主题
+     *
+     * @param topic 要订阅的主题
+     * @return 订阅成功的主题
+     * @throws MqttException MQTT异常
+     */
+    public String subscribe(String topic) throws MqttException {
+        return subscribe(topic, mqttProperties.getDefaultQos());
+    }
+
+    /**
+     * 使用指定QoS订阅主题
+     *
+     * @param topic 要订阅的主题
+     * @param qos 服务质量
+     * @return 订阅成功的主题
+     * @throws MqttException MQTT异常
+     */
+    public String subscribe(String topic, int qos) throws MqttException {
+        if (!isConnected()) {
+            throw new MqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED);
+        }
+
+        mqttClient.subscribe(topic, qos).waitForCompletion(mqttProperties.getCommandTimeout() * 1000L);
+        log.info("已订阅主题: {}, QoS: {}", topic, qos);
+        return topic;
+    }
+
+    /**
+     * 批量订阅主题
+     *
+     * @param topics 要订阅的主题列表
+     * @return 订阅成功的主题列表
+     * @throws MqttException MQTT异常
+     */
+    public List<String> subscribe(List<String> topics) throws MqttException {
+        if (!isConnected()) {
+            throw new MqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED);
+        }
+
+        String[] topicArray = topics.toArray(new String[0]);
+        int[] qosArray = new int[topics.size()];
+
+        // 默认使用相同的QoS级别
+        Arrays.fill(qosArray, mqttProperties.getDefaultQos());
+
+        mqttClient.subscribe(topicArray, qosArray).waitForCompletion(mqttProperties.getCommandTimeout() * 1000L);
+        log.info("已批量订阅主题: {}", topics);
+        return topics;
+    }
+
+    /**
+     * 取消订阅主题
+     *
+     * @param topic 要取消订阅的主题
+     * @throws MqttException MQTT异常
+     */
+    public void unsubscribe(String topic) throws MqttException {
+        if (!isConnected()) {
+            throw new MqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED);
+        }
+
+        mqttClient.unsubscribe(topic).waitForCompletion(mqttProperties.getCommandTimeout() * 1000L);
+        log.info("已取消订阅主题: {}", topic);
+    }
+
+    /**
+     * 批量取消订阅主题
+     *
+     * @param topics 要取消订阅的主题列表
+     * @throws MqttException MQTT异常
+     */
+    public void unsubscribe(List<String> topics) throws MqttException {
+        if (!isConnected()) {
+            throw new MqttException(MqttException.REASON_CODE_CLIENT_NOT_CONNECTED);
+        }
+
+        String[] topicArray = topics.toArray(new String[0]);
+        mqttClient.unsubscribe(topicArray).waitForCompletion(mqttProperties.getCommandTimeout() * 1000L);
+        log.info("已批量取消订阅主题: {}", topics);
+    }
+}

+ 37 - 4
ruoyi-system/src/main/resources/mapper/manage/ChannelNumberMapper.xml

@@ -6,8 +6,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     
     <resultMap type="ChannelNumber" id="ChannelNumberResult">
         <result property="channelId"    column="channel_id"    />
+        <result property="equipmentId"    column="equipment_id"    />
+        <result property="equipmentNum"    column="equipment_num"    />
+        <result property="equipmentName"    column="equipment_name"    />
         <result property="channelNum"    column="channel_num"    />
+        <result property="port"    column="port"    />
         <result property="videoAddress"    column="video_address"    />
+        <result property="account"    column="account"    />
+        <result property="password"    column="password"    />
         <result property="protocolType"    column="protocol_type"    />
         <result property="channelDetails"    column="channel_details"    />
         <result property="delFlag"    column="del_flag"    />
@@ -19,12 +25,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     </resultMap>
 
     <sql id="selectChannelNumberVo">
-        select channel_id, channel_num, video_address, protocol_type, channel_details, del_flag, create_by, create_time, update_by, update_time, remark from channel_number
+        select channel_id, channel_num,equipment_id, equipment_num, equipment_name, video_address,port, account,password,protocol_type, channel_details, del_flag, create_by, create_time, update_by, update_time, remark from channel_number
     </sql>
 
     <select id="selectChannelNumberList" parameterType="ChannelNumber" resultMap="ChannelNumberResult">
         <include refid="selectChannelNumberVo"/>
-        <where>  
+        <where>
+            <if test="equipmentNum != null  and equipmentNum != ''"> and equipment_num  like concat('%', #{equipmentNum}, '%')</if>
+            <if test="equipmentName != null  and equipmentName != ''"> and equipment_name like concat('%', #{equipmentName}, '%')</if>
             <if test="channelNum != null  and channelNum != ''"> and channel_num = #{channelNum}</if>
             <if test="videoAddress != null  and videoAddress != ''"> and video_address = #{videoAddress}</if>
             <if test="protocolType != null  and protocolType != ''"> and protocol_type = #{protocolType}</if>
@@ -32,16 +40,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         </where>
     </select>
     
-    <select id="selectChannelNumberByChannelId" parameterType="Long" resultMap="ChannelNumberResult">
+    <select id="selectChannelNumberByChannelId" parameterType="ChannelNumber" resultMap="ChannelNumberResult">
         <include refid="selectChannelNumberVo"/>
         where channel_id = #{channelId}
     </select>
-
+    <select id="selectChannelNumberByChannelNum" parameterType="string" resultMap="ChannelNumberResult">
+        <include refid="selectChannelNumberVo"/>
+        <where>
+            <if test="equipmentId != null "> and equipment_id  =#{equipmentId}</if>
+            <if test="channelNum != null  and channelNum != ''"> and channel_num = #{channelNum}</if>
+        </where>
+        limit 1
+    </select>
     <insert id="insertChannelNumber" parameterType="ChannelNumber" useGeneratedKeys="true" keyProperty="channelId">
         insert into channel_number
         <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="equipmentId != null">equipment_id,</if>
+            <if test="equipmentNum != null">equipment_num,</if>
+            <if test="equipmentName != null">equipment_name,</if>
             <if test="channelNum != null">channel_num,</if>
             <if test="videoAddress != null">video_address,</if>
+            <if test="account != null">account,</if>
+            <if test="port != null">port,</if>
+            <if test="password != null">password,</if>
             <if test="protocolType != null">protocol_type,</if>
             <if test="channelDetails != null">channel_details,</if>
             <if test="delFlag != null">del_flag,</if>
@@ -52,8 +73,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="remark != null">remark,</if>
          </trim>
         <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="equipmentId != null">#{equipmentId},</if>
+            <if test="equipmentNum != null">#{equipmentNum},</if>
+            <if test="equipmentName != null">#{equipmentName},</if>
             <if test="channelNum != null">#{channelNum},</if>
             <if test="videoAddress != null">#{videoAddress},</if>
+            <if test="account != null">#{account},</if>
+            <if test="port != null">#{port},</if>
+            <if test="password != null">#{password},</if>
             <if test="protocolType != null">#{protocolType},</if>
             <if test="channelDetails != null">#{channelDetails},</if>
             <if test="delFlag != null">#{delFlag},</if>
@@ -68,8 +95,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <update id="updateChannelNumber" parameterType="ChannelNumber">
         update channel_number
         <trim prefix="SET" suffixOverrides=",">
+            <if test="equipmentId != null">equipment_id = #{equipmentId},</if>
+            <if test="equipmentNum != null">equipment_num = #{equipmentNum},</if>
+            <if test="equipmentName != null">equipment_name = #{equipmentName},</if>
             <if test="channelNum != null">channel_num = #{channelNum},</if>
             <if test="videoAddress != null">video_address = #{videoAddress},</if>
+            <if test="account != null">account = #{account},</if>
+            <if test="port != null">port = #{port},</if>
+            <if test="password != null">password = #{password},</if>
             <if test="protocolType != null">protocol_type = #{protocolType},</if>
             <if test="channelDetails != null">channel_details = #{channelDetails},</if>
             <if test="delFlag != null">del_flag = #{delFlag},</if>

+ 8 - 2
ruoyi-system/src/main/resources/mapper/manage/EquipmentManageMapper.xml

@@ -29,7 +29,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
     <select id="selectEquipmentManageList" parameterType="EquipmentManage" resultMap="EquipmentManageResult">
         <include refid="selectEquipmentManageVo"/>
         <where>  
-            <if test="equipmentNum != null  and equipmentNum != ''"> and equipment_num = #{equipmentNum}</if>
+            <if test="equipmentNum != null  and equipmentNum != ''"> and equipment_num  like concat('%', #{equipmentNum}, '%')</if>
             <if test="equipmentName != null  and equipmentName != ''"> and equipment_name like concat('%', #{equipmentName}, '%')</if>
             <if test="equipmentType != null  and equipmentType != ''"> and equipment_type = #{equipmentType}</if>
             <if test="equipmentAddress != null  and equipmentAddress != ''"> and equipment_address = #{equipmentAddress}</if>
@@ -39,7 +39,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
             <if test="runState != null  and runState != ''"> and run_state = #{runState}</if>
         </where>
     </select>
-    
+
+    <select id="selectEquipmentManageByEquipmentNum" parameterType="EquipmentManage" resultMap="EquipmentManageResult">
+        <include refid="selectEquipmentManageVo"/>
+        <where>
+            <if test="equipmentNum != null  and equipmentNum != ''">and equipment_num = #{equipmentNum}</if>
+        </where>
+    </select>
     <select id="selectEquipmentManageByEquipmentId" parameterType="Long" resultMap="EquipmentManageResult">
         <include refid="selectEquipmentManageVo"/>
         where equipment_id = #{equipmentId}

+ 128 - 0
ruoyi-system/src/main/resources/mapper/manage/ParameterSetMapper.xml

@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.manage.mapper.ParameterSetMapper">
+    
+    <resultMap type="ParameterSet" id="ParameterSetResult">
+        <result property="parameterId"    column="parameter_id"    />
+        <result property="channelId"    column="channel_id"    />
+        <result property="equipmentId"    column="equipment_id"    />
+        <result property="equipmentNum"    column="equipment_num"    />
+        <result property="equipmentName"    column="equipment_name"    />
+        <result property="channelNum"    column="channel_num"    />
+        <result property="channelRange"    column="channel_range"    />
+        <result property="leaveTime"    column="leave_time"    />
+        <result property="playTime"    column="play_time"    />
+        <result property="leaveRate"    column="leave_rate"    />
+        <result property="playRate"    column="play_rate"    />
+        <result property="delFlag"    column="del_flag"    />
+        <result property="createBy"    column="create_by"    />
+        <result property="createTime"    column="create_time"    />
+        <result property="updateBy"    column="update_by"    />
+        <result property="updateTime"    column="update_time"    />
+        <result property="remark"    column="remark"    />
+    </resultMap>
+
+    <sql id="selectParameterSetVo">
+        select parameter_id, channel_id, equipment_id, equipment_num, equipment_name, channel_num, channel_range, leave_time, play_time, leave_rate, play_rate, del_flag, create_by, create_time, update_by, update_time, remark from parameter_set
+    </sql>
+
+    <select id="selectParameterSetList" parameterType="ParameterSet" resultMap="ParameterSetResult">
+        <include refid="selectParameterSetVo"/>
+        <where>  
+            <if test="channelId != null "> and channel_id = #{channelId}</if>
+            <if test="equipmentId != null "> and equipment_id = #{equipmentId}</if>
+            <if test="equipmentNum != null  and equipmentNum != ''"> and equipment_num = #{equipmentNum}</if>
+            <if test="equipmentName != null  and equipmentName != ''"> and equipment_name like concat('%', #{equipmentName}, '%')</if>
+            <if test="channelNum != null  and channelNum != ''"> and channel_num = #{channelNum}</if>
+            <if test="channelRange != null  and channelRange != ''"> and channel_range = #{channelRange}</if>
+            <if test="leaveTime != null  and leaveTime != ''"> and leave_time = #{leaveTime}</if>
+            <if test="playTime != null  and playTime != ''"> and play_time = #{playTime}</if>
+            <if test="leaveRate != null  and leaveRate != ''"> and leave_rate = #{leaveRate}</if>
+            <if test="playRate != null  and playRate != ''"> and play_rate = #{playRate}</if>
+        </where>
+    </select>
+    
+    <select id="selectParameterSetByParameterId" parameterType="Long" resultMap="ParameterSetResult">
+        <include refid="selectParameterSetVo"/>
+        where parameter_id = #{parameterId}
+    </select>
+    <select id="selectParameterSetByChannelId" parameterType="Long" resultMap="ParameterSetResult">
+        <include refid="selectParameterSetVo"/>
+        where channel_id = #{channelId}
+    </select>
+    <insert id="insertParameterSet" parameterType="ParameterSet" useGeneratedKeys="true" keyProperty="parameterId">
+        insert into parameter_set
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="channelId != null">channel_id,</if>
+            <if test="equipmentId != null">equipment_id,</if>
+            <if test="equipmentNum != null">equipment_num,</if>
+            <if test="equipmentName != null">equipment_name,</if>
+            <if test="channelNum != null">channel_num,</if>
+            <if test="channelRange != null">channel_range,</if>
+            <if test="leaveTime != null">leave_time,</if>
+            <if test="playTime != null">play_time,</if>
+            <if test="leaveRate != null">leave_rate,</if>
+            <if test="playRate != null">play_rate,</if>
+            <if test="delFlag != null">del_flag,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="updateBy != null">update_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="remark != null">remark,</if>
+         </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="channelId != null">#{channelId},</if>
+            <if test="equipmentId != null">#{equipmentId},</if>
+            <if test="equipmentNum != null">#{equipmentNum},</if>
+            <if test="equipmentName != null">#{equipmentName},</if>
+            <if test="channelNum != null">#{channelNum},</if>
+            <if test="channelRange != null">#{channelRange},</if>
+            <if test="leaveTime != null">#{leaveTime},</if>
+            <if test="playTime != null">#{playTime},</if>
+            <if test="leaveRate != null">#{leaveRate},</if>
+            <if test="playRate != null">#{playRate},</if>
+            <if test="delFlag != null">#{delFlag},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="remark != null">#{remark},</if>
+         </trim>
+    </insert>
+
+    <update id="updateParameterSet" parameterType="ParameterSet">
+        update parameter_set
+        <trim prefix="SET" suffixOverrides=",">
+            <if test="channelId != null">channel_id = #{channelId},</if>
+            <if test="equipmentId != null">equipment_id = #{equipmentId},</if>
+            <if test="equipmentNum != null">equipment_num = #{equipmentNum},</if>
+            <if test="equipmentName != null">equipment_name = #{equipmentName},</if>
+            <if test="channelNum != null">channel_num = #{channelNum},</if>
+            <if test="channelRange != null">channel_range = #{channelRange},</if>
+            <if test="leaveTime != null">leave_time = #{leaveTime},</if>
+            <if test="playTime != null">play_time = #{playTime},</if>
+            <if test="leaveRate != null">leave_rate = #{leaveRate},</if>
+            <if test="playRate != null">play_rate = #{playRate},</if>
+            <if test="delFlag != null">del_flag = #{delFlag},</if>
+            <if test="createBy != null">create_by = #{createBy},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+            <if test="updateBy != null">update_by = #{updateBy},</if>
+            <if test="updateTime != null">update_time = #{updateTime},</if>
+            <if test="remark != null">remark = #{remark},</if>
+        </trim>
+        where parameter_id = #{parameterId}
+    </update>
+
+    <delete id="deleteParameterSetByParameterId" parameterType="Long">
+        delete from parameter_set where parameter_id = #{parameterId}
+    </delete>
+
+    <delete id="deleteParameterSetByParameterIds" parameterType="String">
+        delete from parameter_set where parameter_id in 
+        <foreach item="parameterId" collection="array" open="(" separator="," close=")">
+            #{parameterId}
+        </foreach>
+    </delete>
+</mapper>

BIN
ruoyi-ui/src/assets/images/search/bficoa.png


BIN
ruoyi-ui/src/assets/images/search/bficob.png


BIN
ruoyi-ui/src/assets/images/search/bficoc.png


BIN
ruoyi-ui/src/assets/images/search/cir.png


BIN
ruoyi-ui/src/assets/images/search/pic_jsjg_slt_sxt.png


+ 391 - 0
ruoyi-ui/src/components/jiansuo/VideoPlayer.vue

@@ -0,0 +1,391 @@
+<template>
+  <div class="video-player-container">
+    <div class="video-container" ref="videoContainer">
+      <video
+        ref="videoPlayer"
+        class="video-js vjs-big-play-centered"
+        preload="auto"
+        crossOrigin="anonymous"
+      ></video>
+      <!-- :poster="poster" -->
+
+      <!-- 自定义控件 -->
+      <div class="lcfbox">
+        <div class="lcbox">
+          <div class="tit">下一次可能出现的设备:</div>
+          <div class="txt">1#旁 2号摄像头</div>
+        </div>
+        <div class="flexcj">
+          <div class="bigbox flexc">
+            <img src="@/assets/images/search/biga.png" @click="zoomOut" style="margin-left: 0;"/>
+            <div class="custom-dot-steps">
+                <div
+                  v-for="i in 5"
+                  :key="i"
+                  class="step-dot"
+                  :class="{ 'active': i == stepsactive}"
+                ></div>
+              </div>
+            <img src="@/assets/images/search/bigb.png" @click="zoomIn"/>
+          </div>
+          <div class="bigbox flexc" style="padding-left: 7px;">
+            <el-select v-model="playbackRate" @change="changeSpeed"  placeholder="1.0x" popper-class="slectbox">
+                <el-option
+                  v-for="item in speeds"
+                  :key="item.value"
+                  :label="item.text"
+                  :value="item.value">
+                </el-option>
+            </el-select>
+            <img src="@/assets/images/search/bficoa.png" @click="takeScreenshot" />
+            <img src="@/assets/images/search/bficob.png" @click="replay"/>
+            <img src="@/assets/images/search/bficoc.png" @click="toggleFullscreen"/>
+
+          </div>
+        </div>
+        <!-- 进度条 -->
+        <div class="vjs-progress-container" @click="seek">
+          <div class="vjs-progress-bar">
+            <div class="vjs-progress-filled" :style="{ width: progressPercentage + '%' }">
+               <img src="@/assets/images/search/cir.png" v-if="progressPercentage"/>
+            </div>
+          </div>
+        </div>
+        <div class="playbox">
+          <div class="pltit">
+            <div class="tit">10:32:18</div>
+            <div class="txt">2025.06.13</div>
+          </div>
+          <div class="playbtn flexcc flex1">
+            <img src="@/assets/images/search/pre.png"/>
+            <div @click="paly=!paly" class="playb">
+              <img  src="@/assets/images/search/videob.png" @click="handlePlay"  v-if="paly"/>
+              <img  src="@/assets/images/search/videoa.png" @click="handlePause" v-else/>
+            </div>
+            <img src="@/assets/images/search/next.png"/>
+          </div>
+          <div class="pltit txr">
+            <div class="tit">10:47:56</div>
+            <div class="txt">2025.06.13</div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import videojs from 'video.js'
+import 'video.js/dist/video-js.css'
+import html2canvas from 'html2canvas'
+
+export default {
+  name: 'VideoPlayer',
+  props: {
+    src: {
+      type: String,
+      required: true
+    },
+    poster: {
+      type: String,
+      default: ''
+    },
+    options: {
+      type: Object,
+      default: () => ({})
+    }
+  },
+  data() {
+    return {
+      player: null,
+      paly:false,
+      playerLoading: true,
+      zoomLevel: 1,
+      minZoom: 0.5,
+      maxZoom: 1.5,
+      zoomStep: 0.25,
+      playbackRate: 1,
+      stepsactive:3,
+      speeds: [
+        { value: 0.5, text: '0.5x' },
+        { value: 0.75, text: '0.75x' },
+        { value: 1, text: '1.0x' },
+        { value: 1.25, text: '1.25x' },
+        { value: 1.5, text: '1.5x' },
+        { value: 2, text: '2.0x' }
+      ],
+      progressPercentage: 0
+    }
+  },
+  computed: {
+    zoomPercentage() {
+      return Math.round(this.zoomLevel * 100)
+    }
+  },
+  mounted() {
+    this.initPlayer()
+  },
+  beforeDestroy() {
+    if (this.player) {
+      this.player.dispose()
+    }
+  },
+  methods: {
+    togglePlay() {
+      if (this.player.paused()) {
+        this.player.play()
+      } else {
+        this.player.pause()
+      }
+    },
+    handlePlay(){
+      this.player.pause();   // 会触发videoPlay()函数
+    },
+    handlePause(){
+      this.player.play();   // 会触发videoPlay()函数
+    },
+    initPlayer() {
+      const videoElement = this.$refs.videoPlayer
+
+      // 合并默认选项和传入的选项
+      const defaultOptions = {
+        controls: false,
+        autoplay: false,
+        fluid: true,
+        sources: [{
+          src: this.src,
+          type: 'video/mp4'
+        }]
+      }
+
+      const finalOptions = Object.assign({}, defaultOptions, this.options)
+
+      // 初始化播放器
+      this.player = videojs(videoElement, finalOptions, () => {
+        this.playerLoading = false
+
+        // 监听时间更新事件来更新进度条
+        this.player.on('timeupdate', this.updateProgress)
+
+        // 监听全屏变化
+        this.player.on('fullscreenchange', () => {
+          if (!this.player.isFullscreen()) {
+            this.resetZoom()
+          }
+        })
+      })
+    },
+
+    updateProgress() {
+      if (this.player.duration()) {
+        this.progressPercentage = (this.player.currentTime() / this.player.duration()) * 100
+      }
+    },
+
+    seek(event) {
+      if (!this.player) return
+
+      const progressBar = event.currentTarget
+      const percent = event.offsetX / progressBar.offsetWidth
+      this.player.currentTime(this.player.duration() * percent)
+    },
+
+    replay() {
+      this.player.currentTime(0)
+      this.player.play()
+    },
+
+    zoomIn() {
+      if (this.zoomLevel < this.maxZoom) {
+        this.zoomLevel += this.zoomStep
+        this.stepsactive++
+        this.applyZoom()
+      }
+    },
+
+    zoomOut() {
+      if (this.zoomLevel > this.minZoom) {
+        this.zoomLevel -= this.zoomStep
+        this.stepsactive--
+        this.applyZoom()
+      }
+    },
+
+    applyZoom() {
+      const videoElement = this.$refs.videoPlayer
+      if (videoElement) {
+        videoElement.style.transform = `scale(${this.zoomLevel})`
+        videoElement.style.transformOrigin = 'center center'
+      }
+    },
+
+    resetZoom() {
+      this.zoomLevel = 1
+      const videoElement = this.$refs.videoPlayer
+      if (videoElement) {
+        videoElement.style.transform = 'none'
+      }
+    },
+
+    changeSpeed() {
+      this.player.playbackRate(this.playbackRate)
+    },
+    takeScreenshot() {
+      const videoElement = this.$refs.videoPlayer
+      // videoElement.setAttribute("crossOrigin", "anonymous"); // 处理跨域
+      // 		video.setAttribute("src", url);
+      // 		// 静音操作,防止播放失败
+      // 		video.setAttribute("muted", "muted");
+      // 创建一个canvas来绘制视频帧
+      const canvas = document.createElement('canvas')
+      canvas.width = videoElement.videoWidth
+      canvas.height = videoElement.videoHeight
+      const ctx = canvas.getContext('2d')
+      ctx.drawImage(videoElement, 0, 0, canvas.width, canvas.height)
+      // 创建下载链接
+      const link = document.createElement('a')
+      link.download = `screenshot-${new Date().getTime()}.png`
+      link.href = canvas.toDataURL('image/png')
+      document.body.appendChild(link)
+      link.click()
+      document.body.removeChild(link)
+
+      // 或者使用html2canvas捕获整个播放器
+      // html2canvas(this.$refs.videoContainer).then(canvas => {
+      //   const link = document.createElement('a')
+      //   link.download = `screenshot-${new Date().getTime()}.png`
+      //   link.href = canvas.toDataURL('image/png')
+      //   link.click()
+      // })
+    },
+
+    toggleFullscreen() {
+      if (this.player.isFullscreen()) {
+        this.player.exitFullscreen()
+      } else {
+        this.player.requestFullscreen()
+      }
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.flex{display: flex;}
+.flex0{flex: 0 0 auto;}
+.flex1{flex: 1;}
+.flexc{display: flex;align-items: center;}
+.flexcc{display: flex;align-items: center;justify-content: center;}
+.flexcj{display: flex;justify-content: space-between;}
+.lcfbox{position: absolute;left: 17px;right: 17px;bottom: 15px;
+      .lcbox{background: rgba(0, 0, 0, 0.5);border-radius: 10px;padding: 10px 12px;display: inline-block;margin-bottom: 13px;
+        .tit{font-size: 14px;color: #FFFFFF;margin-bottom: 6px;}
+        .txt{font-weight: bold;font-size: 14px;color: #FFFFFF;}
+      }
+      .bigbox{background: rgba(0, 0, 0, 0.5);border-radius: 10px;padding: 6px 19px;min-height: 30px;margin-bottom: 12px;
+        img{width: 17px;height: 17px;margin-left: 11px;}
+      }
+      .playbox{min-height: 60px;padding: 9px 18px 9px 28px;
+        background: rgba(255, 255, 255, 0.8);display: flex;align-items: center;
+        .pltit{
+          .tit{font-size: 18px;color: #3D455B;margin-bottom: 3px;}
+          .txt{font-size: 12px;color: #666666;}
+        }
+        .playbtn{
+          .pre{width: 18px;height: 20px;}
+          .playb{width: 36px;height: 36px;margin: 0 29px;
+            img{width: 100%;height: 100%;}
+          }
+        }
+      }
+    }
+.custom-dot-steps {margin-left: 13px;width: 82px;position: relative;display: flex;align-items: center;justify-content: space-between;}
+
+.custom-dot-steps::before {content: '';position: absolute;top: 50%;left: 0;right: 0;height: 2px;background: #FFFFFF;transform: translateY(-50%);z-index: 1;}
+
+.step-dot {width: 4px;height: 4px;border-radius: 50%;background: #FFFFFF;position: relative;
+  &.active{width: 8px;height: 8px;}
+}
+.video-player-container {
+  position: relative;
+  width: 100%;height: 100%;
+  /* max-width: 800px;
+  margin: 0 auto; */
+}
+
+.video-container {
+  width: 100%;height: 100%;
+  position: relative;
+  overflow: hidden;
+}
+
+.video-js {
+  width: 100%;
+  height: 100% !important;
+  padding-top: 50% !important;
+  box-sizing: border-box;
+  background-color: #000;
+  transition: transform 0.3s ease;
+}
+
+.vjs-custom-controls {
+  display: flex;
+  align-items: center;
+  padding: 0 10px;
+  background: rgba(0, 0, 0, 0.7);
+  position: absolute;
+  bottom: 150px;
+  left: 0;
+  right: 0;
+  z-index: 1;
+}
+
+.vjs-custom-button {
+  background: none;
+  border: none;
+  color: white;
+  cursor: pointer;
+  font-size: 16px;
+  margin: 0 5px;
+  padding: 5px 10px;
+}
+
+.vjs-custom-button:hover {
+  color: #00a8ff;
+}
+
+.vjs-progress-container {
+  flex-grow: 1;
+  margin-bottom:14px;
+  cursor: pointer;
+}
+
+.vjs-progress-bar {
+  height: 8px;
+  border-radius:4px;
+  background: rgba(0, 0, 0, 0.5);
+  position: relative;
+}
+
+.vjs-progress-filled {
+  height: 100%;border-radius: 4px;
+  background: red;position: relative;
+  img{width: 24px;height: 24px;position: absolute;right: -6px;top: -8px;}
+}
+
+.vjs-speed-selector {
+  background: rgba(0, 0, 0, 0.8);
+  color: white;
+  border: none;
+  padding: 5px;
+  margin: 0 5px;
+  cursor: pointer;
+}
+
+.vjs-zoom-level {
+  color: white;
+  margin: 0 5px;
+  min-width: 40px;
+  text-align: center;
+}
+</style>

+ 4 - 2
ruoyi-ui/src/components/jiansuo/index.vue

@@ -8,7 +8,7 @@
               <div slot="dot" class="sidx" :class="index==list.length-1?'act':''">
                      {{Number(index)+1}}
               </div>
-              <div class="trlist flexc">
+              <div class="trlist flexc" @click="getDetail(activity)">
                 <img src="@/assets/images/profile.jpg" class="limg flex0"/>
                 <div class="flex1 trlbox">
                   <div class="flexc">
@@ -57,7 +57,9 @@ export default {
     // this.initConnect()
   },
   methods: {
-
+      getDetail(ite){
+        this.$emit('getDetail',ite)
+      }
   },
 };
 </script>

+ 32 - 61
ruoyi-ui/src/views/shipinggaoj/jiansuo/detail.vue

@@ -106,43 +106,27 @@
       </el-col>
       <el-col :span="18" class="jsright">
         <div class="jsbox flex1">
-          <!-- <div class="videos"></div> -->
-          <video ref="videoPlayer"
-    id="videoElement" :src="videourl" class="videos"></video>
-          <!--  -->
+          <div style="height: 100%;width: 100%;">
+            <!-- poster="your-poster.jpg" -->
+           <VideoPlayer
+                  :src="videourl"
+                  :options="{
+                    autoplay: false,
+                    controls: false,
+                    fluid: true
+                  }"
+            />
+          </div>
           <div class="lttit left">4# 中间道路西南侧</div>
           <div class="lttit right">2025-06-13  10:33:24</div>
-          <div class="lcfbox">
-            <div class="lcbox">
-              <div class="tit">下一次可能出现的设备:</div>
-              <div class="txt">1#旁 2号摄像头</div>
-            </div>
-            <div class="playbox">
-              <div class="pltit">
-                <div class="tit">10:32:18</div>
-                <div class="txt">2025.06.13</div>
-              </div>
-              <div class="playbtn flexcc flex1">
-                <img src="../../../assets/images/search/pre.png"/>
-                <div @click="paly=!paly" class="playb">
-                  <img  src="../../../assets/images/search/videob.png" @click="handlePlay"  v-if="paly"/>
-                  <img  src="../../../assets/images/search/videoa.png" @click="handlePause" v-else/>
-                </div>
-                <img src="../../../assets/images/search/next.png"/>
-              </div>
-              <div class="pltit txr">
-                <div class="tit">10:47:56</div>
-                <div class="txt">2025.06.13</div>
-              </div>
-            </div>
-          </div>
+
 
         </div>
         <div class="jsswbox flex0">
-          <el-carousel indicator-position="outside" height="140px" :autoplay="false" arrow="always">
+          <el-carousel indicator-position="outside" height="120px" :autoplay="false" arrow="always">
             <el-carousel-item v-for="item in 2" :key="item">
               <div class="flexc swiper">
-                <div class="imgs act" v-for="ite in 5" :key="ite">
+                <div class="imgs" @click="getChange(ite)" :class="playidx==ite?'act':''" v-for="ite in 5" :key="ite">
                   <img src="../../../assets/images/pic_gjgl_lt.png" />
                   <div class="tit over">相似度 94%</div>
                 </div>
@@ -158,31 +142,29 @@
 </template>
 
 <script>
-  import trackBox from "@/components/jiansuo"
+  import trackBox from "@/components/jiansuo/index"
+  import VideoPlayer from "@/components/jiansuo/VideoPlayer"
   export default {
     components: {
-      trackBox
+      trackBox,VideoPlayer
     },
     data() {
       return {
         loading: false,
         videourl:'https://qiniu-web-assets.dcloud.net.cn/unidoc/zh/2minute-demo.mp4',
         paly:false,
+        playidx:1,
         list: [{
           tit: '123'
         }, {
           tit: '123'
-        }, ]
+        }, ],
       }
     },
     methods: {
-
-      handlePlay(){
-        this.$refs.videoPlayer.pause();   // 会触发videoPlay()函数
-      },
-      handlePause(){
-        this.$refs.videoPlayer.play();   // 会触发videoPlay()函数
-      },
+      getChange(idx){
+        this.playidx=idx
+      }
     }
   }
 </script>
@@ -197,6 +179,12 @@
   .el-carousel__arrow--left{left:5px}
   .el-icon-arrow-right:before{font-weight: bold;}
   .el-icon-arrow-left:before{font-weight: bold;}
+  .bigbox {
+    .el-select{
+      .el-input__suffix{display: none;}
+      input{font-weight: 500;background: transparent;font-size: 14px;color: #FFFFFF;width: 40px;padding-right: 0;text-align: right;height: 18px;line-height: 18px;}
+      }
+  }
 }
 p{margin: 0;padding: 0;}
 .mb12{margin-bottom: 12px;}
@@ -206,6 +194,7 @@ p{margin: 0;padding: 0;}
 .flexcc{display: flex;align-items: center;justify-content: center;}
 .flexcdc{display: flex;align-items: center;flex-direction: column;}
 .flexcw{display: flex;align-items: center;flex-wrap: wrap;}
+.flexcj{display: flex;justify-content: space-between;}
 .flex0{flex: 0 0 auto;}
 .flex1{flex: 1;}
 .f16{font-size: 16px;}
@@ -243,35 +232,17 @@ p{margin: 0;padding: 0;}
   }
 }
 .jsright{height: 100%;display: flex;flex-direction: column;overflow: hidden;
-  .jsbox{background: #FFFFFF;border-radius: 10px;position: relative;
+  .jsbox{background: #FFFFFF;border-radius: 10px;position: relative;overflow: hidden;
     .lttit{font-weight: 500;font-size: 18px;color: #FFFFFF;position: absolute;top: 10px;line-height: 24px;
       &.left{left: 24px;}
       &.right{right: 24px;}
     }
 
-    .lcfbox{position: absolute;left: 17px;right: 17px;bottom: 15px;
-      .lcbox{background: rgba(0, 0, 0, 0.5);border-radius: 10px;padding: 10px 12px;display: inline-block;margin-bottom: 13px;
-        .tit{font-size: 14px;color: #FFFFFF;margin-bottom: 6px;}
-        .txt{font-weight: bold;font-size: 14px;color: #FFFFFF;}
-      }
-      .playbox{min-height: 60px;padding: 9px 18px 9px 28px;
-        background: rgba(255, 255, 255, 0.8);display: flex;align-items: center;
-        .pltit{
-          .tit{font-size: 18px;color: #3D455B;margin-bottom: 3px;}
-          .txt{font-size: 12px;color: #666666;}
-        }
-        .playbtn{
-          .pre{width: 18px;height: 20px;}
-          .playb{width: 36px;height: 36px;margin: 0 29px;
-            img{width: 100%;height: 100%;}
-          }
-        }
-      }
-    }
+
 
   }
     .jsswbox{background: #FFFFFF;margin-top: 10px;
-border-radius: 10px;height: 140px;padding: 14px 0px;box-sizing: border-box;}
+border-radius: 10px;height: 146px;padding: 12px 0px;box-sizing: border-box;}
     .swiper{padding: 0 44px;}
     .imgs{position: relative;width: 20%;height: 120px;border-radius: 8px;overflow: hidden;margin-right: 12px;
     &:nth-of-type(5n){margin-right: 0;}

+ 40 - 24
ruoyi-ui/src/views/shipinggaoj/jiansuo/index.vue

@@ -62,7 +62,7 @@
            </div>
          </div>
          <!-- 有数据 轨迹-->
-         <trackBox :list="list" :reverse="reverse"></trackBox>
+         <trackBox :list="list" :reverse="reverse" @getDetail="getDetail"></trackBox>
        </div>
       </el-col>
         <el-col :span="18" class="jsright">
@@ -76,7 +76,8 @@
                   <div class="flexc swiper">
                     <div class="imgs act">
                       <img src="../../../assets/images/pic_gjgl_lt.png"/>
-                      <div class="tit over">园区鸟瞰</div>
+                      <!-- <div class="tit over">园区鸟瞰</div> -->
+                      <div class="tit over">1号轨迹</div>
                     </div>
                     <div class="zhantit">到底啦</div>
                   </div>
@@ -109,15 +110,20 @@
         //标记点的位置信息
               mapData: [
                 {
-                  longitude: '117.211860',
-                  latitude: '31.839696',
-                  type:1
+                  longitude: '117.211954',
+                  latitude: '31.839676',
+                  type:1,
                 },
                 {
-                  longitude: '117.210150',
-                  latitude: '31.839850',
+                  longitude: '117.211021',
+                  latitude: '31.839374',
                   type:2
-                }
+                },
+                {
+                  longitude: '117.211649',
+                  latitude: '31.842398',
+                  type:3
+                },
               ],
       }
     },
@@ -125,6 +131,14 @@
       this.initMap()
     },
     methods:{
+      getDetail(item){
+        this.$router.push({
+          path: '/jiansuodetail',
+          query: {
+          	'id':2,
+          }
+        })
+      },
       initMap() {
       		AMapLoader.reset()
             AMapLoader.load({
@@ -141,8 +155,8 @@
                 // center: [116.310791, 40.003419], //初始化地图中心点位置
                 pitch: 40, // 地图俯仰角度,有效范围 0 度- 83 度
                 terrain: true, // 开启地形图
-
-                buildingAnimation: true,//楼块出现是否带动画
+                resizeEnable: true,
+                // buildingAnimation: true,//楼块出现是否带动画
               });
       		// this.getCenter()
               this.markPoints()
@@ -155,14 +169,14 @@
       // 途径点
       getpoints(){
         var that=this;
-        var driving = new AMap.Driving({
-          map:that.map,
-          // policy: 0, //驾车路线规划策略,0是速度优先的策略
-        });
-        // var driving = new AMap.Walking({
+        // var driving = new AMap.Driving({
         //   map:that.map,
         //   // policy: 0, //驾车路线规划策略,0是速度优先的策略
         // });
+        var driving = new AMap.Walking({
+          map:that.map,
+          // policy: 0, //驾车路线规划策略,0是速度优先的策略
+        });
         // 地址
         // var points = [
         //   { keyword: "合肥市蜀山区时代数码港", city: "合肥" }, //起始点
@@ -180,7 +194,7 @@
           var opts = {
             waypoints: [[117.208859,31.840713]], //途经点参数,最多支持传入16个途经点
           };
-          driving.search(startLngLat, endLngLat, opts, function (status, result) {
+          driving.search(startLngLat, endLngLat, function (status, result) {
             //status:complete 表示查询成功,no_data 为查询无结果,error 代表查询错误
             //查询成功时,result 即为对应的驾车导航信息
           });
@@ -188,22 +202,24 @@
       //遍历显示标记点
       markPoints() {
       	const icona = new AMap.Icon({
-      	        image: require('@/assets/images/search/gjicoa.png'), // 确保路径正确,根据实际情况调整
-      	        // size: new AMap.Size(58, 105), // 图标大小
-      	        // imageSize: new AMap.Size(58, 105) // 图标实际大小,如果图片本身大小与此不符,可以调整此属性以适应图片大小
+      	       image: require('@/assets/images/search/gjicoa.png'), // 确保路径正确,根据实际情况调整
       	});
       	const iconb = new AMap.Icon({
-      	        image: require('@/assets/images/search/gjicob.png'), // 确保路径正确,根据实际情况调整
+      	      image: require('@/assets/images/search/gjicob.png'), // 确保路径正确,根据实际情况调整
       	});
         const iconc = new AMap.Icon({
-                image: require('@/assets/images/search/gjicoc.png'), // 确保路径正确,根据实际情况调整
-      });
+              image: require('@/assets/images/search/gjicoc.png'), // 确保路径正确,根据实际情况调整
+        });
+      var xya=new AMap.Pixel(-51, -28)//103,215
+      var xyb=new AMap.Pixel(-51, -190)//105,217
+      var xyc=new AMap.Pixel(-188, -54)//216,104
         this.mapData.forEach(item => {
           // 创建一个 Marker 实例:
           const marker = new AMap.Marker({
             position: new AMap.LngLat(item.longitude, item.latitude),   // 经纬度对象,也可以是经纬度构成的一维数组[lng, lat]
             icon: item.type == 1 ? icona : item.type == 2 ? iconb : item.type == 3 ? iconc : icona,
-            offset: new AMap.Pixel(-25, -10),
+            offset: item.type == 1 ? xya : item.type == 2 ? xyb : item.type == 3 ? xyc : xya,
+
           });
           // 将创建的点标记添加到已有的地图实例:
           this.map.add(marker);
@@ -280,7 +296,7 @@ p{margin: 0;padding: 0;}
 .jsright{height: 100%;display: flex;flex-direction: column;overflow: hidden;
   .jsbox{background: #FFFFFF;border-radius: 10px;}
     .jsswbox{background: #FFFFFF;margin-top: 10px;
-border-radius: 10px;height: 140px;padding: 14px 0px;box-sizing: border-box;}
+border-radius: 10px;height: 146px;padding: 12px 0px;box-sizing: border-box;}
     .swiper{padding: 0 44px;}
     .imgs{position: relative;width: 20%;height: 120px;border-radius: 8px;overflow: hidden;
       &.act{border: 2px solid #00B278;}