Pārlūkot izejas kodu

Merge branch 'master' of http://47.111.81.118:3000/uskycloud/usky-modules into fanghuisheng

fanghuisheng 2 dienas atpakaļ
vecāks
revīzija
fe23e1b11e
100 mainītis faili ar 4974 papildinājumiem un 336 dzēšanām
  1. 19 10
      service-ai/service-ai-biz/pom.xml
  2. 17 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/AITestApplication.java
  3. 3 6
      service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/AiChatController.java
  4. 1 1
      service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/AiQuestionController.java
  5. 3 3
      service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/AiSessionController.java
  6. 32 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/BoardPingController.java
  7. 69 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/DeviceController.java
  8. 128 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/EdgeAlgController.java
  9. 62 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/EdgeMediaController.java
  10. 59 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/RepositoryController.java
  11. 27 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/TaskPreviewController.java
  12. 158 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/UniversalUploadController.java
  13. 104 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/domain/AiAlarmLog.java
  14. 83 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/domain/AiDevice.java
  15. 1 1
      service-ai/service-ai-biz/src/main/java/com/usky/ai/domain/AiQuestion.java
  16. 1 1
      service-ai/service-ai-biz/src/main/java/com/usky/ai/domain/AiQuestionItem.java
  17. 1 1
      service-ai/service-ai-biz/src/main/java/com/usky/ai/domain/AiSession.java
  18. 13 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgAbilityFetchRequestDTO.java
  19. 131 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgAbilityFetchResponseDTO.java
  20. 80 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgScheduleCreateRequestDTO.java
  21. 25 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgScheduleCreateResponseDTO.java
  22. 16 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgScheduleDeleteRequestDTO.java
  23. 40 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgScheduleDeleteResponseDTO.java
  24. 13 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgScheduleFetchRequestDTO.java
  25. 113 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgScheduleFetchResponseDTO.java
  26. 20 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitAppendRequestDTO.java
  27. 36 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitAppendResponseDTO.java
  28. 16 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitCreateRequestDTO.java
  29. 36 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitCreateResponseDTO.java
  30. 13 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitFetchRequestDTO.java
  31. 65 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitFetchResponseDTO.java
  32. 18 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitGroupRemoveRequestDTO.java
  33. 33 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitGroupRemoveResponseDTO.java
  34. 20 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitRemoveRequestDTO.java
  35. 33 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitRemoveResponseDTO.java
  36. 16 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgTaskSnapRequestDTO.java
  37. 43 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgTaskSnapResponseDTO.java
  38. 109 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/BoardPingDTO.java
  39. 13 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/DeleteFaceRequestDTO.java
  40. 34 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/DeleteFaceResponseDTO.java
  41. 14 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAlgTaskCommandRequestDTO.java
  42. 82 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAlgTaskConfigRequestDTO.java
  43. 15 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAlgTaskFetchRequestDTO.java
  44. 177 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAlgTaskListResponseDTO.java
  45. 34 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAlgTaskResponseDTO.java
  46. 32 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAppControllerReplyDTO.java
  47. 42 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAppControllerRequestDTO.java
  48. 16 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAppDeleteRequestDTO.java
  49. 37 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAppDeleteResponseDTO.java
  50. 116 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAppFetchResponseDTO.java
  51. 15 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/FaceRecognitionRequestDTO.java
  52. 50 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/FaceRecognitionResponseDTO.java
  53. 16 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RegisterFaceRequestDTO.java
  54. 43 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RegisterFaceResponseDTO.java
  55. 52 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoriesInfoResponseDTO.java
  56. 11 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryCreateRequestDTO.java
  57. 36 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryCreateResponseDTO.java
  58. 10 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryDeleteRequestDTO.java
  59. 33 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryDeleteResponseDTO.java
  60. 16 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryFacesRequestDTO.java
  61. 48 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryFacesResponseDTO.java
  62. 13 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryUpdateRequestDTO.java
  63. 36 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryUpdateResponseDTO.java
  64. 19 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/UpdateFaceRequestDTO.java
  65. 43 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/UpdateFaceResponseDTO.java
  66. 27 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/mapper/AiDeviceMapper.java
  67. 1 1
      service-ai/service-ai-biz/src/main/java/com/usky/ai/mapper/AiQuestionMapper.java
  68. 2 2
      service-ai/service-ai-biz/src/main/java/com/usky/ai/mapper/AiSessionMapper.java
  69. 22 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/service/AiDeviceService.java
  70. 332 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/service/EdgeMediaService.java
  71. 13 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/service/config/AppConfig.java
  72. 48 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/service/enums/TopListener.java
  73. 60 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/service/impl/AiDeviceServiceImpl.java
  74. 299 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/service/listener/BoardPingListener.java
  75. 50 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/service/mqtt/MqttBaseConfig.java
  76. 17 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/service/mqtt/MqttGateway.java
  77. 56 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/service/mqtt/MqttInConfig.java
  78. 41 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/service/mqtt/MqttOutConfig.java
  79. 34 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/service/util/GlobalExceptionHandler.java
  80. 15 0
      service-ai/service-ai-biz/src/main/java/com/usky/ai/service/util/JsonUtils.java
  81. 62 0
      service-ai/service-ai-biz/src/main/resources/mapper/AiDeviceMapper.xml
  82. 3 2
      service-ai/service-ai-biz/src/main/resources/static/tyqw.html
  83. 11 33
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/controller/AlarmDataController.java
  84. 2 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/enums/MqttTopics.java
  85. 32 147
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataTransferService.java
  86. 50 38
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseDataTransferService.java
  87. 109 10
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/CdiDeliveryLogServiceImpl.java
  88. 266 27
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java
  89. 381 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/AirDefenseSimulator.java
  90. 12 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataQuery.java
  91. 49 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/PeopleFlowData.java
  92. 6 1
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/IotDataTransferVO.java
  93. 0 2
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/alarm/AlarmMessageVO.java
  94. 49 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/GeneratorMonitoringVO.java
  95. 54 0
      service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/HeadcountVO.java
  96. 9 12
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAnalysisController.java
  97. 1 1
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsModelController.java
  98. 33 35
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsOverviewController.java
  99. 60 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsProjectController.java
  100. 58 0
      service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsReportController.java

+ 19 - 10
service-ai/service-ai-biz/pom.xml

@@ -35,6 +35,15 @@
             <artifactId>mysql-connector-java</artifactId>
             <scope>runtime</scope>
         </dependency>
+        <!--MQTT依赖-->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-integration</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.integration</groupId>
+            <artifactId>spring-integration-mqtt</artifactId>
+        </dependency>
 
         <!-- 阿里dashscope依赖 -->
         <dependency>
@@ -48,8 +57,15 @@
                 </exclusion>
             </exclusions>
         </dependency>
-
-
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-jpa</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+            <version>3.4.0</version>
+        </dependency>
     </dependencies>
 
     <build>
@@ -72,17 +88,10 @@
                 <artifactId>smart-doc-maven-plugin</artifactId>
                 <version>2.1.1</version>
                 <configuration>
-                    <!--指定生成文档的使用的配置文件,配置文件放在自己的项目中-->
                     <configFile>./src/main/resources/smart-doc.json</configFile>
-                    <!--指定项目名称-->
                     <projectName>test</projectName>
-                    <!--                    <excludes>-->
-                    <!--                        <exclude>com.bizmatics:product-service-provider</exclude>-->
-                    <!--                        <exclude>cn.afterturn:easypoi-web</exclude>-->
-                    <!--                    </excludes>-->
                 </configuration>
             </plugin>
         </plugins>
     </build>
-
-</project>
+</project>

+ 17 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/AITestApplication.java

@@ -0,0 +1,17 @@
+package com.usky.ai;
+
+import com.usky.ai.dto.AlgScheduleFetchResponseDTO;
+import java.util.List;
+
+public class AITestApplication {
+    public static void main(String[] args) {
+        String value = "100000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
+        AlgScheduleFetchResponseDTO.Schedule schedule = new AlgScheduleFetchResponseDTO.Schedule();
+        schedule.setValue(value);
+
+        List<String> timePeriods = schedule.getReadableTimePeriods();
+        for (String period : timePeriods) {
+            System.out.println(period);
+        }
+    }
+}

+ 3 - 6
service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/AiChatController.java

@@ -11,11 +11,10 @@ import com.alibaba.fastjson.JSONObject;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.usky.ai.mapper.AiQuestionMapper;
 import com.usky.ai.mapper.AiSessionMapper;
-import com.usky.ai.service.AiQuestion;
-import com.usky.ai.service.AiQuestionItem;
-import com.usky.ai.service.AiSession;
+import com.usky.ai.domain.AiQuestion;
+import com.usky.ai.domain.AiQuestionItem;
+import com.usky.ai.domain.AiSession;
 import com.usky.ai.service.vo.AiStreamOutputVO;
-import com.usky.common.core.bean.ApiResult;
 import com.usky.common.security.utils.SecurityUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -27,12 +26,10 @@ import org.springframework.web.bind.annotation.*;
 import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
 
 import javax.annotation.Resource;
-import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.time.LocalDateTime;
 import java.util.ArrayList;
 import java.util.List;
-import java.util.stream.Collectors;
 
 @Slf4j
 @RestController

+ 1 - 1
service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/AiQuestionController.java

@@ -1,7 +1,7 @@
 package com.usky.ai.controller.web;
 
 import com.usky.ai.mapper.AiQuestionMapper;
-import com.usky.ai.service.AiQuestion;
+import com.usky.ai.domain.AiQuestion;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;

+ 3 - 3
service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/AiSessionController.java

@@ -2,9 +2,9 @@ package com.usky.ai.controller.web;
 
 import com.usky.ai.mapper.AiQuestionMapper;
 import com.usky.ai.mapper.AiSessionMapper;
-import com.usky.ai.service.AiQuestion;
-import com.usky.ai.service.AiQuestionItem;
-import com.usky.ai.service.AiSession;
+import com.usky.ai.domain.AiQuestion;
+import com.usky.ai.domain.AiQuestionItem;
+import com.usky.ai.domain.AiSession;
 import com.usky.common.core.bean.ApiResult;
 import com.usky.common.security.utils.SecurityUtils;
 import lombok.extern.slf4j.Slf4j;

+ 32 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/BoardPingController.java

@@ -0,0 +1,32 @@
+package com.usky.ai.controller.web;
+
+
+import com.usky.ai.dto.BoardPingDTO;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@RestController
+@RequestMapping("/api/heartbeat")
+public class BoardPingController {
+
+    // 每个 BoardId 最新一条心跳
+    public static final Map<String, BoardPingDTO> CACHE = new ConcurrentHashMap<>();
+
+    /**
+     * 返回全部设备最新心跳
+     */
+    @GetMapping
+    public Map<String, BoardPingDTO> all() {
+        return CACHE;
+    }
+
+    /**
+     * 返回指定设备最新心跳
+     */
+    @GetMapping("/{boardId}")
+    public BoardPingDTO one(@PathVariable String boardId) {
+        return CACHE.get(boardId);
+    }
+}

+ 69 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/DeviceController.java

@@ -0,0 +1,69 @@
+package com.usky.ai.controller.web;
+
+import com.usky.ai.domain.AiDevice;
+import com.usky.ai.service.AiDeviceService;
+import com.usky.common.core.bean.ApiResult;
+import com.usky.common.core.bean.CommonPage;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/device")
+public class DeviceController {
+
+    private final AiDeviceService aiDeviceService;
+
+    @Autowired
+    public DeviceController(AiDeviceService aiDeviceService) {
+        this.aiDeviceService = aiDeviceService;
+    }
+
+    // 返回设备信息,支持分页
+    @GetMapping
+    public ApiResult<CommonPage<AiDevice>> devices(
+            @RequestParam(required = false) String boardId,
+            @RequestParam(defaultValue = "1") int pageNum,
+            @RequestParam(defaultValue = "10") int pageSize) {
+        List<AiDevice> devices = aiDeviceService.getDevices(boardId, pageNum, pageSize);
+        long total = aiDeviceService.getTotalDevices(boardId); // 获取总记录数
+        CommonPage<AiDevice> page = new CommonPage<>(devices, total, pageSize, pageNum);
+        return ApiResult.success(page);
+    }
+
+    // 新增设备
+    @PostMapping
+    public ApiResult<Void> addDevice(@RequestBody AiDevice device) {
+        try {
+            aiDeviceService.addDevice(device);
+            return ApiResult.success();
+        } catch (IllegalArgumentException e) {
+            return ApiResult.error(e.getMessage());
+        } catch (Exception e) {
+            return ApiResult.error("新增失败", e.getMessage());
+        }
+    }
+
+    // 编辑设备
+    @PutMapping
+    public ApiResult<Void> editDevice(@RequestBody AiDevice device) {
+        try {
+            aiDeviceService.editDevice(device);
+            return ApiResult.success();
+        } catch (Exception e) {
+            return ApiResult.error("编辑失败", e.getMessage());
+        }
+    }
+
+    // 删除指定设备信息
+    @DeleteMapping("/{boardId}")
+    public ApiResult<Void> delete(@PathVariable String boardId) {
+        try {
+            aiDeviceService.deleteDevice(boardId);
+            return ApiResult.success();
+        } catch (Exception e) {
+            return ApiResult.error("删除失败", e.getMessage());
+        }
+    }
+}

+ 128 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/EdgeAlgController.java

@@ -0,0 +1,128 @@
+package com.usky.ai.controller.web;
+
+import com.usky.ai.dto.*;
+import com.usky.ai.service.EdgeMediaService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/edge-alg")
+@RequiredArgsConstructor
+public class EdgeAlgController {
+
+    private final EdgeMediaService service;
+
+    //算法任务配置
+    @PostMapping("/task/config")
+    public Object configTask(@RequestBody EdgeAlgTaskConfigRequestDTO req) {
+        return service.configTask(req);
+    }
+
+    //算法任务控制
+    @PostMapping("/task/control")
+    public Object controlTask(@RequestParam String boardId,
+                              @RequestParam String session,
+                              @RequestParam Integer command) {
+        return service.controlTask(boardId, session, command);
+    }
+
+    //算法任务删除
+    @DeleteMapping("/task/{boardId}/{session}")
+    public Object deleteTask(@PathVariable String boardId,
+                             @PathVariable String session) {
+        return service.deleteTask(boardId, session);
+    }
+
+    //算法任务查询
+    @GetMapping("/tasks/{boardId}")
+    public Object fetchTasks(@PathVariable String boardId) {
+        return service.fetchTasks(boardId);
+    }
+
+    //新建计划模板
+    @PostMapping("/schedule/create")
+    public Object createSchedule(@RequestBody AlgScheduleCreateRequestDTO req) {
+        return service.createSchedule(req);
+    }
+
+    //查询计划模板
+    @GetMapping("/schedule/fetch/{boardId}")
+    public Object fetchSchedules(@PathVariable String boardId) {
+        AlgScheduleFetchResponseDTO response = service.fetchSchedules(boardId);
+        System.out.println("获取到的计划模板响应: " + response);
+
+        // 获取计划模板数据,优先从result.content获取,其次从content获取
+        List<AlgScheduleFetchResponseDTO.Schedule> schedules = new ArrayList<>();
+        if (response.isSuccess()) {
+            if (response.getResult() != null && response.getResult().getContent() != null && !response.getResult().getContent().isEmpty()) {
+                schedules = response.getResult().getContent();
+            } else if (response.getContent() != null && !response.getContent().isEmpty()) {
+                schedules = response.getContent();
+            }
+        }
+
+        // 直接返回原始响应数据,不进行解析
+        if (!schedules.isEmpty()) {
+            // 打印每个计划模板的信息用于调试
+            for (AlgScheduleFetchResponseDTO.Schedule schedule : schedules) {
+                System.out.println("计划模板: ID=" + schedule.getId() +
+                        ", Name=" + schedule.getName() +
+                        ", Value长度=" + (schedule.getValue() != null ? schedule.getValue().length() : "null") +
+                        ", Value=" + schedule.getValue());
+            }
+            return schedules;
+        } else {
+            List<String> result = new ArrayList<>();
+            result.add("No schedules found");
+            return result;
+        }
+    }
+
+    //删除计划模板
+    @DeleteMapping("/schedule/delete/{boardId}/{id}")
+    public Object deleteSchedule(@PathVariable String boardId,
+                                 @PathVariable Long id) {
+        return service.deleteSchedule(boardId, id);
+    }
+
+    //新建工装组
+    @PostMapping("/suit/create")
+    public Object createSuitGroup(@RequestBody AlgSuitCreateRequestDTO req) {
+        return service.createSuitGroup(req);
+    }
+
+    //删除工装组
+    @DeleteMapping("/suit/group/{boardId}/{sid}")
+    public Object removeSuitGroup(@PathVariable String boardId, @PathVariable int sid) {
+        AlgSuitGroupRemoveRequestDTO req = new AlgSuitGroupRemoveRequestDTO();
+        req.setBoardId(boardId);
+        req.setSid(sid);
+        return service.removeSuitGroup(req);
+    }
+
+    //上传工装模版
+    @PostMapping("/suit/append")
+    public Object appendSuitTemplate(@RequestBody AlgSuitAppendRequestDTO req) {
+        return service.appendSuitTemplate(req);
+    }
+
+    //删除工装模版
+    @DeleteMapping("/suit/remove/{boardId}/{sid}/{fid}")
+    public Object removeSuitTemplate(@PathVariable String boardId, @PathVariable int sid, @PathVariable int fid) {
+        AlgSuitRemoveRequestDTO req = new AlgSuitRemoveRequestDTO();
+        req.setBoardId(boardId);
+        req.setSid(sid);
+        req.setFid(fid);
+        return service.removeSuitTemplate(req);
+    }
+
+    //获取当前配置的模板信息
+    @GetMapping("/suit/fetch/{boardId}")
+    public Object fetchSuitGroup(@PathVariable String boardId) {
+        return service.fetchSuitGroup(boardId);
+    }
+
+}

+ 62 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/EdgeMediaController.java

@@ -0,0 +1,62 @@
+package com.usky.ai.controller.web;
+
+import com.usky.ai.dto.*;
+import com.usky.ai.service.EdgeMediaService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/edge-media")
+@RequiredArgsConstructor
+public class EdgeMediaController {
+
+    private final EdgeMediaService service;
+
+    /**
+     * 新增/修改通道
+     */
+    @PostMapping("/channel")
+    public Object addChannel(@RequestBody EdgeAppControllerRequestDTO req) {
+        // 简单参数校验
+        if (req.getBoardId() == null || req.getMediaName() == null || req.getMediaUrl() == null) {
+            return "BoardId/MediaName/MediaUrl 不能为空";
+        }
+        EdgeAppControllerReplyDTO reply = service.addChannel(req);
+        if (reply.isSuccess()) {
+            // mediaRepository.save(req);
+            return "success";
+        }
+        return reply.getResult();
+    }
+
+
+    /**
+     * 查询通道
+     */
+    @GetMapping("/channels/{boardId}")
+    public Object fetchChannels(@PathVariable String boardId) {
+        EdgeAppFetchResponseDTO resp = service.fetchChannels(boardId);
+        if (resp.isSuccess()) {
+            return resp.getContent();
+        }
+        return resp.getResult();
+    }
+
+    /**
+     * 删除通道
+     */
+    @DeleteMapping("/delchannel/{boardId}/{mediaName}")
+    public Object deleteChannel(@PathVariable String boardId, @PathVariable String mediaName) {
+        EdgeAppDeleteResponseDTO resp = service.deleteChannel(boardId, mediaName);
+        return resp.isSuccess() ? "deleted" : resp.getResult();
+    }
+
+    @GetMapping("/algorithm/ability/{boardId}")
+    public Object fetchAlgorithmAbility(@PathVariable String boardId) {
+        AlgAbilityFetchResponseDTO resp = service.fetchAlgorithmAbility(boardId);
+        if (resp.isSuccess()) {
+            return resp.getAbility();
+        }
+        return resp.getResult();
+    }
+}

+ 59 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/RepositoryController.java

@@ -0,0 +1,59 @@
+package com.usky.ai.controller.web;
+
+import com.usky.ai.dto.*;
+import com.usky.ai.service.EdgeMediaService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/repositories")
+@RequiredArgsConstructor
+public class RepositoryController {
+
+    private final EdgeMediaService service;
+
+    @GetMapping
+    public RepositoriesInfoResponseDTO listRepositories() {
+        return service.listRepositories();
+    }
+
+    @PostMapping("/create")
+    public RepositoryCreateResponseDTO createRepository(@RequestBody RepositoryCreateRequestDTO request) {
+        return service.createRepository(request);
+    }
+
+    @PostMapping("/update")
+    public RepositoryUpdateResponseDTO updateRepository(@RequestBody RepositoryUpdateRequestDTO request) {
+        return service.updateRepository(request);
+    }
+
+    @DeleteMapping("/delete")
+    public RepositoryDeleteResponseDTO deleteRepository(@RequestBody RepositoryDeleteRequestDTO request) {
+        return service.deleteRepository(request);
+    }
+
+    @PostMapping("/faces")
+    public RepositoryFacesResponseDTO getRepositoryFaces(@RequestBody RepositoryFacesRequestDTO request) {
+        return service.getRepositoryFaces(request);
+    }
+
+    @PostMapping("/register")
+    public RegisterFaceResponseDTO registerFace(@RequestBody RegisterFaceRequestDTO request) {
+        return service.registerFace(request);
+    }
+
+    @PostMapping("/update-face")
+    public UpdateFaceResponseDTO updateFace(@RequestBody UpdateFaceRequestDTO request) {
+        return service.updateFace(request);
+    }
+
+    @DeleteMapping("/delete-face")
+    public DeleteFaceResponseDTO deleteFace(@RequestBody DeleteFaceRequestDTO request) {
+        return service.deleteFace(request);
+    }
+
+    @PostMapping("/recognize")
+    public FaceRecognitionResponseDTO recognizeFace(@RequestBody FaceRecognitionRequestDTO request) {
+        return service.recognizeFace(request);
+    }
+}

+ 27 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/TaskPreviewController.java

@@ -0,0 +1,27 @@
+package com.usky.ai.controller.web;
+
+import com.usky.ai.service.EdgeMediaService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class TaskPreviewController {
+
+    @Autowired
+    private EdgeMediaService edgeMediaService;
+
+    @GetMapping("/api/task/preview/{boardId}/{algTaskSession}")
+    public ResponseEntity<String> getTaskPreview(
+            @PathVariable String boardId,
+            @PathVariable String algTaskSession) {
+        try {
+            String imageUrl = edgeMediaService.getTaskPreviewUrl(boardId, algTaskSession);
+            return ResponseEntity.ok(imageUrl);
+        } catch (Exception e) {
+            return ResponseEntity.badRequest().body("Failed to get task preview: " + e.getMessage());
+        }
+    }
+}

+ 158 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/UniversalUploadController.java

@@ -0,0 +1,158 @@
+package com.usky.ai.controller.web;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.usky.ai.domain.AiAlarmLog;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.RestTemplate;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import org.springframework.http.*;
+import java.time.format.DateTimeFormatter;
+import java.util.Base64;
+import java.util.concurrent.atomic.AtomicInteger;
+
+@RestController
+@RequestMapping("/upload")
+public class UniversalUploadController {
+
+    @Value("${file.domain}")
+    private String fileDomain;
+
+    @Value("${file.path}")
+    private String filePath;
+
+    @Value("${file.prefix}")
+    private String filePrefix;
+
+    @Value("${aiAlarmLog.api}")
+    private String alarmLogApi;
+
+    // 定义一个线程安全的计数器
+    private static final AtomicInteger counter = new AtomicInteger(0);
+
+    @PostMapping(value = "/anything", consumes = "*/*")
+    public String receiveAnything(HttpServletRequest request) {
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(request.getInputStream()))) {
+            StringBuilder sb = new StringBuilder();
+            String line;
+            while ((line = reader.readLine()) != null) {
+                sb.append(line).append("\n");
+            }
+            String body = sb.toString();
+            System.out.println("Received raw data:");
+            System.out.println(body);
+
+            // 解析 JSON 数据
+            ObjectMapper objectMapper = new ObjectMapper();
+            JsonNode jsonNode = objectMapper.readTree(body);
+
+            // 创建告警日志对象
+            AiAlarmLog alarmLog = new AiAlarmLog();
+
+            // 设置字段
+            alarmLog.setDeviceId(getTextValue(jsonNode, "BoardId"));
+            alarmLog.setAlarmTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
+            alarmLog.setAlarmType("666");
+            alarmLog.setAlarmObject(getTextValue(jsonNode, "TaskSession"));
+            alarmLog.setAlarmData("0");
+            alarmLog.setAlarmAttribute(getTextValue(jsonNode, "Summary"));
+            alarmLog.setAlarmContent(getTextValue(jsonNode.get("Result"), "Description"));
+            alarmLog.setAlarmGrade(1);
+            alarmLog.setAlarmAddress("二楼研发中心");
+            alarmLog.setProductCode("513_202");
+
+
+            // 处理图片存储
+            String imageDataLabeled = getTextValue(jsonNode, "ImageDataLabeled");
+            if (!imageDataLabeled.isEmpty()) {
+                try {
+                    String imageUrl = saveImage(imageDataLabeled, "sitePhoto");
+                    alarmLog.setSitePhoto(imageUrl);
+                } catch (Exception e) {
+                    System.err.println("Failed to save image: " + e.getMessage());
+                    e.printStackTrace();
+                }
+            }
+
+            // 调用新增告警日志接口
+            if (!sendAlarmLog(alarmLog)) {
+                return "Failed to send alarm log";
+            }
+
+            return "Received data:\n" + body;
+        } catch (Exception e) {
+            e.printStackTrace();
+            return "Error reading request body: " + e.getMessage();
+        }
+    }
+
+    // 添加辅助方法处理可能的空值
+    private String getTextValue(JsonNode node, String fieldName) {
+        JsonNode fieldNode = node.get(fieldName);
+        if (fieldNode != null && !fieldNode.isNull()) {
+            return fieldNode.asText();
+        }
+        return "";
+    }
+
+    // 保存图片并返回 URL
+    private String saveImage(String base64Image, String fileName) throws IOException {
+        // 检查 Base64 数据是否符合预期格式
+        byte[] imageBytes;
+
+        // 处理带有前缀的Base64数据 (如: data:image/jpeg;base64,/9j/...)
+        if (base64Image.contains(",")) {
+            String[] parts = base64Image.split(",", 2);
+            if (parts.length < 2) {
+                throw new IllegalArgumentException("Invalid Base64 image data format");
+            }
+            imageBytes = Base64.getDecoder().decode(parts[1]);
+        } else {
+            // 处理不带前缀的Base64数据 (如: /9j/...)
+            imageBytes = Base64.getDecoder().decode(base64Image);
+        }
+
+
+        // 生成文件路径 (使用yyyyMMddHHmmss格式)
+        LocalDateTime now = LocalDateTime.now();
+        String monthPath = now.format(DateTimeFormatter.ofPattern("yyyyMM"));
+        String formattedTime = now.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
+        int counterSuffix = counter.getAndIncrement(); // 获取并递增计数器
+        String filePathWithPrefix = filePath + "/" + monthPath + "/" + fileName + "_" + formattedTime + "_" + counterSuffix + ".jpg";
+        Path path = Paths.get(filePathWithPrefix);
+
+        // 保存图片到文件系统
+        Files.createDirectories(path.getParent());
+        Files.write(path, imageBytes);
+
+        // 返回可访问的 URL
+        return fileDomain + filePrefix + "/" + monthPath + "/" + fileName + "_" + formattedTime + "_" + counterSuffix + ".jpg";
+    }
+
+    // 调用新增告警日志接口
+    private boolean sendAlarmLog(AiAlarmLog alarmLog) {
+        RestTemplate restTemplate = new RestTemplate();
+        HttpHeaders headers = new HttpHeaders();
+        headers.setContentType(MediaType.APPLICATION_JSON);
+
+        HttpEntity<AiAlarmLog> request = new HttpEntity<>(alarmLog, headers);
+
+        try {
+            restTemplate.postForObject(alarmLogApi, request, String.class);
+            return true;
+        } catch (Exception e) {
+            System.err.println("Failed to send alarm log: " + e.getMessage());
+            e.printStackTrace();
+            return false;
+        }
+    }
+}

+ 104 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/domain/AiAlarmLog.java

@@ -0,0 +1,104 @@
+package com.usky.ai.domain;
+
+public class AiAlarmLog {
+    private String deviceId;
+    private String alarmTime;
+    private String alarmType;
+    private String alarmObject;
+    private String alarmData;
+    private String alarmAttribute;
+    private String alarmContent;
+    private Integer alarmGrade;
+    private String alarmAddress;
+    private String productCode;
+    private String sitePhoto;
+
+    // Getters and Setters
+    public String getDeviceId() {
+        return deviceId;
+    }
+
+    public void setDeviceId(String deviceId) {
+        this.deviceId = deviceId;
+    }
+
+    public String getAlarmTime() {
+        return alarmTime;
+    }
+
+    public void setAlarmTime(String alarmTime) {
+        this.alarmTime = alarmTime;
+    }
+
+    public String getAlarmType() {
+        return alarmType;
+    }
+
+    public void setAlarmType(String alarmType) {
+        this.alarmType = alarmType;
+    }
+
+    public String getAlarmObject() {
+        return alarmObject;
+    }
+
+    public void setAlarmObject(String alarmObject) {
+        this.alarmObject = alarmObject;
+    }
+
+    public String getAlarmData() {
+        return alarmData;
+    }
+
+    public void setAlarmData(String alarmData) {
+        this.alarmData = alarmData;
+    }
+
+    public String getAlarmAttribute() {
+        return alarmAttribute;
+    }
+
+    public void setAlarmAttribute(String alarmAttribute) {
+        this.alarmAttribute = alarmAttribute;
+    }
+
+    public String getAlarmContent() {
+        return alarmContent;
+    }
+
+    public void setAlarmContent(String alarmContent) {
+        this.alarmContent = alarmContent;
+    }
+
+    public Integer getAlarmGrade() {
+        return alarmGrade;
+    }
+
+    public void setAlarmGrade(Integer alarmGrade) {
+        this.alarmGrade = alarmGrade;
+    }
+
+    public String getAlarmAddress() {
+        return alarmAddress;
+    }
+
+    public void setAlarmAddress(String alarmAddress) {
+        this.alarmAddress = alarmAddress;
+    }
+
+    public String getProductCode() {
+        return productCode;
+    }
+
+    public void setProductCode(String productCode) {
+        this.productCode = productCode;
+    }
+
+    public String getSitePhoto() {
+        return sitePhoto;
+    }
+
+    public void setSitePhoto(String sitePhoto) {
+        this.sitePhoto = sitePhoto;
+    }
+}

+ 83 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/domain/AiDevice.java

@@ -0,0 +1,83 @@
+package com.usky.ai.domain;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+
+import java.sql.Timestamp;
+
+public class AiDevice {
+    private String boardId;
+    private String boardIp;
+    private String description;
+    private Integer status;
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Timestamp createTime;
+    private String createBy;
+    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Timestamp updateTime;
+    private String updateBy;
+
+    // Getters and Setters
+    public String getBoardId() {
+        return boardId;
+    }
+
+    public void setBoardId(String boardId) {
+        this.boardId = boardId;
+    }
+
+    public String getBoardIp() {
+        return boardIp;
+    }
+
+    public void setBoardIp(String boardIp) {
+        this.boardIp = boardIp;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public String getDescription() { // 新增 getter
+        return description;
+    }
+
+    public void setDescription(String description) { // 新增 setter
+        this.description = description;
+    }
+
+    public Timestamp getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Timestamp createTime) {
+        this.createTime = createTime;
+    }
+
+    public String getCreateBy() {
+        return createBy;
+    }
+
+    public void setCreateBy(String createBy) {
+        this.createBy = createBy;
+    }
+
+    public Timestamp getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(Timestamp updateTime) {
+        this.updateTime = updateTime;
+    }
+
+    public String getUpdateBy() {
+        return updateBy;
+    }
+
+    public void setUpdateBy(String updateBy) {
+        this.updateBy = updateBy;
+    }
+}

+ 1 - 1
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/AiQuestion.java → service-ai/service-ai-biz/src/main/java/com/usky/ai/domain/AiQuestion.java

@@ -1,4 +1,4 @@
-package com.usky.ai.service;
+package com.usky.ai.domain;
 
 import java.time.LocalDateTime;
 

+ 1 - 1
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/AiQuestionItem.java → service-ai/service-ai-biz/src/main/java/com/usky/ai/domain/AiQuestionItem.java

@@ -1,4 +1,4 @@
-package com.usky.ai.service;
+package com.usky.ai.domain;
 
 import java.time.LocalDateTime;
 

+ 1 - 1
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/AiSession.java → service-ai/service-ai-biz/src/main/java/com/usky/ai/domain/AiSession.java

@@ -1,4 +1,4 @@
-package com.usky.ai.service;
+package com.usky.ai.domain;
 
 import java.time.LocalDateTime;
 import java.util.List;

+ 13 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgAbilityFetchRequestDTO.java

@@ -0,0 +1,13 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class AlgAbilityFetchRequestDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_ability_fetch"; // 固定值
+}

+ 131 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgAbilityFetchResponseDTO.java

@@ -0,0 +1,131 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AlgAbilityFetchResponseDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("Ability")
+    private List<Algorithm> ability;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    private long time = System.currentTimeMillis();
+
+    public boolean isSuccess() {
+        return result != null && Integer.valueOf(0).equals(result.getCode());
+    }
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Algorithm {
+        @JsonProperty("attribute")
+        private JsonNode attribute;
+
+        @JsonProperty("parameters")
+        private List<Parameter> parameters;
+
+        @JsonProperty("permitted")
+        private boolean permitted;
+
+        @JsonProperty("code")
+        private int code;
+
+        @JsonProperty("sub")
+        private boolean sub;
+
+        @JsonProperty("name")
+        private String name;
+
+        @JsonProperty("desc")
+        private String desc;
+
+        @JsonProperty("item")
+        private int item;
+
+        @JsonProperty("policy")
+        private List<Policy> policy;
+
+        @Data
+        @JsonIgnoreProperties(ignoreUnknown = true)
+        public static class Parameter {
+            @JsonProperty("class")
+            private String classType;
+
+            @JsonProperty("type")
+            private int type;
+
+            @JsonProperty("max")
+            private Object max;
+
+            @JsonProperty("min")
+            private Object min;
+
+            @JsonProperty("default")
+            private Object defaultValue;
+
+            @JsonProperty("key")
+            private String key;
+
+            @JsonProperty("name")
+            private String name;
+
+            @JsonProperty("required")
+            private boolean required;
+
+            @JsonProperty("value")
+            private Object value;
+
+            @JsonProperty("options")
+            private List<Option> options;
+
+            @Data
+            @JsonIgnoreProperties(ignoreUnknown = true)
+            public static class Option {
+                @JsonProperty("enable")
+                private boolean enable;
+
+                @JsonProperty("key")
+                private String key;
+
+                @JsonProperty("value")
+                private int value;
+
+                @JsonProperty("name")
+                private String name;
+            }
+        }
+
+        @Data
+        @JsonIgnoreProperties(ignoreUnknown = true)
+        public static class Policy {
+            @JsonProperty("property")
+            private String property;
+
+            @JsonProperty("name")
+            private String name;
+        }
+    }
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Result {
+        @JsonProperty("Code")
+        private int code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+}

+ 80 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgScheduleCreateRequestDTO.java

@@ -0,0 +1,80 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@NoArgsConstructor
+public class AlgScheduleCreateRequestDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_schedule_create";
+
+    @JsonProperty("name")
+    private String name;
+
+    @JsonProperty("summary")
+    private String summary;
+
+    @JsonProperty("timePeriods")
+    private List<TimePeriod> timePeriods;
+
+    @JsonProperty("value")
+    private String value;
+
+    public void convertTimePeriodsToBinary() {
+        StringBuilder binaryValue = new StringBuilder();
+        for (int i = 0; i < 336; i++) {
+            binaryValue.append("0");
+        }
+
+        for (TimePeriod period : timePeriods) {
+            processTimePeriod(binaryValue, period);
+        }
+
+        this.value = binaryValue.toString();
+    }
+
+    private void processTimePeriod(StringBuilder binaryValue, TimePeriod period) {
+        // 将 dayOfWeek 转换为从 0 开始的索引
+        int startDay = period.getDayOfWeek() - 1;
+        int endDay = startDay; // 默认结束天数与开始天数相同
+        if (period.getToDayOfWeek() != null) {
+            endDay = period.getToDayOfWeek() - 1; // 如果有结束天数,也转换为从 0 开始的索引
+        }
+
+        // 计算开始和结束时间的分钟数
+        int startMinutes = Integer.parseInt(period.getStartTime().substring(0, 2)) * 60 + Integer.parseInt(period.getStartTime().substring(3));
+        int endMinutes = Integer.parseInt(period.getEndTime().substring(0, 2)) * 60 + Integer.parseInt(period.getEndTime().substring(3));
+
+        // 计算开始和结束时间的索引
+        int startIdx = (startDay * 48) + (startMinutes / 30);
+        int endIdx = (endDay * 48) + (endMinutes / 30);
+
+        // 填充时间段
+        for (int i = startIdx; i < endIdx; i++) {
+            binaryValue.setCharAt(i, '1');
+        }
+    }
+
+    @Data
+    @NoArgsConstructor
+    public static class TimePeriod {
+        @JsonProperty("dayOfWeek")
+        private int dayOfWeek; // 0-6 表示周日到周六
+
+        @JsonProperty("toDayOfWeek")
+        private Integer toDayOfWeek; // 可选,表示连续的结束星期几
+
+        @JsonProperty("startTime")
+        private String startTime; // 格式为 "HH:mm"
+
+        @JsonProperty("endTime")
+        private String endTime; // 格式为 "HH:mm"
+    }
+}

+ 25 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgScheduleCreateResponseDTO.java

@@ -0,0 +1,25 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AlgScheduleCreateResponseDTO {
+
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("ScheduleId")
+    private String scheduleId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_schedule_create";
+
+    private long time = System.currentTimeMillis();
+
+}

+ 16 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgScheduleDeleteRequestDTO.java

@@ -0,0 +1,16 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class AlgScheduleDeleteRequestDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_schedule_delete";
+
+    @JsonProperty("id")
+    private Long id;
+}

+ 40 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgScheduleDeleteResponseDTO.java

@@ -0,0 +1,40 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AlgScheduleDeleteResponseDTO {
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_schedule_delete";
+
+    @JsonProperty("Result")
+    private Result result;
+
+    @JsonProperty("ScheduleId")
+    private Long scheduleId;
+
+    public boolean isSuccess() {
+        return result != null && result.getCode() == 0;
+    }
+
+    private long time = System.currentTimeMillis();
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Result {
+        @JsonProperty("Code")
+        private int code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+}

+ 13 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgScheduleFetchRequestDTO.java

@@ -0,0 +1,13 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class AlgScheduleFetchRequestDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_schedule_fetch";
+}

+ 113 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgScheduleFetchResponseDTO.java

@@ -0,0 +1,113 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AlgScheduleFetchResponseDTO {
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_schedule_fetch";
+
+    @JsonProperty("Result")
+    private Result result;
+
+    @JsonProperty("Content")
+    private List<Schedule> content;
+
+    public boolean isSuccess() {
+        return result != null && result.getCode() == 0;
+    }
+
+    private long time = System.currentTimeMillis();
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Result {
+        @JsonProperty("Code")  //0成功/其他失败
+        private int code;
+
+        @JsonProperty("Content")
+        private List<Schedule> content; // Content 属性在 Result 中
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Schedule {
+        @JsonProperty("Id")
+        private int id;
+
+        @JsonProperty("Name")
+        private String name;
+
+        @JsonProperty("Summary")
+        private String summary;
+
+        @JsonProperty("Value")
+        private String value;
+
+        public List<String> getReadableTimePeriods() {
+            List<String> timePeriods = new ArrayList<>();
+            char[] binaryValue = value.toCharArray();
+            String[] days = {"星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日",};
+
+            // 检查 Value 字段的长度是否正确
+            if (binaryValue.length != 336) {
+                //System.err.println("Value 字段长度不正确,预期长度为 336,实际长度为 " + binaryValue.length);
+                return timePeriods; // 如果长度不足,返回空列表
+            }
+
+            for (int day = 0; day < 7; day++) {
+                StringBuilder dayPeriods = new StringBuilder();
+                boolean inPeriod = false;
+                int startHour = -1;
+                int startMinute = -1;
+
+                for (int halfHour = 0; halfHour < 48; halfHour++) {
+                    int hour = halfHour / 2;
+                    int minute = (halfHour % 2) * 30;
+                    int index = day * 48 + halfHour;
+
+                    if (index < binaryValue.length && binaryValue[index] == '1') {
+                        if (!inPeriod) {
+                            inPeriod = true;
+                            startHour = hour;
+                            startMinute = minute;
+                        }
+                    } else {
+                        if (inPeriod) {
+                            inPeriod = false;
+                            dayPeriods.append(String.format("%02d:%02d~%02d:%02d", startHour, startMinute, hour, minute)).append(", ");
+                        }
+                    }
+                }
+
+                if (inPeriod) {
+                    int hour = 23;
+                    int minute = 30;
+                    dayPeriods.append(String.format("%02d:%02d~%02d:%02d", startHour, startMinute, hour, minute)).append(", ");
+                }
+
+                if (dayPeriods.length() > 0) {
+                    timePeriods.add(days[day] + " " + dayPeriods.toString().trim().replaceAll(", $", ""));
+                }
+            }
+
+            System.out.println("Readable time periods: " + timePeriods);
+            return timePeriods;
+        }
+    }
+}

+ 20 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitAppendRequestDTO.java

@@ -0,0 +1,20 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class AlgSuitAppendRequestDTO {
+
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_suit_append";
+
+    @JsonProperty("sid")
+    private int sid;
+
+    @JsonProperty("jpeg")
+    private String jpeg;
+}

+ 36 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitAppendResponseDTO.java

@@ -0,0 +1,36 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AlgSuitAppendResponseDTO {
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    private long time = System.currentTimeMillis();
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Result {
+        @JsonProperty("Code")
+        private int code;
+
+        @JsonProperty("Desc")
+        private String desc;
+
+        @JsonProperty("FeatureId")
+        private Integer featureId;
+    }
+}

+ 16 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitCreateRequestDTO.java

@@ -0,0 +1,16 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class AlgSuitCreateRequestDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_suit_create";
+
+    @JsonProperty("name")
+    private String name;
+}

+ 36 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitCreateResponseDTO.java

@@ -0,0 +1,36 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AlgSuitCreateResponseDTO {
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    private long time = System.currentTimeMillis();
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Result {
+        @JsonProperty("Code")
+        private int code;
+
+        @JsonProperty("Desc")
+        private String desc;
+
+        @JsonProperty("SuitId")
+        private Integer suitId;
+    }
+}

+ 13 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitFetchRequestDTO.java

@@ -0,0 +1,13 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class AlgSuitFetchRequestDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_suit_fetch";
+}

+ 65 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitFetchResponseDTO.java

@@ -0,0 +1,65 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AlgSuitFetchResponseDTO {
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Result {
+        @JsonProperty("Code")
+        private int code;
+
+        @JsonProperty("Desc")
+        private String desc;
+
+        @JsonProperty("Content")
+        private List<SuitGroup> content;
+    }
+
+    @Data
+    public static class SuitGroup {
+        @JsonProperty("id")
+        private int id;
+
+        @JsonProperty("name")
+        private String name;
+
+        @JsonProperty("features")
+        private List<Feature> features;
+    }
+
+    @Data
+    public static class Feature {
+        @JsonProperty("feature")
+        private String feature;
+
+        @JsonProperty("fid")
+        private int fid;
+
+        @JsonProperty("jpeg")
+        private String jpeg;
+
+        @JsonProperty("size")
+        private int size;
+    }
+
+    private long time = System.currentTimeMillis();
+}

+ 18 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitGroupRemoveRequestDTO.java

@@ -0,0 +1,18 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class AlgSuitGroupRemoveRequestDTO {
+
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_suit_group_remove";
+
+    @JsonProperty("sid")
+    private int sid;
+}

+ 33 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitGroupRemoveResponseDTO.java

@@ -0,0 +1,33 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AlgSuitGroupRemoveResponseDTO {
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    private long time = System.currentTimeMillis();
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Result {
+        @JsonProperty("Code")
+        private int code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+}

+ 20 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitRemoveRequestDTO.java

@@ -0,0 +1,20 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class AlgSuitRemoveRequestDTO {
+
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_suit_remove";
+
+    @JsonProperty("sid")
+    private int sid;
+
+    @JsonProperty("fid")
+    private int fid;
+}

+ 33 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgSuitRemoveResponseDTO.java

@@ -0,0 +1,33 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AlgSuitRemoveResponseDTO {
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    private long time = System.currentTimeMillis();
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Result {
+        @JsonProperty("Code")
+        private int code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+}

+ 16 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgTaskSnapRequestDTO.java

@@ -0,0 +1,16 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class AlgTaskSnapRequestDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_task_snap";
+
+    @JsonProperty("AlgTaskSession")
+    private String algTaskSession;
+}

+ 43 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/AlgTaskSnapResponseDTO.java

@@ -0,0 +1,43 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class AlgTaskSnapResponseDTO {
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    @JsonProperty("AlgTaskSession")
+    private String algTaskSession;
+
+    @JsonProperty("Base64")
+    private String base64;
+
+    public boolean isSuccess() {
+        return result != null && Integer.valueOf(0).equals(result.getCode());
+    }
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Result {
+        @JsonProperty("Code")
+        private int code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+
+    private long time = System.currentTimeMillis();
+}

+ 109 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/BoardPingDTO.java

@@ -0,0 +1,109 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class BoardPingDTO {
+    //当前盒子唯一表示
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    //当前ip
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    //当前设备硬件类型
+    @JsonProperty("BoardPlatform")
+    private String boardPlatform;
+
+    //当前设备芯片温度
+    @JsonProperty("BoardTemp")
+    private String boardTemp;
+
+    //当前设备接入方式 WAN/4G
+    @JsonProperty("BoardType")
+    private String boardType;
+
+    //GB28181 设备号配置后透传
+    @JsonProperty("GBClientId")
+    private String gbClientId;
+
+    @JsonProperty("GrantCode")
+    private String grantCode;
+
+    @JsonProperty("GrantDesc")
+    private String grantDesc;
+
+    //当前设备状态
+    @JsonProperty("Key")
+    private String key;
+
+    //GPS 信息 部分设备支持
+    @JsonProperty("Latitude")
+    private String latitude;
+
+    //GPS 信息 部分设备支持
+    @JsonProperty("Longitude")
+    private String longitude;
+
+    // 当前应用VmRSS信息
+    @JsonProperty("Malloc")
+    private String malloc;
+
+    //本次应用PID
+    @JsonProperty("Pid")
+    private String pid;
+
+    //特定设备时透传主控端IP
+    @JsonProperty("Se6ip")
+    private String se6ip;
+
+    @JsonProperty("Status")
+    private String status;
+
+    //当前设备系统版本
+    @JsonProperty("System")
+    private String system;
+
+    //当前时间戳毫秒
+    @JsonProperty("Time")
+    private Long time;
+
+    //软件版本
+    @JsonProperty("Version")
+    private String version;
+
+    //GPS 信息 部分设备支持
+    @JsonProperty("angleCourse")
+    private String angleCourse;
+    @JsonProperty("kSpeed")
+    private String kSpeed;
+    @JsonProperty("nSpeed")
+    private String nSpeed;
+
+
+
+    //当前设备硬盘情况kB
+    @JsonProperty("HostDisk")
+    private JsonNode hostDisk;
+
+    //当前设备内存使用情况
+    @JsonProperty("HostMemory")
+    private JsonNode hostMemory;
+
+    //当前设备添加的通道信息
+    @JsonProperty("Medias")
+    private JsonNode medias;
+
+    //当前设备配置的任务信息
+    @JsonProperty("Tasks")
+    private JsonNode tasks;
+
+    //当前设备的算力资源使用情况
+    @JsonProperty("Tpu")
+    private JsonNode tpu;
+}

+ 13 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/DeleteFaceRequestDTO.java

@@ -0,0 +1,13 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class DeleteFaceRequestDTO {
+    @JsonProperty("albumName")
+    private String albumName;
+
+    @JsonProperty("photoId")
+    private int photoId;
+}

+ 34 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/DeleteFaceResponseDTO.java

@@ -0,0 +1,34 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class DeleteFaceResponseDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    @JsonProperty("albumId")
+    private int albumId;
+
+    @JsonProperty("photoId")
+    private int photoId;
+
+    @Data
+    public static class Result {
+        @JsonProperty("Code")
+        private Integer code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+}

+ 14 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAlgTaskCommandRequestDTO.java

@@ -0,0 +1,14 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class EdgeAlgTaskCommandRequestDTO {
+    @JsonProperty("BoardId") private String boardId;
+    @JsonProperty("Event") private String event; // /alg_task_control 或 /alg_task_delete
+    @JsonProperty("ControlCommand") private Integer controlCommand; // 0 停止 1 启动
+    @JsonProperty("AlgTaskSession") private String algTaskSession;
+}

+ 82 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAlgTaskConfigRequestDTO.java

@@ -0,0 +1,82 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class EdgeAlgTaskConfigRequestDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+    @JsonProperty("Event")
+    private String event = "/alg_task_config";
+    @JsonProperty("AlgTaskSession")
+    private String algTaskSession;
+    @JsonProperty("TaskDesc")
+    private String taskDesc;
+    @JsonProperty("AlgInfo")
+    private List<Integer> algInfo;
+    @JsonProperty("MediaName")
+    private String mediaName;
+    @JsonProperty("MetadataUrl")
+    private List<String> metadataUrl;
+    @JsonProperty("ScheduleId")
+    private Integer scheduleId = -1;
+    @JsonProperty("UserData")
+    private UserData userData;
+    @JsonProperty("RuleProperty")
+    private List<RuleProperty> ruleProperty;
+    @JsonProperty("Template")
+    private String template = "";
+    @JsonProperty("GB28181Channel")
+    private String gb28181Channel = "";
+    @JsonProperty("AlarmProtocol")
+    private Integer alarmProtocol = 0;
+    @JsonProperty("AlarmBody")
+    private Integer alarmBody = 0;
+    @JsonProperty("Restart")
+    private Boolean restart = true;
+
+    @Data
+    public static class UserData {
+        @JsonProperty("MethodConfig")
+        private List<Integer> methodConfig;
+
+        /* 离岗算法业务字段 */
+        @JsonProperty("staff_sec")
+        private Integer staffSec;
+        @JsonProperty("staff_number")
+        private Integer staffNumber;
+    }
+
+    @Data
+    public static class RuleProperty {
+        @JsonProperty("Algo")
+        private Algo algo;
+        @JsonProperty("Points")
+        private List<Point> points;
+        @JsonProperty("RuleId")
+        private String ruleId;
+        @JsonProperty("RuleType")
+        private Integer ruleType;
+
+        @Data
+        public static class Algo {
+            @JsonProperty("majorId")
+            private Integer majorId;
+            @JsonProperty("minorId")
+            private Integer minorId;
+        }
+
+        @Data
+        public static class Point {
+            @JsonProperty("X")
+            private Double x;
+            @JsonProperty("Y")
+            private Double y;
+        }
+    }
+}

+ 15 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAlgTaskFetchRequestDTO.java

@@ -0,0 +1,15 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class EdgeAlgTaskFetchRequestDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_task_fetch";
+}

+ 177 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAlgTaskListResponseDTO.java

@@ -0,0 +1,177 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class EdgeAlgTaskListResponseDTO {
+
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("Content")
+    private List<TaskItem> content;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    public boolean isSuccess() {
+        return result != null && Integer.valueOf(0).equals(result.getCode());
+    }
+
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class TaskItem {
+        @JsonProperty("AlgTaskSession")
+        private String algTaskSession;
+
+        @JsonProperty("MediaName")
+        private String mediaName;
+
+        @JsonProperty("AlgTaskStatus")
+        private AlgTaskStatus algTaskStatus;
+
+        @JsonProperty("AlarmBody")
+        private Integer alarmBody;
+
+        @JsonProperty("AlarmProtocol")
+        private Integer alarmProtocol;
+
+        @JsonProperty("MetadataUrl")
+        private String metadataUrl;
+
+        @JsonProperty("TaskDesc")
+        private String taskDesc;
+
+        @JsonProperty("UserData")
+        private UserData userData;
+
+        @JsonProperty("AlgInfo")
+        private List<Integer> algInfo;
+
+        @JsonProperty("GB28181Channel")
+        private String gb28181Channel;
+
+        @JsonProperty("Template")
+        private String template;
+
+        @JsonProperty("Restart")
+        private Boolean restart;
+
+        @JsonProperty("RuleProperty")
+        private List<RuleProperty> ruleProperty;
+
+        @JsonProperty("BaseAlgItem")
+        private List<BaseAlgItem> baseAlgItem;
+    }
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class AlgTaskStatus {
+        @JsonProperty("label")
+        private String label;
+
+        @JsonProperty("style")
+        private String style;
+
+        @JsonProperty("type")
+        private Integer type;
+    }
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class UserData {
+        @JsonProperty("MethodConfig")
+        private List<Integer> methodConfig;
+    }
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class RuleProperty {
+        @JsonProperty("RuleId")
+        private String ruleId;
+
+        @JsonProperty("RuleType")
+        private Integer ruleType;
+
+        @JsonProperty("RuleTypeName")
+        private String ruleTypeName;
+
+        @JsonProperty("Algo")
+        private Algo algo;
+
+        @JsonProperty("Points")
+        private List<Point> points;
+
+        @Data
+        @JsonIgnoreProperties(ignoreUnknown = true)
+        public static class Algo {
+            @JsonProperty("majorId")
+            private Integer majorId;
+
+            @JsonProperty("minorId")
+            private Integer minorId;
+        }
+
+        @Data
+        @JsonIgnoreProperties(ignoreUnknown = true)
+        public static class Point {
+            @JsonProperty("X")
+            private Double x;
+
+            @JsonProperty("Y")
+            private Double y;
+        }
+    }
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class BaseAlgItem {
+        @JsonProperty("lineDesc")
+        private String lineDesc;
+
+        @JsonProperty("lineRequired")
+        private Boolean lineRequired;
+
+        @JsonProperty("majorId")
+        private Integer majorId;
+
+        @JsonProperty("majorOnly")
+        private Boolean majorOnly;
+
+        @JsonProperty("minorId")
+        private Integer minorId;
+
+        @JsonProperty("name")
+        private String name;
+
+        @JsonProperty("zoneDesc")
+        private String zoneDesc;
+
+        @JsonProperty("zoneRequired")
+        private Boolean zoneRequired;
+    }
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Result {
+        @JsonProperty("Code")
+        private Integer code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+
+    private long time = System.currentTimeMillis();
+}

+ 34 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAlgTaskResponseDTO.java

@@ -0,0 +1,34 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class EdgeAlgTaskResponseDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+    @JsonProperty("BoardIp")
+    private String boardIp;
+    @JsonProperty("Event")
+    private String event;
+    @JsonProperty("AlgTaskSession")
+    private String algTaskSession;
+    @JsonProperty("Result")
+    private Result result;
+
+    @Data
+    public static class Result {
+        @JsonProperty("Code")
+        private Integer code;
+        @JsonProperty("Desc")
+        private String desc;
+    }
+
+    public boolean isSuccess() {
+        return result != null && Integer.valueOf(0).equals(result.code);
+    }
+
+    private long time = System.currentTimeMillis();
+}

+ 32 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAppControllerReplyDTO.java

@@ -0,0 +1,32 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class EdgeAppControllerReplyDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+    @JsonProperty("BoardIp")
+    private String boardIp;
+    @JsonProperty("Event")
+    private String event = "/alg_media_config"; // 固定值
+    @JsonProperty("MediaName")
+    private String mediaName;
+    @JsonProperty("Result")
+    private Result result;
+
+    private long time = System.currentTimeMillis();
+
+    @Data
+    public static class Result {
+        @JsonProperty("Code")
+        private Integer code;
+        @JsonProperty("Desc")
+        private String desc;
+    }
+
+    public boolean isSuccess() {
+        return result != null && Integer.valueOf(0).equals(result.code);
+    }
+}

+ 42 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAppControllerRequestDTO.java

@@ -0,0 +1,42 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class EdgeAppControllerRequestDTO {
+
+    /** 与文档一致,首字母大写以兼容盒子 */
+    @JsonProperty("BoardId")
+    private String boardId;
+    @JsonProperty("Event")
+    private String event;
+    @JsonProperty("MediaName")
+    private String mediaName;
+    @JsonProperty("MediaUrl")
+    private String mediaUrl;
+    @JsonProperty("MediaDesc")
+    private String mediaDesc;
+    @JsonProperty("RtspTransport")
+    private Boolean rtspTransport;
+    @JsonProperty("GBTransport")
+    private Boolean gbTransport;
+    @JsonProperty("SubId")
+    private String subId;
+    @JsonProperty("Params")
+    private List<Param> params;
+
+    @Data
+    public static class Param {
+        @JsonProperty("Key")
+        private String key;
+        @JsonProperty("Name")
+        private String name;
+        @JsonProperty("Type")
+        private String type;
+        @JsonProperty("Value")
+        private String value;
+    }
+}

+ 16 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAppDeleteRequestDTO.java

@@ -0,0 +1,16 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class EdgeAppDeleteRequestDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("Event")
+    private String event = "/alg_media_delete"; // 固定值
+
+    @JsonProperty("MediaName")
+    private String mediaName;
+}

+ 37 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAppDeleteResponseDTO.java

@@ -0,0 +1,37 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class EdgeAppDeleteResponseDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("MediaName")
+    private String mediaName;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    private long time = System.currentTimeMillis();
+
+    @Data
+    public static class Result {
+        @JsonProperty("Code")
+        private Integer code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+
+    public boolean isSuccess() {
+        return result != null && Integer.valueOf(0).equals(result.code);
+    }
+}

+ 116 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/EdgeAppFetchResponseDTO.java

@@ -0,0 +1,116 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class EdgeAppFetchResponseDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("Content")
+    private List<MediaContent> content;
+
+    @JsonProperty("Event")
+    private String event = "/alg_media_fetch"; // 固定值
+
+    @JsonProperty("Result")
+    private Result result;
+
+    private long time = System.currentTimeMillis();
+
+    public boolean isSuccess() {
+        return result != null && Integer.valueOf(0).equals(result.getCode());
+    }
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class Result {
+        @JsonProperty("Code")
+        private Integer code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+
+    @Data
+    @JsonIgnoreProperties(ignoreUnknown = true)
+    public static class MediaContent {
+        @JsonProperty("MediaName")
+        private String mediaName;
+
+        @JsonProperty("MediaDesc")
+        private String mediaDesc;
+
+        @JsonProperty("MediaUrl")
+        private String mediaUrl;
+
+        @JsonProperty("RtspTransport")
+        private Boolean rtspTransport;
+
+        @JsonProperty("GBTransport")
+        private Boolean gbTransport;
+
+        @JsonProperty("SubId")
+        private String subId;
+
+        @JsonProperty("MediaStatus")
+        private MediaStatus mediaStatus;
+
+        @JsonProperty("Params")
+        private List<Param> params;
+
+        @JsonProperty("ProtocolType")
+        private Integer protocolType;
+
+        @JsonProperty("SipBChannelId")
+        private String sipBChannelId;
+
+        @Data
+        @JsonIgnoreProperties(ignoreUnknown = true)
+        public static class Param {
+            @JsonProperty("Key")
+            private String key;
+            @JsonProperty("Name")
+            private String name;
+            @JsonProperty("Type")
+            private String type;
+
+            // 既能是 String 也能是 List<String>
+            @JsonProperty("Value")
+            private Object value;
+        }
+
+        @Data
+        @JsonIgnoreProperties(ignoreUnknown = true)
+        public static class MediaStatus {
+            @JsonProperty("type")
+            private Integer type;
+
+            @JsonProperty("style")
+            private String style;
+
+            @JsonProperty("label")
+            private String label;
+
+            @JsonProperty("size")
+            private Size size;
+
+            @Data
+            public static class Size {
+                @JsonProperty("width")
+                private Integer width;
+
+                @JsonProperty("height")
+                private Integer height;
+            }
+        }
+    }
+}

+ 15 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/FaceRecognitionRequestDTO.java

@@ -0,0 +1,15 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class FaceRecognitionRequestDTO {
+    @JsonProperty("albumIdList")
+    private List<Integer> albumIdList;
+
+    @JsonProperty("imageBase64")
+    private String imageBase64;
+}

+ 50 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/FaceRecognitionResponseDTO.java

@@ -0,0 +1,50 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+import java.util.List;
+
+@Data
+public class FaceRecognitionResponseDTO {
+    @JsonProperty("Result")
+    private Result result;
+
+    @JsonProperty("album1vnList")
+    private List<Album1vn> album1vnList;
+
+    @JsonProperty("totalCount")
+    private int totalCount;
+
+    @Data
+    public static class Result {
+        @JsonProperty("Code")
+        private Integer code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+
+    @Data
+    public static class Album1vn {
+        @JsonProperty("Height")
+        private int height;
+
+        @JsonProperty("Width")
+        private int width;
+
+        @JsonProperty("X")
+        private int x;
+
+        @JsonProperty("Y")
+        private int y;
+
+        @JsonProperty("albumId")
+        private int albumId;
+
+        @JsonProperty("photoId")
+        private int photoId;
+
+        @JsonProperty("score")
+        private double score;
+    }
+}

+ 16 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RegisterFaceRequestDTO.java

@@ -0,0 +1,16 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class RegisterFaceRequestDTO {
+    @JsonProperty("albumName")
+    private String albumName;
+
+    @JsonProperty("name")
+    private String name;
+
+    @JsonProperty("image")
+    private String image;
+}

+ 43 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RegisterFaceResponseDTO.java

@@ -0,0 +1,43 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class RegisterFaceResponseDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    @JsonProperty("albumId")
+    private int albumId;
+
+    @JsonProperty("alignedImage")
+    private String alignedImage;
+
+    @JsonProperty("croppedImage")
+    private String croppedImage;
+
+    @JsonProperty("photoId")
+    private int photoId;
+
+    @JsonProperty("photoName")
+    private String photoName;
+
+    @Data
+    public static class Result {
+        @JsonProperty("Code")
+        private Integer code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+}

+ 52 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoriesInfoResponseDTO.java

@@ -0,0 +1,52 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+@JsonIgnoreProperties(ignoreUnknown = true)
+public class RepositoriesInfoResponseDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    @JsonProperty("data")
+    private List<Album> data;
+
+    @JsonProperty("totalCount")
+    private int totalCount;
+
+    @Data
+    public static class Album {
+        @JsonProperty("albumId")
+        private int albumId;
+
+        @JsonProperty("albumName")
+        private String albumName;
+
+        @JsonProperty("pictureNo")
+        private int pictureNo;
+    }
+
+    @Data
+    public static class Result {
+        @JsonProperty("Code")
+        private Integer code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+
+    private long time = System.currentTimeMillis();
+}

+ 11 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryCreateRequestDTO.java

@@ -0,0 +1,11 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class RepositoryCreateRequestDTO {
+    @JsonProperty("albumName")
+    private String albumName;
+}
+

+ 36 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryCreateResponseDTO.java

@@ -0,0 +1,36 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class RepositoryCreateResponseDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    @JsonProperty("albumId")
+    private int albumId;
+
+    @JsonProperty("albumName")
+    private String albumName;
+
+    @Data
+    public static class Result {
+        @JsonProperty("Code")
+        private Integer code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+
+    private long time = System.currentTimeMillis();
+}

+ 10 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryDeleteRequestDTO.java

@@ -0,0 +1,10 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class RepositoryDeleteRequestDTO {
+    @JsonProperty("albumName")
+    private String albumName;
+}

+ 33 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryDeleteResponseDTO.java

@@ -0,0 +1,33 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class RepositoryDeleteResponseDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    @JsonProperty("albumName")
+    private String albumName;
+
+    @Data
+    public static class Result {
+        @JsonProperty("Code")
+        private Integer code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+
+    private long time = System.currentTimeMillis();
+}

+ 16 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryFacesRequestDTO.java

@@ -0,0 +1,16 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class RepositoryFacesRequestDTO {
+    @JsonProperty("albumName")
+    private String albumName;
+
+    @JsonProperty("page")
+    private int page;
+
+    @JsonProperty("size")
+    private int size;
+}

+ 48 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryFacesResponseDTO.java

@@ -0,0 +1,48 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class RepositoryFacesResponseDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    @JsonProperty("data")
+    private List<Face> data;
+
+    @JsonProperty("total")
+    private int total;
+
+    @Data
+    public static class Face {
+        @JsonProperty("albumId")
+        private String albumId;
+
+        @JsonProperty("photoId")
+        private int photoId;
+
+        @JsonProperty("photoName")
+        private String photoName;
+    }
+
+    @Data
+    public static class Result {
+        @JsonProperty("Code")
+        private Integer code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+}

+ 13 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryUpdateRequestDTO.java

@@ -0,0 +1,13 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class RepositoryUpdateRequestDTO {
+    @JsonProperty("albumName")
+    private String albumName;
+
+    @JsonProperty("name")
+    private String name;
+}

+ 36 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/RepositoryUpdateResponseDTO.java

@@ -0,0 +1,36 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class RepositoryUpdateResponseDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    @JsonProperty("albumId")
+    private int albumId;
+
+    @JsonProperty("albumName")
+    private String albumName;
+
+    @Data
+    public static class Result {
+        @JsonProperty("Code")
+        private Integer code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+
+    private long time = System.currentTimeMillis();
+}

+ 19 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/UpdateFaceRequestDTO.java

@@ -0,0 +1,19 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class UpdateFaceRequestDTO {
+    @JsonProperty("albumName")
+    private String albumName;
+
+    @JsonProperty("name")
+    private String name;
+
+    @JsonProperty("photoId")
+    private int photoId;
+
+    @JsonProperty("image")
+    private String image;
+}

+ 43 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/dto/UpdateFaceResponseDTO.java

@@ -0,0 +1,43 @@
+package com.usky.ai.dto;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Data;
+
+@Data
+public class UpdateFaceResponseDTO {
+    @JsonProperty("BoardId")
+    private String boardId;
+
+    @JsonProperty("BoardIp")
+    private String boardIp;
+
+    @JsonProperty("Event")
+    private String event;
+
+    @JsonProperty("Result")
+    private Result result;
+
+    @JsonProperty("albumId")
+    private int albumId;
+
+    @JsonProperty("alignedImage")
+    private String alignedImage;
+
+    @JsonProperty("croppedImage")
+    private String croppedImage;
+
+    @JsonProperty("photoId")
+    private int photoId;
+
+    @JsonProperty("photoName")
+    private String photoName;
+
+    @Data
+    public static class Result {
+        @JsonProperty("Code")
+        private Integer code;
+
+        @JsonProperty("Desc")
+        private String desc;
+    }
+}

+ 27 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/mapper/AiDeviceMapper.java

@@ -0,0 +1,27 @@
+package com.usky.ai.mapper;
+
+import com.usky.ai.domain.AiDevice;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface AiDeviceMapper {
+
+    void insertDevice(AiDevice device);
+
+    void updateDevice(AiDevice device);
+
+    void saveOrUpdateDevice(AiDevice device);
+
+    void updateDeviceStatus();
+
+    List<AiDevice> getDevices(@Param("boardId") String boardId, @Param("offset") int offset, @Param("limit") int limit);
+
+    long getTotalDevices(@Param("boardId") String boardId);
+
+    void deleteDevice(@Param("boardId") String boardId);
+
+    boolean checkExists(@Param("boardId") String boardId, @Param("boardIp") String boardIp);
+}

+ 1 - 1
service-ai/service-ai-biz/src/main/java/com/usky/ai/mapper/AiQuestionMapper.java

@@ -1,6 +1,6 @@
 package com.usky.ai.mapper;
 
-import com.usky.ai.service.AiQuestion;
+import com.usky.ai.domain.AiQuestion;
 import org.apache.ibatis.annotations.*;
 
 import java.util.List;

+ 2 - 2
service-ai/service-ai-biz/src/main/java/com/usky/ai/mapper/AiSessionMapper.java

@@ -1,7 +1,7 @@
 package com.usky.ai.mapper;
 
-import com.usky.ai.service.AiQuestion;
-import com.usky.ai.service.AiSession;
+import com.usky.ai.domain.AiQuestion;
+import com.usky.ai.domain.AiSession;
 import org.apache.ibatis.annotations.*;
 
 import java.util.List;

+ 22 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/AiDeviceService.java

@@ -0,0 +1,22 @@
+package com.usky.ai.service;
+
+import com.usky.ai.domain.AiDevice;
+
+import java.util.List;
+
+public interface AiDeviceService {
+
+    void addDevice(AiDevice device);
+
+    void editDevice(AiDevice device);
+
+    void saveOrUpdateDevice(AiDevice device);
+
+    void updateDeviceStatus();
+
+    List<AiDevice> getDevices(String boardId, int pageNum, int pageSize);
+
+    long getTotalDevices(String boardId);
+
+    void deleteDevice(String boardId);
+}

+ 332 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/EdgeMediaService.java

@@ -0,0 +1,332 @@
+package com.usky.ai.service;
+
+
+import com.usky.ai.dto.*;
+import com.usky.ai.service.listener.BoardPingListener;
+import com.usky.ai.service.mqtt.MqttGateway;
+import com.usky.ai.service.util.JsonUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Service;
+import org.springframework.util.Base64Utils;
+import org.springframework.web.client.RestTemplate;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static com.usky.ai.service.listener.BoardPingListener.ReplyCache.*;
+
+/**
+ * 统一封装所有与盒子交互的 MQTT 请求
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class EdgeMediaService {
+
+    private final MqttGateway gateway;
+
+    private final RestTemplate restTemplate;
+
+    @Value("${api.base-url}")
+    private String baseUrl;
+
+    @Value("${file.path}")
+    private String filePath;
+
+    @Value("${file.domain}")
+    private String fileDomain;
+
+    @Value("${file.prefix}")
+    private String filePrefix;
+
+    public EdgeAppControllerReplyDTO addChannel(EdgeAppControllerRequestDTO req) {
+        req.setEvent("/alg_media_config");
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(REPLY, req.getBoardId() + "-" + req.getMediaName());
+    }
+
+    public EdgeAppFetchResponseDTO fetchChannels(String boardId) {
+        EdgeAppControllerRequestDTO req = new EdgeAppControllerRequestDTO();
+        req.setBoardId(boardId);
+        req.setEvent("/alg_media_fetch");
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        log.info("【发送】查询通道请求:{}", JsonUtils.toJson(req));
+        return waitCache(FETCH, boardId);
+    }
+
+    public EdgeAppDeleteResponseDTO deleteChannel(String boardId, String mediaName) {
+        EdgeAppControllerRequestDTO req = new EdgeAppControllerRequestDTO();
+        req.setBoardId(boardId);
+        req.setEvent("/alg_media_delete");
+        req.setMediaName(mediaName);
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(DELETE, boardId + "-" + mediaName);
+    }
+
+    public EdgeAlgTaskResponseDTO configTask(EdgeAlgTaskConfigRequestDTO req) {
+        req.setEvent("/alg_task_config");
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(TASK, req.getBoardId() + "-" + req.getAlgTaskSession());
+    }
+
+    public EdgeAlgTaskResponseDTO controlTask(String boardId, String algTaskSession, int command) {
+        EdgeAlgTaskCommandRequestDTO req = new EdgeAlgTaskCommandRequestDTO();
+        req.setBoardId(boardId);
+        req.setEvent("/alg_task_control");
+        req.setControlCommand(command);
+        req.setAlgTaskSession(algTaskSession);
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(TASK, boardId + "-" + algTaskSession);
+    }
+
+    public EdgeAlgTaskResponseDTO deleteTask(String boardId, String algTaskSession) {
+        EdgeAlgTaskCommandRequestDTO req = new EdgeAlgTaskCommandRequestDTO();
+        req.setBoardId(boardId);
+        req.setEvent("/alg_task_delete");
+        req.setAlgTaskSession(algTaskSession);
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(TASK, boardId + "-" + algTaskSession);
+    }
+
+    public EdgeAlgTaskListResponseDTO fetchTasks(String boardId) {
+        EdgeAlgTaskFetchRequestDTO req = new EdgeAlgTaskFetchRequestDTO();
+        req.setBoardId(boardId);
+        req.setEvent("/alg_task_fetch");
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(ALG_TASK_LIST, boardId);
+    }
+
+    public AlgAbilityFetchResponseDTO fetchAlgorithmAbility(String boardId) {
+        AlgAbilityFetchRequestDTO req = new AlgAbilityFetchRequestDTO();
+        req.setBoardId(boardId);
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(BoardPingListener.ReplyCache.ALG_ABILITY, boardId + "-alg_ability_fetch");
+    }
+
+
+    public RepositoriesInfoResponseDTO listRepositories() {
+        String url = baseUrl + "/api_repositories_info";
+        ResponseEntity<RepositoriesInfoResponseDTO> response = restTemplate.getForEntity(url, RepositoriesInfoResponseDTO.class);
+        if (response.getStatusCode().is2xxSuccessful()) {
+            return response.getBody();
+        } else {
+            throw new RuntimeException("Failed to list repositories: " + response.getStatusCode());
+        }
+    }
+
+    public RepositoryCreateResponseDTO createRepository(RepositoryCreateRequestDTO request) {
+        String url = baseUrl + "/api_repository_create";
+        ResponseEntity<RepositoryCreateResponseDTO> response = restTemplate.postForEntity(url, request, RepositoryCreateResponseDTO.class);
+        if (response.getStatusCode().is2xxSuccessful()) {
+            return response.getBody();
+        } else {
+            throw new RuntimeException("Failed to create repository: " + response.getStatusCode());
+        }
+    }
+
+    public RepositoryUpdateResponseDTO updateRepository(RepositoryUpdateRequestDTO request) {
+        String url = baseUrl + "/api_repository_update";
+        ResponseEntity<RepositoryUpdateResponseDTO> response = restTemplate.postForEntity(url, request, RepositoryUpdateResponseDTO.class);
+        if (response.getStatusCode().is2xxSuccessful()) {
+            return response.getBody();
+        } else {
+            throw new RuntimeException("Failed to update repository: " + response.getStatusCode());
+        }
+    }
+
+    public RepositoryDeleteResponseDTO deleteRepository(RepositoryDeleteRequestDTO request) {
+        String url = baseUrl + "/api_repository_delete";
+        ResponseEntity<RepositoryDeleteResponseDTO> response = restTemplate.postForEntity(url, request, RepositoryDeleteResponseDTO.class);
+        if (response.getStatusCode().is2xxSuccessful()) {
+            return response.getBody();
+        } else {
+            throw new RuntimeException("Failed to delete repository: " + response.getStatusCode());
+        }
+    }
+
+    public RepositoryFacesResponseDTO getRepositoryFaces(RepositoryFacesRequestDTO request) {
+        String url = baseUrl + "/api_repository_faces";
+        ResponseEntity<RepositoryFacesResponseDTO> response = restTemplate.postForEntity(url, request, RepositoryFacesResponseDTO.class);
+        if (response.getStatusCode().is2xxSuccessful()) {
+            return response.getBody();
+        } else {
+            throw new RuntimeException("Failed to get repository faces: " + response.getStatusCode());
+        }
+    }
+
+    public RegisterFaceResponseDTO registerFace(RegisterFaceRequestDTO request) {
+        String url = baseUrl + "/api_register_face";
+        ResponseEntity<RegisterFaceResponseDTO> response = restTemplate.postForEntity(url, request, RegisterFaceResponseDTO.class);
+        if (response.getStatusCode().is2xxSuccessful()) {
+            return response.getBody();
+        } else {
+            throw new RuntimeException("Failed to register face: " + response.getStatusCode());
+        }
+    }
+
+    public UpdateFaceResponseDTO updateFace(UpdateFaceRequestDTO request) {
+        String url = baseUrl + "/api_face_update";
+        ResponseEntity<UpdateFaceResponseDTO> response = restTemplate.postForEntity(url, request, UpdateFaceResponseDTO.class);
+        if (response.getStatusCode().is2xxSuccessful()) {
+            return response.getBody();
+        } else {
+            throw new RuntimeException("Failed to update face: " + response.getStatusCode());
+        }
+    }
+
+    public DeleteFaceResponseDTO deleteFace(DeleteFaceRequestDTO request) {
+        String url = baseUrl + "/api_face_delete";
+        ResponseEntity<DeleteFaceResponseDTO> response = restTemplate.postForEntity(url, request, DeleteFaceResponseDTO.class);
+        if (response.getStatusCode().is2xxSuccessful()) {
+            return response.getBody();
+        } else {
+            throw new RuntimeException("Failed to delete face: " + response.getStatusCode());
+        }
+    }
+
+    public FaceRecognitionResponseDTO recognizeFace(FaceRecognitionRequestDTO request) {
+        String url = baseUrl + "/api/faceengine/face/1vn";
+        ResponseEntity<FaceRecognitionResponseDTO> response = restTemplate.postForEntity(url, request, FaceRecognitionResponseDTO.class);
+        if (response.getStatusCode().is2xxSuccessful()) {
+            return response.getBody();
+        } else {
+            throw new RuntimeException("Failed to recognize face: " + response.getStatusCode());
+        }
+    }
+
+    public AlgTaskSnapResponseDTO getTaskPreview(String boardId, String algTaskSession) {
+        AlgTaskSnapRequestDTO req = new AlgTaskSnapRequestDTO();
+        req.setBoardId(boardId);
+        req.setAlgTaskSession(algTaskSession);
+
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(BoardPingListener.ReplyCache.TASK_PREVIEW, boardId + "-" + algTaskSession);
+    }
+
+    public AlgScheduleCreateResponseDTO createSchedule(AlgScheduleCreateRequestDTO req) {
+        req.setEvent("/alg_schedule_create");
+        req.convertTimePeriodsToBinary(); // 转换时间段为二进制数据
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(BoardPingListener.ReplyCache.SCHEDULE_CREATE, req.getBoardId() + "-schedule-create");
+    }
+
+    public AlgScheduleFetchResponseDTO fetchSchedules(String boardId) {
+        AlgScheduleFetchRequestDTO req = new AlgScheduleFetchRequestDTO();
+        req.setBoardId(boardId);
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(BoardPingListener.ReplyCache.SCHEDULE_FETCH, boardId + "-schedule-fetch");
+    }
+
+    public AlgScheduleDeleteResponseDTO deleteSchedule(String boardId, Long id) {
+        AlgScheduleDeleteRequestDTO req = new AlgScheduleDeleteRequestDTO();
+        req.setBoardId(boardId);
+        req.setId(id);
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(BoardPingListener.ReplyCache.SCHEDULE_DELETE, boardId + "-schedule-delete-" + id);
+    }
+
+    public AlgSuitCreateResponseDTO createSuitGroup(AlgSuitCreateRequestDTO req) {
+        log.info("收到创建工装组请求:{}", req);
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(BoardPingListener.ReplyCache.SUIT_CREATE, req.getBoardId() + "-suit-create");
+    }
+
+    public AlgSuitGroupRemoveResponseDTO removeSuitGroup(AlgSuitGroupRemoveRequestDTO req) {
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(BoardPingListener.ReplyCache.SUIT_REMOVE, req.getBoardId() + "-suit-remove");
+    }
+
+    public AlgSuitAppendResponseDTO appendSuitTemplate(AlgSuitAppendRequestDTO req) {
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(BoardPingListener.ReplyCache.SUIT_APPEND, req.getBoardId() + "-suit-append");
+    }
+
+    public AlgSuitRemoveResponseDTO removeSuitTemplate(AlgSuitRemoveRequestDTO req) {
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(BoardPingListener.ReplyCache.SUIT_REMOVE_TEMPLATE, req.getBoardId() + "-suit-remove-template");
+    }
+
+    public AlgSuitFetchResponseDTO fetchSuitGroup(String boardId) {
+        AlgSuitFetchRequestDTO req = new AlgSuitFetchRequestDTO();
+        req.setBoardId(boardId);
+        gateway.sendToMqtt("/edge_app_controller", 2, JsonUtils.toJson(req));
+        return waitCache(BoardPingListener.ReplyCache.SUIT_FETCH, boardId + "-suit-fetch");
+    }
+
+    public String getTaskPreviewUrl(String boardId, String algTaskSession) {
+        AlgTaskSnapResponseDTO response = getTaskPreview(boardId, algTaskSession);
+        if (response.isSuccess() && response.getBase64() != null) {
+            try {
+                return saveImage(response.getBase64(), "taskPreview");
+            } catch (IOException e) {
+                log.error("Failed to save task preview image", e);
+                throw new RuntimeException("Failed to save task preview image", e);
+            }
+        } else {
+            throw new RuntimeException("Failed to get task preview: " + response.getResult().getDesc());
+        }
+    }
+
+    private String saveImage(String base64Image, String fileName) throws IOException {
+        // 检查 Base64 数据是否符合预期格式
+        byte[] imageBytes = Base64Utils.decodeFromString(base64Image);
+
+        // 生成文件路径 (使用yyyyMMddHHmmss格式)
+        LocalDateTime now = LocalDateTime.now();
+        String monthPath = now.format(DateTimeFormatter.ofPattern("yyyyMM"));
+        String formattedTime = now.format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
+        int counterSuffix = new AtomicInteger(0).getAndIncrement(); // 获取并递增计数器
+        String filePathWithPrefix = filePath + "/" + monthPath + "/" + fileName + "_" + formattedTime + "_" + counterSuffix + ".jpg";
+        Path path = Paths.get(filePathWithPrefix);
+
+        // 保存图片到文件系统
+        Files.createDirectories(path.getParent());
+        Files.write(path, imageBytes);
+
+        // 返回可访问的 URL
+        return fileDomain + filePrefix + "/" + monthPath + "/" + fileName + "_" + formattedTime + "_" + counterSuffix + ".jpg";
+    }
+
+    private <T> T waitCache(Map<String, T> cache, String key) {
+        for (int i = 0; i < 50; i++) {
+            T resp = cache.get(key);
+            if (resp == null) {
+                try {
+                    TimeUnit.MILLISECONDS.sleep(100);
+                } catch (InterruptedException ignored) {
+                }
+                continue;
+            }
+
+            if (resp instanceof EdgeAppControllerReplyDTO) {
+                EdgeAppControllerReplyDTO reply = (EdgeAppControllerReplyDTO) resp;
+                if (!reply.isSuccess()) {
+                    cache.remove(key);
+                    throw new RuntimeException(reply.getResult().getDesc());
+                }
+            }
+
+            if (resp instanceof AlgAbilityFetchResponseDTO) {
+                AlgAbilityFetchResponseDTO reply = (AlgAbilityFetchResponseDTO) resp;
+                if (!reply.isSuccess()) {
+                    cache.remove(key);
+                    throw new RuntimeException(reply.getResult().getDesc());
+                }
+            }
+
+            cache.remove(key);
+            return resp;
+        }
+        throw new RuntimeException("等待盒子响应超时");
+    }
+}

+ 13 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/config/AppConfig.java

@@ -0,0 +1,13 @@
+package com.usky.ai.service.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class AppConfig {
+    @Bean
+    public RestTemplate restTemplate() {
+        return new RestTemplate();
+    }
+}

+ 48 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/enums/TopListener.java

@@ -0,0 +1,48 @@
+package com.usky.ai.service.enums;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public enum TopListener {
+
+    /**
+     * AI算法识别
+     */
+    // 主动心跳上报
+    BOARD_PING("boardPing", "/board_ping", 1),
+    //新增媒体通道
+    EDGE_APP_CONTROLLER("edgeAppController", "/edge_app_controller", 0),
+    //媒体通道响应结果
+    EDGE_APP_CONTROLLER_REPLY("edgeAppControllerReply", "/edge_app_controller_reply", 1);
+
+
+
+    private final String name;
+    private final String code;
+    private final Integer type;
+
+    TopListener(String name, String code, Integer type) {
+        this.name = name;
+        this.code = code;
+        this.type = type;
+    }
+
+    public static TopListener parse(String code) {
+        for (TopListener t : values()) {
+            if (t.code.equals(code)) return t;
+        }
+        return null;
+    }
+
+    public static List<TopListener> parse(Integer type) {
+        List<TopListener> list = new ArrayList<>();
+        for (TopListener t : values()) {
+            if (t.type.equals(type)) list.add(t);
+        }
+        return list;
+    }
+
+    public String getName() { return name; }
+    public String getCode() { return code; }
+    public Integer getType() { return type; }
+}

+ 60 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/impl/AiDeviceServiceImpl.java

@@ -0,0 +1,60 @@
+package com.usky.ai.service.impl;
+
+import com.usky.ai.domain.AiDevice;
+import com.usky.ai.mapper.AiDeviceMapper;
+import com.usky.ai.service.AiDeviceService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class AiDeviceServiceImpl implements AiDeviceService {
+
+    private final AiDeviceMapper aiDeviceMapper;
+
+    @Autowired
+    public AiDeviceServiceImpl(AiDeviceMapper aiDeviceMapper) {
+        this.aiDeviceMapper = aiDeviceMapper;
+    }
+
+    @Override
+    public void addDevice(AiDevice device) {
+        if (aiDeviceMapper.checkExists(device.getBoardId(), device.getBoardIp())) {
+            throw new IllegalArgumentException("设备已存在, 设备Id 或 设备Ip 已存在");
+        }
+        aiDeviceMapper.insertDevice(device);
+    }
+
+    @Override
+    public void editDevice(AiDevice device) {
+        aiDeviceMapper.updateDevice(device);
+    }
+
+    @Override
+    public void saveOrUpdateDevice(AiDevice device) {
+        aiDeviceMapper.saveOrUpdateDevice(device);
+    }
+
+    @Override
+    public void updateDeviceStatus() {
+        aiDeviceMapper.updateDeviceStatus();
+    }
+
+    @Override
+    public List<AiDevice> getDevices(String boardId, int pageNum, int pageSize) {
+        updateDeviceStatus(); // 在查询之前更新设备的 status 状态
+        int offset = (pageNum - 1) * pageSize;
+        return aiDeviceMapper.getDevices(boardId, offset, pageSize);
+    }
+
+    @Override
+    public long getTotalDevices(String boardId) {
+        return aiDeviceMapper.getTotalDevices(boardId);
+    }
+
+    @Override
+    public void deleteDevice(String boardId) {
+        aiDeviceMapper.deleteDevice(boardId);
+    }
+}

+ 299 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/listener/BoardPingListener.java

@@ -0,0 +1,299 @@
+package com.usky.ai.service.listener;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.usky.ai.controller.web.BoardPingController;
+import com.usky.ai.domain.AiDevice;
+import com.usky.ai.dto.*;
+import com.usky.ai.mapper.AiDeviceMapper;
+import com.usky.ai.service.mqtt.MqttInConfig;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.messaging.Message;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Slf4j
+@Component
+@ConditionalOnProperty(prefix = "mqtt", value = "enabled", havingValue = "true")
+public class BoardPingListener {
+
+    private static final Map<String, Long> LAST_PING = new ConcurrentHashMap<>();
+    private static final long TIMEOUT = 6_000;
+
+    private final AiDeviceMapper aiDeviceMapper;
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @Autowired
+    public BoardPingListener(AiDeviceMapper aiDeviceMapper) {
+        this.aiDeviceMapper = aiDeviceMapper;
+    }
+
+    public static class ReplyCache {
+        public static final Map<String, EdgeAppControllerReplyDTO> REPLY = new ConcurrentHashMap<>();
+        public static final Map<String, EdgeAppFetchResponseDTO> FETCH = new ConcurrentHashMap<>();
+        public static final Map<String, EdgeAppDeleteResponseDTO> DELETE = new ConcurrentHashMap<>();
+        public static final Map<String, EdgeAlgTaskResponseDTO> TASK = new ConcurrentHashMap<>();
+        public static final Map<String, EdgeAlgTaskListResponseDTO> ALG_TASK_LIST = new ConcurrentHashMap<>();
+        public static final Map<String, AlgAbilityFetchResponseDTO> ALG_ABILITY = new ConcurrentHashMap<>();
+        public static final Map<String, RepositoriesInfoResponseDTO> REPOSITORIES_INFO = new ConcurrentHashMap<>();
+        public static final Map<String, RepositoryCreateResponseDTO> REPOSITORY_CREATE = new ConcurrentHashMap<>();
+        public static final Map<String, RepositoryUpdateResponseDTO> REPOSITORY_UPDATE = new ConcurrentHashMap<>();
+        public static final Map<String, RepositoryDeleteResponseDTO> REPOSITORY_DELETE = new ConcurrentHashMap<>();
+        public static final Map<String, AlgTaskSnapResponseDTO> TASK_PREVIEW = new ConcurrentHashMap<>();
+        public static final Map<String, AlgScheduleCreateResponseDTO> SCHEDULE_CREATE = new ConcurrentHashMap<>();
+        public static final Map<String, AlgScheduleFetchResponseDTO> SCHEDULE_FETCH = new ConcurrentHashMap<>();
+        public static final Map<String, AlgScheduleDeleteResponseDTO> SCHEDULE_DELETE = new ConcurrentHashMap<>();
+        public static final Map<String, AlgSuitCreateResponseDTO> SUIT_CREATE = new ConcurrentHashMap<>();
+        public static final Map<String, AlgSuitGroupRemoveResponseDTO> SUIT_REMOVE = new ConcurrentHashMap<>();
+        public static final Map<String, AlgSuitAppendResponseDTO> SUIT_APPEND = new ConcurrentHashMap<>();
+        public static final Map<String, AlgSuitRemoveResponseDTO> SUIT_REMOVE_TEMPLATE = new ConcurrentHashMap<>();
+        public static final Map<String, AlgSuitFetchResponseDTO> SUIT_FETCH = new ConcurrentHashMap<>();
+    }
+
+    @ServiceActivator(inputChannel = MqttInConfig.CHANNEL_NAME_INPUT)
+    public void handleHeartbeat(Message<?> message) {
+        String topic = (String) message.getHeaders().get("mqtt_receivedTopic");
+        if (!"/board_ping".equals(topic)) return;
+
+        String payload = (String) message.getPayload();
+        try {
+            if (payload == null) {
+                log.error("接收到的 payload 为空");
+                return;
+            }
+
+            BoardPingDTO dto = objectMapper.readValue(payload, BoardPingDTO.class);
+            if (dto == null) {
+                log.error("解析心跳消息失败,结果为 null");
+                return;
+            }
+
+            BoardPingController.CACHE.put(dto.getBoardId(), dto);
+            log.info("收到心跳:{}", dto.getBoardId());
+
+            // 将 BoardPingDTO 转换为 AiDevice
+            AiDevice aiDevice = new AiDevice();
+            aiDevice.setBoardId(dto.getBoardId());
+            aiDevice.setBoardIp(dto.getBoardIp());
+            aiDevice.setDescription(null);
+            aiDevice.setStatus(dto.getKey().equals("运行中") ? 1 : 0);
+            aiDevice.setCreateTime(new java.sql.Timestamp(System.currentTimeMillis()));
+            aiDevice.setCreateBy("system");
+            aiDevice.setUpdateTime(new java.sql.Timestamp(System.currentTimeMillis()));
+            aiDevice.setUpdateBy("system");
+
+            aiDeviceMapper.saveOrUpdateDevice(aiDevice);
+        } catch (Exception e) {
+            log.error("解析心跳失败", e);
+        }
+    }
+
+    @ServiceActivator(inputChannel = MqttInConfig.CHANNEL_NAME_INPUT)
+    public void handleAllReplies(Message<?> message) {
+        String topic = (String) message.getHeaders().get("mqtt_receivedTopic");
+        if (!"/edge_app_controller_reply".equals(topic)) return;
+
+        String payload = (String) message.getPayload();
+        log.info("收到 /edge_app_controller_reply 原始消息: {}", payload);
+        try {
+            if (payload == null) {
+                log.error("接收到的 payload 为空");
+                return;
+            }
+
+            JsonNode root = objectMapper.readTree(payload);
+            if (root == null) {
+                log.error("解析 payload 为空");
+                return;
+            }
+
+            JsonNode eventNode = root.get("Event");
+            if (eventNode == null) {
+                log.error("JSON 中缺少 'Event' 字段");
+                return;
+            }
+
+            String event = eventNode.asText();
+            if (event == null || event.isEmpty()) {
+                log.error("Event 字段为空");
+                return;
+            }
+
+            switch (event) {
+                case "/alg_media_config":
+                    EdgeAppControllerReplyDTO configResp = objectMapper.readValue(payload, EdgeAppControllerReplyDTO.class);
+                    String configKey = configResp.getBoardId() + "-" + configResp.getMediaName();
+                    ReplyCache.REPLY.put(configKey, configResp);
+                    log.info("收到通道配置响应:{}", configKey);
+                    break;
+
+                case "/alg_media_fetch":
+                    EdgeAppFetchResponseDTO fetchResp = objectMapper.readValue(payload, EdgeAppFetchResponseDTO.class);
+                    ReplyCache.FETCH.put(fetchResp.getBoardId(), fetchResp);
+                    log.info("收到通道查询响应:{}", fetchResp.getBoardId());
+                    break;
+
+                case "/alg_media_delete":
+                    EdgeAppDeleteResponseDTO deleteResp = objectMapper.readValue(payload, EdgeAppDeleteResponseDTO.class);
+                    String deleteKey = deleteResp.getBoardId() + "-" + deleteResp.getMediaName();
+                    ReplyCache.DELETE.put(deleteKey, deleteResp);
+                    log.info("收到通道删除响应:{}", deleteKey);
+                    break;
+
+                case "/alg_task_config":
+                case "/alg_task_control":
+                case "/alg_task_delete":
+                    EdgeAlgTaskResponseDTO taskResp = objectMapper.readValue(payload, EdgeAlgTaskResponseDTO.class);
+                    String taskKey = taskResp.getBoardId() + "-" + taskResp.getAlgTaskSession();
+                    ReplyCache.TASK.put(taskKey, taskResp);
+                    log.info("收到任务响应:{}-{}", taskResp.getBoardId(), taskResp.getAlgTaskSession());
+                    break;
+
+                case "/alg_task_fetch":
+                    EdgeAlgTaskListResponseDTO taskListResp = objectMapper.readValue(payload, EdgeAlgTaskListResponseDTO.class);
+                    ReplyCache.ALG_TASK_LIST.put(taskListResp.getBoardId(), taskListResp);
+                    log.info("✅ 收到任务列表响应:{},内容数量:{}", taskListResp.getBoardId(), taskListResp.getContent().size());
+                    break;
+
+                case "/alg_ability_fetch":
+                    AlgAbilityFetchResponseDTO abilityResp = objectMapper.readValue(payload, AlgAbilityFetchResponseDTO.class);
+                    String abilityKey = abilityResp.getBoardId() + "-alg_ability_fetch";
+                    ReplyCache.ALG_ABILITY.put(abilityKey, abilityResp);
+                    log.info("收到算法能力获取响应:{}", abilityKey);
+                    break;
+
+                case "/api_repositories_info":
+                    RepositoriesInfoResponseDTO repositoriesInfoResp = objectMapper.readValue(payload, RepositoriesInfoResponseDTO.class);
+                    ReplyCache.REPOSITORIES_INFO.put("repositories_info", repositoriesInfoResp);
+                    log.info("收到人脸库列表响应");
+                    break;
+
+                case "/api_repository_create":
+                    RepositoryCreateResponseDTO createResp = objectMapper.readValue(payload, RepositoryCreateResponseDTO.class);
+                    String createKey = "repository_create-" + createResp.getAlbumName();
+                    ReplyCache.REPOSITORY_CREATE.put(createKey, createResp);
+                    log.info("收到人脸库创建响应:{}", createKey);
+                    break;
+
+                case "/api_repository_update":
+                    RepositoryUpdateResponseDTO updateResp = objectMapper.readValue(payload, RepositoryUpdateResponseDTO.class);
+                    String updateKey = "repository_update-" + updateResp.getAlbumName();
+                    ReplyCache.REPOSITORY_UPDATE.put(updateKey, updateResp);
+                    log.info("收到人脸库更新响应:{}", updateKey);
+                    break;
+
+                case "/api_repository_delete":
+                    RepositoryDeleteResponseDTO repoDeleteResp = objectMapper.readValue(payload, RepositoryDeleteResponseDTO.class);
+                    String repoDeleteKey = "repository_delete-" + repoDeleteResp.getAlbumName();
+                    ReplyCache.REPOSITORY_DELETE.put(repoDeleteKey, repoDeleteResp);
+                    log.info("收到人脸库删除响应:{}", repoDeleteKey);
+                    break;
+
+                case "/alg_task_snap":
+                    AlgTaskSnapResponseDTO snapResp = objectMapper.readValue(payload, AlgTaskSnapResponseDTO.class);
+                    String snapKey = snapResp.getBoardId() + "-" + snapResp.getAlgTaskSession();
+                    ReplyCache.TASK_PREVIEW.put(snapKey, snapResp);
+                    log.info("收到任务预览图响应:{}", snapKey);
+                    break;
+
+                case "/alg_schedule_create":
+                    AlgScheduleCreateResponseDTO scheduleResp = objectMapper.readValue(payload, AlgScheduleCreateResponseDTO.class);
+                    String scheduleKey = scheduleResp.getBoardId() + "-schedule-create";
+                    ReplyCache.SCHEDULE_CREATE.put(scheduleKey, scheduleResp);
+                    log.info("收到计划模板创建响应:{}", scheduleKey);
+                    break;
+
+                case "/alg_schedule_fetch":
+                    AlgScheduleFetchResponseDTO scfetchResp = objectMapper.readValue(payload, AlgScheduleFetchResponseDTO.class);
+                    String scfetchKey = scfetchResp.getBoardId() + "-schedule-fetch";
+                    ReplyCache.SCHEDULE_FETCH.put(scfetchKey, scfetchResp);
+                    log.info("收到计划模板查询响应:{}", scfetchKey);
+                    break;
+
+                case "/alg_schedule_delete":
+                    AlgScheduleDeleteResponseDTO scheduleDeleteResp = objectMapper.readValue(payload, AlgScheduleDeleteResponseDTO.class);
+                    String scheduleDeleteKey = scheduleDeleteResp.getBoardId() + "-schedule-delete-" + scheduleDeleteResp.getScheduleId();
+                    ReplyCache.SCHEDULE_DELETE.put(scheduleDeleteKey, scheduleDeleteResp);
+                    log.info("收到计划模板删除响应:{}", scheduleDeleteKey);
+                    break;
+
+                case "/alg_suit_create":
+                    AlgSuitCreateResponseDTO suitCreateResp = objectMapper.readValue(payload, AlgSuitCreateResponseDTO.class);
+                    String suitCreateKey = suitCreateResp.getBoardId() + "-suit-create";
+                    ReplyCache.SUIT_CREATE.put(suitCreateKey, suitCreateResp);
+                    log.info("收到创建工装组响应:{}", suitCreateKey);
+                    break;
+
+                case "/alg_suit_group_remove":
+                    AlgSuitGroupRemoveResponseDTO suitRemoveResp = objectMapper.readValue(payload, AlgSuitGroupRemoveResponseDTO.class);
+                    String suitRemoveKey = suitRemoveResp.getBoardId() + "-suit-remove";
+                    ReplyCache.SUIT_REMOVE.put(suitRemoveKey, suitRemoveResp);
+                    log.info("收到删除工装组响应:{}", suitRemoveKey);
+                    break;
+
+                case "/alg_suit_append":
+                    AlgSuitAppendResponseDTO suitAppendResp = objectMapper.readValue(payload, AlgSuitAppendResponseDTO.class);
+                    String suitAppendKey = suitAppendResp.getBoardId() + "-suit-append";
+                    ReplyCache.SUIT_APPEND.put(suitAppendKey, suitAppendResp);
+                    log.info("收到上传工装模板响应:{}", suitAppendKey);
+                    break;
+
+                case "/alg_suit_remove":
+                    AlgSuitRemoveResponseDTO suitRemoveTemplateResp = objectMapper.readValue(payload, AlgSuitRemoveResponseDTO.class);
+                    String suitRemoveTemplateKey = suitRemoveTemplateResp.getBoardId() + "-suit-remove-template";
+                    ReplyCache.SUIT_REMOVE_TEMPLATE.put(suitRemoveTemplateKey, suitRemoveTemplateResp);
+                    log.info("收到删除工装模板响应:{}", suitRemoveTemplateKey);
+                    break;
+
+                case "/alg_suit_fetch":
+                    AlgSuitFetchResponseDTO suitFetchResp = objectMapper.readValue(payload, AlgSuitFetchResponseDTO.class);
+                    String suitFetchKey = suitFetchResp.getBoardId() + "-suit-fetch";
+                    ReplyCache.SUIT_FETCH.put(suitFetchKey, suitFetchResp);
+                    log.info("收到工装组信息响应:{}", suitFetchKey);
+                    break;
+
+                default:
+                    log.warn("未知事件类型:{}", event);
+            }
+        } catch (Exception e) {
+            log.error("解析 /edge_app_controller_reply 失败,原始消息: {}", payload, e);
+        }
+    }
+
+    @Scheduled(fixedDelay = 300_000) // 每 5 分钟
+    public void cleanExpiredCache() {
+        long now = System.currentTimeMillis();
+        long timeout = 300_000;
+
+        ReplyCache.REPLY.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.FETCH.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.DELETE.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.TASK.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.ALG_TASK_LIST.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.ALG_ABILITY.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.REPOSITORIES_INFO.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.REPOSITORY_CREATE.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.REPOSITORY_UPDATE.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.REPOSITORY_DELETE.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.TASK_PREVIEW.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.SCHEDULE_CREATE.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.SCHEDULE_FETCH.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.SCHEDULE_DELETE.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.SUIT_CREATE.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.SUIT_REMOVE.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.SUIT_APPEND.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.SUIT_REMOVE_TEMPLATE.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+        ReplyCache.SUIT_FETCH.entrySet().removeIf(e -> now - e.getValue().getTime() > timeout);
+
+        log.info("清理过期缓存:REPLY={} FETCH={} DELETE={} TASK={} TASK_FETCH={}",
+                ReplyCache.REPLY.size(), ReplyCache.FETCH.size(), ReplyCache.DELETE.size(),
+                ReplyCache.TASK.size(), ReplyCache.ALG_TASK_LIST.size());
+    }
+}

+ 50 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/mqtt/MqttBaseConfig.java

@@ -0,0 +1,50 @@
+package com.usky.ai.service.mqtt;
+
+import lombok.Data;
+import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
+import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
+import org.springframework.stereotype.Component;
+
+@ConditionalOnProperty(prefix = "mqtt", value = {"enabled"}, havingValue = "true")
+@Data
+@Component
+@ConfigurationProperties(prefix = "mqtt")
+public class MqttBaseConfig {
+
+    @Value("${mqtt.username}")
+    private String username;
+
+    @Value("${mqtt.password}")
+    private String password;
+
+    @Value("${mqtt.url}")
+    private String hostUrl;
+
+    @Value("${mqtt.sub-topics}")
+    private String msgTopic;
+
+    @Value("${mqtt.keep-alive-interval}")
+    //心跳间隔
+    private int keepAliveInterval;
+    @Value("${mqtt.completionTimeout}")
+    //心跳间隔
+    private int completionTimeout;
+
+
+    @Bean
+    public MqttPahoClientFactory mqttClientFactory() {
+        DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
+        MqttConnectOptions options = new MqttConnectOptions();
+        options.setServerURIs(new String[]{this.getHostUrl()});
+        options.setUserName(this.getUsername());
+        options.setPassword(this.getPassword().toCharArray());
+        factory.setConnectionOptions(options);
+        return factory;
+    }
+
+}

+ 17 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/mqtt/MqttGateway.java

@@ -0,0 +1,17 @@
+package com.usky.ai.service.mqtt;
+
+import org.springframework.integration.annotation.MessagingGateway;
+import org.springframework.integration.mqtt.support.MqttHeaders;
+import org.springframework.messaging.handler.annotation.Header;
+
+@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
+public interface MqttGateway {
+
+    void sendToMqtt(String payload);
+
+    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic, String payload);
+
+    void sendToMqtt(@Header(MqttHeaders.TOPIC) String topic,
+                    @Header(MqttHeaders.QOS) int qos,
+                    String payload);
+}

+ 56 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/mqtt/MqttInConfig.java

@@ -0,0 +1,56 @@
+package com.usky.ai.service.mqtt;
+
+import com.usky.ai.service.enums.TopListener;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.integration.channel.DirectChannel;
+import org.springframework.integration.endpoint.MessageProducerSupport;
+import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
+import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
+import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
+import org.springframework.messaging.MessageChannel;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Configuration
+public class MqttInConfig {
+
+    public static final String CHANNEL_NAME_INPUT = "mqttInboundChannel";
+    public static final String CHANNEL_BOARD_PING = "boardPingChannel";
+
+    @Bean
+    public MessageProducerSupport mqttInbound(MqttPahoClientFactory factory) {
+        // 获取所有需要订阅的主题
+        List<String> topics = TopListener.parse(1).stream()
+                .map(TopListener::getCode)
+                .collect(Collectors.toList()); // 使用 collect(Collectors.toList())
+
+        // 创建适配器实例,订阅所有主题
+        MqttPahoMessageDrivenChannelAdapter adapter =
+                new MqttPahoMessageDrivenChannelAdapter(
+                        "server-in", factory, topics.toArray(new String[0]));
+
+        // 设置消息转换器
+        adapter.setConverter(new DefaultPahoMessageConverter());
+        // 设置 QoS
+        adapter.setQos(1);
+        // 设置完成超时时间
+        adapter.setCompletionTimeout(5000);
+
+        // 设置输出通道
+        adapter.setOutputChannel(mqttInboundChannel());
+
+        return adapter;
+    }
+
+    @Bean(name = CHANNEL_NAME_INPUT)
+    public MessageChannel mqttInboundChannel() {
+        return new DirectChannel();
+    }
+
+    @Bean(name = CHANNEL_BOARD_PING)
+    public MessageChannel boardPingChannel() {
+        return new DirectChannel();
+    }
+}

+ 41 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/mqtt/MqttOutConfig.java

@@ -0,0 +1,41 @@
+package com.usky.ai.service.mqtt;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.integration.annotation.IntegrationComponentScan;
+import org.springframework.integration.annotation.ServiceActivator;
+import org.springframework.integration.channel.DirectChannel;
+import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
+import org.springframework.messaging.MessageChannel;
+import org.springframework.messaging.MessageHandler;
+
+@Configuration
+@IntegrationComponentScan
+@ConditionalOnProperty(prefix = "mqtt", value = {"enabled"}, havingValue = "true")
+public class MqttOutConfig {
+
+    @Autowired
+    public MqttBaseConfig mqttBaseConfig;
+
+    public static final String CHANNEL_NAME_OUT = "mqttOutboundChannel";
+    public static final String MESSAGE_NAME = "messageOut";
+    public static final String DEFAULT_TOPIC = "testTopic";
+
+    @Bean(name = CHANNEL_NAME_OUT)
+    public MessageChannel mqttOutboundChannel() {
+        return new DirectChannel();
+    }
+
+    @Bean(name = MESSAGE_NAME)
+    @ServiceActivator(inputChannel = CHANNEL_NAME_OUT)
+    public MessageHandler outbound() {
+        String clientId = "h-backend-mqtt-out-" + System.currentTimeMillis();
+        MqttPahoMessageHandler messageHandler =
+                new MqttPahoMessageHandler(clientId, mqttBaseConfig.mqttClientFactory());
+        messageHandler.setAsync(true);
+        messageHandler.setDefaultTopic(DEFAULT_TOPIC);
+        return messageHandler;
+    }
+}

+ 34 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/util/GlobalExceptionHandler.java

@@ -0,0 +1,34 @@
+package com.usky.ai.service.util;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import java.time.LocalDateTime;
+import java.util.HashMap;
+import java.util.Map;
+
+@ControllerAdvice
+public class GlobalExceptionHandler {
+
+    @ExceptionHandler(RuntimeException.class)
+    public ResponseEntity<Map<String, Object>> handleRuntimeException(RuntimeException ex) {
+        Map<String, Object> response = new HashMap<>();
+        response.put("timestamp", LocalDateTime.now());
+        response.put("message", ex.getMessage());
+        response.put("status", HttpStatus.NOT_FOUND.value());
+
+        return new ResponseEntity<>(response, HttpStatus.NOT_FOUND);
+    }
+
+    @ExceptionHandler(Exception.class)
+    public ResponseEntity<Map<String, Object>> handleGenericException(Exception ex) {
+        Map<String, Object> response = new HashMap<>();
+        response.put("timestamp", LocalDateTime.now());
+        response.put("message", "An error occurred: " + ex.getMessage());
+        response.put("status", HttpStatus.INTERNAL_SERVER_ERROR.value());
+
+        return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
+    }
+}

+ 15 - 0
service-ai/service-ai-biz/src/main/java/com/usky/ai/service/util/JsonUtils.java

@@ -0,0 +1,15 @@
+package com.usky.ai.service.util;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class JsonUtils {
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+    public static String toJson(Object obj) {
+        try {
+            return MAPPER.writeValueAsString(obj);
+        } catch (JsonProcessingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}

+ 62 - 0
service-ai/service-ai-biz/src/main/resources/mapper/AiDeviceMapper.xml

@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.usky.ai.mapper.AiDeviceMapper">
+
+    <insert id="insertDevice" useGeneratedKeys="false">
+        INSERT INTO ai_device (board_id, board_ip, description, status, create_time, create_by, update_time, update_by)
+        VALUES (#{boardId}, #{boardIp}, #{description}, #{status}, #{createTime}, #{createBy}, #{updateTime}, #{updateBy})
+    </insert>
+
+    <insert id="saveOrUpdateDevice" useGeneratedKeys="false">
+        INSERT INTO ai_device (board_id, board_ip, description, status, create_time, create_by, update_time, update_by)
+        VALUES (#{boardId}, #{boardIp}, #{description}, #{status}, #{createTime}, #{createBy}, #{updateTime}, #{updateBy})
+            ON DUPLICATE KEY UPDATE
+                                 board_ip = VALUES(board_ip),
+                                 description = VALUES(description),
+                                 status = VALUES(status),
+                                 update_time = VALUES(update_time),
+                                 update_by = VALUES(update_by)
+    </insert>
+
+    <update id="updateDevice">
+        UPDATE ai_device
+        SET board_ip = #{boardIp},
+            description = #{description},
+            status = #{status},
+            update_time = #{updateTime},
+            update_by = #{updateBy}
+        WHERE board_id = #{boardId}
+    </update>
+
+    <update id="updateDeviceStatus">
+        UPDATE ai_device SET status = 0 WHERE update_time &lt;= NOW() - INTERVAL 10 MINUTE
+    </update>
+
+    <select id="getDevices" resultType="com.usky.ai.domain.AiDevice">
+        SELECT * FROM ai_device
+        <where>
+            <if test="boardId != null and boardId != ''">
+                AND board_id = #{boardId}
+            </if>
+        </where>
+        LIMIT #{limit} OFFSET #{offset}
+    </select>
+
+    <select id="getTotalDevices" resultType="long">
+        SELECT COUNT(*) FROM ai_device
+        <where>
+            <if test="boardId != null and boardId != ''">
+                AND board_id = #{boardId}
+            </if>
+        </where>
+    </select>
+
+    <delete id="deleteDevice">
+        DELETE FROM ai_device WHERE board_id = #{boardId}
+    </delete>
+
+    <select id="checkExists" resultType="boolean">
+        SELECT COUNT(*) FROM ai_device
+        WHERE board_id = #{boardId} OR board_ip = #{boardIp}
+    </select>
+</mapper>

+ 3 - 2
service-ai/service-ai-biz/src/main/resources/static/tyqw.html

@@ -92,7 +92,7 @@
 
         const requestBody = JSON.stringify({content: content});
 
-        const token = "eyJhbGciOiJIUzUxMiJ9.eyIiOjEwMDMsInVzZXJfaWQiOjIxMywidXNlcl9rZXkiOiJlYzUxODMzNjdmYTk0ODgwOGQwZjEwODEyOWVmNjgwOSIsInVzZXJuYW1lIjoi6LW16YeR6ZuoIn0.zWulXcesI1TRcDmiAHuQ9P2WHDE2l7mDmuunx13TmVl6E5Yvs8nZvu1ddtINdw0lrnnR3Q5lZaRH3mJJTaDhig";
+        const token = "eyJhbGciOiJIUzUxMiJ9.eyIiOjEwMDMsIm9zSW5mbyI6IldpbmRvd3MgMTAiLCJ1c2VyX2lkIjoxLCJ1c2VyX2tleSI6IjRlYTE2MTQ0NWRlMTQ5MzBiYTQ3N2ExN2M3YzVhNmEzIiwiYnJvd3NlckluZm8iOiJDaHJvbWUgMTQiLCJ1c2VybmFtZSI6ImFkbWluIn0.FGLbxHpe0nbIanCy5sCdnMCiAc2SNad5TrneVZsnmPyjz4CMF9Rix39U3IJm7C30gaDFYlCwER2Pqa02NqIXbw";
 
         fetch('/ai/aliTyqw', {
             method: 'POST',
@@ -147,4 +147,5 @@
     })
 </script>
 </body>
-</html>-->
+</html>
+-->

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

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

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

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

+ 32 - 147
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/AlarmDataTransferService.java

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

+ 50 - 38
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/BaseDataTransferService.java

@@ -69,9 +69,21 @@ public class BaseDataTransferService {
         this.idGenerator = new SnowflakeIdGenerator(1L, 1L);
     }
 
-    /**
-     * 获取当前时间字符串
-     */
+    private String convertFloor(String floor) {
+        if (floor == null || floor.trim().isEmpty()) {
+            return floor;
+        }
+        try {
+            int floorNum = Integer.parseInt(floor.trim());
+            if (floorNum < 0) {
+                return "B" + Math.abs(floorNum);
+            }
+        } catch (NumberFormatException e) {
+            log.warn("楼层格式转换失败,原始值: {}", floor);
+        }
+        return floor;
+    }
+
     private String getCurrentTime() {
         return timeFormat.format(new Date());
     }
@@ -156,62 +168,55 @@ public class BaseDataTransferService {
                 vo.setDataPacketID(generateDataPacketID());
             }
             if (vo.getPublishTime() == null) {
-                vo.setPublishTime(timeFormat.format(new Date()));
+                vo.setPublishTime(getCurrentTime());
             }
 
-            // ========== 2. 读取本地图片 ==========
-            String imagePath = "C:\\Users\\f\\Downloads\\45_平面图.jpg";
+            String imagePath = "D://142.jpg";
+            // 将图片文件读取为字节数组
             byte[] imageBytes = Files.readAllBytes(Paths.get(imagePath));
 
-            // 大小校验 ≤5MB
-            if (imageBytes.length > 5 * 1024 * 1024) {
-                System.err.println("文件超过5MB");
-                return false;
-            }
-
-            // 格式校验
-            if (!Arrays.asList("jpg", "jpeg", "png").contains(vo.getFloorFileSuffix().toLowerCase())) {
-                System.err.println("不支持的格式");
+            // 检查文件大小(不超过5MB)
+            if (vo.getFloorFile() != null && imageBytes.length > 5 * 1024 * 1024) {
+                log.error("楼层平面图文件大小超过5MB限制,FileID: {}", vo.getFloorFileID());
                 return false;
             }
 
-            // 获取宽高
-            BufferedImage image = ImageIO.read(new ByteArrayInputStream(imageBytes));
-            int width = image.getWidth();
-            int height = image.getHeight();
-
-            // ========== 3. 时间格式化 ==========
-
-            // ========== 4. 构建标准JSON消息体 ==========
-            Map<String, Object> map = new HashMap<>();
+            HashMap<String, Object> map = new HashMap<>();
             map.put("dataPacketID", vo.getDataPacketID());
             map.put("engineeringID", vo.getEngineeringID());
             map.put("floor", vo.getFloor());
             map.put("floorFileID", vo.getFloorFileID());
             map.put("floorFileName", vo.getFloorFileName());
             map.put("floorFileSuffix", vo.getFloorFileSuffix());
-            map.put("filePixWidth", width);
-            map.put("filePixHeight", height);
+            map.put("filePixWidth", vo.getFilePixWidth());
+            map.put("filePixHeight", vo.getFilePixHeight());
             map.put("floorFile", imageBytes);
             map.put("publishTime", vo.getPublishTime());
-
-            //使用Gson:
             Gson gson = new Gson();
+            // 将字节数组转换为Base64编码
+            JSONObject jsonObject = (JSONObject) JSON.toJSON(vo);
+            vo.setFloorFile(imageBytes);
+//            jsonObject.put("floorFile", imageBytes);
+            if (vo.getFloorFile() != null) {
+                // 使用Base64编码传输二进制数据
+                String base64File = java.util.Base64.getEncoder().encodeToString(vo.getFloorFile());
+                jsonObject.put("floorFile", imageBytes);
+            }
 
-            // ========== 5. MQTT发送(修复版) ==========
+            String json = jsonObject.toJSONString();
+            System.out.println(gson.toJson(map));
             String topic = "base/floorPlane";
-            MqttConnectionTool.MqttGateway gateway = mqttConnectionTool.connectOrRefresh("3101100021", "SIixzph1");
 
-            // 发送JSON字符串
+            log.info("发送楼层平面图信息,Topic: {}, FileID: {}, FileSize: {} bytes",
+                    topic, vo.getFloorFileID(),
+                    vo.getFloorFile() != null ? vo.getFloorFile().length : 0);
+
+            MqttConnectionTool.MqttGateway gateway = mqttConnectionTool.connectOrRefresh(vo.getUserName(), vo.getPassword());
             gateway.sendToMqtt(topic, gson.toJson(map));
 
-            System.out.println("✅ MQTT发送成功 TOPIC: " + topic);
             return true;
-
         } catch (Exception e) {
-            // 打印完整异常
-            e.printStackTrace();
-            System.err.println("❌ 发送失败:" + e.getMessage());
+            log.error("发送楼层平面图信息失败,FileID: {}", vo.getFloorFileID(), e);
             return false;
         }
     }
@@ -242,6 +247,11 @@ public class BaseDataTransferService {
         try {
             Map<Integer, Integer> userIdToName = new HashMap<>();
             userIdToName.put(702, 31);
+            // 30:网络监控摄像机(可兼用平时摄像机)
+            // userIdToName.put(717, 30);
+            // 20:人员统计-掩蔽人数双目统计摄像机
+            // userIdToName.put(719, 20);
+            // 33:人员闯入-人员闯入监测传感器
             userIdToName.put(703, 33);
             userIdToName.put(704, 11);
             userIdToName.put(705, 11);
@@ -254,11 +264,13 @@ public class BaseDataTransferService {
             // userIdToName.put(713, 36);
             userIdToName.put(714, 37);
             userIdToName.put(716, 26);
+            // 柴油发电机蓄电池监测传感器
+            userIdToName.put(720, 12);
 
             HashMap<String, Object> map = new HashMap<>();
             map.put("dataPacketID", generateDataPacketID());
             map.put("engineeringID", vo.getEngineeringID());
-            map.put("floor", vo.getFloor());
+            map.put("floor", convertFloor(vo.getFloor()));
             map.put("floorFileID", 1);
             map.put("sensorID", Integer.parseInt(vo.getDeviceId()));
             map.put("sensorNo", vo.getDeviceUuid());
@@ -275,7 +287,7 @@ public class BaseDataTransferService {
             Gson gson = new Gson();
             String topic = "base/sensorInfo";
             System.out.println(gson.toJson(map));
-//            log.info("发送智能监管物联设施信息,Topic: {}, SensorID: {}", topic, vo.getSensorID());
+            log.info("发送智能监管物联设施信息,Topic: {}, SensorID: {}", topic, vo.getDeviceId());
             MqttConnectionTool.MqttGateway gateway = mqttConnectionTool.connectOrRefresh(vo.getUserName(), vo.getPassword());
             gateway.sendToMqtt(topic, gson.toJson(map));
 

+ 109 - 10
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/CdiDeliveryLogServiceImpl.java

@@ -36,6 +36,7 @@ import org.springframework.beans.factory.annotation.Value;
 import javax.annotation.PostConstruct;
 import javax.imageio.ImageIO;
 import java.awt.image.BufferedImage;
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
@@ -636,19 +637,70 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
         String time = DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS");
 
         List<FloorPlaneVO> result = new ArrayList<>(filteredPlaneList.size());
+        int skipCount = 0;
+
         for (BaseBuildPlane buildPlane : filteredPlaneList) {
-            String planeViewUrl = buildPlane.getPlaneViewUrl();
-            FloorPlaneVO vo = new FloorPlaneVO();
-            checkFileSize(vo, planeViewUrl);
-            fillImageInfo(vo, planeViewUrl);
+            try {
+                String planeViewUrl = buildPlane.getPlaneViewUrl();
 
-            vo.setDataPacketID(generateDataPacketID());
-            vo.setEngineeringID(engineeringId);
-            vo.setFloor(buildPlane.getFloor());
-            vo.setFloorFileID(Long.valueOf(buildPlane.getId()));
-            vo.setPublishTime(time);
+                // ✅ 修复1: 增加详细的处理日志
+                log.info("正在处理楼层平面图:ID={}, Floor={}, URL={}",
+                        buildPlane.getId(), buildPlane.getFloor(), planeViewUrl);
 
-            result.add(vo);
+                FloorPlaneVO vo = new FloorPlaneVO();
+
+                // ✅ 修复2: 单条记录异常不中断整个批次
+                if (StrUtil.isBlank(planeViewUrl)) {
+                    log.warn("楼层平面图URL为空,跳过该记录:ID={}, Floor={}", buildPlane.getId(), buildPlane.getFloor());
+                    skipCount++;
+                    continue;
+                }
+
+                // 读取文件并校验大小
+                checkFileSize(vo, planeViewUrl);
+
+                // ✅ 修复3: 复用已读取的文件数据来获取图片尺寸(避免重复I/O)
+                fillImageInfoWithBytes(vo, planeViewUrl, vo.getFloorFile());
+
+                // 设置基础字段
+                vo.setDataPacketID(generateDataPacketID());
+                vo.setEngineeringID(engineeringId);
+                vo.setFloor(convertFloor(buildPlane.getFloor()));
+                vo.setFloorFileID(Long.valueOf(buildPlane.getId()));
+                vo.setPublishTime(time);
+
+                // ✅ 修复4: 校验关键字段完整性
+                if (vo.getFloorFile() == null || vo.getFloorFile().length == 0) {
+                    log.warn("楼层平面图文件为空,跳过该记录:ID={}, Floor={}", buildPlane.getId(), buildPlane.getFloor());
+                    skipCount++;
+                    continue;
+                }
+
+                // ✅ 修复5: 记录成功处理的详细信息
+                log.info("楼层平面图处理成功:FileID={}, Floor={}, FileName={}, FileSize={}bytes, Pix={}x{}",
+                        vo.getFloorFileID(),
+                        vo.getFloor(),
+                        vo.getFloorFileName(),
+                        vo.getFloorFile().length,
+                        vo.getFilePixWidth(),
+                        vo.getFilePixHeight());
+
+                result.add(vo);
+
+            } catch (BusinessException e) {
+                log.error("楼层平面图业务校验失败,跳过该记录:ID={}, Floor={}, 错误={}",
+                        buildPlane.getId(), buildPlane.getFloor(), e.getMessage());
+                skipCount++;
+            } catch (Exception e) {
+                log.error("楼层平面图处理异常,跳过该记录:ID={}, Floor={}",
+                        buildPlane.getId(), buildPlane.getFloor(), e);
+                skipCount++;
+            }
+        }
+
+        if (skipCount > 0) {
+            log.warn("楼层平面图处理完成:成功{}条,跳过{}条(文件不存在/超限/读取失败等)",
+                    result.size(), skipCount);
         }
 
         return result;
@@ -720,6 +772,21 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
         }
     }
 
+    private String convertFloor(String floor) {
+        if (floor == null || floor.trim().isEmpty()) {
+            return floor;
+        }
+        try {
+            int floorNum = Integer.parseInt(floor.trim());
+            if (floorNum < 0) {
+                return "B" + Math.abs(floorNum);
+            }
+        } catch (NumberFormatException e) {
+            log.warn("楼层格式转换失败,原始值: {}", floor);
+        }
+        return floor;
+    }
+
     private String encodeUrl(String url) throws UnsupportedEncodingException {
         if (StrUtil.isBlank(url)) {
             return url;
@@ -753,6 +820,38 @@ public class CdiDeliveryLogServiceImpl extends AbstractCrudService<CdiDeliveryLo
         return protocol + hostAndPort + encodedPath.toString();
     }
 
+    /**
+     * 填充图片信息(使用已读取的字节数组,避免重复I/O)
+     * @param vo 楼层平面图VO
+     * @param imageUrl 图片URL或本地路径(用于提取文件名和后缀)
+     * @param imageBytes 已读取的图片字节数组
+     */
+    private void fillImageInfoWithBytes(FloorPlaneVO vo, String imageUrl, byte[] imageBytes) {
+        if (StrUtil.isBlank(imageUrl)) {
+            return;
+        }
+
+        // 从URL/路径中提取文件名和后缀
+        String fileName = FileUtil.getName(imageUrl);
+        vo.setFloorFileName(FileUtil.mainName(fileName));
+        vo.setFloorFileSuffix(FileUtil.extName(fileName));
+
+        // ✅ 优化:直接从已读取的byte[]获取尺寸,避免重复读取文件
+        try {
+            if (imageBytes != null && imageBytes.length > 0) {
+                ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes);
+                BufferedImage image = ImageIO.read(bais);
+
+                if (image != null) {
+                    vo.setFilePixWidth(image.getWidth());
+                    vo.setFilePixHeight(image.getHeight());
+                }
+            }
+        } catch (IOException e) {
+            log.error("从字节数组获取图片尺寸失败: {}", imageUrl, e);
+        }
+    }
+
     private void fillImageInfo(FloorPlaneVO vo, String imageUrl) {
         if (StrUtil.isBlank(imageUrl)) {
             return;

+ 266 - 27
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/impl/IotDataTransferService.java

@@ -2,18 +2,14 @@ package com.usky.cdi.service.impl;
 
 import com.alibaba.fastjson.JSON;
 import com.alibaba.fastjson.JSONObject;
+import com.alibaba.nacos.shaded.com.google.gson.Gson;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.StringUtils;
-import com.usky.cdi.domain.CdiDefenseProject;
-import com.usky.cdi.domain.CdiDeliveryLog;
-import com.usky.cdi.domain.DmpDevice;
-import com.usky.cdi.domain.DmpProduct;
-import com.usky.cdi.mapper.CdiDefenseProjectMapper;
-import com.usky.cdi.mapper.CdiDeliveryLogMapper;
-import com.usky.cdi.mapper.DmpDeviceMapper;
-import com.usky.cdi.mapper.DmpProductMapper;
+import com.usky.cdi.domain.*;
+import com.usky.cdi.mapper.*;
 import com.usky.cdi.service.enums.MqttTopics;
 import com.usky.cdi.service.mqtt.MqttConnectionTool;
+import com.usky.cdi.service.util.AirDefenseSimulator;
 import com.usky.cdi.service.util.DeviceDataQuery;
 import com.usky.cdi.service.util.SnowflakeIdGenerator;
 import com.usky.cdi.service.vo.IotDataTransferVO;
@@ -37,6 +33,7 @@ import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.stream.Collectors;
 
 /**
@@ -92,6 +89,9 @@ public class IotDataTransferService {
     @Autowired
     private CdiDeliveryLogMapper cdiDeliveryLogMapper;
 
+    @Autowired
+    private BaseBuildUnitMapper baseBuildUnitMapper;
+
     @PostConstruct
     public void init() {
         this.idGenerator = new SnowflakeIdGenerator(workerId, dataCenterId);
@@ -160,6 +160,9 @@ public class IotDataTransferService {
                     continue;
                 }
 
+                // 为每条数据的监测时间添加毫秒级微差
+                dataEndTime = addTimeOffset(dataEndTime);
+
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
                 Integer value = deviceDataItem.getInteger("leach_status");
                 if (value == null) {
@@ -177,6 +180,7 @@ public class IotDataTransferService {
                 vo.setDataEndTime(dataEndTime);
 
                 try {
+                    log.info("【水浸数据】开始推送,设备:{},数据:{}", deviceId, JSON.toJSONString(vo));
                     sendMqttMessage(MqttTopics.IotInfo.WATER_LEAK.getTopic(), vo, MqttTopics.IotInfo.WATER_LEAK.getDesc(), transferVO.getUsername());
                     result.put("successCount", result.get("successCount") + 1);
                 } catch (Exception e) {
@@ -248,6 +252,9 @@ public class IotDataTransferService {
                     continue;
                 }
 
+                // 为每条数据的监测时间添加毫秒级微差
+                dataEndTime = addTimeOffset(dataEndTime);
+
                 // 检查业务字段是否为空,为空则计入失败(修复:原逻辑静默跳过并误计为成功)
                 boolean fieldMissing = false;
                 switch (deviceType) {
@@ -387,6 +394,88 @@ public class IotDataTransferService {
         return cdiDefenseProject.getTenantId();
     }
 
+    /**
+     * 发送人员统计情况(703)-掩蔽人数
+     *
+     * @return 推送结果,包含成功数和失败数
+     **/
+    public Map<String, Integer> sendPersonCount(IotDataTransferVO transferVO) {
+        Map<String, Integer> result = new HashMap<>();
+        result.put("successCount", 0);
+        result.put("failureCount", 0);
+
+        if (!validateMqttGateway(transferVO.getUsername())) {
+            return result;
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        long startTime = System.currentTimeMillis();
+
+        // 模拟人员统计
+        log.info("开始执行【人员统计】数据推送,工程ID:{}", transferVO.getEngineeringId());
+        try {
+            LambdaQueryWrapper<BaseBuildUnit> buildUnitQuery = new LambdaQueryWrapper<>();
+            buildUnitQuery.eq(BaseBuildUnit::getTenantId, transferVO.getTenantId());
+            List<BaseBuildUnit> buildUnitList = baseBuildUnitMapper.selectList(buildUnitQuery);
+
+            // 修复3:单元为空,打印日志+标记失败,不静默返回
+            if (buildUnitList.isEmpty()) {
+                log.warn("【人员统计】未查询到建筑单元,租户ID:{},推送终止", transferVO.getTenantId());
+                result.put("failureCount", 1);
+                return result;
+            }
+
+            log.info("【人员统计】获取到建筑单元数量:{}", buildUnitList.size());
+            for (BaseBuildUnit buildUnit : buildUnitList) {
+                int peopleCount = AirDefenseSimulator.calculatePeopleCount(now);
+                HeadcountVO headcountVO = new HeadcountVO();
+                headcountVO.setDataPacketID(generateDataPacketID());
+                headcountVO.setEngineeringID(transferVO.getEngineeringId());
+                headcountVO.setSensorValue(peopleCount);
+                // headcountVO.setSensorID(buildUnit.getId());
+
+                // 为每条数据的监测时间添加毫秒级微差
+                LocalDateTime dataEndTime = addTimeOffset(now);
+
+                headcountVO.setDataEndTime(dataEndTime);
+                headcountVO.setPublishTime(getCurrentTime());
+                headcountVO.setUnitName(buildUnit.getUnitName());
+                headcountVO.setFloor(buildUnit.getFloor());
+
+                try {
+                    log.info("【人员统计】开始推送,单元:{},数据:{}", buildUnit.getUnitName(), JSON.toJSONString(headcountVO));
+                    sendMqttMessage(
+                            MqttTopics.IotInfo.PERSON.getTopic(),
+                            headcountVO,
+                            MqttTopics.IotInfo.PERSON.getDesc(),
+                            transferVO.getUsername()
+                    );
+                    // 添加2S延时
+                    Thread.sleep(2000);
+                    result.put("successCount", result.get("successCount") + 1);
+                    log.info("【人员统计】推送成功,单元:{},人数:{}", buildUnit.getUnitName(), peopleCount);
+                } catch (Exception e) {
+                    log.warn("【人员统计】数据推送失败,单元:{},异常:{}", buildUnit.getUnitName(), e.getMessage());
+                    result.put("failureCount", result.get("failureCount") + 1);
+                }
+            }
+
+            // 修复4:统计推送完成,打印汇总日志
+            log.info("【人员统计】推送完成,成功:{},失败:{}", result.get("successCount"), result.get("failureCount"));
+            // 可选:和闯入一样保存汇总日志(推荐加上)
+            long endTime = System.currentTimeMillis();
+            saveLog(transferVO, now, startTime, endTime, buildUnitList.size(),
+                    result.get("successCount"), result.get("failureCount"), 0,
+                    result.get("successCount") > 0 ? 1 : 0, SecurityUtils.getUsername());
+
+        } catch (Exception e) {
+            // 修复5:全局异常兜底,绝不丢失
+            log.error("【人员统计】推送发生全局异常", e);
+            result.put("failureCount", result.getOrDefault("failureCount", 0) + 1);
+        }
+        return result;
+    }
+
     /**
      * 发送人员闯入情况(703)
      *
@@ -403,7 +492,6 @@ public class IotDataTransferService {
 
         LocalDateTime now = LocalDateTime.now();
         long startTime = System.currentTimeMillis();
-
         try {
             List<JSONObject> deviceData = deviceDataQuery.getDeviceData(transferVO);
             Integer deviceType = transferVO.getDeviceType();
@@ -434,6 +522,9 @@ public class IotDataTransferService {
                     continue;
                 }
 
+                // 处理数据时间
+                dataEndTime = addTimeOffset(dataEndTime);
+
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
 
                 PersonPresenceVO vo = new PersonPresenceVO();
@@ -445,6 +536,7 @@ public class IotDataTransferService {
                 vo.setSensorValue(0); // 固定值(根据业务需求)
 
                 try {
+                    log.info("【人员闯入】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(vo));
                     sendMqttMessage(
                             MqttTopics.IotInfo.PERSON_PRESENCE.getTopic(),
                             vo,
@@ -533,6 +625,7 @@ public class IotDataTransferService {
                     result.put("failureCount", result.get("failureCount") + 1);
                     continue;
                 }
+                dataEndTime = addTimeOffset(dataEndTime);
 
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
 
@@ -560,6 +653,7 @@ public class IotDataTransferService {
                 vo.setTotalPower(totalPower);
 
                 try {
+                    log.info("【人防用电负荷情况】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(vo));
                     sendMqttMessage(
                             MqttTopics.IotInfo.ELECTRICITY_LOAD.getTopic(),
                             vo,
@@ -625,6 +719,8 @@ public class IotDataTransferService {
         tempVO.setSensorValue(value);
         tempVO.setDataEndTime(dataEndTime);
         System.out.println("监测时间:" + dataEndTime);
+
+        log.info("【温度】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(tempVO));
         sendMqttMessage(MqttTopics.IotInfo.TEMP.getTopic(), tempVO, MqttTopics.IotInfo.TEMP.getDesc(), username);
     }
 
@@ -650,6 +746,8 @@ public class IotDataTransferService {
         humidityVO.setPublishTime(getCurrentTime());
         humidityVO.setSensorValue(value);
         humidityVO.setDataEndTime(dataEndTime);
+
+        log.info("【湿度】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(humidityVO));
         sendMqttMessage(MqttTopics.IotInfo.HUMIDITY.getTopic(), humidityVO, MqttTopics.IotInfo.HUMIDITY.getDesc(), username);
     }
 
@@ -675,6 +773,8 @@ public class IotDataTransferService {
         oxygenVO.setPublishTime(getCurrentTime());
         oxygenVO.setSensorValue(value);
         oxygenVO.setDataEndTime(dataEndTime);
+
+        log.info("【氧气浓度】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(oxygenVO));
         sendMqttMessage(MqttTopics.IotInfo.OXYGEN.getTopic(), oxygenVO, MqttTopics.IotInfo.OXYGEN.getDesc(), username);
     }
 
@@ -700,6 +800,8 @@ public class IotDataTransferService {
         coVO.setPublishTime(getCurrentTime());
         coVO.setSensorValue(value);
         coVO.setDataEndTime(dataEndTime);
+
+        log.info("【一氧化碳浓度】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(coVO));
         sendMqttMessage(MqttTopics.IotInfo.CO.getTopic(), coVO, MqttTopics.IotInfo.CO.getDesc(), username);
     }
 
@@ -723,8 +825,14 @@ public class IotDataTransferService {
         co2VO.setSensorID(deviceId);
         co2VO.setEngineeringID(engineeringID);
         co2VO.setPublishTime(getCurrentTime());
-        co2VO.setSensorValue(value);
+        // 将value除以10000并保留三位小数
+        Float processedValue = new java.math.BigDecimal(value / 10000f)
+                .setScale(3, java.math.RoundingMode.HALF_UP)
+                .floatValue();
+        co2VO.setSensorValue(processedValue);
         co2VO.setDataEndTime(dataEndTime);
+
+        log.info("【二氧化碳浓度】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(co2VO));
         sendMqttMessage(MqttTopics.IotInfo.CO2.getTopic(), co2VO, MqttTopics.IotInfo.CO2.getDesc(), username);
     }
 
@@ -912,6 +1020,7 @@ public class IotDataTransferService {
                     result.put("failureCount", result.get("failureCount") + 1);
                     continue;
                 }
+                dataEndTime = addTimeOffset(dataEndTime);
 
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
                 Integer value = deviceDataItem.getInteger("wy");
@@ -930,6 +1039,7 @@ public class IotDataTransferService {
                 vo.setSensorValue(value == 0 ? 0 : 1);
 
                 try {
+                    log.info("【位移数据】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(vo));
                     sendMqttMessage(MqttTopics.IotInfo.DEVIATION.getTopic(), vo, MqttTopics.IotInfo.DEVIATION.getDesc(), transferVO.getUsername());
                     result.put("successCount", result.get("successCount") + 1);
                 } catch (Exception e) {
@@ -981,7 +1091,7 @@ public class IotDataTransferService {
 
         try {
 
-            log.info("开始推送集水井水位数据,设备类型:{},设备数量:{},获取到的数据条数:{}",
+            log.info("开始推送集水井水位数据,设备类型:{},设备数量:{},获取到的数据条数:{}",
                     deviceType, totalDevices, deviceData.size());
 
             if (deviceData.isEmpty()) {
@@ -997,6 +1107,7 @@ public class IotDataTransferService {
                     result.put("failureCount", result.get("failureCount") + 1);
                     continue;
                 }
+                dataEndTime = addTimeOffset(dataEndTime);
 
                 Integer deviceId = deviceDataItem.getIntValue("device_id");
                 Double value = deviceDataItem.getDouble("sensorValue");
@@ -1015,6 +1126,7 @@ public class IotDataTransferService {
                 vo.setSensorValue(value);
 
                 try {
+                    log.info("【水位数据】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(vo));
                     sendMqttMessage(MqttTopics.IotInfo.SEWAGE_LEVEL.getTopic(), vo, MqttTopics.IotInfo.SEWAGE_LEVEL.getDesc(), transferVO.getUsername());
                     result.put("successCount", result.get("successCount") + 1);
                 } catch (Exception e) {
@@ -1042,6 +1154,92 @@ public class IotDataTransferService {
         }
     }
 
+    /**
+     * 发送发电机蓄电池监测数据(720)
+     *
+     * @return 推送结果,包含成功数和失败数
+     **/
+    public Map<String, Integer> sendGeneratorMonitoring(IotDataTransferVO transferVO) {
+        Map<String, Integer> result = new HashMap<>();
+        result.put("successCount", 0);
+        result.put("failureCount", 0);
+
+        if (!validateMqttGateway(transferVO.getUsername())) {
+            return result;
+        }
+
+        LocalDateTime now = LocalDateTime.now();
+        long startTime = System.currentTimeMillis();
+        long endTime;
+
+        List<JSONObject> deviceData = deviceDataQuery.getDeviceData(transferVO);
+        Integer deviceType = transferVO.getDeviceType();
+        Integer totalDevices = transferVO.getDevices().size();
+
+        try {
+
+            log.info("开始推送【战时柴油发电机每日启动时长】数据,设备类型:{},设备数量:{},获取到的数据条数:{}",
+                    deviceType, totalDevices, deviceData.size());
+
+            if (deviceData.isEmpty()) {
+                log.warn("没有获取到战时柴油发电机每日启动时长数据!设备类型:{}", deviceType);
+                result.put("failureCount", totalDevices);
+                return result;
+            }
+
+            Long engineeringId = transferVO.getEngineeringId();
+            for (JSONObject deviceDataItem : deviceData) {
+                LocalDateTime dataEndTime = parseDataTime(deviceDataItem);
+                if (dataEndTime == null) {
+                    result.put("failureCount", result.get("failureCount") + 1);
+                    continue;
+                }
+                dataEndTime = addTimeOffset(dataEndTime);
+
+                Integer deviceId = deviceDataItem.getIntValue("device_id");
+                Integer startDuration = deviceDataItem.get("startDuration") == null ? 0 : deviceDataItem.getIntValue("startDuration");
+
+                GeneratorMonitoringVO vo = new GeneratorMonitoringVO();
+                vo.setDataPacketID(generateDataPacketID());
+                vo.setSensorID(deviceId);
+                vo.setEngineeringID(engineeringId);
+                vo.setPublishTime(getCurrentTime());
+                vo.setDataEndTime(dataEndTime);
+                vo.setSensorDate(dataEndTime.toLocalDate());
+                vo.setStartDuration(startDuration);
+                if (deviceDataItem.containsKey("batteryVoltage")) {
+                    vo.setBatteryVoltage(deviceDataItem.getDouble("batteryVoltage"));
+                }
+
+                try {
+                    log.info("【战时柴油发电机每日启动时长】开始推送,设备ID:{},数据:{}", deviceId, JSON.toJSONString(vo));
+                    sendMqttMessage(MqttTopics.IotInfo.ALTERNATOR_STARTUP_TIME.getTopic(), vo, MqttTopics.IotInfo.ALTERNATOR_STARTUP_TIME.getDesc(), transferVO.getUsername());
+                    result.put("successCount", result.get("successCount") + 1);
+                } catch (Exception e) {
+                    log.warn("设备{}的战时柴油发电机每日启动时长数据推送失败:{}", deviceId, e.getMessage());
+                    result.put("failureCount", result.get("failureCount") + 1);
+                }
+            }
+
+            log.info("战时柴油发电机每日启动时长数据推送完成,设备类型:{},成功:{},失败:{}",
+                    deviceType, result.get("successCount"), result.get("failureCount"));
+
+            endTime = System.currentTimeMillis();
+            saveLog(transferVO, now, startTime, endTime, totalDevices,
+                    result.get("successCount"), result.get("failureCount"),
+                    totalDevices - result.get("successCount") - result.get("failureCount"), 1, SecurityUtils.getUsername());
+            return result;
+        } catch (Exception e) {
+            log.error("战时柴油发电机每日启动时长数据推送发生异常", e);
+            result.put("failureCount", transferVO.getDevices().size());
+            endTime = System.currentTimeMillis();
+            saveLog(transferVO, now, startTime, endTime, totalDevices,
+                    result.get("successCount"), result.get("failureCount"),
+                    totalDevices - result.get("successCount") - result.get("failureCount"), 0, SecurityUtils.getUsername());
+            return result;
+        }
+    }
+
     /**
      * 同步设备数据
      * @param tenantId 租户ID
@@ -1083,6 +1281,7 @@ public class IotDataTransferService {
             transferVO.setDevices(devices);
             transferVO.setEngineeringId(engineeringId);
             transferVO.setUsername(username); // 保存当前任务的用户名
+            transferVO.setTenantId(tenantId);
             transferList.add(transferVO);
         });
 
@@ -1128,8 +1327,13 @@ public class IotDataTransferService {
                     result = sendWaterLeak(transferVO);
                     break;
                 case 703:
+                    if (transferVO.getEngineeringId() == 3101100024L) {
+                        result = sendPersonCount(transferVO);
+                        break;
+                    }
                     result = sendPersonPresence(transferVO);
                     break;
+                case 705:
                 case 704:
                     if (tenantId == 1205) {
                         // 设置默认值,避免空指针
@@ -1151,6 +1355,12 @@ public class IotDataTransferService {
                 case 716:
                     result = sendWaterLevel(transferVO);
                     break;
+                case 719:
+                    result = sendPersonCount(transferVO);
+                    break;
+                case 720:
+                    result = sendGeneratorMonitoring(transferVO);
+                    break;
                 default:
                     log.debug("不支持的设备类型:{}", deviceType);
                     continue;
@@ -1268,7 +1478,10 @@ public class IotDataTransferService {
         } else if (raw instanceof Number) {
             dataTime = ((Number) raw).longValue();
         } else if (raw instanceof String) {
-            try { dataTime = Long.parseLong((String) raw); } catch (Exception ignored) {}
+            try {
+                dataTime = Long.parseLong((String) raw);
+            } catch (Exception ignored) {
+            }
         }
         if (dataTime == null) return null;
         return LocalDateTime.ofInstant(Instant.ofEpochMilli(dataTime), ZoneId.systemDefault());
@@ -1305,8 +1518,7 @@ public class IotDataTransferService {
      * 序列化楼层平面图VO(将 floorFile byte[] 转为 Base64 字符串)
      */
     private String serializeFloorPlaneVO(com.usky.cdi.service.vo.info.FloorPlaneVO vo) {
-        com.alibaba.fastjson.JSONObject jsonObject = new com.alibaba.fastjson.JSONObject();
-
+        Map<String, Object> jsonObject = new HashMap<>();
         jsonObject.put("dataPacketID", vo.getDataPacketID());
         jsonObject.put("engineeringID", vo.getEngineeringID());
         jsonObject.put("floor", vo.getFloor());
@@ -1316,23 +1528,50 @@ public class IotDataTransferService {
         jsonObject.put("filePixWidth", vo.getFilePixWidth());
         jsonObject.put("filePixHeight", vo.getFilePixHeight());
         jsonObject.put("publishTime", vo.getPublishTime());
-
-        // 关键:将 byte[] 转为 Base64 字符串
-        if (vo.getFloorFile() != null) {
-            String base64File = java.util.Base64.getEncoder().encodeToString(vo.getFloorFile());
-            jsonObject.put("floorFile", base64File);
-            log.info("平面图文件转换Base64成功,FileID: {}, 原始大小: {} bytes, Base64长度: {}",
-                    vo.getFloorFileID(), vo.getFloorFile().length, base64File.length());
-        } else {
-            jsonObject.put("floorFile", "");
-            log.warn("平面图文件为空,FileID: {}", vo.getFloorFileID());
-        }
-
-        return jsonObject.toJSONString();
+        jsonObject.put("floorFile", vo.getFloorFile());
+        Gson gson = new Gson();
+        return gson.toJson(jsonObject);
     }
 
     public void allData(Long engineeringId, String username, String password) {
         Integer tenantId = 0;
         synchronizeDeviceData(tenantId, engineeringId, username, password);
     }
+
+    /**
+     * 为监测时间添加随机偏移(用于模拟真实采集场景)
+     * @param originalTime 原始监测时间
+     * @return 偏移后的时间
+     */
+    private LocalDateTime addTimeOffset(LocalDateTime originalTime) {
+        if (originalTime == null) {
+            return null;
+        }
+
+        // 获取当前时间作为上限
+        LocalDateTime now = LocalDateTime.now();
+
+        // 如果原始时间已经是未来时间,先修正为当前时间
+        if (originalTime.isAfter(now)) {
+            log.warn("检测到未来时间的监测数据,已修正为当前时间:{}", originalTime);
+            originalTime = now;
+        }
+
+        // 随机秒偏移:0、1、2 秒(向前偏移,使用减法)
+        int secondsOffset = ThreadLocalRandom.current().nextInt(0, 3);
+        // 随机毫秒偏移:0 ~ 999 毫秒(向前偏移,使用减法)
+        int millisOffset = ThreadLocalRandom.current().nextInt(0, 1000);
+
+        LocalDateTime offsetTime = originalTime.minusSeconds(secondsOffset)
+                .minusNanos(millisOffset * 1_000_000L);
+
+        // 最终校验:确保不会晚于当前时间
+        if (offsetTime.isAfter(now)) {
+            log.warn("偏移后时间仍然晚于当前时间,已修正:原始={}, 偏移后={}, 当前={}",
+                    originalTime, offsetTime, now);
+            return now;
+        }
+
+        return offsetTime;
+    }
 }

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

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

+ 12 - 2
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/util/DeviceDataQuery.java

@@ -12,7 +12,6 @@ import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
 import java.text.DecimalFormat;
@@ -502,7 +501,12 @@ public class DeviceDataQuery {
                         double value = FixedWaterLevelGenerator.getSensorValue(deviceId1);
                         simData.put("sensorValue", formatNumber(value, FORMAT_1_2));
                         break;
-
+                    // case 719:
+                    //     break;
+                    case 720:
+                        simData.put("startDuration", 0);
+                        simData.put("batteryVoltage", randomDouble261_266());
+                        break;
                     default:
                         log.warn("未知设备类型:{},无法生成模拟数据", deviceType);
                         continue;
@@ -516,6 +520,12 @@ public class DeviceDataQuery {
         return simulationList;
     }
 
+    private double randomDouble261_266() {
+        Random random = new Random();
+        int intVal = 2610 + random.nextInt(51);
+        return intVal / 100.0;
+    }
+
     private void addNoiseToNumericFields(JSONObject data, Integer deviceType) {
         Map<String, Double> noiseConfig = getNoiseConfigByDeviceType(deviceType);
 

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

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

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

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

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

@@ -93,7 +93,6 @@ public class AlarmMessageVO<T extends Number> implements Serializable {
      * 查询告警表 base_alarm 时新增告警取 alarm_type 字段,更新数据则取 handle_time 字段
      * 时间型(带毫秒),格式为 yyyy-MM-DD hh :mm :ss.SSS
      **/
-    @JSONField(format = "yyyy-MM-dd HH:mm:ss.SSS")
     private String alarmUpdateTime;
 
     /**
@@ -113,7 +112,6 @@ public class AlarmMessageVO<T extends Number> implements Serializable {
      * 获取当前时间
      * 时间型(带毫秒),格式为 yyyy-MM-DD hh :mm :ss.SSS
      **/
-    @JSONField(format = "yyyy-MM-dd HH:mm:ss.SSS")
     private String publishTime;
 
     /** 告警数据字段 必填、通用

+ 49 - 0
service-cdi/service-cdi-biz/src/main/java/com/usky/cdi/service/vo/info/GeneratorMonitoringVO.java

@@ -0,0 +1,49 @@
+package com.usky.cdi.service.vo.info;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import com.usky.cdi.service.vo.base.BaseEnvMonitorPushVO;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.time.LocalDate;
+
+/**
+ * 发电机蓄电池监测数据
+ * @author fyc
+ * @email yuchuan.fu@chinausky.com
+ * @date 2026/6/18
+ */
+@EqualsAndHashCode(callSuper = true)
+@Data
+public class GeneratorMonitoringVO extends BaseEnvMonitorPushVO {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 监测日期(必填)
+     * 类型:Date(yyyy-MM-dd)
+     */
+    @JSONField(format = "yyyy-MM-dd")
+    private LocalDate sensorDate;
+
+    /**
+     * 启动时长(必填)
+     * 类型:Int,长度4,单位分钟
+     */
+    private Integer startDuration;
+
+    /**
+     * 蓄电池电压(非必填)
+     * 类型:Float(2,2),单位V
+     */
+    private Double batteryVoltage;
+
+    @Override
+    public Number getSensorValue() {
+        return null;
+    }
+
+    @Override
+    protected void validateSensorValue() {
+
+    }
+}

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

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

+ 9 - 12
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsAnalysisController.java

@@ -19,14 +19,14 @@ public class EmsAnalysisController {
 
     @Autowired
     private EmsAnalysisService emsAnalysisService;
-    
+
     /**
      * 获取当前日期的默认值
      */
     private String getCurrentDefaultTimeDimension() {
         return "D"; // 按日统计
     }
-    
+
     /**
      * 获取当前日期的默认值(根据时间维度返回对应格式)
      */
@@ -80,16 +80,13 @@ public class EmsAnalysisController {
         return ApiResult.success(emsAnalysisService.getTrendCategory(projectId, timeDimension, timeValue, energyTypeId));
     }
 
-    @GetMapping("/region")
-    public ApiResult<EmsRegionAnalysisResponse> getRegionAnalysis(
-            @RequestParam(required = false) Long projectId,
-            @RequestParam(required = false) String regionIds,
-            @RequestParam(required = false) String timeDimension,
-            @RequestParam(required = false) String timeValue,
-            @RequestParam(required = false) Long energyTypeId) {
-        if (timeDimension == null) timeDimension = getCurrentDefaultTimeDimension();
-        if (timeValue == null) timeValue = getCurrentDefaultTimeValue(timeDimension);
-        return ApiResult.success(emsAnalysisService.getRegionAnalysis(projectId, regionIds, timeDimension, timeValue, energyTypeId));
+    /**
+     * 区域能耗时序分析
+     * 按分项编码与区域列表,查询各区域在时间轴上的能耗数据
+     */
+    @PostMapping("/region")
+    public ApiResult<EmsAverageResponseVO> getRegionAnalysis(@RequestBody EmsAverageRequest request) {
+        return ApiResult.success(emsAnalysisService.getRegionalAnalysis(request));
     }
 
     @PostMapping("/compare")

+ 1 - 1
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsModelController.java

@@ -32,7 +32,7 @@ public class EmsModelController {
 //    }
 
     /**
-     * 能源类型列表(电、水、气
+     * 能源类型列表(与概览 /overview/item 同源:租户产品关联的能源类型
      */
     @GetMapping("/energy-type/list")
     public ApiResult<List<EmsEnergyTypeVO>> getEnergyTypeList() {

+ 33 - 35
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsOverviewController.java

@@ -1,6 +1,7 @@
 package com.usky.ems.controller.web;
 
 import com.usky.common.core.bean.ApiResult;
+import com.usky.common.security.utils.SecurityUtils;
 import com.usky.ems.service.EmsOverviewService;
 import com.usky.ems.service.vo.EmsProjectResponse;
 import com.usky.ems.service.vo.EmsSummaryRequest;
@@ -10,6 +11,9 @@ import com.usky.ems.service.vo.EmsOverviewEnergyItemVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
+import java.util.Map;
+
 /**
  * 能源能耗 - 概览模块 API
  * 建筑排名等涉及空间树的数据来自 base_space(见 {@link com.usky.ems.service.impl.EmsOverviewServiceImpl})。
@@ -26,7 +30,8 @@ public class EmsOverviewController {
      * 获取项目信息
      */
     @GetMapping("/project")
-    public ApiResult<EmsProjectResponse> getProject(@RequestParam(required = false) Integer projectId) {
+    public ApiResult<EmsProjectResponse> getProject(
+            @RequestParam(required = false) Integer projectId) {
         return ApiResult.success(emsOverviewService.getProject(projectId));
     }
 
@@ -43,65 +48,60 @@ public class EmsOverviewController {
      * 获取概览页能源类型条目(如电/水/气)
      */
     @GetMapping("/item")
-    public ApiResult<java.util.List<EmsOverviewEnergyItemVO>> queryOverviewItem(
-            @RequestParam(required = false) Long projectId) {
-        return ApiResult.success(emsOverviewService.queryOverviewItem(projectId));
+    public ApiResult<List<EmsOverviewEnergyItemVO>> queryOverviewItem() {
+        return ApiResult.success(emsOverviewService.queryOverviewItem());
     }
 
     /**
      * 获取概览页设备系统统计(每个系统下设备数量)
      */
     @GetMapping("/device-info")
-    public ApiResult<java.util.List<EmsOverviewDeviceSystemStatVO>> queryOverviewDeviceInfo(
+    public ApiResult<List<EmsOverviewDeviceSystemStatVO>> queryOverviewDeviceInfo(
             @RequestParam(required = false) Long projectId) {
         return ApiResult.success(emsOverviewService.queryOverviewDeviceInfo(projectId));
     }
 
     /**
-     * 分类能耗统计(模拟数据
+     * 分类能耗统计(按能源类型关联产品,调用 TSDB 分项汇总
      * 参数说明:
-     * - dateType:时间类型(1-日,2-月,3-年等,暂仅作为占位,不影响当前模拟结果)
-     * - itemCode:能耗条目编码
-     * - energyType:能耗类型
-     * - projectId:项目 ID(可选,当前实现未做真实查询,仅占位)
+     * - dateType:时间类型(1-日,2-月,3-年)
+     * - energyType:能源类型(1电 2水 3冷 4热;分项 identifier 从 ems_energy_item_code 根节点解析)
+     * - projectId:项目 ID(可选,不传则取当前租户第一个项目;用于查询折算系数)
      */
     @GetMapping("/classification-energy")
-    public ApiResult<java.util.Map<String, Object>> queryClassificationEnergy(
+    public ApiResult<Map<String, Object>> queryClassificationEnergy(
             @RequestParam Integer dateType,
-            @RequestParam String itemCode,
             @RequestParam Integer energyType,
             @RequestParam(required = false) Long projectId) {
-        return ApiResult.success(emsOverviewService.queryClassificationEnergy(dateType, itemCode, energyType, projectId));
+        return ApiResult.success(emsOverviewService.queryClassificationEnergy(dateType, energyType, projectId));
     }
 
     /**
-     * 能耗用能趋势(模拟数据
+     * 能耗用能趋势(按能源类型关联产品,调用 TSDB 分项按时间粒度汇总,并与去年同期对比
      * 参数说明:
-     * - dateType:时间类型(1-日,2-月,3-年)
-     * - itemCode:能耗条目编码
-     * - spaceId:空间ID(可选,当前仍为模拟数据占位
+     * - dateType:时间类型(1-日/小时,2-月/天,3-年/月
+     * - identifier:分项字段编码
+     * - energyType:能源类型(1电 2水 3冷 4热
      */
     @GetMapping("/energy-trend")
-    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryEnergyTrend(
+    public ApiResult<List<Map<String, Object>>> queryEnergyTrend(
             @RequestParam Integer dateType,
-            @RequestParam String itemCode,
-            @RequestParam(required = false) Long spaceId) {
-        return ApiResult.success(emsOverviewService.queryEnergyTrend(dateType, itemCode, spaceId));
+            @RequestParam Integer energyType) {
+        return ApiResult.success(emsOverviewService.queryEnergyTrend(dateType, energyType));
     }
 
     /**
-     * 建筑能耗分析(模拟数据
+     * 建筑能耗分析(按建筑排名
      * 参数说明:
      * - dateType:时间类型(1-日,2-月,3-年)
-     * - itemCode:能耗条目编码
-     * - spaceId:空间ID(可选;下钻时与 base_space 子节点一致
+     * - itemCode:能耗分项字段编码
+     * - energyType:能源类型(1电 2水 3冷 4热
      */
     @GetMapping("/building-ranking")
-    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryBuildingRanking(
+    public ApiResult<List<Map<String, Object>>> queryBuildingRanking(
             @RequestParam Integer dateType,
-            @RequestParam String itemCode,
-            @RequestParam(required = false) Long spaceId) {
-        return ApiResult.success(emsOverviewService.queryBuildingRanking(dateType, itemCode, spaceId));
+            @RequestParam Integer energyType) {
+        return ApiResult.success(emsOverviewService.queryBuildingRanking(dateType, energyType));
     }
 
     /**
@@ -111,7 +111,7 @@ public class EmsOverviewController {
      * - projectId:项目ID(可选,不传则取第一个项目)
      */
     @GetMapping("/top")
-    public ApiResult<java.util.Map<String, Object>> queryOverviewTop(
+    public ApiResult<Map<String, Object>> queryOverviewTop(
             @RequestParam Integer dateType,
             @RequestParam(required = false) Long projectId) {
         return ApiResult.success(emsOverviewService.queryOverviewTop(dateType, projectId));
@@ -121,14 +121,12 @@ public class EmsOverviewController {
      * 分项能耗占比
      * 参数说明:
      * - dateType:时间类型(1-日,2-月,3-年)
-     * - itemCode:分项编码(作为父项,展开下级分项)
-     * - projectId:项目ID(可选,不传则取第一个项目)
+     * - energyType:能源类型(1电 2水 3冷 4热;根分项从 ems_energy_item_code 解析)
      */
     @GetMapping("/item-ratio")
-    public ApiResult<java.util.List<java.util.Map<String, Object>>> queryItemRatio(
+    public ApiResult<List<Map<String, Object>>> queryItemRatio(
             @RequestParam Integer dateType,
-            @RequestParam String itemCode,
-            @RequestParam(required = false) Long projectId) {
-        return ApiResult.success(emsOverviewService.queryItemRatio(dateType, itemCode, projectId));
+            @RequestParam Integer energyType) {
+        return ApiResult.success(emsOverviewService.queryItemRatio(dateType, energyType));
     }
 }

+ 60 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsProjectController.java

@@ -1,12 +1,19 @@
 package com.usky.ems.controller.web;
 
 import com.usky.common.core.bean.ApiResult;
+import com.usky.ems.domain.DmpDevice;
+import com.usky.ems.domain.EmsEnergyItemCode;
+import com.usky.ems.service.EmsEnergyItemCodeService;
 import com.usky.ems.service.EmsProjectService;
+import com.usky.ems.service.vo.EmsDeviceItemCodeSaveRequest;
+import com.usky.ems.service.vo.EmsProductEnergyTypeSaveRequest;
 import com.usky.ems.service.vo.EmsProjectResponse;
 import com.usky.ems.service.vo.EmsProjectSaveRequest;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.List;
+
 /**
  * 项目(ems_project)新增、修改、删除
  * 基础路径前缀:/project(网关再加 /prod-api/service-ems)
@@ -17,6 +24,59 @@ public class EmsProjectController {
 
     @Autowired
     private EmsProjectService emsProjectService;
+    @Autowired
+    private EmsEnergyItemCodeService emsEnergyItemCodeService;
+
+    /**
+     * 能耗类型列表(SELECT * FROM ems_energy_item_code WHERE parent_code = 0)
+     */
+    @GetMapping("/energy-type/list")
+    public ApiResult<List<EmsEnergyItemCode>> listEnergyTypes(
+            @RequestParam(required = false) Integer energyType) {
+        return ApiResult.success(emsEnergyItemCodeService.listEnergyTypes(energyType));
+    }
+
+    /**
+     * 能耗分项列表(parent_code 为能耗类型编码)
+     */
+    @GetMapping("/energy-item/list")
+    public ApiResult<List<EmsEnergyItemCode>> listEnergySubItems() {
+        return ApiResult.success(emsEnergyItemCodeService.listEnergySubItems());
+    }
+
+    /**
+     * 区域设备信息:按空间及其子空间关联网关,查询 dmp_device 子设备列表
+     */
+    @GetMapping("/area/device/list")
+    public ApiResult<List<DmpDevice>> listAreaDevices(@RequestParam Integer spaceId) {
+        return ApiResult.success(emsProjectService.listAreaDevices(spaceId));
+    }
+
+    /**
+     * 区域能耗分项类型信息:按区域设备关联的分项编码查询 ems_energy_item_code
+     */
+    @GetMapping("/area/device-item-code/list")
+    public ApiResult<List<EmsEnergyItemCode>> listAreaDeviceItemCodes(@RequestParam Integer spaceId) {
+        return ApiResult.success(emsProjectService.listAreaDeviceItemCodes(spaceId));
+    }
+
+    /**
+     * 产品关联的能源类型(ems_product_energy_type)
+     */
+    @PostMapping("/product-energy-type")
+    public ApiResult<Void> saveProductEnergyTypes(@RequestBody EmsProductEnergyTypeSaveRequest request) {
+        emsProjectService.saveProductEnergyTypes(request);
+        return ApiResult.success();
+    }
+
+    /**
+     * 设备关联的能源分项(ems_device_item_code)
+     */
+    @PostMapping("/device-item-code")
+    public ApiResult<Void> saveDeviceItemCodes(@RequestBody EmsDeviceItemCodeSaveRequest request) {
+        emsProjectService.saveDeviceItemCodes(request);
+        return ApiResult.success();
+    }
 
     @PostMapping
     public ApiResult<EmsProjectResponse> create(@RequestBody EmsProjectSaveRequest request) {

+ 58 - 0
service-ems/service-ems-biz/src/main/java/com/usky/ems/controller/web/EmsReportController.java

@@ -4,6 +4,7 @@ import com.usky.common.core.bean.ApiResult;
 import com.usky.ems.service.EmsReportService;
 import com.usky.ems.service.EmsModelService;
 import com.usky.ems.service.vo.*;
+import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 
@@ -109,4 +110,61 @@ public class EmsReportController {
             HttpServletResponse response) {
         emsReportService.exportCollection(deviceIds, attributePointIds, timeDimension, timeValue, format, response);
     }
+
+    /**
+     * 1. 能源报表
+     * 支持按设备列表、功能点、时间类型(时/日/月/年)聚合数据
+     *
+     * @param request 能源报表请求参数
+     * @return 能源报表响应数据
+     */
+    @PostMapping("/energy")
+    @ApiOperation("能源报表")
+    public ApiResult<EnergyReportResponse> getEnergyReport(@RequestBody EnergyReportRequest request) {
+        return ApiResult.success(emsReportService.getEnergyReport(request));
+    }
+
+    /**
+     * 能源报表导出 Excel(请求体与 /report/energy 一致)
+     */
+    @PostMapping("/energy/export")
+    @ApiOperation("能源报表导出")
+    public void exportEnergyReport(@RequestBody EnergyReportRequest request, HttpServletResponse response) {
+        emsReportService.exportEnergyReport(request, response);
+    }
+
+    /**
+     * 2. 分项报表
+     * 支持按空间ID、分项编码、日期类型统计能耗
+     *
+     * @param request 分项报表请求参数
+     * @return 分项报表响应数据
+     */
+    @PostMapping("/item")
+    @ApiOperation("分项报表")
+    public ApiResult<ItemReportResponse> getItemReport(@RequestBody ItemReportRequest request) {
+        return ApiResult.success(emsReportService.getItemReport(request));
+    }
+
+    /**
+     * 3. 区域报表
+     * 支持按区域、分项、日期类型统计能耗
+     *
+     * @param request 区域报表请求参数
+     * @return 区域报表响应数据
+     */
+    @PostMapping("/space")
+    @ApiOperation("区域报表")
+    public ApiResult<SpaceReportResponse> getSpaceReport(@RequestBody SpaceReportRequest request) {
+        return ApiResult.success(emsReportService.getSpaceReport(request));
+    }
+
+    /**
+     * 区域报表导出 Excel(请求体与 /report/space 一致)
+     */
+    @PostMapping("/space/export")
+    @ApiOperation("区域报表导出")
+    public void exportSpaceReport(@RequestBody SpaceReportRequest request, HttpServletResponse response) {
+        emsReportService.exportSpaceReport(request, response);
+    }
 }

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels