Commit 9f8c6a944c80afa3b60b547d5eb92afc7c288d80

Authored by alwayssuper
2 parents 6d059eae 4cefea68

Merge branch 'feature/iot' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into origin/feature/iot

# Conflicts:
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
Showing 74 changed files with 3398 additions and 361 deletions
yudao-dependencies/pom.xml
@@ -67,7 +67,7 @@ @@ -67,7 +67,7 @@
67 <netty.version>4.1.116.Final</netty.version> 67 <netty.version>4.1.116.Final</netty.version>
68 <mqtt.version>1.2.5</mqtt.version> 68 <mqtt.version>1.2.5</mqtt.version>
69 <pf4j-spring.version>0.9.0</pf4j-spring.version> 69 <pf4j-spring.version>0.9.0</pf4j-spring.version>
70 - <vertx.version>4.5.11</vertx.version> 70 + <vertx.version>4.5.13</vertx.version>
71 <!-- 三方云服务相关 --> 71 <!-- 三方云服务相关 -->
72 <commons-io.version>2.17.0</commons-io.version> 72 <commons-io.version>2.17.0</commons-io.version>
73 <commons-compress.version>1.27.1</commons-compress.version> 73 <commons-compress.version>1.27.1</commons-compress.version>
yudao-framework/yudao-spring-boot-starter-mq/pom.xml
@@ -36,6 +36,7 @@ @@ -36,6 +36,7 @@
36 <dependency> 36 <dependency>
37 <groupId>org.apache.rocketmq</groupId> 37 <groupId>org.apache.rocketmq</groupId>
38 <artifactId>rocketmq-spring-boot-starter</artifactId> 38 <artifactId>rocketmq-spring-boot-starter</artifactId>
  39 + <optional>true</optional>
39 </dependency> 40 </dependency>
40 </dependencies> 41 </dependencies>
41 42
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/IotDeviceUpstreamApi.java
@@ -71,6 +71,15 @@ public interface IotDeviceUpstreamApi { @@ -71,6 +71,15 @@ public interface IotDeviceUpstreamApi {
71 @PostMapping(PREFIX + "/add-topology") 71 @PostMapping(PREFIX + "/add-topology")
72 CommonResult<Boolean> addDeviceTopology(@Valid @RequestBody IotDeviceTopologyAddReqDTO addReqDTO); 72 CommonResult<Boolean> addDeviceTopology(@Valid @RequestBody IotDeviceTopologyAddReqDTO addReqDTO);
73 73
  74 + // TODO @芋艿:考虑 http 认证
  75 + /**
  76 + * 认证 Emqx 连接
  77 + *
  78 + * @param authReqDTO 认证 Emqx 连接 DTO
  79 + */
  80 + @PostMapping(PREFIX + "/authenticate-emqx-connection")
  81 + CommonResult<Boolean> authenticateEmqxConnection(@Valid @RequestBody IotDeviceEmqxAuthReqDTO authReqDTO);
  82 +
74 // ========== 插件相关 ========== 83 // ========== 插件相关 ==========
75 84
76 /** 85 /**
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceEmqxAuthReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
  2 +
  3 +import jakarta.validation.constraints.NotEmpty;
  4 +import lombok.Data;
  5 +
  6 +// TODO @芋艿:要不要继承 IotDeviceUpstreamAbstractReqDTO
  7 +/**
  8 + * IoT 认证 Emqx 连接 Request DTO
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +public class IotDeviceEmqxAuthReqDTO {
  14 +
  15 + /**
  16 + * 客户端 ID
  17 + */
  18 + @NotEmpty(message = "客户端 ID 不能为空")
  19 + private String clientId;
  20 +
  21 + /**
  22 + * 用户名
  23 + */
  24 + @NotEmpty(message = "用户名不能为空")
  25 + private String username;
  26 +
  27 + /**
  28 + * 密码
  29 + */
  30 + @NotEmpty(message = "密码不能为空")
  31 + private String password;
  32 +
  33 +}
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
@@ -51,5 +51,20 @@ public interface ErrorCodeConstants { @@ -51,5 +51,20 @@ public interface ErrorCodeConstants {
51 51
52 // ========== 插件实例 1-050-007-000 ========== 52 // ========== 插件实例 1-050-007-000 ==========
53 53
  54 + // ========== 固件相关 1-050-008-000 ==========
  55 +
  56 + ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在");
  57 + ErrorCode OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE = new ErrorCode(1_050_008_001, "产品版本号重复");
  58 +
  59 + // TODO @li:1_050_008_100,这样有点间隔?
  60 + ErrorCode OTA_UPGRADE_TASK_NOT_EXISTS = new ErrorCode(1_050_008_002, "升级任务不存在");
  61 + ErrorCode OTA_UPGRADE_TASK_NAME_DUPLICATE = new ErrorCode(1_050_008_003, "升级任务名称重复");
  62 + ErrorCode OTA_UPGRADE_TASK_PARAMS_INVALID = new ErrorCode(1_050_008_004, "升级任务参数无效");
  63 + ErrorCode OTA_UPGRADE_TASK_CANNOT_CANCEL = new ErrorCode(1_050_008_005, "升级任务不能取消");
  64 +
  65 + // TODO @li:1_050_008_200
  66 + ErrorCode OTA_UPGRADE_RECORD_NOT_EXISTS = new ErrorCode(1_050_008_006, "升级记录不存在");
  67 + ErrorCode OTA_UPGRADE_RECORD_DUPLICATE = new ErrorCode(1_050_008_007, "升级记录重复");
  68 + ErrorCode OTA_UPGRADE_RECORD_CANNOT_RETRY = new ErrorCode(1_050_008_008, "升级记录不能重试");
54 69
55 } 70 }
56 \ No newline at end of file 71 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/pom.xml
@@ -75,10 +75,11 @@ @@ -75,10 +75,11 @@
75 <!-- <groupId>org.eclipse.paho</groupId> &lt;!&ndash; MQTT &ndash;&gt;--> 75 <!-- <groupId>org.eclipse.paho</groupId> &lt;!&ndash; MQTT &ndash;&gt;-->
76 <!-- <artifactId>org.eclipse.paho.client.mqttv3</artifactId>--> 76 <!-- <artifactId>org.eclipse.paho.client.mqttv3</artifactId>-->
77 <!-- </dependency>--> 77 <!-- </dependency>-->
78 - <!-- 工具类相关 --> 78 + <!-- 消息队列相关 -->
79 <dependency> 79 <dependency>
80 - <groupId>cn.iocoder.boot</groupId>  
81 - <artifactId>yudao-spring-boot-starter-mq</artifactId> 80 + <groupId>org.apache.rocketmq</groupId>
  81 + <artifactId>rocketmq-spring-boot-starter</artifactId>
  82 + <optional>true</optional>
82 </dependency> 83 </dependency>
83 84
84 <dependency> 85 <dependency>
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java
@@ -4,15 +4,14 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -4,15 +4,14 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*; 4 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
5 import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceUpstreamService; 5 import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceUpstreamService;
6 import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService; 6 import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService;
  7 +import jakarta.annotation.Resource;
7 import org.springframework.validation.annotation.Validated; 8 import org.springframework.validation.annotation.Validated;
8 import org.springframework.web.bind.annotation.RestController; 9 import org.springframework.web.bind.annotation.RestController;
9 10
10 -import jakarta.annotation.Resource;  
11 -  
12 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 11 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
13 12
14 /** 13 /**
15 - * * 设备数据 Upstream 上行 API 实现类 14 + * * 设备数据 Upstream 上行 API 实现类
16 */ 15 */
17 @RestController 16 @RestController
18 @Validated 17 @Validated
@@ -61,6 +60,12 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi { @@ -61,6 +60,12 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
61 return success(true); 60 return success(true);
62 } 61 }
63 62
  63 + @Override
  64 + public CommonResult<Boolean> authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
  65 + boolean result = deviceUpstreamService.authenticateEmqxConnection(authReqDTO);
  66 + return success(result);
  67 + }
  68 +
64 // ========== 插件相关 ========== 69 // ========== 插件相关 ==========
65 70
66 @Override 71 @Override
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java
@@ -7,8 +7,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -7,8 +7,8 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 8 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceDownstreamReqVO; 9 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceDownstreamReqVO;
10 -import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;  
11 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO; 10 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.control.IotDeviceUpstreamReqVO;
  11 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
12 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 12 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
13 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 13 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
14 import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceDownstreamService; 14 import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceDownstreamService;
@@ -177,4 +177,12 @@ public class IotDeviceController { @@ -177,4 +177,12 @@ public class IotDeviceController {
177 return success(true); 177 return success(true);
178 } 178 }
179 179
  180 + // TODO @haohao:是不是默认详情接口,不返回 secret,然后这个接口,用于统一返回。然后接口名可以更通用一点。
  181 + @GetMapping("/mqtt-connection-params")
  182 + @Operation(summary = "获取 MQTT 连接参数")
  183 + @PreAuthorize("@ss.hasPermission('iot:device:mqtt-connection-params')")
  184 + public CommonResult<IotDeviceMqttConnectionParamsRespVO> getMqttConnectionParams(@RequestParam("deviceId") Long deviceId) {
  185 + return success(deviceService.getMqttConnectionParams(deviceId));
  186 + }
  187 +
180 } 188 }
181 \ No newline at end of file 189 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceMqttConnectionParamsRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
  2 +
  3 +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
  4 +import com.alibaba.excel.annotation.ExcelProperty;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import lombok.Data;
  7 +
  8 +@Schema(description = "管理后台 - IoT 设备 MQTT 连接参数 Response VO")
  9 +@Data
  10 +@ExcelIgnoreUnannotated
  11 +public class IotDeviceMqttConnectionParamsRespVO {
  12 +
  13 + @Schema(description = "MQTT 客户端 ID", example = "24602")
  14 + @ExcelProperty("MQTT 客户端 ID")
  15 + private String mqttClientId;
  16 +
  17 + @Schema(description = "MQTT 用户名", example = "芋艿")
  18 + @ExcelProperty("MQTT 用户名")
  19 + private String mqttUsername;
  20 +
  21 + @Schema(description = "MQTT 密码")
  22 + @ExcelProperty("MQTT 密码")
  23 + private String mqttPassword;
  24 +
  25 +}
0 \ No newline at end of file 26 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDeviceRespVO.java
@@ -79,18 +79,6 @@ public class IotDeviceRespVO { @@ -79,18 +79,6 @@ public class IotDeviceRespVO {
79 @ExcelProperty("设备密钥") 79 @ExcelProperty("设备密钥")
80 private String deviceSecret; 80 private String deviceSecret;
81 81
82 - @Schema(description = "MQTT 客户端 ID", example = "24602")  
83 - @ExcelProperty("MQTT 客户端 ID")  
84 - private String mqttClientId;  
85 -  
86 - @Schema(description = "MQTT 用户名", example = "芋艿")  
87 - @ExcelProperty("MQTT 用户名")  
88 - private String mqttUsername;  
89 -  
90 - @Schema(description = "MQTT 密码")  
91 - @ExcelProperty("MQTT 密码")  
92 - private String mqttPassword;  
93 -  
94 @Schema(description = "认证类型(如一机一密、动态注册)", example = "2") 82 @Schema(description = "认证类型(如一机一密、动态注册)", example = "2")
95 @ExcelProperty("认证类型(如一机一密、动态注册)") 83 @ExcelProperty("认证类型(如一机一密、动态注册)")
96 private String authType; 84 private String authType;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaFirmwareController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.ota;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwarePageReqVO;
  7 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareRespVO;
  8 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareCreateReqVO;
  9 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareUpdateReqVO;
  10 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
  11 +import cn.iocoder.yudao.module.iot.service.ota.IotOtaFirmwareService;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.tags.Tag;
  14 +import jakarta.annotation.Resource;
  15 +import jakarta.validation.Valid;
  16 +import org.springframework.security.access.prepost.PreAuthorize;
  17 +import org.springframework.validation.annotation.Validated;
  18 +import org.springframework.web.bind.annotation.*;
  19 +
  20 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  21 +
  22 +@Validated
  23 +@RestController
  24 +@Tag(name = "管理后台 - IoT OTA 固件")
  25 +@RequestMapping("/iot/ota-firmware")
  26 +public class IotOtaFirmwareController {
  27 +
  28 + @Resource
  29 + private IotOtaFirmwareService otaFirmwareService;
  30 +
  31 + @PostMapping("/create")
  32 + @Operation(summary = "创建 OTA 固件")
  33 + @PreAuthorize("@ss.hasPermission('iot:ota-firmware:create')")
  34 + public CommonResult<Long> createOtaFirmware(@Valid @RequestBody IotOtaFirmwareCreateReqVO createReqVO) {
  35 + return success(otaFirmwareService.createOtaFirmware(createReqVO));
  36 + }
  37 +
  38 + @PutMapping("/update")
  39 + @Operation(summary = "更新 OTA 固件")
  40 + @PreAuthorize("@ss.hasPermission('iot:ota-firmware:update')")
  41 + public CommonResult<Boolean> updateOtaFirmware(@Valid @RequestBody IotOtaFirmwareUpdateReqVO updateReqVO) {
  42 + otaFirmwareService.updateOtaFirmware(updateReqVO);
  43 + return success(true);
  44 + }
  45 +
  46 + @GetMapping("/get")
  47 + @Operation(summary = "获得 OTA 固件")
  48 + @PreAuthorize("@ss.hasPermission('iot:ota-firmware:query')")
  49 + public CommonResult<IotOtaFirmwareRespVO> getOtaFirmware(@RequestParam("id") Long id) {
  50 + IotOtaFirmwareDO otaFirmware = otaFirmwareService.getOtaFirmware(id);
  51 + return success(BeanUtils.toBean(otaFirmware, IotOtaFirmwareRespVO.class));
  52 + }
  53 +
  54 + @GetMapping("/page")
  55 + @Operation(summary = "获得 OTA 固件分页")
  56 + @PreAuthorize("@ss.hasPermission('iot:ota-firmware:query')")
  57 + public CommonResult<PageResult<IotOtaFirmwareRespVO>> getOtaFirmwarePage(@Valid IotOtaFirmwarePageReqVO pageReqVO) {
  58 + PageResult<IotOtaFirmwareDO> pageResult = otaFirmwareService.getOtaFirmwarePage(pageReqVO);
  59 + return success(BeanUtils.toBean(pageResult, IotOtaFirmwareRespVO.class));
  60 + }
  61 +
  62 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaUpgradeRecordController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.ota;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record.IotOtaUpgradeRecordPageReqVO;
  7 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record.IotOtaUpgradeRecordRespVO;
  8 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeRecordDO;
  9 +import cn.iocoder.yudao.module.iot.service.ota.IotOtaUpgradeRecordService;
  10 +import io.swagger.v3.oas.annotations.Operation;
  11 +import io.swagger.v3.oas.annotations.Parameter;
  12 +import io.swagger.v3.oas.annotations.tags.Tag;
  13 +import jakarta.annotation.Resource;
  14 +import jakarta.validation.Valid;
  15 +import org.springframework.security.access.prepost.PreAuthorize;
  16 +import org.springframework.validation.annotation.Validated;
  17 +import org.springframework.web.bind.annotation.*;
  18 +
  19 +import java.util.Map;
  20 +
  21 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  22 +
  23 +@Validated
  24 +@RestController
  25 +@Tag(name = "管理后台 - OTA 升级记录")
  26 +@RequestMapping("/iot/ota-upgrade-record")
  27 +public class IotOtaUpgradeRecordController {
  28 +
  29 + @Resource
  30 + private IotOtaUpgradeRecordService upgradeRecordService;
  31 +
  32 + @GetMapping("/get-statistics")
  33 + @Operation(summary = "固件升级设备统计")
  34 + @PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:query')")
  35 + @Parameter(name = "firmwareId", description = "固件编号", required = true, example = "1024")
  36 + public CommonResult<Map<Integer, Long>> getOtaUpgradeRecordStatistics(
  37 + @RequestParam(value = "firmwareId") Long firmwareId) {
  38 + return success(upgradeRecordService.getOtaUpgradeRecordStatistics(firmwareId));
  39 + }
  40 +
  41 + @GetMapping("/get-count")
  42 + @Operation(summary = "获得升级记录分页 tab 数量")
  43 + @PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:query')")
  44 + public CommonResult<Map<Integer, Long>> getOtaUpgradeRecordCount(
  45 + @Valid IotOtaUpgradeRecordPageReqVO pageReqVO) {
  46 + return success(upgradeRecordService.getOtaUpgradeRecordCount(pageReqVO));
  47 + }
  48 +
  49 + @GetMapping("/page")
  50 + @Operation(summary = "获得升级记录分页")
  51 + @PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:query')")
  52 + public CommonResult<PageResult<IotOtaUpgradeRecordRespVO>> getUpgradeRecordPage(
  53 + @Valid IotOtaUpgradeRecordPageReqVO pageReqVO) {
  54 + PageResult<IotOtaUpgradeRecordDO> pageResult = upgradeRecordService.getUpgradeRecordPage(pageReqVO);
  55 + return success(BeanUtils.toBean(pageResult, IotOtaUpgradeRecordRespVO.class));
  56 + }
  57 +
  58 + @GetMapping("/get")
  59 + @Operation(summary = "获得升级记录")
  60 + @PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:query')")
  61 + @Parameter(name = "id", description = "升级记录编号", required = true, example = "1024")
  62 + public CommonResult<IotOtaUpgradeRecordRespVO> getUpgradeRecord(@RequestParam("id") Long id) {
  63 + IotOtaUpgradeRecordDO upgradeRecord = upgradeRecordService.getUpgradeRecord(id);
  64 + return success(BeanUtils.toBean(upgradeRecord, IotOtaUpgradeRecordRespVO.class));
  65 + }
  66 +
  67 + // TODO @li:使用 Putmapping
  68 + @PostMapping("/retry")
  69 + @Operation(summary = "重试升级记录")
  70 + @PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:retry')")
  71 + @Parameter(name = "id", description = "升级记录编号", required = true, example = "1024")
  72 + public CommonResult<Boolean> retryUpgradeRecord(@RequestParam("id") Long id) {
  73 + upgradeRecordService.retryUpgradeRecord(id);
  74 + return success(true);
  75 + }
  76 +
  77 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaUpgradeTaskController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.ota;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task.IotOtaUpgradeTaskPageReqVO;
  7 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task.IotOtaUpgradeTaskRespVO;
  8 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task.IotOtaUpgradeTaskSaveReqVO;
  9 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
  10 +import cn.iocoder.yudao.module.iot.service.ota.IotOtaUpgradeTaskService;
  11 +import io.swagger.v3.oas.annotations.Operation;
  12 +import io.swagger.v3.oas.annotations.Parameter;
  13 +import io.swagger.v3.oas.annotations.tags.Tag;
  14 +import jakarta.annotation.Resource;
  15 +import jakarta.validation.Valid;
  16 +import org.springframework.security.access.prepost.PreAuthorize;
  17 +import org.springframework.validation.annotation.Validated;
  18 +import org.springframework.web.bind.annotation.*;
  19 +
  20 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  21 +
  22 +@Validated
  23 +@RestController
  24 +@Tag(name = "管理后台 - OTA升级任务")
  25 +@RequestMapping("/iot/ota-upgrade-task")
  26 +public class IotOtaUpgradeTaskController {
  27 +
  28 + @Resource
  29 + private IotOtaUpgradeTaskService upgradeTaskService;
  30 +
  31 + @PostMapping("/create")
  32 + @Operation(summary = "创建升级任务")
  33 + @PreAuthorize(value = "@ss.hasPermission('iot:ota-upgrade-task:create')")
  34 + public CommonResult<Long> createUpgradeTask(@Valid @RequestBody IotOtaUpgradeTaskSaveReqVO createReqVO) {
  35 + return success(upgradeTaskService.createUpgradeTask(createReqVO));
  36 + }
  37 +
  38 + @PostMapping("/cancel")
  39 + @Operation(summary = "取消升级任务")
  40 + @Parameter(name = "id", description = "升级任务编号", required = true)
  41 + @PreAuthorize(value = "@ss.hasPermission('iot:ota-upgrade-task:cancel')")
  42 + public CommonResult<Boolean> cancelUpgradeTask(@RequestParam("id") Long id) {
  43 + upgradeTaskService.cancelUpgradeTask(id);
  44 + return success(true);
  45 + }
  46 +
  47 + // TODO @li:get 接口,不是 @RequestBody 哈
  48 + @GetMapping("/page")
  49 + @Operation(summary = "获得升级任务分页")
  50 + @PreAuthorize(value = "@ss.hasPermission('iot:ota-upgrade-task:query')")
  51 + public CommonResult<PageResult<IotOtaUpgradeTaskRespVO>> getUpgradeTaskPage(@Valid @RequestBody IotOtaUpgradeTaskPageReqVO pageReqVO) {
  52 + PageResult<IotOtaUpgradeTaskDO> pageResult = upgradeTaskService.getUpgradeTaskPage(pageReqVO);
  53 + return success(BeanUtils.toBean(pageResult, IotOtaUpgradeTaskRespVO.class));
  54 + }
  55 +
  56 + @GetMapping("/get")
  57 + @Operation(summary = "获得升级任务")
  58 + @Parameter(name = "id", description = "升级任务编号", required = true, example = "1024")
  59 + @PreAuthorize(value = "@ss.hasPermission('iot:ota-upgrade-task:query')")
  60 + public CommonResult<IotOtaUpgradeTaskRespVO> getUpgradeTask(@RequestParam("id") Long id) {
  61 + IotOtaUpgradeTaskDO upgradeTask = upgradeTaskService.getUpgradeTask(id);
  62 + return success(BeanUtils.toBean(upgradeTask, IotOtaUpgradeTaskRespVO.class));
  63 + }
  64 +
  65 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCommonReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import lombok.Data;
  6 +
  7 +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
  8 +
  9 +// TODO @li:因为 create 和 update 可以公用的字段比较少,建议不用 IotOtaFirmwareCommonReqVO
  10 +@Data
  11 +@Schema(description = "管理后台 - OTA固件信息 Request VO")
  12 +public class IotOtaFirmwareCommonReqVO {
  13 +
  14 + /**
  15 + * 固件名称
  16 + */
  17 + @NotEmpty(message = "固件名称不能为空")
  18 + @Schema(description = "固件名称", requiredMode = REQUIRED, example = "智能开关固件")
  19 + private String name;
  20 +
  21 + /**
  22 + * 固件描述
  23 + */
  24 + @Schema(description = "固件描述", example = "某品牌型号固件,测试用")
  25 + private String description;
  26 +
  27 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
  9 +
  10 +// TODO @li:中英文之间,有个空格。中文写作习惯哈。
  11 +@Schema(description = "管理后台 - OTA固件创建 Request VO")
  12 +@Data
  13 +public class IotOtaFirmwareCreateReqVO extends IotOtaFirmwareCommonReqVO {
  14 +
  15 + // TODO @li:因为有了注解,注释可以不写哈
  16 + // TODO @li:swagger 注解,写在 validator 注解之前,保持项目统一哈。
  17 + /**
  18 + * 版本号
  19 + */
  20 + @NotEmpty(message = "版本号不能为空")
  21 + @Schema(description = "版本号", requiredMode = REQUIRED, example = "1.0.0")
  22 + private String version;
  23 +
  24 + /**
  25 + * 产品编号
  26 + * <p>
  27 + * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
  28 + */
  29 + @NotNull(message = "产品编号不能为空")
  30 + @Schema(description = "产品编号", requiredMode = REQUIRED, example = "1024")
  31 + private String productId;
  32 +
  33 + // TODO @li:productId 即可,而 productKey 通过 productId 查询
  34 + /**
  35 + * 产品标识
  36 + * <p>
  37 + * 冗余 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getProductKey()}
  38 + */
  39 + @NotEmpty(message = "产品标识不能为空")
  40 + @Schema(description = "产品标识", requiredMode = REQUIRED, example = "yudao")
  41 + private String productKey;
  42 +
  43 + /**
  44 + * 签名方式
  45 + * <p>
  46 + * 例如说:MD5、SHA256
  47 + */
  48 + @Schema(description = "签名方式", example = "MD5")
  49 + private String signMethod;
  50 +
  51 + // TODO @li:fileSign、fileSize 通过后端下载文件,计算出来。对前端屏蔽这个细节。
  52 +
  53 + /**
  54 + * 固件文件签名
  55 + */
  56 + @Schema(description = "固件文件签名", example = "d41d8cd98f00b204e9800998ecf8427e")
  57 + private String fileSign;
  58 +
  59 + /**
  60 + * 固件文件大小
  61 + */
  62 + @NotNull(message = "固件文件大小不能为空")
  63 + @Schema(description = "固件文件大小(单位:byte)", example = "1024")
  64 + private Long fileSize;
  65 +
  66 + /**
  67 + * 固件文件 URL
  68 + */
  69 + @NotEmpty(message = "固件文件 URL 不能为空")
  70 + @Schema(description = "固件文件 URL", requiredMode = REQUIRED, example = "https://www.iocoder.cn/yudao-firmware.zip")
  71 + private String fileUrl;
  72 +
  73 + /**
  74 + * 自定义信息,建议使用 JSON 格式
  75 + */
  76 + @Schema(description = "自定义信息,建议使用 JSON 格式", example = "{\"key1\":\"value1\",\"key2\":\"value2\"}")
  77 + private String information;
  78 +
  79 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwarePageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +
  7 +@Data
  8 +@Schema(description = "管理后台 - OTA 固件分页 Request VO")
  9 +public class IotOtaFirmwarePageReqVO extends PageParam {
  10 +
  11 + /**
  12 + * 固件名称
  13 + */
  14 + @Schema(description = "固件名称", example = "智能开关固件")
  15 + private String name;
  16 +
  17 + /**
  18 + * 产品标识
  19 + */
  20 + @Schema(description = "产品标识", example = "1024")
  21 + private String productId;
  22 +
  23 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
  2 +
  3 +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
  4 +import com.fhs.core.trans.anno.Trans;
  5 +import com.fhs.core.trans.constant.TransType;
  6 +import com.fhs.core.trans.vo.VO;
  7 +import io.swagger.v3.oas.annotations.media.Schema;
  8 +import lombok.Data;
  9 +
  10 +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
  11 +
  12 +@Data
  13 +@Schema(description = "管理后台 - OTA 固件 Response VO")
  14 +public class IotOtaFirmwareRespVO implements VO {
  15 +
  16 + /**
  17 + * 固件编号
  18 + */
  19 + @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
  20 + private Long id;
  21 + /**
  22 + * 固件名称
  23 + */
  24 + @Schema(description = "固件名称", requiredMode = REQUIRED, example = "OTA固件")
  25 + private String name;
  26 + /**
  27 + * 固件描述
  28 + */
  29 + @Schema(description = "固件描述")
  30 + private String description;
  31 + /**
  32 + * 版本号
  33 + */
  34 + @Schema(description = "版本号", requiredMode = REQUIRED, example = "1.0.0")
  35 + private String version;
  36 +
  37 + /**
  38 + * 产品编号
  39 + * <p>
  40 + * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
  41 + */
  42 + @Schema(description = "产品编号", requiredMode = REQUIRED, example = "1024")
  43 + @Trans(type = TransType.SIMPLE, target = IotProductDO.class, fields = {"name"}, refs = {"productName"})
  44 + private String productId;
  45 + /**
  46 + * 产品标识
  47 + * <p>
  48 + * 冗余 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getProductKey()}
  49 + */
  50 + @Schema(description = "产品标识", requiredMode = REQUIRED, example = "iot-product-key")
  51 + private String productKey;
  52 + /**
  53 + * 产品名称
  54 + */
  55 + @Schema(description = "产品名称", requiredMode = REQUIRED, example = "OTA产品")
  56 + private String productName;
  57 + /**
  58 + * 签名方式
  59 + * <p>
  60 + * 例如说:MD5、SHA256
  61 + */
  62 + @Schema(description = "签名方式", example = "MD5")
  63 + private String signMethod;
  64 + /**
  65 + * 固件文件签名
  66 + */
  67 + @Schema(description = "固件文件签名", example = "1024")
  68 + private String fileSign;
  69 + /**
  70 + * 固件文件大小
  71 + */
  72 + @Schema(description = "固件文件大小", requiredMode = REQUIRED, example = "1024")
  73 + private Long fileSize;
  74 + /**
  75 + * 固件文件 URL
  76 + */
  77 + @Schema(description = "固件文件 URL", requiredMode = REQUIRED, example = "https://www.iocoder.cn")
  78 + private String fileUrl;
  79 + /**
  80 + * 自定义信息,建议使用 JSON 格式
  81 + */
  82 + @Schema(description = "自定义信息,建议使用 JSON 格式")
  83 + private String information;
  84 +
  85 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
  8 +
  9 +@Data
  10 +@Schema(description = "管理后台 - OTA固件更新 Request VO")
  11 +public class IotOtaFirmwareUpdateReqVO extends IotOtaFirmwareCommonReqVO {
  12 +
  13 + /**
  14 + * 固件编号
  15 + */
  16 + @NotNull(message = "固件编号不能为空")
  17 + @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
  18 + private Long id;
  19 +
  20 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/record/IotOtaUpgradeRecordPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
  9 +
  10 +@Data
  11 +@Schema(description = "管理后台 - OTA升级记录分页 Request VO")
  12 +public class IotOtaUpgradeRecordPageReqVO extends PageParam {
  13 +
  14 + // TODO @li:使用 IotOtaUpgradeRecordStatusEnum 枚举哈
  15 + /**
  16 + * 待处理状态
  17 + */
  18 + public static final Integer PENDING = 0;
  19 + /**
  20 + * 已推送状态
  21 + */
  22 + public static final Integer PUSHED = 10;
  23 + /**
  24 + * 正在升级状态
  25 + */
  26 + public static final Integer UPGRADING = 20;
  27 + /**
  28 + * 升级成功状态
  29 + */
  30 + public static final Integer SUCCESS = 30;
  31 + /**
  32 + * 升级失败状态
  33 + */
  34 + public static final Integer FAILURE = 40;
  35 + /**
  36 + * 升级已取消状态
  37 + */
  38 + public static final Integer CANCELED = 50;
  39 +
  40 + /**
  41 + * 升级任务编号字段。
  42 + * <p>
  43 + * 该字段用于标识升级任务的唯一编号,不能为空。
  44 + */
  45 + @NotNull(message = "升级任务编号不能为空")
  46 + @Schema(description = "升级任务编号", requiredMode = REQUIRED, example = "1024")
  47 + private Long taskId;
  48 +
  49 + /**
  50 + * 设备标识字段。
  51 + * <p>
  52 + * 该字段用于标识设备的名称,通常用于区分不同的设备。
  53 + */
  54 + @Schema(description = "设备标识", requiredMode = REQUIRED, example = "摄像头A1-1")
  55 + private String deviceName;
  56 +
  57 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/record/IotOtaUpgradeRecordRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record;
  2 +
  3 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  4 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
  6 +import com.fhs.core.trans.anno.Trans;
  7 +import com.fhs.core.trans.constant.TransType;
  8 +import io.swagger.v3.oas.annotations.media.Schema;
  9 +import lombok.Data;
  10 +
  11 +import java.time.LocalDateTime;
  12 +
  13 +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
  14 +
  15 +@Data
  16 +@Schema(description = "管理后台 - OTA升级记录 Response VO")
  17 +public class IotOtaUpgradeRecordRespVO {
  18 +
  19 + /**
  20 + * 升级记录编号
  21 + */
  22 + @Schema(description = "升级记录编号", requiredMode = REQUIRED, example = "1024")
  23 + private Long id;
  24 + /**
  25 + * 固件编号
  26 + * <p>
  27 + * 关联 {@link IotOtaFirmwareDO#getId()}
  28 + */
  29 + @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
  30 + @Trans(type = TransType.SIMPLE, target = IotOtaFirmwareDO.class, fields = {"version"}, refs = {"firmwareVersion"})
  31 + private Long firmwareId;
  32 + /**
  33 + * 固件版本
  34 + */
  35 + @Schema(description = "固件版本", requiredMode = REQUIRED, example = "v1.0.0")
  36 + private String firmwareVersion;
  37 + /**
  38 + * 任务编号
  39 + * <p>
  40 + * 关联 {@link IotOtaUpgradeTaskDO#getId()}
  41 + */
  42 + @Schema(description = "任务编号", requiredMode = REQUIRED, example = "1024")
  43 + private Long taskId;
  44 + /**
  45 + * 产品标识
  46 + * <p>
  47 + * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
  48 + */
  49 + @Schema(description = "产品标识", requiredMode = REQUIRED, example = "iot")
  50 + private String productKey;
  51 + /**
  52 + * 设备名称
  53 + * <p>
  54 + * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO#getId()}
  55 + */
  56 + @Schema(description = "设备名称", requiredMode = REQUIRED, example = "iot")
  57 + private String deviceName;
  58 + /**
  59 + * 设备编号
  60 + * <p>
  61 + * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO#getId()}
  62 + */
  63 + @Schema(description = "设备编号", requiredMode = REQUIRED, example = "1024")
  64 + private String deviceId;
  65 + /**
  66 + * 来源的固件编号
  67 + * <p>
  68 + * 关联 {@link IotDeviceDO#getFirmwareId()}
  69 + */
  70 + @Schema(description = "来源的固件编号", requiredMode = REQUIRED, example = "1024")
  71 + @Trans(type = TransType.SIMPLE, target = IotOtaFirmwareDO.class, fields = {"version"}, refs = {"fromFirmwareVersion"})
  72 + private Long fromFirmwareId;
  73 + /**
  74 + * 来源的固件版本
  75 + */
  76 + @Schema(description = "来源的固件版本", requiredMode = REQUIRED, example = "v1.0.0")
  77 + private String fromFirmwareVersion;
  78 + /**
  79 + * 升级状态
  80 + * <p>
  81 + * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum}
  82 + */
  83 + @Schema(description = "升级状态", requiredMode = REQUIRED, allowableValues = {"0", "10", "20", "30", "40", "50"})
  84 + private Integer status;
  85 + /**
  86 + * 升级进度,百分比
  87 + */
  88 + @Schema(description = "升级进度,百分比", requiredMode = REQUIRED, example = "10")
  89 + private Integer progress;
  90 + /**
  91 + * 升级进度描述
  92 + * <p>
  93 + * 注意,只记录设备最后一次的升级进度描述
  94 + * 如果想看历史记录,可以查看 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO} 设备日志
  95 + */
  96 + @Schema(description = "升级进度描述", requiredMode = REQUIRED, example = "10")
  97 + private String description;
  98 + /**
  99 + * 升级开始时间
  100 + */
  101 + @Schema(description = "升级开始时间", requiredMode = REQUIRED, example = "2022-07-08 07:30:00")
  102 + private LocalDateTime startTime;
  103 + /**
  104 + * 升级结束时间
  105 + */
  106 + @Schema(description = "升级结束时间", requiredMode = REQUIRED, example = "2022-07-08 07:30:00")
  107 + private LocalDateTime endTime;
  108 +
  109 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
  9 +
  10 +@Data
  11 +@Schema(description = "管理后台 - OTA升级任务分页 Request VO")
  12 +public class IotOtaUpgradeTaskPageReqVO extends PageParam {
  13 +
  14 + /**
  15 + * 任务名称字段,用于描述任务的名称
  16 + */
  17 + @Schema(description = "任务名称", example = "升级任务")
  18 + private String name;
  19 +
  20 + /**
  21 + * 固件编号字段,用于唯一标识固件,不能为空
  22 + */
  23 + @NotNull(message = "固件编号不能为空")
  24 + @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
  25 + private Long firmwareId;
  26 +
  27 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task;
  2 +
  3 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  4 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
  5 +import com.fhs.core.trans.vo.VO;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import lombok.Data;
  8 +
  9 +import java.time.LocalDateTime;
  10 +import java.util.List;
  11 +
  12 +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
  13 +
  14 +@Data
  15 +@Schema(description = "管理后台 - OTA升级任务 Response VO")
  16 +public class IotOtaUpgradeTaskRespVO implements VO {
  17 +
  18 + /**
  19 + * 任务编号
  20 + */
  21 + @Schema(description = "任务编号", requiredMode = REQUIRED, example = "1024")
  22 + private Long id;
  23 + /**
  24 + * 任务名称
  25 + */
  26 + @Schema(description = "任务名称", requiredMode = REQUIRED, example = "升级任务")
  27 + private String name;
  28 + /**
  29 + * 任务描述
  30 + */
  31 + @Schema(description = "任务描述", example = "升级任务")
  32 + private String description;
  33 + /**
  34 + * 固件编号
  35 + * <p>
  36 + * 关联 {@link IotOtaFirmwareDO#getId()}
  37 + */
  38 + @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
  39 + private Long firmwareId;
  40 + /**
  41 + * 任务状态
  42 + * <p>
  43 + * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskStatusEnum}
  44 + */
  45 + @Schema(description = "任务状态", requiredMode = REQUIRED, allowableValues = {"10", "20", "21", "30"})
  46 + private Integer status;
  47 + /**
  48 + * 任务状态名称
  49 + */
  50 + @Schema(description = "任务状态名称", requiredMode = REQUIRED, example = "进行中")
  51 + private String statusName;
  52 + /**
  53 + * 升级范围
  54 + * <p>
  55 + * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskScopeEnum}
  56 + */
  57 + @Schema(description = "升级范围", requiredMode = REQUIRED, allowableValues = {"1", "2"})
  58 + private Integer scope;
  59 + /**
  60 + * 设备数量
  61 + */
  62 + @Schema(description = "设备数量", requiredMode = REQUIRED, example = "1024")
  63 + private Long deviceCount;
  64 + /**
  65 + * 选中的设备编号数组
  66 + * <p>
  67 + * 关联 {@link IotDeviceDO#getId()}
  68 + */
  69 + @Schema(description = "选中的设备编号数组", example = "1024")
  70 + private List<Long> deviceIds;
  71 + /**
  72 + * 选中的设备名字数组
  73 + * <p>
  74 + * 关联 {@link IotDeviceDO#getDeviceName()}
  75 + */
  76 + @Schema(description = "选中的设备名字数组", example = "1024")
  77 + private List<String> deviceNames;
  78 + /**
  79 + * 创建时间
  80 + */
  81 + @Schema(description = "创建时间", requiredMode = REQUIRED, example = "2022-07-08 07:30:00")
  82 + private LocalDateTime createTime;
  83 +
  84 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
  6 +import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskScopeEnum;
  7 +import io.swagger.v3.oas.annotations.media.Schema;
  8 +import jakarta.validation.constraints.NotEmpty;
  9 +import jakarta.validation.constraints.NotNull;
  10 +import lombok.Data;
  11 +
  12 +import java.util.List;
  13 +
  14 +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
  15 +
  16 +@Data
  17 +@Schema(description = "管理后台 - OTA升级任务创建/修改 Request VO")
  18 +public class IotOtaUpgradeTaskSaveReqVO {
  19 +
  20 + /**
  21 + * 任务名称
  22 + */
  23 + @NotEmpty(message = "任务名称不能为空")
  24 + @Schema(description = "任务名称", requiredMode = REQUIRED, example = "升级任务")
  25 + private String name;
  26 +
  27 + /**
  28 + * 任务描述
  29 + */
  30 + @Schema(description = "任务描述", example = "升级任务")
  31 + private String description;
  32 +
  33 + /**
  34 + * 固件编号
  35 + * <p>
  36 + * 关联 {@link IotOtaFirmwareDO#getId()}
  37 + */
  38 + @NotNull(message = "固件编号不能为空")
  39 + @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
  40 + private Long firmwareId;
  41 +
  42 + /**
  43 + * 升级范围
  44 + * <p>
  45 + * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskScopeEnum}
  46 + */
  47 + @NotNull(message = "升级范围不能为空")
  48 + @InEnum(value = IotOtaUpgradeTaskScopeEnum.class)
  49 + @Schema(description = "升级范围", requiredMode = REQUIRED, example = "1")
  50 + private Integer scope;
  51 +
  52 + /**
  53 + * 选中的设备编号数组
  54 + * <p>
  55 + * 关联 {@link IotDeviceDO#getId()}
  56 + */
  57 + @Schema(description = "选中的设备编号数组", requiredMode = REQUIRED, example = "[1,2,3,4]")
  58 + private List<Long> deviceIds;
  59 +
  60 + // TODO @li:通过 deviceIds 查询 deviceNames,前端不传递哈
  61 + /**
  62 + * 选中的设备名字数组
  63 + * <p>
  64 + * 关联 {@link IotDeviceDO#getDeviceName()}
  65 + */
  66 + @Schema(description = "选中的设备名字数组", requiredMode = REQUIRED, example = "[设备1,设备2,设备3,设备4]")
  67 + private List<String> deviceNames;
  68 +
  69 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/ota/IotOtaUpgradeRecordConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.convert.ota;
  2 +
  3 +import cn.hutool.core.convert.Convert;
  4 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
  7 +import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum;
  8 +import cn.iocoder.yudao.module.iot.service.ota.bo.IotOtaUpgradeRecordCreateReqBO;
  9 +import org.mapstruct.Mapper;
  10 +import org.mapstruct.factory.Mappers;
  11 +
  12 +import java.util.List;
  13 +
  14 +@Mapper
  15 +public interface IotOtaUpgradeRecordConvert {
  16 +
  17 + IotOtaUpgradeRecordConvert INSTANCE = Mappers.getMapper(IotOtaUpgradeRecordConvert.class);
  18 +
  19 + // TODO @li:一般情况下,这种 convert 直接写 service 就好啦。不用特别写一个哈
  20 + default List<IotOtaUpgradeRecordCreateReqBO> convertBOList(IotOtaUpgradeTaskDO upgradeTask, IotOtaFirmwareDO firmware, List<IotDeviceDO> deviceList) {
  21 + return deviceList.stream().map(device -> {
  22 + IotOtaUpgradeRecordCreateReqBO createReqBO = new IotOtaUpgradeRecordCreateReqBO();
  23 + createReqBO.setFirmwareId(firmware.getId());
  24 + createReqBO.setTaskId(upgradeTask.getId());
  25 + createReqBO.setProductKey(device.getProductKey());
  26 + createReqBO.setDeviceName(device.getDeviceName());
  27 + createReqBO.setDeviceId(Convert.toStr(device.getId()));
  28 + createReqBO.setFromFirmwareId(Convert.toLong(device.getFirmwareId()));
  29 + createReqBO.setStatus(IotOtaUpgradeRecordStatusEnum.PENDING.getStatus());
  30 + createReqBO.setProgress(0);
  31 + return createReqBO;
  32 + }).toList();
  33 + }
  34 +
  35 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaUpgradeTaskDO.java
@@ -41,30 +41,43 @@ public class IotOtaUpgradeTaskDO extends BaseDO { @@ -41,30 +41,43 @@ public class IotOtaUpgradeTaskDO extends BaseDO {
41 41
42 /** 42 /**
43 * 固件编号 43 * 固件编号
44 - * 44 + * <p>
45 * 关联 {@link IotOtaFirmwareDO#getId()} 45 * 关联 {@link IotOtaFirmwareDO#getId()}
46 */ 46 */
47 private Long firmwareId; 47 private Long firmwareId;
48 48
49 /** 49 /**
50 * 任务状态 50 * 任务状态
51 - * 51 + * <p>
52 * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskStatusEnum} 52 * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskStatusEnum}
53 */ 53 */
54 private Integer status; 54 private Integer status;
55 55
56 /** 56 /**
57 * 升级范围 57 * 升级范围
58 - * 58 + * <p>
59 * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskScopeEnum} 59 * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskScopeEnum}
60 */ 60 */
61 private Integer scope; 61 private Integer scope;
62 /** 62 /**
  63 + * 设备数量
  64 + */
  65 + private Long deviceCount;
  66 + /**
  67 + * 选中的设备编号数组
  68 + * <p>
  69 + * 关联 {@link IotDeviceDO#getId()}
  70 + */
  71 + @TableField(typeHandler = JacksonTypeHandler.class)
  72 + private List<Long> deviceIds;
  73 +
  74 + // TODO @li:这个通过查询,不用冗余
  75 + /**
63 * 选中的设备名字数组 76 * 选中的设备名字数组
64 - * 77 + * <p>
65 * 关联 {@link IotDeviceDO#getDeviceName()} 78 * 关联 {@link IotDeviceDO#getDeviceName()}
66 */ 79 */
67 @TableField(typeHandler = JacksonTypeHandler.class) 80 @TableField(typeHandler = JacksonTypeHandler.class)
68 private List<String> deviceNames; 81 private List<String> deviceNames;
69 82
70 -}  
71 \ No newline at end of file 83 \ No newline at end of file
  84 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceMapper.java
@@ -61,10 +61,13 @@ public interface IotDeviceMapper extends BaseMapperX&lt;IotDeviceDO&gt; { @@ -61,10 +61,13 @@ public interface IotDeviceMapper extends BaseMapperX&lt;IotDeviceDO&gt; {
61 return selectList(IotDeviceDO::getState, state); 61 return selectList(IotDeviceDO::getState, state);
62 } 62 }
63 63
  64 + default List<IotDeviceDO> selectListByProductId(Long productId) {
  65 + return selectList(IotDeviceDO::getProductId, productId);
  66 + }
  67 +
64 default Long selectCountByGroupId(Long groupId) { 68 default Long selectCountByGroupId(Long groupId) {
65 return selectCount(new LambdaQueryWrapperX<IotDeviceDO>() 69 return selectCount(new LambdaQueryWrapperX<IotDeviceDO>()
66 - .apply("FIND_IN_SET(" + groupId + ",group_ids) > 0")  
67 - .orderByDesc(IotDeviceDO::getId)); 70 + .apply("FIND_IN_SET(" + groupId + ",group_ids) > 0"));
68 } 71 }
69 72
70 /** 73 /**
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaFirmwareMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.mysql.ota;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwarePageReqVO;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +import java.util.List;
  11 +
  12 +// TODO @li:这里的注释,可以去掉哈,多了点点
  13 +/**
  14 + * IotOtaFirmwareMapper 接口用于操作 IotOtaFirmwareDO 实体类对应的数据库表。
  15 + * 该接口继承自 BaseMapperX,提供了基本的 CRUD 操作,并扩展了特定查询方法。
  16 + */
  17 +@Mapper
  18 +public interface IotOtaFirmwareMapper extends BaseMapperX<IotOtaFirmwareDO> {
  19 +
  20 + /**
  21 + * 根据产品ID和固件版本号查询固件信息列表。
  22 + *
  23 + * @param productId 产品ID,用于筛选固件信息。
  24 + * @param version 固件版本号,用于筛选固件信息。
  25 + * @return 返回符合条件的固件信息列表。
  26 + */
  27 + default List<IotOtaFirmwareDO> selectByProductIdAndVersion(String productId, String version) {
  28 + return selectList(IotOtaFirmwareDO::getProductId, productId,
  29 + IotOtaFirmwareDO::getVersion, version);
  30 + }
  31 +
  32 + /**
  33 + * 分页查询固件信息,支持根据名称和产品ID进行筛选,并按创建时间降序排列。
  34 + *
  35 + * @param pageReqVO 分页查询请求对象,包含分页参数和筛选条件。
  36 + * @return 返回分页查询结果,包含符合条件的固件信息列表。
  37 + */
  38 + default PageResult<IotOtaFirmwareDO> selectPage(IotOtaFirmwarePageReqVO pageReqVO) {
  39 + return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaFirmwareDO>()
  40 + .likeIfPresent(IotOtaFirmwareDO::getName, pageReqVO.getName())
  41 + .eqIfPresent(IotOtaFirmwareDO::getProductId, pageReqVO.getProductId())
  42 + .orderByDesc(IotOtaFirmwareDO::getCreateTime));
  43 + }
  44 +
  45 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaUpgradeRecordMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.mysql.ota;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record.IotOtaUpgradeRecordPageReqVO;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeRecordDO;
  8 +import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum;
  9 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  10 +import org.apache.ibatis.annotations.Mapper;
  11 +import org.apache.ibatis.annotations.Param;
  12 +
  13 +import java.util.List;
  14 +
  15 +// TODO @li:这里的注释,可以去掉哈,多了点点
  16 +/**
  17 + * OTA 升级记录 Mapper 接口
  18 + */
  19 +@Mapper
  20 +public interface IotOtaUpgradeRecordMapper extends BaseMapperX<IotOtaUpgradeRecordDO> {
  21 +
  22 + /**
  23 + * 根据条件查询单个OTA升级记录
  24 + *
  25 + * @param firmwareId 固件ID,可选参数,用于筛选固件ID匹配的记录
  26 + * @param taskId 任务ID,可选参数,用于筛选任务ID匹配的记录
  27 + * @param deviceId 设备ID,可选参数,用于筛选设备ID匹配的记录
  28 + * @return 返回符合条件的单个OTA升级记录,如果不存在则返回null
  29 + */
  30 + default IotOtaUpgradeRecordDO selectByConditions(Long firmwareId, Long taskId, String deviceId) {
  31 + // 使用LambdaQueryWrapperX构建查询条件,根据传入的参数动态添加查询条件
  32 + return selectOne(new LambdaQueryWrapperX<IotOtaUpgradeRecordDO>()
  33 + .eqIfPresent(IotOtaUpgradeRecordDO::getFirmwareId, firmwareId)
  34 + .eqIfPresent(IotOtaUpgradeRecordDO::getTaskId, taskId)
  35 + .eqIfPresent(IotOtaUpgradeRecordDO::getDeviceId, deviceId));
  36 + }
  37 +
  38 + /**
  39 + * 获取OTA升级记录的数量
  40 + *
  41 + * @param taskId 任务ID,用于筛选特定任务的升级记录
  42 + * @param deviceName 设备名称,用于筛选特定设备的升级记录
  43 + * @param status 状态,用于筛选特定状态的升级记录
  44 + * @return 返回符合条件的OTA升级记录的数量
  45 + */
  46 + Long getOtaUpgradeRecordCount(@Param("taskId") Long taskId,
  47 + @Param("deviceName") String deviceName,
  48 + @Param("status") Integer status);
  49 +
  50 + /**
  51 + * 获取OTA升级记录的统计信息
  52 + *
  53 + * @param firmwareId 固件ID,用于筛选特定固件的升级记录
  54 + * @param status 状态,用于筛选特定状态的升级记录
  55 + * @return 返回符合条件的OTA升级记录的统计信息
  56 + */
  57 + Long getOtaUpgradeRecordStatistics(@Param("firmwareId") Long firmwareId,
  58 + @Param("status") Integer status);
  59 +
  60 +
  61 + /**
  62 + * 根据分页查询条件获取IOT OTA升级记录的分页结果
  63 + *
  64 + * @param pageReqVO 分页查询请求参数,包含设备名称、任务ID等查询条件
  65 + * @return 返回分页查询结果,包含符合条件的IOT OTA升级记录列表
  66 + */
  67 + default PageResult<IotOtaUpgradeRecordDO> selectUpgradeRecordPage(IotOtaUpgradeRecordPageReqVO pageReqVO) {
  68 + // 使用LambdaQueryWrapperX构建查询条件,并根据请求参数动态添加查询条件
  69 + return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaUpgradeRecordDO>()
  70 + .likeIfPresent(IotOtaUpgradeRecordDO::getDeviceName, pageReqVO.getDeviceName()) // 如果设备名称存在,则添加模糊查询条件
  71 + .eqIfPresent(IotOtaUpgradeRecordDO::getTaskId, pageReqVO.getTaskId())); // 如果任务ID存在,则添加等值查询条件
  72 + }
  73 +
  74 + /**
  75 + * 根据任务ID取消升级记录。
  76 + * 该方法通过任务ID查找状态为“待处理”的升级记录,并将其状态更新为“已取消”。
  77 + *
  78 + * @param taskId 任务ID,用于查找对应的升级记录。
  79 + */
  80 + default void cancelUpgradeRecordByTaskId(Long taskId) {
  81 + // 使用LambdaUpdateWrapper构建更新条件,将状态为“待处理”的记录更新为“已取消”
  82 + // TODO @li:哪些可以更新,通过 service 传递。mapper 尽量不要有逻辑
  83 + update(new LambdaUpdateWrapper<IotOtaUpgradeRecordDO>()
  84 + .set(IotOtaUpgradeRecordDO::getStatus, IotOtaUpgradeRecordStatusEnum.CANCELED.getStatus())
  85 + .eq(IotOtaUpgradeRecordDO::getTaskId, taskId)
  86 + .eq(IotOtaUpgradeRecordDO::getStatus, IotOtaUpgradeRecordStatusEnum.PENDING.getStatus())
  87 + );
  88 + }
  89 +
  90 + /**
  91 + * 根据状态查询符合条件的升级记录列表
  92 + * <p>
  93 + * 该函数使用LambdaQueryWrapperX构建查询条件,查询指定状态的升级记录。
  94 + *
  95 + * @param state 升级记录的状态,用于筛选符合条件的记录
  96 + * @return 返回符合指定状态的升级记录列表,类型为List<IotOtaUpgradeRecordDO>
  97 + */
  98 + default List<IotOtaUpgradeRecordDO> selectUpgradeRecordListByState(Integer state) {
  99 + // 使用LambdaQueryWrapperX构建查询条件,根据状态查询符合条件的升级记录
  100 + return selectList(new LambdaQueryWrapperX<IotOtaUpgradeRecordDO>()
  101 + .eq(IotOtaUpgradeRecordDO::getStatus, state));
  102 + }
  103 +
  104 + /**
  105 + * 更新升级记录状态
  106 + * <p>
  107 + * 该函数用于批量更新指定ID列表中的升级记录状态。通过传入的ID列表和状态值,使用LambdaUpdateWrapper构建更新条件,
  108 + * 并执行更新操作。
  109 + *
  110 + * @param ids 需要更新的升级记录ID列表,类型为List<Long>。传入的ID列表中的记录将被更新。
  111 + * @param status 要更新的状态值,类型为Integer。该值将被设置到符合条件的升级记录中。
  112 + */
  113 + default void updateUpgradeRecordStatus(List<Long> ids, Integer status) {
  114 + // 使用LambdaUpdateWrapper构建更新条件,设置状态字段,并根据ID列表进行筛选
  115 + update(new LambdaUpdateWrapper<IotOtaUpgradeRecordDO>()
  116 + .set(IotOtaUpgradeRecordDO::getStatus, status)
  117 + .in(IotOtaUpgradeRecordDO::getId, ids)
  118 + );
  119 + }
  120 +
  121 + /**
  122 + * 根据任务ID查询升级记录列表
  123 + * <p>
  124 + * 该函数通过任务ID查询符合条件的升级记录,并返回查询结果列表。
  125 + *
  126 + * @param taskId 任务ID,用于筛选升级记录
  127 + * @return 返回符合条件的升级记录列表,若未找到则返回空列表
  128 + */
  129 + default List<IotOtaUpgradeRecordDO> selectUpgradeRecordListByTaskId(Long taskId) {
  130 + // 使用LambdaQueryWrapperX构建查询条件,根据任务ID查询符合条件的升级记录
  131 + return selectList(new LambdaQueryWrapperX<IotOtaUpgradeRecordDO>()
  132 + .eq(IotOtaUpgradeRecordDO::getTaskId, taskId));
  133 + }
  134 +
  135 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaUpgradeTaskMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.mysql.ota;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task.IotOtaUpgradeTaskPageReqVO;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +import java.util.List;
  11 +
  12 +// TODO @li:这里的注释,可以去掉哈,多了点点
  13 +/**
  14 + * IotOtaUpgradeTaskMapper 接口用于操作 IotOtaUpgradeTaskDO 数据库表。
  15 + * 该接口继承自 BaseMapperX,提供了基本的数据库操作方法。
  16 + */
  17 +@Mapper
  18 +public interface IotOtaUpgradeTaskMapper extends BaseMapperX<IotOtaUpgradeTaskDO> {
  19 +
  20 + /**
  21 + * 根据固件ID和任务名称查询升级任务列表。
  22 + *
  23 + * @param firmwareId 固件ID,用于筛选升级任务
  24 + * @param name 任务名称,用于筛选升级任务
  25 + * @return 符合条件的升级任务列表
  26 + */
  27 + default List<IotOtaUpgradeTaskDO> selectByFirmwareIdAndName(Long firmwareId, String name) {
  28 + return selectList(new LambdaQueryWrapperX<IotOtaUpgradeTaskDO>()
  29 + .eqIfPresent(IotOtaUpgradeTaskDO::getFirmwareId, firmwareId)
  30 + .eqIfPresent(IotOtaUpgradeTaskDO::getName, name));
  31 + }
  32 +
  33 + /**
  34 + * 分页查询升级任务列表,支持根据固件ID和任务名称进行筛选。
  35 + *
  36 + * @param pageReqVO 分页查询请求对象,包含分页参数和筛选条件
  37 + * @return 分页结果,包含符合条件的升级任务列表
  38 + */
  39 + default PageResult<IotOtaUpgradeTaskDO> selectUpgradeTaskPage(IotOtaUpgradeTaskPageReqVO pageReqVO) {
  40 + return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaUpgradeTaskDO>()
  41 + .eqIfPresent(IotOtaUpgradeTaskDO::getFirmwareId, pageReqVO.getFirmwareId())
  42 + .likeIfPresent(IotOtaUpgradeTaskDO::getName, pageReqVO.getName()));
  43 + }
  44 +
  45 + /**
  46 + * 根据任务状态查询升级任务列表
  47 + * <p>
  48 + * 该函数通过传入的任务状态,查询数据库中符合条件的升级任务列表。
  49 + *
  50 + * @param status 任务状态,用于筛选升级任务的状态值
  51 + * @return 返回符合条件的升级任务列表,列表中的每个元素为 IotOtaUpgradeTaskDO 对象
  52 + */
  53 + default List<IotOtaUpgradeTaskDO> selectUpgradeTaskByState(Integer status) {
  54 + return selectList(IotOtaUpgradeTaskDO::getStatus, status);
  55 + }
  56 +
  57 +
  58 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeRecordJob.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.job.ota;
  2 +
  3 +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
  4 +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeRecordDO;
  6 +import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum;
  7 +import cn.iocoder.yudao.module.iot.service.ota.IotOtaUpgradeRecordService;
  8 +import jakarta.annotation.Resource;
  9 +import org.springframework.stereotype.Component;
  10 +
  11 +import java.util.List;
  12 +
  13 +@Component
  14 +public class IotOtaUpgradeRecordJob implements JobHandler {
  15 +
  16 + @Resource
  17 + private IotOtaUpgradeRecordService upgradeRecordService;
  18 +
  19 + @Override
  20 + @TenantJob
  21 + public String execute(String param) throws Exception {
  22 + // 1. 查询待处理的升级记录
  23 + List<IotOtaUpgradeRecordDO> upgradeRecords = upgradeRecordService
  24 + .getUpgradeRecordListByState(IotOtaUpgradeRecordStatusEnum.PENDING.getStatus());
  25 +
  26 + // TODO @芋艿 2.执行升级动作
  27 + // TODO @li:应该是逐条 push,逐条更新。不用批量哈
  28 +
  29 + // 3. 最终,更新升级记录状态
  30 + List<Long> ids = upgradeRecords.stream().map(IotOtaUpgradeRecordDO::getId).toList();
  31 + upgradeRecordService.updateUpgradeRecordStatus(ids, IotOtaUpgradeRecordStatusEnum.PUSHED.getStatus());
  32 + return "";
  33 + }
  34 +
  35 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeTaskJob.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.job.ota;
  2 +
  3 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
  4 +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
  5 +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeRecordDO;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
  8 +import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum;
  9 +import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskStatusEnum;
  10 +import cn.iocoder.yudao.module.iot.service.ota.IotOtaUpgradeRecordService;
  11 +import cn.iocoder.yudao.module.iot.service.ota.IotOtaUpgradeTaskService;
  12 +import jakarta.annotation.Resource;
  13 +import lombok.extern.slf4j.Slf4j;
  14 +import org.springframework.stereotype.Component;
  15 +
  16 +import java.util.List;
  17 +
  18 +// TODO @li:也不用通过 job 去统计。可以通过 record update status 后,主动去更新 task 的状态。
  19 +@Slf4j
  20 +@Component
  21 +public class IotOtaUpgradeTaskJob implements JobHandler {
  22 +
  23 + @Resource
  24 + private IotOtaUpgradeTaskService upgradeTaskService;
  25 + @Resource
  26 + private IotOtaUpgradeRecordService upgradeRecordService;
  27 +
  28 + @Override
  29 + @TenantJob
  30 + public String execute(String param) throws Exception {
  31 + // 1.这个任务主要是为了检查并更新升级任务的状态
  32 + List<IotOtaUpgradeTaskDO> upgradeTasks = upgradeTaskService
  33 + .getUpgradeTaskByState(IotOtaUpgradeTaskStatusEnum.IN_PROGRESS.getStatus());
  34 + // 2.遍历并且确定升级任务的状态
  35 + upgradeTasks.forEach(this::checkUpgradeTaskState);
  36 + // TODO @芋艿: 其他的一些业务逻辑
  37 + return "";
  38 + }
  39 +
  40 + private void checkUpgradeTaskState(IotOtaUpgradeTaskDO upgradeTask) {
  41 + // 1.查询任务所有的升级记录
  42 + List<IotOtaUpgradeRecordDO> upgradeRecords =
  43 + upgradeRecordService.getUpgradeRecordListByTaskId(upgradeTask.getId());
  44 + if (upgradeRecords.stream().anyMatch(upgradeRecord ->
  45 + ObjectUtils.equalsAny(upgradeRecord.getStatus(),
  46 + IotOtaUpgradeRecordStatusEnum.PENDING.getStatus(),
  47 + IotOtaUpgradeRecordStatusEnum.PUSHED.getStatus(),
  48 + IotOtaUpgradeRecordStatusEnum.UPGRADING.getStatus()))) {
  49 + // 如果存在正在升级的升级记录,则升级任务的状态为进行中
  50 + log.debug("升级任务 {} 状态为进行中", upgradeTask.getId());
  51 + } else if (upgradeRecords.stream().allMatch(upgradeRecord ->
  52 + ObjectUtils.equalsAny(upgradeRecord.getStatus(),
  53 + IotOtaUpgradeRecordStatusEnum.SUCCESS.getStatus()))) {
  54 + // 如果全部升级成功,则升级任务的状态为已完成
  55 + upgradeTaskService.updateUpgradeTaskStatus(upgradeTask.getId(),
  56 + IotOtaUpgradeTaskStatusEnum.COMPLETED.getStatus());
  57 + } else if (upgradeRecords.stream().noneMatch(upgradeRecord ->
  58 + ObjectUtils.equalsAny(upgradeRecord.getStatus(),
  59 + IotOtaUpgradeRecordStatusEnum.PENDING.getStatus(),
  60 + IotOtaUpgradeRecordStatusEnum.PUSHED.getStatus(),
  61 + IotOtaUpgradeRecordStatusEnum.UPGRADING.getStatus())) &&
  62 + upgradeRecords.stream().anyMatch(upgradeRecord ->
  63 + ObjectUtils.equalsAny(upgradeRecord.getStatus(),
  64 + IotOtaUpgradeRecordStatusEnum.FAILURE.getStatus(),
  65 + IotOtaUpgradeRecordStatusEnum.CANCELED.getStatus()))) {
  66 + // 如果全部升级完毕,但是存在升级失败或者取消的升级记录,则升级任务的状态为失败
  67 + upgradeTaskService.updateUpgradeTaskStatus(upgradeTask.getId(),
  68 + IotOtaUpgradeTaskStatusEnum.INCOMPLETE.getStatus());
  69 + }
  70 + }
  71 +
  72 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
@@ -35,8 +35,8 @@ public interface IotDeviceService { @@ -35,8 +35,8 @@ public interface IotDeviceService {
35 * @return 设备 35 * @return 设备
36 */ 36 */
37 IotDeviceDO createDevice(@NotEmpty(message = "产品标识不能为空") String productKey, 37 IotDeviceDO createDevice(@NotEmpty(message = "产品标识不能为空") String productKey,
38 - @NotEmpty(message = "设备名称不能为空") String deviceName,  
39 - Long gatewayId); 38 + @NotEmpty(message = "设备名称不能为空") String deviceName,
  39 + Long gatewayId);
40 40
41 /** 41 /**
42 * 更新设备 42 * 更新设备
@@ -46,6 +46,7 @@ public interface IotDeviceService { @@ -46,6 +46,7 @@ public interface IotDeviceService {
46 void updateDevice(@Valid IotDeviceSaveReqVO updateReqVO); 46 void updateDevice(@Valid IotDeviceSaveReqVO updateReqVO);
47 47
48 // TODO @芋艿:先这么实现。未来看情况,要不要自己实现 48 // TODO @芋艿:先这么实现。未来看情况,要不要自己实现
  49 +
49 /** 50 /**
50 * 更新设备的所属网关 51 * 更新设备的所属网关
51 * 52 *
@@ -110,7 +111,7 @@ public interface IotDeviceService { @@ -110,7 +111,7 @@ public interface IotDeviceService {
110 IotDeviceDO getDeviceByDeviceKey(String deviceKey); 111 IotDeviceDO getDeviceByDeviceKey(String deviceKey);
111 112
112 /** 113 /**
113 - * 获得设备分页 114 + * ��得设备分页
114 * 115 *
115 * @param pageReqVO 分页查询 116 * @param pageReqVO 分页查询
116 * @return IoT 设备分页 117 * @return IoT 设备分页
@@ -151,7 +152,7 @@ public interface IotDeviceService { @@ -151,7 +152,7 @@ public interface IotDeviceService {
151 152
152 /** 153 /**
153 * 【缓存】根据产品 key 和设备名称,获得设备信息 154 * 【缓存】根据产品 key 和设备名称,获得设备信息
154 - * 155 + * <p>
155 * 注意:该方法会忽略租户信息,所以调用时,需要确认会不会有跨租户访问的风险!!! 156 * 注意:该方法会忽略租户信息,所以调用时,需要确认会不会有跨租户访问的风险!!!
156 * 157 *
157 * @param productKey 产品 key 158 * @param productKey 产品 key
@@ -192,4 +193,12 @@ public interface IotDeviceService { @@ -192,4 +193,12 @@ public interface IotDeviceService {
192 */ 193 */
193 List<IotDeviceDO> getDeviceList(); 194 List<IotDeviceDO> getDeviceList();
194 195
  196 + /**
  197 + * 获取 MQTT 连接参数
  198 + *
  199 + * @param deviceId 设备 ID
  200 + * @return MQTT 连接参数
  201 + */
  202 + IotDeviceMqttConnectionParamsRespVO getMqttConnectionParams(Long deviceId);
  203 +
195 } 204 }
196 \ No newline at end of file 205 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
@@ -20,6 +20,8 @@ import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants; @@ -20,6 +20,8 @@ import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
20 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum; 20 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStateEnum;
21 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum; 21 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
22 import cn.iocoder.yudao.module.iot.service.product.IotProductService; 22 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
  23 +import cn.iocoder.yudao.module.iot.util.MqttSignUtils;
  24 +import cn.iocoder.yudao.module.iot.util.MqttSignUtils.MqttSignResult;
23 import jakarta.annotation.Resource; 25 import jakarta.annotation.Resource;
24 import jakarta.validation.ConstraintViolationException; 26 import jakarta.validation.ConstraintViolationException;
25 import lombok.extern.slf4j.Slf4j; 27 import lombok.extern.slf4j.Slf4j;
@@ -99,7 +101,7 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -99,7 +101,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
99 } 101 }
100 102
101 private void validateCreateDeviceParam(String productKey, String deviceName, String deviceKey, 103 private void validateCreateDeviceParam(String productKey, String deviceName, String deviceKey,
102 - Long gatewayId, IotProductDO product) { 104 + Long gatewayId, IotProductDO product) {
103 TenantUtils.executeIgnore(() -> { 105 TenantUtils.executeIgnore(() -> {
104 // 校验设备名称在同一产品下是否唯一 106 // 校验设备名称在同一产品下是否唯一
105 if (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null) { 107 if (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null) {
@@ -121,12 +123,8 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -121,12 +123,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
121 private void initDevice(IotDeviceDO device, IotProductDO product) { 123 private void initDevice(IotDeviceDO device, IotProductDO product) {
122 device.setProductId(product.getId()).setProductKey(product.getProductKey()) 124 device.setProductId(product.getId()).setProductKey(product.getProductKey())
123 .setDeviceType(product.getDeviceType()); 125 .setDeviceType(product.getDeviceType());
124 - // 生成并设置必要的字段  
125 - // TODO @芋艿:各种 mqtt 是不是可以简化!  
126 - device.setDeviceSecret(generateDeviceSecret())  
127 - .setMqttClientId(generateMqttClientId())  
128 - .setMqttUsername(generateMqttUsername(device.getDeviceName(), device.getProductKey()))  
129 - .setMqttPassword(generateMqttPassword()); 126 + // 生成密钥
  127 + device.setDeviceSecret(generateDeviceSecret());
130 // 设置设备状态为未激活 128 // 设置设备状态为未激活
131 device.setState(IotDeviceStateEnum.INACTIVE.getState()); 129 device.setState(IotDeviceStateEnum.INACTIVE.getState());
132 } 130 }
@@ -262,6 +260,16 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -262,6 +260,16 @@ public class IotDeviceServiceImpl implements IotDeviceService {
262 } 260 }
263 261
264 @Override 262 @Override
  263 + public List<IotDeviceDO> getDeviceListByProductId(Long productId) {
  264 + return deviceMapper.selectListByProductId(productId);
  265 + }
  266 +
  267 + @Override
  268 + public List<IotDeviceDO> getDeviceListByIdList(List<Long> deviceIdList) {
  269 + return deviceMapper.selectByIds(deviceIdList);
  270 + }
  271 +
  272 + @Override
265 public void updateDeviceState(Long id, Integer state) { 273 public void updateDeviceState(Long id, Integer state) {
266 // 1. 校验存在 274 // 1. 校验存在
267 IotDeviceDO device = validateDeviceExists(id); 275 IotDeviceDO device = validateDeviceExists(id);
@@ -318,35 +326,6 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -318,35 +326,6 @@ public class IotDeviceServiceImpl implements IotDeviceService {
318 return IdUtil.fastSimpleUUID(); 326 return IdUtil.fastSimpleUUID();
319 } 327 }
320 328
321 - /**  
322 - * 生成 MQTT Client ID  
323 - *  
324 - * @return 生成的 MQTT Client ID  
325 - */  
326 - private String generateMqttClientId() {  
327 - return IdUtil.fastSimpleUUID();  
328 - }  
329 -  
330 - /**  
331 - * 生成 MQTT Username  
332 - *  
333 - * @param deviceName 设备名称  
334 - * @param productKey 产品 Key  
335 - * @return 生成的 MQTT Username  
336 - */  
337 - private String generateMqttUsername(String deviceName, String productKey) {  
338 - return deviceName + "&" + productKey;  
339 - }  
340 -  
341 - /**  
342 - * 生成 MQTT Password  
343 - *  
344 - * @return 生成的 MQTT Password  
345 - */  
346 - private String generateMqttPassword() {  
347 - return RandomUtil.randomString(32);  
348 - }  
349 -  
350 @Override 329 @Override
351 @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入 330 @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
352 public IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport) { 331 public IotDeviceImportRespVO importDevice(List<IotDeviceImportExcelVO> importDevices, boolean updateSupport) {
@@ -417,6 +396,17 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -417,6 +396,17 @@ public class IotDeviceServiceImpl implements IotDeviceService {
417 return respVO; 396 return respVO;
418 } 397 }
419 398
  399 + @Override
  400 + public IotDeviceMqttConnectionParamsRespVO getMqttConnectionParams(Long deviceId) {
  401 + IotDeviceDO device = validateDeviceExists(deviceId);
  402 + MqttSignResult mqttSignResult = MqttSignUtils.calculate(device.getProductKey(), device.getDeviceName(),
  403 + device.getDeviceSecret());
  404 + return new IotDeviceMqttConnectionParamsRespVO()
  405 + .setMqttClientId(mqttSignResult.getClientId())
  406 + .setMqttUsername(mqttSignResult.getUsername())
  407 + .setMqttPassword(mqttSignResult.getPassword());
  408 + }
  409 +
420 private void deleteDeviceCache(IotDeviceDO device) { 410 private void deleteDeviceCache(IotDeviceDO device) {
421 // 保证 Spring AOP 触发 411 // 保证 Spring AOP 触发
422 getSelf().deleteDeviceCache0(device); 412 getSelf().deleteDeviceCache0(device);
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamService.java
@@ -62,4 +62,11 @@ public interface IotDeviceUpstreamService { @@ -62,4 +62,11 @@ public interface IotDeviceUpstreamService {
62 */ 62 */
63 void addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO); 63 void addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO);
64 64
  65 + /**
  66 + * Emqx 连接认证
  67 + *
  68 + * @param authReqDTO Emqx 连接认证 DTO
  69 + */
  70 + boolean authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO);
  71 +
65 } 72 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java
@@ -20,6 +20,8 @@ import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer; @@ -20,6 +20,8 @@ import cn.iocoder.yudao.module.iot.mq.producer.device.IotDeviceProducer;
20 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 20 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
21 import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService; 21 import cn.iocoder.yudao.module.iot.service.device.data.IotDevicePropertyService;
22 import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService; 22 import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService;
  23 +import cn.iocoder.yudao.module.iot.util.MqttSignUtils;
  24 +import cn.iocoder.yudao.module.iot.util.MqttSignUtils.MqttSignResult;
23 import jakarta.annotation.Resource; 25 import jakarta.annotation.Resource;
24 import lombok.extern.slf4j.Slf4j; 26 import lombok.extern.slf4j.Slf4j;
25 import org.springframework.stereotype.Service; 27 import org.springframework.stereotype.Service;
@@ -58,25 +60,26 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { @@ -58,25 +60,26 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
58 // 2.1 情况一:属性上报 60 // 2.1 情况一:属性上报
59 String requestId = IdUtil.fastSimpleUUID(); 61 String requestId = IdUtil.fastSimpleUUID();
60 if (Objects.equals(simulatorReqVO.getType(), IotDeviceMessageTypeEnum.PROPERTY.getType())) { 62 if (Objects.equals(simulatorReqVO.getType(), IotDeviceMessageTypeEnum.PROPERTY.getType())) {
61 - reportDeviceProperty(((IotDevicePropertyReportReqDTO)  
62 - new IotDevicePropertyReportReqDTO().setRequestId(requestId).setReportTime(LocalDateTime.now())  
63 - .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName())) 63 + reportDeviceProperty(((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO()
  64 + .setRequestId(requestId).setReportTime(LocalDateTime.now())
  65 + .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()))
64 .setProperties((Map<String, Object>) simulatorReqVO.getData())); 66 .setProperties((Map<String, Object>) simulatorReqVO.getData()));
65 return; 67 return;
66 } 68 }
67 // 2.2 情况二:事件上报 69 // 2.2 情况二:事件上报
68 if (Objects.equals(simulatorReqVO.getType(), IotDeviceMessageTypeEnum.EVENT.getType())) { 70 if (Objects.equals(simulatorReqVO.getType(), IotDeviceMessageTypeEnum.EVENT.getType())) {
69 - reportDeviceEvent(((IotDeviceEventReportReqDTO)  
70 - new IotDeviceEventReportReqDTO().setRequestId(requestId).setReportTime(LocalDateTime.now())  
71 - .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()))  
72 - .setIdentifier(simulatorReqVO.getIdentifier()).setParams((Map<String, Object>) simulatorReqVO.getData())); 71 + reportDeviceEvent(((IotDeviceEventReportReqDTO) new IotDeviceEventReportReqDTO().setRequestId(requestId)
  72 + .setReportTime(LocalDateTime.now())
  73 + .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()))
  74 + .setIdentifier(simulatorReqVO.getIdentifier())
  75 + .setParams((Map<String, Object>) simulatorReqVO.getData()));
73 return; 76 return;
74 } 77 }
75 // 2.3 情况三:状态变更 78 // 2.3 情况三:状态变更
76 if (Objects.equals(simulatorReqVO.getType(), IotDeviceMessageTypeEnum.STATE.getType())) { 79 if (Objects.equals(simulatorReqVO.getType(), IotDeviceMessageTypeEnum.STATE.getType())) {
77 - updateDeviceState(((IotDeviceStateUpdateReqDTO)  
78 - new IotDeviceStateUpdateReqDTO().setRequestId(IdUtil.fastSimpleUUID()).setReportTime(LocalDateTime.now())  
79 - .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName())) 80 + updateDeviceState(((IotDeviceStateUpdateReqDTO) new IotDeviceStateUpdateReqDTO()
  81 + .setRequestId(IdUtil.fastSimpleUUID()).setReportTime(LocalDateTime.now())
  82 + .setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName()))
80 .setState((Integer) simulatorReqVO.getData())); 83 .setState((Integer) simulatorReqVO.getData()));
81 return; 84 return;
82 } 85 }
@@ -171,7 +174,7 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { @@ -171,7 +174,7 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
171 } 174 }
172 175
173 private void registerDevice0(String productKey, String deviceName, Long gatewayId, 176 private void registerDevice0(String productKey, String deviceName, Long gatewayId,
174 - IotDeviceUpstreamAbstractReqDTO registerReqDTO) { 177 + IotDeviceUpstreamAbstractReqDTO registerReqDTO) {
175 // 1.1 注册设备 178 // 1.1 注册设备
176 IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(productKey, deviceName); 179 IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(productKey, deviceName);
177 boolean registerNew = device == null; 180 boolean registerNew = device == null;
@@ -277,6 +280,37 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService { @@ -277,6 +280,37 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
277 sendDeviceMessage(message, device); 280 sendDeviceMessage(message, device);
278 } 281 }
279 282
  283 + @Override
  284 + public boolean authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
  285 + log.info("[authenticateEmqxConnection][认证 Emqx 连接: {}]", authReqDTO);
  286 + // 1. 校验设备是否存在
  287 + // username 格式:${DeviceName}&${ProductKey}
  288 + String[] usernameParts = authReqDTO.getUsername().split("&");
  289 + if (usernameParts.length != 2) {
  290 + log.error("[authenticateEmqxConnection][认证失败,username 格式不正确]");
  291 + return false;
  292 + }
  293 + String deviceName = usernameParts[0];
  294 + String productKey = usernameParts[1];
  295 + IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
  296 + productKey, deviceName);
  297 + if (device == null) {
  298 + log.error("[authenticateEmqxConnection][设备({}/{}) 不存在]",
  299 + productKey, deviceName);
  300 + return false;
  301 + }
  302 + // 2. 校验密码
  303 + String deviceSecret = device.getDeviceSecret();
  304 + String clientId = authReqDTO.getClientId();
  305 + MqttSignResult sign = MqttSignUtils.calculate(productKey, deviceName, deviceSecret, clientId);
  306 + if (StrUtil.equals(sign.getPassword(), authReqDTO.getPassword())) {
  307 + log.info("[authenticateEmqxConnection][认证成功]");
  308 + return true;
  309 + }
  310 + log.error("[authenticateEmqxConnection][认证失败,密码不正确]");
  311 + return false;
  312 + }
  313 +
280 private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) { 314 private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) {
281 // 1. 【异步】记录设备与插件实例的映射 315 // 1. 【异步】记录设备与插件实例的映射
282 pluginInstanceService.updateDevicePluginInstanceProcessIdAsync(device.getDeviceKey(), reqDTO.getProcessId()); 316 pluginInstanceService.updateDevicePluginInstanceProcessIdAsync(device.getDeviceKey(), reqDTO.getProcessId());
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaFirmwareService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.ota;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareCreateReqVO;
  5 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwarePageReqVO;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareUpdateReqVO;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
  8 +import jakarta.validation.Valid;
  9 +
  10 +// TODO @li:类、方法注释有点冗余,可以参考别的模块哈
  11 +/**
  12 + * OTA固件管理服务接口
  13 + * 提供OTA固件的创建、更新和查询等功能
  14 + */
  15 +public interface IotOtaFirmwareService {
  16 +
  17 + /**
  18 + * 创建OTA固件
  19 + *
  20 + * @param saveReqVO OTA固件保存请求对象,包含固件的相关信息
  21 + * @return 返回新创建的固件的ID
  22 + */
  23 + Long createOtaFirmware(@Valid IotOtaFirmwareCreateReqVO saveReqVO);
  24 +
  25 + /**
  26 + * 更新OTA固件信息
  27 + *
  28 + * @param updateReqVO OTA固件保存请求对象,包含需要更新的固件信息
  29 + */
  30 + void updateOtaFirmware(@Valid IotOtaFirmwareUpdateReqVO updateReqVO);
  31 +
  32 + /**
  33 + * 根据ID获取OTA固件信息
  34 + *
  35 + * @param id OTA固件的唯一标识符
  36 + * @return 返回OTA固件的详细信息对象
  37 + */
  38 + IotOtaFirmwareDO getOtaFirmware(Long id);
  39 +
  40 + /**
  41 + * 分页查询OTA固件信息
  42 + *
  43 + * @param pageReqVO 包含分页查询条件的请求对象
  44 + * @return 返回分页查询结果,包含固件信息列表和分页信息
  45 + */
  46 + PageResult<IotOtaFirmwareDO> getOtaFirmwarePage(@Valid IotOtaFirmwarePageReqVO pageReqVO);
  47 +
  48 + /**
  49 + * 验证物联网OTA固件是否存在
  50 + *
  51 + * @param id 固件的唯一标识符
  52 + * 该方法用于检查系统中是否存在与给定ID关联的物联网OTA固件信息
  53 + * 主要目的是在进行固件更新操作前,确保目标固件已经存在并可以被访问
  54 + * 如果固件不存在,该方法可能抛出异常或返回错误信息,具体行为未定义
  55 + */
  56 + IotOtaFirmwareDO validateFirmwareExists(Long id);
  57 +
  58 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaFirmwareServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.ota;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareCreateReqVO;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwarePageReqVO;
  7 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareUpdateReqVO;
  8 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
  9 +import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaFirmwareMapper;
  10 +import jakarta.annotation.Resource;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +import org.springframework.stereotype.Service;
  13 +import org.springframework.validation.annotation.Validated;
  14 +
  15 +import java.util.List;
  16 +import java.util.Objects;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  19 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_FIRMWARE_NOT_EXISTS;
  20 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE;
  21 +
  22 +@Slf4j
  23 +@Service
  24 +@Validated
  25 +public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService {
  26 +
  27 + @Resource
  28 + private IotOtaFirmwareMapper otaFirmwareMapper;
  29 +
  30 + @Override
  31 + public Long createOtaFirmware(IotOtaFirmwareCreateReqVO saveReqVO) {
  32 + // 1. 校验固件产品 + 版本号不能重复
  33 + // TODO @li:需要考虑设备也存在
  34 + validateProductAndVersionDuplicate(saveReqVO.getProductId(), saveReqVO.getVersion());
  35 +
  36 + // 2.转化数据格式,准备存储到数据库中
  37 + IotOtaFirmwareDO firmware = BeanUtils.toBean(saveReqVO, IotOtaFirmwareDO.class);
  38 + otaFirmwareMapper.insert(firmware);
  39 + return firmware.getId();
  40 + }
  41 +
  42 + @Override
  43 + public void updateOtaFirmware(IotOtaFirmwareUpdateReqVO updateReqVO) {
  44 + // TODO @li:如果序号只有一个,直接写 1. 更好哈
  45 + // 1.1. 校验存在
  46 + validateFirmwareExists(updateReqVO.getId());
  47 +
  48 + // 2. 更新数据
  49 + IotOtaFirmwareDO updateObj = BeanUtils.toBean(updateReqVO, IotOtaFirmwareDO.class);
  50 + otaFirmwareMapper.updateById(updateObj);
  51 + }
  52 +
  53 + @Override
  54 + public IotOtaFirmwareDO getOtaFirmware(Long id) {
  55 + return otaFirmwareMapper.selectById(id);
  56 + }
  57 +
  58 + @Override
  59 + public PageResult<IotOtaFirmwareDO> getOtaFirmwarePage(IotOtaFirmwarePageReqVO pageReqVO) {
  60 + return otaFirmwareMapper.selectPage(pageReqVO);
  61 + }
  62 +
  63 + @Override
  64 + public IotOtaFirmwareDO validateFirmwareExists(Long id) {
  65 + IotOtaFirmwareDO firmware = otaFirmwareMapper.selectById(id);
  66 + if (firmware == null) {
  67 + throw exception(OTA_FIRMWARE_NOT_EXISTS);
  68 + }
  69 + return firmware;
  70 + }
  71 +
  72 + /**
  73 + * 验证产品和版本号是否重复
  74 + * <p>
  75 + * 该方法用于确保在系统中不存在具有相同产品ID和版本号的固件条目
  76 + * 它通过调用otaFirmwareMapper的selectByProductIdAndVersion方法来查询数据库中是否存在匹配的产品ID和版本号的固件信息
  77 + * 如果查询结果非空且不为null,则抛出异常,提示固件信息已存在,从而避免数据重复
  78 + *
  79 + * @param productId 产品ID,用于数据库查询
  80 + * @param version 版本号,用于数据库查询
  81 + * @throws cn.iocoder.yudao.framework.common.exception.ServiceException,则抛出异常,提示固件信息已存在
  82 + */
  83 + private void validateProductAndVersionDuplicate(String productId, String version) {
  84 + // 查询数据库中是否存在具有相同产品ID和版本号的固件信息
  85 + List<IotOtaFirmwareDO> list = otaFirmwareMapper.selectByProductIdAndVersion(productId, version);
  86 + // 如果查询结果非空且不为null,则抛出异常,提示固件信息已存在
  87 + // TODO @li:使用 isNotEmpty 这种 方法,简化
  88 + if (Objects.nonNull(list) && !list.isEmpty()) {
  89 + throw exception(OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE);
  90 + }
  91 + }
  92 +
  93 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeRecordService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.ota;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record.IotOtaUpgradeRecordPageReqVO;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeRecordDO;
  6 +import cn.iocoder.yudao.module.iot.service.ota.bo.IotOtaUpgradeRecordCreateReqBO;
  7 +import cn.iocoder.yudao.module.iot.service.ota.bo.IotOtaUpgradeRecordUpdateReqBO;
  8 +import jakarta.validation.Valid;
  9 +
  10 +import java.util.List;
  11 +import java.util.Map;
  12 +
  13 +/**
  14 + * IotOtaUpgradeRecordService 接口定义了与物联网设备OTA升级记录相关的操作。
  15 + * 该接口提供了创建、更新、查询、统计和重试升级记录的功能。
  16 + */
  17 +public interface IotOtaUpgradeRecordService {
  18 +
  19 + // TODO @createOtaUpgradeRecordBatch 哈,需要补充方法里,缺少 Ota 关键字的
  20 +
  21 + /**
  22 + * 批量创建物联网OTA升级记录
  23 + * <p>
  24 + * 该函数用于处理一组物联网OTA升级记录的创建请求,并将这些记录批量保存到系统中。
  25 + *
  26 + * @param createList 包含多个物联网OTA升级记录创建请求的列表,每个请求对象都经过校验(@Valid注解确保)
  27 + * 列表中的每个元素都是IotOtaUpgradeRecordCreateReqBO类型的对象,表示一个独立的升级记录创建请求。
  28 + */
  29 + void createUpgradeRecordBatch(@Valid List<IotOtaUpgradeRecordCreateReqBO> createList);
  30 +
  31 + // TODO @li:尽量避免写比较大的通用 update。而是根据场景提供,这样才能收敛
  32 + /**
  33 + * 更新现有的 OTA 升级记录
  34 + *
  35 + * @param updateReqBO 包含更新升级记录所需信息的请求对象,必须经过验证。
  36 + */
  37 + void updateUpgradeRecord(@Valid IotOtaUpgradeRecordUpdateReqBO updateReqBO);
  38 +
  39 + /**
  40 + * 获取OTA升级记录的数量统计。
  41 + *
  42 + * @return 返回一个 Map,其中键为状态码,值为对应状态的升级记录数量
  43 + */
  44 + Map<Integer, Long> getOtaUpgradeRecordCount(@Valid IotOtaUpgradeRecordPageReqVO pageReqVO);
  45 +
  46 + /**
  47 + * 获取 OTA 升级记录的统计信息。
  48 + *
  49 + * @return 返回一个 Map,其中键为状态码,值为对应状态的升级记录统计信息
  50 + */
  51 + Map<Integer, Long> getOtaUpgradeRecordStatistics(Long firmwareId);
  52 +
  53 + /**
  54 + * 重试指定的OTA升级记录。
  55 + *
  56 + * @param id 需要重试的升级记录的ID。
  57 + */
  58 + void retryUpgradeRecord(Long id);
  59 +
  60 + /**
  61 + * 获取指定ID的OTA升级记录的详细信息。
  62 + *
  63 + * @param id 需要查询的升级记录的ID。
  64 + * @return 返回包含升级记录详细信息的响应对象。
  65 + */
  66 + IotOtaUpgradeRecordDO getUpgradeRecord(Long id);
  67 +
  68 + /**
  69 + * 分页查询OTA升级记录。
  70 + *
  71 + * @param pageReqVO 包含分页查询条件的请求对象,必须经过验证。
  72 + * @return 返回包含分页查询结果的响应对象。
  73 + */
  74 + PageResult<IotOtaUpgradeRecordDO> getUpgradeRecordPage(@Valid IotOtaUpgradeRecordPageReqVO pageReqVO);
  75 +
  76 + /**
  77 + * 根据任务ID取消升级记录。
  78 + * <p>
  79 + * 该函数用于根据给定的任务ID,取消与该任务相关的升级记录。通常用于在任务执行失败或用户手动取消时,
  80 + * 清理或标记相关的升级记录为取消状态。
  81 + *
  82 + * @param taskId 要取消升级记录的任务ID。该ID唯一标识一个任务,通常由任务管理系统生成。
  83 + */
  84 + void cancelUpgradeRecordByTaskId(Long taskId);
  85 +
  86 + /**
  87 + * 根据升级状态获取升级记录列表
  88 + *
  89 + * @param state 升级状态,用于筛选符合条件的升级记录
  90 + * @return 返回符合指定状态的升级记录列表,列表中的元素为 {@link IotOtaUpgradeRecordDO} 对象
  91 + */
  92 + List<IotOtaUpgradeRecordDO> getUpgradeRecordListByState(Integer state);
  93 +
  94 + /**
  95 + * 更新升级记录的状态。
  96 + * <p>
  97 + * 该函数用于批量更新指定升级记录的状态。通过传入的ID列表和状态值,将对应的升级记录的状态更新为指定的值。
  98 + *
  99 + * @param ids 需要更新状态的升级记录的ID列表。列表中的每个元素代表一个升级记录的ID。
  100 + * @param status 要更新的状态值。该值应为有效的状态标识符,通常为整数类型。
  101 + */
  102 + void updateUpgradeRecordStatus(List<Long> ids, Integer status);
  103 +
  104 + /**
  105 + * 根据任务ID获取升级记录列表
  106 + * <p>
  107 + * 该函数通过给定的任务ID,查询并返回与该任务相关的所有升级记录。
  108 + *
  109 + * @param taskId 任务ID,用于指定需要查询的任务
  110 + * @return 返回一个包含升级记录的列表,列表中的每个元素为IotOtaUpgradeRecordDO对象
  111 + */
  112 + List<IotOtaUpgradeRecordDO> getUpgradeRecordListByTaskId(Long taskId);
  113 +
  114 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeRecordServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.ota;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record.IotOtaUpgradeRecordPageReqVO;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeRecordDO;
  8 +import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaUpgradeRecordMapper;
  9 +import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum;
  10 +import cn.iocoder.yudao.module.iot.service.ota.bo.IotOtaUpgradeRecordCreateReqBO;
  11 +import cn.iocoder.yudao.module.iot.service.ota.bo.IotOtaUpgradeRecordUpdateReqBO;
  12 +import jakarta.annotation.Resource;
  13 +import lombok.extern.slf4j.Slf4j;
  14 +import org.springframework.stereotype.Service;
  15 +import org.springframework.transaction.annotation.Transactional;
  16 +import org.springframework.validation.annotation.Validated;
  17 +
  18 +import java.util.List;
  19 +import java.util.Map;
  20 +
  21 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  22 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
  23 +
  24 +@Slf4j
  25 +@Service
  26 +@Validated
  27 +public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordService {
  28 +
  29 + @Resource
  30 + private IotOtaUpgradeRecordMapper upgradeRecordMapper;
  31 +
  32 + @Override
  33 + public void createUpgradeRecordBatch(List<IotOtaUpgradeRecordCreateReqBO> createList) {
  34 + // 1. 批量校验参数信息
  35 + createList.forEach(saveBO -> validateUpgradeRecordDuplicate(saveBO.getFirmwareId(), saveBO.getTaskId(), saveBO.getDeviceId()));
  36 +
  37 + // 2. 将数据批量存储到数据库里
  38 + List<IotOtaUpgradeRecordDO> upgradeRecords = BeanUtils.toBean(createList, IotOtaUpgradeRecordDO.class);
  39 + upgradeRecordMapper.insertBatch(upgradeRecords);
  40 + }
  41 +
  42 + @Override
  43 + @Transactional
  44 + public void updateUpgradeRecord(IotOtaUpgradeRecordUpdateReqBO updateReqBO) {
  45 + // 1. 校验升级记录信息是否存在
  46 + validateUpgradeRecordExists(updateReqBO.getId());
  47 +
  48 + // 2. 将数据转化成数据库存储的格式
  49 + IotOtaUpgradeRecordDO updateRecord = BeanUtils.toBean(updateReqBO, IotOtaUpgradeRecordDO.class);
  50 + upgradeRecordMapper.updateById(updateRecord);
  51 + // TODO @芋艿: 更新升级记录触发的其他Action
  52 + }
  53 +
  54 + /**
  55 + * 获取OTA升级记录的数量统计。
  56 + * 该方法根据传入的查询条件,统计不同状态的OTA升级记录数量,并返回一个包含各状态数量的映射。
  57 + *
  58 + * @param pageReqVO 包含查询条件的请求对象,主要包括任务ID和设备名称等信息。
  59 + * @return 返回一个Map,其中键为状态常量,值为对应状态的记录数量。
  60 + */
  61 + @Override
  62 + @Transactional
  63 + public Map<Integer, Long> getOtaUpgradeRecordCount(IotOtaUpgradeRecordPageReqVO pageReqVO) {
  64 + // 分别查询不同状态的OTA升级记录数量
  65 + // TODO @li: 通过 groupby 统计下;
  66 + Long pending = upgradeRecordMapper.getOtaUpgradeRecordCount(pageReqVO.getTaskId(), pageReqVO.getDeviceName(), IotOtaUpgradeRecordStatusEnum.PENDING.getStatus());
  67 + Long pushed = upgradeRecordMapper.getOtaUpgradeRecordCount(pageReqVO.getTaskId(), pageReqVO.getDeviceName(), IotOtaUpgradeRecordStatusEnum.PUSHED.getStatus());
  68 + Long upgrading = upgradeRecordMapper.getOtaUpgradeRecordCount(pageReqVO.getTaskId(), pageReqVO.getDeviceName(), IotOtaUpgradeRecordStatusEnum.UPGRADING.getStatus());
  69 + Long success = upgradeRecordMapper.getOtaUpgradeRecordCount(pageReqVO.getTaskId(), pageReqVO.getDeviceName(), IotOtaUpgradeRecordStatusEnum.SUCCESS.getStatus());
  70 + Long failure = upgradeRecordMapper.getOtaUpgradeRecordCount(pageReqVO.getTaskId(), pageReqVO.getDeviceName(), IotOtaUpgradeRecordStatusEnum.FAILURE.getStatus());
  71 + Long canceled = upgradeRecordMapper.getOtaUpgradeRecordCount(pageReqVO.getTaskId(), pageReqVO.getDeviceName(), IotOtaUpgradeRecordStatusEnum.CANCELED.getStatus());
  72 + // 将各状态的数量封装到Map中返回
  73 + // TODO @li:使用 MapUtil,因为 Map.of 是 jdk9 才有,后续不好同步到 master 的 jdk8;
  74 + return Map.of(IotOtaUpgradeRecordPageReqVO.PENDING, pending,
  75 + IotOtaUpgradeRecordPageReqVO.PUSHED, pushed,
  76 + IotOtaUpgradeRecordPageReqVO.UPGRADING, upgrading,
  77 + IotOtaUpgradeRecordPageReqVO.SUCCESS, success,
  78 + IotOtaUpgradeRecordPageReqVO.FAILURE, failure,
  79 + IotOtaUpgradeRecordPageReqVO.CANCELED, canceled);
  80 + }
  81 +
  82 + /**
  83 + * 获取指定固件ID的OTA升级记录统计信息。
  84 + * 该方法通过查询数据库,统计不同状态的OTA升级记录数量,并返回一个包含各状态数量的映射。
  85 + *
  86 + * @param firmwareId 固件ID,用于指定需要统计的固件升级记录。
  87 + * @return 返回一个Map,其中键为升级记录状态(如PENDING、PUSHED等),值为对应状态的记录数量。
  88 + */
  89 + @Override
  90 + @Transactional
  91 + public Map<Integer, Long> getOtaUpgradeRecordStatistics(Long firmwareId) {
  92 + // 查询并统计不同状态的OTA升级记录数量
  93 + // TODO @li: 通过 groupby 统计下;
  94 + Long pending = upgradeRecordMapper.getOtaUpgradeRecordStatistics(firmwareId, IotOtaUpgradeRecordStatusEnum.PENDING.getStatus());
  95 + Long pushed = upgradeRecordMapper.getOtaUpgradeRecordStatistics(firmwareId, IotOtaUpgradeRecordStatusEnum.PUSHED.getStatus());
  96 + Long upgrading = upgradeRecordMapper.getOtaUpgradeRecordStatistics(firmwareId, IotOtaUpgradeRecordStatusEnum.UPGRADING.getStatus());
  97 + Long success = upgradeRecordMapper.getOtaUpgradeRecordStatistics(firmwareId, IotOtaUpgradeRecordStatusEnum.SUCCESS.getStatus());
  98 + Long failure = upgradeRecordMapper.getOtaUpgradeRecordStatistics(firmwareId, IotOtaUpgradeRecordStatusEnum.FAILURE.getStatus());
  99 + Long canceled = upgradeRecordMapper.getOtaUpgradeRecordStatistics(firmwareId, IotOtaUpgradeRecordStatusEnum.CANCELED.getStatus());
  100 + // 将统计结果封装为Map并返回
  101 + return Map.of(IotOtaUpgradeRecordPageReqVO.PENDING, pending,
  102 + IotOtaUpgradeRecordPageReqVO.PUSHED, pushed,
  103 + IotOtaUpgradeRecordPageReqVO.UPGRADING, upgrading,
  104 + IotOtaUpgradeRecordPageReqVO.SUCCESS, success,
  105 + IotOtaUpgradeRecordPageReqVO.FAILURE, failure,
  106 + IotOtaUpgradeRecordPageReqVO.CANCELED, canceled);
  107 + }
  108 +
  109 + @Override
  110 + public void retryUpgradeRecord(Long id) {
  111 + // 1.1.校验升级记录信息是否存在
  112 + IotOtaUpgradeRecordDO upgradeRecord = validateUpgradeRecordExists(id);
  113 + // 1.2.校验升级记录是否可以重新升级
  114 + validateUpgradeRecordCanRetry(upgradeRecord);
  115 +
  116 + // 2.将一些数据重置,这样定时任务轮询就可以重启任务
  117 + upgradeRecordMapper.updateById(new IotOtaUpgradeRecordDO()
  118 + .setId(upgradeRecord.getId()).setProgress(0)
  119 + .setStatus(IotOtaUpgradeRecordStatusEnum.PENDING.getStatus()));
  120 + // TODO @芋艿: 重试升级记录触发的其他Action
  121 + // TODO 如果一个升级记录被取消或者已经执行失败,重试成功,是否会对升级任务的状态有影响?
  122 + }
  123 +
  124 + @Override
  125 + public IotOtaUpgradeRecordDO getUpgradeRecord(Long id) {
  126 + return upgradeRecordMapper.selectById(id);
  127 + }
  128 +
  129 + @Override
  130 + public PageResult<IotOtaUpgradeRecordDO> getUpgradeRecordPage(IotOtaUpgradeRecordPageReqVO pageReqVO) {
  131 + return upgradeRecordMapper.selectUpgradeRecordPage(pageReqVO);
  132 + }
  133 +
  134 + @Override
  135 + public void cancelUpgradeRecordByTaskId(Long taskId) {
  136 + // 暂定只有待推送的升级记录可以取消
  137 + upgradeRecordMapper.cancelUpgradeRecordByTaskId(taskId);
  138 + }
  139 +
  140 + @Override
  141 + public List<IotOtaUpgradeRecordDO> getUpgradeRecordListByState(Integer state) {
  142 + return upgradeRecordMapper.selectUpgradeRecordListByState(state);
  143 + }
  144 +
  145 + @Override
  146 + public void updateUpgradeRecordStatus(List<Long> ids, Integer status) {
  147 + upgradeRecordMapper.updateUpgradeRecordStatus(ids, status);
  148 + }
  149 +
  150 + @Override
  151 + public List<IotOtaUpgradeRecordDO> getUpgradeRecordListByTaskId(Long taskId) {
  152 + return upgradeRecordMapper.selectUpgradeRecordListByTaskId(taskId);
  153 + }
  154 +
  155 + /**
  156 + * 验证指定的升级记录是否存在。
  157 + * <p>
  158 + * 该函数通过给定的ID查询升级记录,如果查询结果为空,则抛出异常,表示升级记录不存在。
  159 + *
  160 + * @param id 升级记录的唯一标识符,类型为Long。
  161 + * @throws cn.iocoder.yudao.framework.common.exception.ServiceException,则抛出异常,异常类型为OTA_UPGRADE_RECORD_NOT_EXISTS。
  162 + */
  163 + private IotOtaUpgradeRecordDO validateUpgradeRecordExists(Long id) {
  164 + // 根据ID查询升级记录
  165 + IotOtaUpgradeRecordDO upgradeRecord = upgradeRecordMapper.selectById(id);
  166 + // 如果查询结果为空,抛出异常
  167 + if (upgradeRecord == null) {
  168 + throw exception(OTA_UPGRADE_RECORD_NOT_EXISTS);
  169 + }
  170 + return upgradeRecord;
  171 + }
  172 +
  173 + /**
  174 + * 验证固件升级记录是否存在。
  175 + * <p>
  176 + * 该函数通过给定的固件ID、任务ID和设备ID查询升级记录,如果查询结果为空,则抛出异常。
  177 + *
  178 + * @param firmwareId 固件ID,用于标识特定的固件版本
  179 + * @param taskId 任务ID,用于标识特定的升级任务
  180 + * @param deviceId 设备ID,用于标识特定的设备
  181 + * @throws cn.iocoder.yudao.framework.common.exception.ServiceException,则抛出OTA_UPGRADE_RECORD_NOT_EXISTS异常
  182 + */
  183 + private void validateUpgradeRecordDuplicate(Long firmwareId, Long taskId, String deviceId) {
  184 + // 根据条件查询升级记录
  185 + IotOtaUpgradeRecordDO upgradeRecord = upgradeRecordMapper.selectByConditions(firmwareId, taskId, deviceId);
  186 + // 如果查询结果为空,抛出异常
  187 + if (upgradeRecord != null) {
  188 + throw exception(OTA_UPGRADE_RECORD_DUPLICATE);
  189 + }
  190 + }
  191 +
  192 + /**
  193 + * 验证升级记录是否可以重试。
  194 + * <p>
  195 + * 该方法用于检查给定的升级记录是否处于允许重试的状态。如果升级记录的状态为
  196 + * PENDING、PUSHED 或 UPGRADING,则抛出异常,表示不允许重试。
  197 + *
  198 + * @param upgradeRecord 需要验证的升级记录对象,类型为 IotOtaUpgradeRecordDO
  199 + * @throws cn.iocoder.yudao.framework.common.exception.ServiceException,则抛出 OTA_UPGRADE_RECORD_CANNOT_RETRY 异常
  200 + */
  201 + private void validateUpgradeRecordCanRetry(IotOtaUpgradeRecordDO upgradeRecord) {
  202 + // 检查升级记录的状态是否为 PENDING、PUSHED 或 UPGRADING
  203 + if (ObjectUtils.equalsAny(upgradeRecord.getStatus(),
  204 + IotOtaUpgradeRecordStatusEnum.PENDING.getStatus(),
  205 + IotOtaUpgradeRecordStatusEnum.PUSHED.getStatus(),
  206 + IotOtaUpgradeRecordStatusEnum.UPGRADING.getStatus())) {
  207 + // 如果升级记录处于上述状态之一,则抛出异常,表示不允许重试
  208 + throw exception(OTA_UPGRADE_RECORD_CANNOT_RETRY);
  209 + }
  210 + }
  211 +
  212 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeTaskService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.ota;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task.IotOtaUpgradeTaskPageReqVO;
  5 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task.IotOtaUpgradeTaskSaveReqVO;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
  7 +import jakarta.validation.Valid;
  8 +
  9 +import java.util.List;
  10 +
  11 +// TODO @li:类、方法注释有点冗余,可以参考别的模块哈
  12 +/**
  13 + * IoT OTA升级任务服务接口
  14 + * 提供OTA升级任务的创建、取消和查询功能
  15 + */
  16 +public interface IotOtaUpgradeTaskService {
  17 +
  18 + /**
  19 + * 创建OTA升级任务
  20 + *
  21 + * @param createReqVO OTA升级任务的创建请求对象,包含创建任务所需的信息
  22 + * @return 创建成功的OTA升级任务的ID
  23 + */
  24 + Long createUpgradeTask(@Valid IotOtaUpgradeTaskSaveReqVO createReqVO);
  25 +
  26 + /**
  27 + * 取消OTA升级任务
  28 + *
  29 + * @param id 要取消的OTA升级任务的ID
  30 + */
  31 + void cancelUpgradeTask(Long id);
  32 +
  33 + /**
  34 + * 根据ID获取OTA升级任务的详细信息
  35 + *
  36 + * @param id OTA升级任务的ID
  37 + * @return OTA升级任务的详细信息对象
  38 + */
  39 + IotOtaUpgradeTaskDO getUpgradeTask(Long id);
  40 +
  41 + /**
  42 + * 分页查询OTA升级任务
  43 + *
  44 + * @param pageReqVO OTA升级任务的分页查询请求对象,包含查询条件和分页信息
  45 + * @return 分页查询结果,包含OTA升级任务列表和总记录数
  46 + */
  47 + PageResult<IotOtaUpgradeTaskDO> getUpgradeTaskPage(@Valid IotOtaUpgradeTaskPageReqVO pageReqVO);
  48 +
  49 + /**
  50 + * 根据任务状态获取升级任务列表
  51 + *
  52 + * @param state 任务状态,用于筛选符合条件的升级任务
  53 + * @return 返回符合指定状态的升级任务列表,列表中的元素为 IotOtaUpgradeTaskDO 对象
  54 + */
  55 + List<IotOtaUpgradeTaskDO> getUpgradeTaskByState(Integer state);
  56 +
  57 + /**
  58 + * 更新升级任务的状态。
  59 + * <p>
  60 + * 该函数用于根据任务ID更新指定升级任务的状态。通常用于在任务执行过程中
  61 + * 更新任务的状态,例如从“进行中”变为“已完成”或“失败”。
  62 + *
  63 + * @param id 升级任务的唯一标识符,类型为Long。不能为null。
  64 + * @param status 要更新的任务状态,类型为Integer。通常表示任务的状态码,如0表示未开始,1表示进行中,2表示已完成等。
  65 + */
  66 + void updateUpgradeTaskStatus(Long id, Integer status);
  67 +
  68 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeTaskServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.ota;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.convert.Convert;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task.IotOtaUpgradeTaskPageReqVO;
  7 +import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task.IotOtaUpgradeTaskSaveReqVO;
  8 +import cn.iocoder.yudao.module.iot.convert.ota.IotOtaUpgradeRecordConvert;
  9 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  10 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
  11 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
  12 +import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaUpgradeTaskMapper;
  13 +import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskScopeEnum;
  14 +import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskStatusEnum;
  15 +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
  16 +import cn.iocoder.yudao.module.iot.service.ota.bo.IotOtaUpgradeRecordCreateReqBO;
  17 +import jakarta.annotation.Resource;
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.springframework.context.annotation.Lazy;
  20 +import org.springframework.stereotype.Service;
  21 +import org.springframework.transaction.annotation.Transactional;
  22 +import org.springframework.validation.annotation.Validated;
  23 +
  24 +import java.util.List;
  25 +import java.util.Objects;
  26 +
  27 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  28 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
  29 +
  30 +@Slf4j
  31 +@Service
  32 +@Validated
  33 +public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
  34 +
  35 + @Resource
  36 + private IotOtaUpgradeTaskMapper upgradeTaskMapper;
  37 +
  38 + @Resource
  39 + @Lazy
  40 + private IotDeviceService deviceService;
  41 + @Resource
  42 + @Lazy
  43 + private IotOtaFirmwareService firmwareService;
  44 + @Resource
  45 + @Lazy
  46 + private IotOtaUpgradeRecordService upgradeRecordService;
  47 +
  48 + @Override
  49 + @Transactional(rollbackFor = Exception.class)
  50 + public Long createUpgradeTask(IotOtaUpgradeTaskSaveReqVO createReqVO) {
  51 + // 1.1 校验同一固件的升级任务名称不重复
  52 + validateFirmwareTaskDuplicate(createReqVO.getFirmwareId(), createReqVO.getName());
  53 + // 1.2 校验固件信息是否存在
  54 + IotOtaFirmwareDO firmware = firmwareService.validateFirmwareExists(createReqVO.getFirmwareId());
  55 + // 1.3 校验升级范围=2(指定设备时),deviceIds deviceNames不为空并且长度相等
  56 + // TODO @li:deviceNames 应该后端查询
  57 + validateScopeAndDevice(createReqVO.getScope(), createReqVO.getDeviceIds(), createReqVO.getDeviceNames());
  58 + // TODO @li:如果全部范围,但是没设备可以升级,需要报错
  59 +
  60 + // 2. 保存 OTA 升级任务信息到数据库
  61 + IotOtaUpgradeTaskDO upgradeTask = initUpgradeTask(createReqVO, firmware.getProductId());
  62 + upgradeTaskMapper.insert(upgradeTask);
  63 +
  64 + // 3. 生成设备升级记录信息并存储,等待定时任务轮询
  65 + List<IotOtaUpgradeRecordCreateReqBO> upgradeRecordList = initUpgradeRecordList(
  66 + upgradeTask, firmware, createReqVO.getDeviceIds());
  67 + // TODO @li:只需要传递 deviceIds、firewareId、剩余的 upgradeRecordService 里面自己处理;这样,后续 record 加字段,都不需要透传太多;解耦
  68 + upgradeRecordService.createUpgradeRecordBatch(upgradeRecordList);
  69 + // TODO @芋艿: 创建任务触发的其他Action
  70 + return upgradeTask.getId();
  71 + }
  72 +
  73 + @Override
  74 + @Transactional(rollbackFor = Exception.class)
  75 + public void cancelUpgradeTask(Long id) {
  76 + // 1.1 校验升级任务是否存在
  77 + IotOtaUpgradeTaskDO upgradeTask = validateUpgradeTaskExists(id);
  78 + // 1.2 校验升级任务是否可以取消
  79 + // TODO @li:这种一次性的,可以不考虑拆分方法
  80 + validateUpgradeTaskCanCancel(upgradeTask);
  81 +
  82 + // 2. 更新 OTA 升级任务状态为已取消
  83 + upgradeTaskMapper.updateById(IotOtaUpgradeTaskDO.builder()
  84 + .id(id).status(IotOtaUpgradeTaskStatusEnum.CANCELED.getStatus())
  85 + .build());
  86 + // 3. 更新 OTA 升级记录状态为已取消
  87 + upgradeRecordService.cancelUpgradeRecordByTaskId(id);
  88 + // TODO @芋艿: 取消任务触发的其他Action
  89 + }
  90 +
  91 + @Override
  92 + public IotOtaUpgradeTaskDO getUpgradeTask(Long id) {
  93 + return upgradeTaskMapper.selectById(id);
  94 + }
  95 +
  96 + @Override
  97 + public PageResult<IotOtaUpgradeTaskDO> getUpgradeTaskPage(IotOtaUpgradeTaskPageReqVO pageReqVO) {
  98 + return upgradeTaskMapper.selectUpgradeTaskPage(pageReqVO);
  99 + }
  100 +
  101 + @Override
  102 + public List<IotOtaUpgradeTaskDO> getUpgradeTaskByState(Integer state) {
  103 + return upgradeTaskMapper.selectUpgradeTaskByState(state);
  104 + }
  105 +
  106 + @Override
  107 + public void updateUpgradeTaskStatus(Long id, Integer status) {
  108 + upgradeTaskMapper.updateById(IotOtaUpgradeTaskDO.builder()
  109 + .id(id).status(status)
  110 + .build());
  111 + }
  112 +
  113 + /**
  114 + * 校验固件升级任务是否重复
  115 + * <p>
  116 + * 该方法用于检查给定固件ID和任务名称组合是否已存在于数据库中,如果存在则抛出异常,
  117 + * 表示任务名称对于该固件而言是重复的此检查确保用户不能创建具有相同名称的任务,
  118 + * 从而避免数据重复和混淆
  119 + *
  120 + * @param firmwareId 固件的唯一标识符,用于区分不同的固件
  121 + * @param taskName 升级任务的名称,用于与固件ID一起检查重复性
  122 + * @throws cn.iocoder.yudao.framework.common.exception.ServerException 则抛出预定义的异常
  123 + */
  124 + private void validateFirmwareTaskDuplicate(Long firmwareId, String taskName) {
  125 + // 查询数据库中是否有相同固件ID和任务名称的升级任务存在
  126 + List<IotOtaUpgradeTaskDO> upgradeTaskList = upgradeTaskMapper.selectByFirmwareIdAndName(firmwareId, taskName);
  127 + // 如果查询结果不为空,说明存在重复的任务名称,抛出异常
  128 + if (CollUtil.isNotEmpty(upgradeTaskList)) {
  129 + throw exception(OTA_UPGRADE_TASK_NAME_DUPLICATE);
  130 + }
  131 + }
  132 +
  133 + /**
  134 + * 验证升级任务的范围和设备参数是否有效
  135 + * 当选择特定设备进行升级时,确保提供的设备ID和设备名称列表有效且对应
  136 + *
  137 + * @param scope 升级任务的范围,表示是选择特定设备还是其他范围
  138 + * @param deviceIds 设备ID列表,用于标识参与升级的设备
  139 + * @param deviceNames 设备名称列表,与设备ID列表对应
  140 + */
  141 + private void validateScopeAndDevice(Integer scope, List<Long> deviceIds, List<String> deviceNames) {
  142 + // 当升级任务范围为选择特定设备时
  143 + if (Objects.equals(scope, IotOtaUpgradeTaskScopeEnum.SELECT.getScope())) {
  144 + // 检查设备ID列表和设备名称列表是否为空或长度不一致,若不符合要求,则抛出异常
  145 + if (CollUtil.isEmpty(deviceIds) || CollUtil.isEmpty(deviceNames) || deviceIds.size() != deviceNames.size()) {
  146 + throw exception(OTA_UPGRADE_TASK_PARAMS_INVALID);
  147 + }
  148 + }
  149 + }
  150 +
  151 + /**
  152 + * 验证升级任务是否存在
  153 + * <p>
  154 + * 通过查询数据库来验证给定ID的升级任务是否存在此方法主要用于确保后续操作所针对的升级任务是有效的
  155 + *
  156 + * @param id 升级任务的唯一标识符如果为null或数据库中不存在对应的记录,则认为任务不存在
  157 + * @throws cn.iocoder.yudao.framework.common.exception.ServiceException 如果升级任务不存在,则抛出异常提示任务不存在
  158 + */
  159 + private IotOtaUpgradeTaskDO validateUpgradeTaskExists(Long id) {
  160 + // 查询数据库中是否有相同固件ID和任务名称的升级任务存在
  161 + IotOtaUpgradeTaskDO upgradeTask = upgradeTaskMapper.selectById(id);
  162 + // 如果查询结果不为空,说明存在重复的任务名称,抛出异常
  163 + if (Objects.isNull(upgradeTask)) {
  164 + throw exception(OTA_UPGRADE_TASK_NOT_EXISTS);
  165 + }
  166 + return upgradeTask;
  167 + }
  168 +
  169 + /**
  170 + * 验证升级任务是否可以被取消
  171 + * <p>
  172 + * 此方法旨在确保只有当升级任务处于进行中状态时,才可以执行取消操作
  173 + * 它通过比较任务的当前状态与预定义的进行中状态来判断是否允许取消操作
  174 + * 如果任务状态不符合条件,则抛出异常,表明该任务无法取消
  175 + *
  176 + * @param upgradeTask 待验证的升级任务对象,包含任务的详细信息,如状态等
  177 + * @throws cn.iocoder.yudao.framework.common.exception.ServiceException 如果任务状态不是进行中,则抛出此异常,表明任务无法取消
  178 + */
  179 + private void validateUpgradeTaskCanCancel(IotOtaUpgradeTaskDO upgradeTask) {
  180 + // 检查升级任务的状态是否为进行中,只有此状态下的任务才允许取消
  181 + if (!Objects.equals(upgradeTask.getStatus(), IotOtaUpgradeTaskStatusEnum.IN_PROGRESS.getStatus())) {
  182 + // 只有进行中的任务才可以取消
  183 + throw exception(OTA_UPGRADE_TASK_CANNOT_CANCEL);
  184 + }
  185 + }
  186 +
  187 + // TODO @li:一次性,不复用的,可以直接写在对应的逻辑里;
  188 + /**
  189 + * 初始化升级任务
  190 + * <p>
  191 + * 根据请求参数创建升级任务对象,并根据选择的范围初始化设备数量
  192 + * 如果选择特定设备进行升级,则设备数量为所选设备的总数
  193 + * 如果选择全部设备进行升级,则设备数量为该固件对应产品下的所有设备总数
  194 + *
  195 + * @param createReqVO 升级任务保存请求对象,包含创建升级任务所需的信息
  196 + * @return 返回初始化后的升级任务对象
  197 + */
  198 + private IotOtaUpgradeTaskDO initUpgradeTask(IotOtaUpgradeTaskSaveReqVO createReqVO, String productId) {
  199 + // 配置各项参数
  200 + IotOtaUpgradeTaskDO upgradeTask = IotOtaUpgradeTaskDO.builder()
  201 + // TODO @li:不用每个占一行。最好相同类型的,放在一行里;
  202 + .name(createReqVO.getName())
  203 + .description(createReqVO.getDescription())
  204 + .firmwareId(createReqVO.getFirmwareId())
  205 + .scope(createReqVO.getScope())
  206 + .deviceIds(createReqVO.getDeviceIds())
  207 + .deviceNames(createReqVO.getDeviceNames())
  208 + .deviceCount(Convert.toLong(CollUtil.size(createReqVO.getDeviceIds())))
  209 + .status(IotOtaUpgradeTaskStatusEnum.IN_PROGRESS.getStatus())
  210 + .build();
  211 + // 如果选择全选,则需要查询设备数量
  212 + if (Objects.equals(createReqVO.getScope(), IotOtaUpgradeTaskScopeEnum.ALL.getScope())) {
  213 + // 根据产品ID查询设备数量
  214 + Long deviceCount = deviceService.getDeviceCountByProductId(Convert.toLong(productId));
  215 + // 设置升级任务的设备数量
  216 + upgradeTask.setDeviceCount(deviceCount);
  217 + }
  218 + // 返回初始化后的升级任务对象
  219 + return upgradeTask;
  220 + }
  221 +
  222 + /**
  223 + * 初始化升级记录列表
  224 + * <p>
  225 + * 根据升级任务的范围(选择设备或按产品ID)获取设备列表,并将其转换为升级记录请求对象列表。
  226 + *
  227 + * @param upgradeTask 升级任务对象,包含升级任务的相关信息
  228 + * @param firmware 固件对象,包含固件的相关信息
  229 + * @param deviceIds 设备ID列表,仅在升级任务范围为选择设备时使用
  230 + * @return 升级记录请求对象列表,包含每个设备的升级记录信息
  231 + */
  232 + private List<IotOtaUpgradeRecordCreateReqBO> initUpgradeRecordList(
  233 + IotOtaUpgradeTaskDO upgradeTask, IotOtaFirmwareDO firmware, List<Long> deviceIds) {
  234 + // TODO @li:需要考虑,如果创建多个任务,相互之间不能重复;
  235 + // 1)指定设备的时候,进行校验;2)如果是全部,则过滤其它已经发起的;;;;;另外,需要排除掉 cancel 的哈。因为 cancal 之后,还可以发起
  236 + // 根据升级任务的范围确定设备列表
  237 + List<IotDeviceDO> deviceList;
  238 + if (Objects.equals(upgradeTask.getScope(), IotOtaUpgradeTaskScopeEnum.SELECT.getScope())) {
  239 + // 如果升级任务范围为选择设备,则根据设备ID列表获取设备信息
  240 + deviceList = deviceService.getDeviceListByIdList(deviceIds);
  241 + } else {
  242 + // 如果升级任务范围为按产品ID,则根据固件的产品ID获取设备信息
  243 + deviceList = deviceService.getDeviceListByProductId(Convert.toLong(firmware.getProductId()));
  244 + }
  245 + // 将升级任务、固件和设备列表转换为升级记录请求对象列表
  246 + return IotOtaUpgradeRecordConvert.INSTANCE.convertBOList(upgradeTask, firmware, deviceList);
  247 + }
  248 +
  249 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/bo/IotOtaUpgradeRecordCreateReqBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.ota.bo;
  2 +
  3 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  4 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import lombok.Data;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +@Data
  12 +public class IotOtaUpgradeRecordCreateReqBO {
  13 +
  14 + /**
  15 + * 固件编号
  16 + * <p>
  17 + * 关联 {@link IotOtaFirmwareDO#getId()}
  18 + */
  19 + @NotNull(message = "固件编号不能为空")
  20 + private Long firmwareId;
  21 + /**
  22 + * 任务编号
  23 + * <p>
  24 + * 关联 {@link IotOtaUpgradeTaskDO#getId()}
  25 + */
  26 + @NotNull(message = "任务编号不能为空")
  27 + private Long taskId;
  28 + /**
  29 + * 产品标识
  30 + * <p>
  31 + * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
  32 + */
  33 + private String productKey;
  34 + /**
  35 + * 设备名称
  36 + * <p>
  37 + * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO#getId()}
  38 + */
  39 + private String deviceName;
  40 + /**
  41 + * 设备编号
  42 + * <p>
  43 + * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO#getId()}
  44 + */
  45 + @NotNull(message = "设备编号不能为空")
  46 + private String deviceId;
  47 + /**
  48 + * 来源的固件编号
  49 + * <p>
  50 + * 关联 {@link IotDeviceDO#getFirmwareId()}
  51 + */
  52 + private Long fromFirmwareId;
  53 + /**
  54 + * 升级状态
  55 + * <p>
  56 + * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum}
  57 + */
  58 + private Integer status;
  59 + /**
  60 + * 升级进度,百分比
  61 + */
  62 + private Integer progress;
  63 + /**
  64 + * 升级进度描述
  65 + * <p>
  66 + * 注意,只记录设备最后一次的升级进度描述
  67 + * 如果想看历史记录,可以查看 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO} 设备日志
  68 + */
  69 + private String description;
  70 + /**
  71 + * 升级开始时间
  72 + */
  73 + private LocalDateTime startTime;
  74 + /**
  75 + * 升级结束时间
  76 + */
  77 + private LocalDateTime endTime;
  78 +
  79 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/bo/IotOtaUpgradeRecordUpdateReqBO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.ota.bo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +import org.hibernate.validator.constraints.Range;
  8 +import org.springframework.format.annotation.DateTimeFormat;
  9 +
  10 +import java.time.LocalDateTime;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  13 +
  14 +// TODO @li:这个类,貌似没用?
  15 +@Data
  16 +public class IotOtaUpgradeRecordUpdateReqBO {
  17 +
  18 + /**
  19 + * 升级记录编号
  20 + */
  21 + @NotNull(message = "升级记录编号不能为空")
  22 + private Long id;
  23 + /**
  24 + * 升级状态
  25 + * <p>
  26 + * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum}
  27 + */
  28 + @InEnum(IotOtaUpgradeRecordStatusEnum.class)
  29 + private Integer status;
  30 + /**
  31 + * 升级进度,百分比
  32 + */
  33 + @Range(min = 0, max = 100, message = "升级进度必须介于 0-100 之间")
  34 + private Integer progress;
  35 + /**
  36 + * 升级开始时间
  37 + */
  38 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  39 + private LocalDateTime startTime;
  40 + /**
  41 + * 升级结束时间
  42 + */
  43 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  44 + private LocalDateTime endTime;
  45 +
  46 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/IotRuleSceneDataBridgeAction.java
1 package cn.iocoder.yudao.module.iot.service.rule.action; 1 package cn.iocoder.yudao.module.iot.service.rule.action;
2 2
3 -import cn.hutool.core.collection.CollUtil;  
4 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
6 -import cn.iocoder.yudao.framework.common.util.http.HttpUtils;  
7 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
8 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataBridgeDO; 5 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataBridgeDO;
9 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO; 6 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotRuleSceneDO;
10 -import cn.iocoder.yudao.module.iot.enums.rule.IotDataBridgTypeEnum;  
11 import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum; 7 import cn.iocoder.yudao.module.iot.enums.rule.IotRuleSceneActionTypeEnum;
12 import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage; 8 import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
13 import cn.iocoder.yudao.module.iot.service.rule.IotDataBridgeService; 9 import cn.iocoder.yudao.module.iot.service.rule.IotDataBridgeService;
  10 +import cn.iocoder.yudao.module.iot.service.rule.action.databridge.IotDataBridgeExecute;
14 import jakarta.annotation.Resource; 11 import jakarta.annotation.Resource;
15 import lombok.extern.slf4j.Slf4j; 12 import lombok.extern.slf4j.Slf4j;
16 -import org.apache.rocketmq.client.producer.DefaultMQProducer;  
17 -import org.apache.rocketmq.client.producer.SendResult;  
18 -import org.apache.rocketmq.client.producer.SendStatus;  
19 -import org.apache.rocketmq.common.message.Message;  
20 -import org.apache.rocketmq.remoting.common.RemotingHelper;  
21 -import org.springframework.http.*;  
22 import org.springframework.stereotype.Component; 13 import org.springframework.stereotype.Component;
23 -import org.springframework.web.client.RestTemplate;  
24 -import org.springframework.web.util.UriComponentsBuilder;  
25 14
26 -import java.time.LocalDateTime;  
27 -import java.util.HashMap;  
28 -import java.util.Map;  
29 -  
30 -import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID; 15 +import java.util.List;
31 16
32 /** 17 /**
33 * IoT 数据桥梁的 {@link IotRuleSceneAction} 实现类 18 * IoT 数据桥梁的 {@link IotRuleSceneAction} 实现类
34 * 19 *
35 * @author 芋道源码 20 * @author 芋道源码
36 */ 21 */
37 -// TODO @芋艿:【优化】因为 bridge 会比较多,所以可以考虑在 rule 下,新建一个 bridge 的 package,然后定义一个 bridgehandler,它有:  
38 -// 1. input 方法、output 方法  
39 -// 2. build 方法,用于有状态的连接,例如说 mq、tcp、websocket  
40 @Component 22 @Component
41 @Slf4j 23 @Slf4j
42 public class IotRuleSceneDataBridgeAction implements IotRuleSceneAction { 24 public class IotRuleSceneDataBridgeAction implements IotRuleSceneAction {
43 25
44 @Resource 26 @Resource
45 - private RestTemplate restTemplate;  
46 -  
47 - @Resource  
48 private IotDataBridgeService dataBridgeService; 27 private IotDataBridgeService dataBridgeService;
  28 + @Resource
  29 + private List<IotDataBridgeExecute> dataBridgeExecutes;
49 30
50 @Override 31 @Override
51 public void execute(IotDeviceMessage message, IotRuleSceneDO.ActionConfig config) { 32 public void execute(IotDeviceMessage message, IotRuleSceneDO.ActionConfig config) {
@@ -65,26 +46,8 @@ public class IotRuleSceneDataBridgeAction implements IotRuleSceneAction { @@ -65,26 +46,8 @@ public class IotRuleSceneDataBridgeAction implements IotRuleSceneAction {
65 return; 46 return;
66 } 47 }
67 48
68 - // 2.1 执行 HTTP 请求  
69 - if (IotDataBridgTypeEnum.HTTP.getType().equals(dataBridge.getType())) {  
70 - executeHttp(message, (IotDataBridgeDO.HttpConfig) dataBridge.getConfig());  
71 - return;  
72 - }  
73 - // 2.2 执行 RocketMQ 发送消息  
74 - if (IotDataBridgTypeEnum.ROCKETMQ.getType().equals(dataBridge.getType())) {  
75 - executeRocketMQ(message, (IotDataBridgeDO.RocketMQConfig) dataBridge.getConfig());  
76 - return;  
77 - }  
78 -  
79 - // TODO @芋艿:因为下面的,都是有状态的,所以通过 guava 缓存连接,然后通过 RemovalNotification 实现关闭。例如说,一次新建有效期是 10 分钟;  
80 - // TODO @芋艿:mq-redis  
81 - // TODO @芋艿:mq-数据库  
82 - // TODO @芋艿:kafka  
83 - // TODO @芋艿:rocketmq  
84 - // TODO @芋艿:rabbitmq  
85 - // TODO @芋艿:mqtt  
86 - // TODO @芋艿:tcp  
87 - // TODO @芋艿:websocket 49 + // 2. 执行数据桥接操作
  50 + dataBridgeExecutes.forEach(execute -> execute.execute(message, dataBridge));
88 } 51 }
89 52
90 @Override 53 @Override
@@ -92,115 +55,4 @@ public class IotRuleSceneDataBridgeAction implements IotRuleSceneAction { @@ -92,115 +55,4 @@ public class IotRuleSceneDataBridgeAction implements IotRuleSceneAction {
92 return IotRuleSceneActionTypeEnum.DATA_BRIDGE; 55 return IotRuleSceneActionTypeEnum.DATA_BRIDGE;
93 } 56 }
94 57
95 - @SuppressWarnings({"unchecked", "deprecation"})  
96 - private void executeHttp(IotDeviceMessage message, IotDataBridgeDO.HttpConfig config) {  
97 - String url = null;  
98 - HttpMethod method = HttpMethod.valueOf(config.getMethod().toUpperCase());  
99 - HttpEntity<String> requestEntity = null;  
100 - ResponseEntity<String> responseEntity = null;  
101 - try {  
102 - // 1.1 构建 Header  
103 - HttpHeaders headers = new HttpHeaders();  
104 - if (CollUtil.isNotEmpty(config.getHeaders())) {  
105 - config.getHeaders().putAll(config.getHeaders());  
106 - }  
107 - headers.add(HEADER_TENANT_ID, message.getTenantId().toString());  
108 - // 1.2 构建 URL  
109 - UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(config.getUrl());  
110 - if (CollUtil.isNotEmpty(config.getQuery())) {  
111 - config.getQuery().forEach(uriBuilder::queryParam);  
112 - }  
113 - // 1.3 构建请求体  
114 - if (method == HttpMethod.GET) {  
115 - uriBuilder.queryParam("message", HttpUtils.encodeUtf8(JsonUtils.toJsonString(message)));  
116 - url = uriBuilder.build().toUriString();  
117 - requestEntity = new HttpEntity<>(headers);  
118 - } else {  
119 - url = uriBuilder.build().toUriString();  
120 - Map<String, Object> requestBody = JsonUtils.parseObject(config.getBody(), Map.class);  
121 - if (requestBody == null) {  
122 - requestBody = new HashMap<>();  
123 - }  
124 - requestBody.put("message", message);  
125 - headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);  
126 - requestEntity = new HttpEntity<>(JsonUtils.toJsonString(requestBody), headers);  
127 - }  
128 -  
129 - // 2.1 发送请求  
130 - responseEntity = restTemplate.exchange(url, method, requestEntity, String.class);  
131 - // 2.2 记录日志  
132 - if (responseEntity.getStatusCode().is2xxSuccessful()) {  
133 - log.info("[executeHttp][message({}) config({}) url({}) method({}) requestEntity({}) 请求成功({})]",  
134 - message, config, url, method, requestEntity, responseEntity);  
135 - } else {  
136 - log.error("[executeHttp][message({}) config({}) url({}) method({}) requestEntity({}) 请求失败({})]",  
137 - message, config, url, method, requestEntity, responseEntity);  
138 - }  
139 - } catch (Exception e) {  
140 - log.error("[executeHttp][message({}) config({}) url({}) method({}) requestEntity({}) 请求异常({})]",  
141 - message, config, url, method, requestEntity, responseEntity, e);  
142 - }  
143 - }  
144 -  
145 - private void executeRocketMQ(IotDeviceMessage message, IotDataBridgeDO.RocketMQConfig config) {  
146 - // 1.1 创建生产者实例,指定生产者组名  
147 - DefaultMQProducer producer = new DefaultMQProducer(config.getGroup());  
148 - // TODO @puhui999:可以考虑,基于 guava 做 cache,使用 config 作为 key,然后假个 listener 超时,销毁 producer  
149 - try {  
150 - // 1.2 设置 NameServer 地址  
151 - producer.setNamesrvAddr(config.getNameServer());  
152 - // 1.3 启动生产者  
153 - producer.start();  
154 -  
155 - // 2.1 创建消息对象,指定Topic、Tag和消息体  
156 - Message msg = new Message(  
157 - config.getTopic(),  
158 - config.getTags(),  
159 - message.toString().getBytes(RemotingHelper.DEFAULT_CHARSET)  
160 - );  
161 - // 2.2 发送同步消息并处理结果  
162 - SendResult sendResult = producer.send(msg);  
163 - // 2.3 处理发送结果  
164 - if (SendStatus.SEND_OK.equals(sendResult.getSendStatus())) {  
165 - log.info("[executeRocketMQ][message({}) config({}) 发送成功,结果({})]", message, config, sendResult);  
166 - } else {  
167 - log.error("[executeRocketMQ][message({}) config({}) 发送失败,结果({})]", message, config, sendResult);  
168 - }  
169 - } catch (Exception e) {  
170 - log.error("[executeRocketMQ][message({}) config({}) 发送异常]", message, config, e);  
171 - } finally {  
172 - // 3. 关闭生产者  
173 - producer.shutdown();  
174 - }  
175 - }  
176 -  
177 - // TODO @芋艿:测试代码,后续清理  
178 - public static void main(String[] args) {  
179 - // 1. 创建 IotRuleSceneDataBridgeAction 实例  
180 - IotRuleSceneDataBridgeAction action = new IotRuleSceneDataBridgeAction();  
181 -  
182 - // 2. 创建测试消息  
183 - IotDeviceMessage message = IotDeviceMessage.builder()  
184 - .requestId("TEST-001")  
185 - .productKey("testProduct")  
186 - .deviceName("testDevice")  
187 - .deviceKey("testDeviceKey")  
188 - .type("property")  
189 - .identifier("temperature")  
190 - .data("{\"value\": 60}")  
191 - .reportTime(LocalDateTime.now())  
192 - .tenantId(1L)  
193 - .build();  
194 -  
195 - // 3. 创建 RocketMQ 配置  
196 - IotDataBridgeDO.RocketMQConfig config = new IotDataBridgeDO.RocketMQConfig();  
197 - config.setNameServer("127.0.0.1:9876");  
198 - config.setGroup("test-group");  
199 - config.setTopic("test-topic");  
200 - config.setTags("test-tag");  
201 -  
202 - // 4. 执行测试  
203 - action.executeRocketMQ(message, config);  
204 - }  
205 -  
206 } 58 }
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotDataBridgeExecute.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.action.databridge;
  2 +
  3 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataBridgeDO;
  4 +import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
  5 +
  6 +
  7 +/**
  8 + * IoT 数据桥梁的执行器 execute 接口
  9 + *
  10 + * @author HUIHUI
  11 + */
  12 +public interface IotDataBridgeExecute {
  13 +
  14 + /**
  15 + * 执行数据桥接操作
  16 + *
  17 + * @param message 设备消息
  18 + * @param dataBridge 数据桥梁
  19 + */
  20 + void execute(IotDeviceMessage message, IotDataBridgeDO dataBridge);
  21 +
  22 + // TODO @芋艿:因为下面的,都是有状态的,所以通过 guava 缓存连接,然后通过 RemovalNotification 实现关闭。例如说,一次新建有效期是 10 分钟;
  23 + // TODO @芋艿:mq-redis
  24 + // TODO @芋艿:mq-数据库
  25 + // TODO @芋艿:kafka
  26 + // TODO @芋艿:rocketmq
  27 + // TODO @芋艿:rabbitmq
  28 + // TODO @芋艿:mqtt
  29 + // TODO @芋艿:tcp
  30 + // TODO @芋艿:websocket
  31 +
  32 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotHttpDataBridgeExecute.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.action.databridge;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
  5 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataBridgeDO;
  7 +import cn.iocoder.yudao.module.iot.enums.rule.IotDataBridgTypeEnum;
  8 +import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
  9 +import jakarta.annotation.Resource;
  10 +import lombok.extern.slf4j.Slf4j;
  11 +import org.springframework.http.*;
  12 +import org.springframework.stereotype.Component;
  13 +import org.springframework.web.client.RestTemplate;
  14 +import org.springframework.web.util.UriComponentsBuilder;
  15 +
  16 +import java.util.HashMap;
  17 +import java.util.Map;
  18 +
  19 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
  20 +
  21 +/**
  22 + * Http 的 {@link IotDataBridgeExecute} 实现类
  23 + *
  24 + * @author HUIHUI
  25 + */
  26 +@Component
  27 +@Slf4j
  28 +public class IotHttpDataBridgeExecute implements IotDataBridgeExecute {
  29 +
  30 + @Resource
  31 + private RestTemplate restTemplate;
  32 +
  33 + @Override
  34 + public void execute(IotDeviceMessage message, IotDataBridgeDO dataBridge) {
  35 + // 1.1 校验数据桥接的类型 == HTTP
  36 + if (!IotDataBridgTypeEnum.HTTP.getType().equals(dataBridge.getType())) {
  37 + return;
  38 + }
  39 + // 1.2 执行 HTTP 请求
  40 + executeHttp(message, (IotDataBridgeDO.HttpConfig) dataBridge.getConfig());
  41 + }
  42 +
  43 + @SuppressWarnings({"unchecked", "deprecation"})
  44 + private void executeHttp(IotDeviceMessage message, IotDataBridgeDO.HttpConfig config) {
  45 + String url = null;
  46 + HttpMethod method = HttpMethod.valueOf(config.getMethod().toUpperCase());
  47 + HttpEntity<String> requestEntity = null;
  48 + ResponseEntity<String> responseEntity = null;
  49 + try {
  50 + // 1.1 构建 Header
  51 + HttpHeaders headers = new HttpHeaders();
  52 + if (CollUtil.isNotEmpty(config.getHeaders())) {
  53 + config.getHeaders().putAll(config.getHeaders());
  54 + }
  55 + headers.add(HEADER_TENANT_ID, message.getTenantId().toString());
  56 + // 1.2 构建 URL
  57 + UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(config.getUrl());
  58 + if (CollUtil.isNotEmpty(config.getQuery())) {
  59 + config.getQuery().forEach(uriBuilder::queryParam);
  60 + }
  61 + // 1.3 构建请求体
  62 + if (method == HttpMethod.GET) {
  63 + uriBuilder.queryParam("message", HttpUtils.encodeUtf8(JsonUtils.toJsonString(message)));
  64 + url = uriBuilder.build().toUriString();
  65 + requestEntity = new HttpEntity<>(headers);
  66 + } else {
  67 + url = uriBuilder.build().toUriString();
  68 + Map<String, Object> requestBody = JsonUtils.parseObject(config.getBody(), Map.class);
  69 + if (requestBody == null) {
  70 + requestBody = new HashMap<>();
  71 + }
  72 + requestBody.put("message", message);
  73 + headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
  74 + requestEntity = new HttpEntity<>(JsonUtils.toJsonString(requestBody), headers);
  75 + }
  76 +
  77 + // 2.1 发送请求
  78 + responseEntity = restTemplate.exchange(url, method, requestEntity, String.class);
  79 + // 2.2 记录日志
  80 + if (responseEntity.getStatusCode().is2xxSuccessful()) {
  81 + log.info("[executeHttp][message({}) config({}) url({}) method({}) requestEntity({}) 请求成功({})]",
  82 + message, config, url, method, requestEntity, responseEntity);
  83 + } else {
  84 + log.error("[executeHttp][message({}) config({}) url({}) method({}) requestEntity({}) 请求失败({})]",
  85 + message, config, url, method, requestEntity, responseEntity);
  86 + }
  87 + } catch (Exception e) {
  88 + log.error("[executeHttp][message({}) config({}) url({}) method({}) requestEntity({}) 请求异常({})]",
  89 + message, config, url, method, requestEntity, responseEntity, e);
  90 + }
  91 + }
  92 +
  93 +}
0 \ No newline at end of file 94 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/action/databridge/IotRocketMQDataBridgeExecute.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.rule.action.databridge;
  2 +
  3 +import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotDataBridgeDO;
  4 +import cn.iocoder.yudao.module.iot.enums.rule.IotDataBridgTypeEnum;
  5 +import cn.iocoder.yudao.module.iot.mq.message.IotDeviceMessage;
  6 +import com.google.common.cache.CacheBuilder;
  7 +import com.google.common.cache.CacheLoader;
  8 +import com.google.common.cache.LoadingCache;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.apache.rocketmq.client.producer.DefaultMQProducer;
  11 +import org.apache.rocketmq.client.producer.SendResult;
  12 +import org.apache.rocketmq.client.producer.SendStatus;
  13 +import org.apache.rocketmq.common.message.Message;
  14 +import org.apache.rocketmq.remoting.common.RemotingHelper;
  15 +import org.springframework.stereotype.Component;
  16 +
  17 +import java.time.Duration;
  18 +import java.time.LocalDateTime;
  19 +import java.util.concurrent.Executors;
  20 +
  21 +/**
  22 + * RocketMQ 的 {@link IotDataBridgeExecute} 实现类
  23 + *
  24 + * @author HUIHUI
  25 + */
  26 +@Component
  27 +@Slf4j
  28 +public class IotRocketMQDataBridgeExecute implements IotDataBridgeExecute {
  29 +
  30 + /**
  31 + * 针对 {@link IotDataBridgeDO.RocketMQConfig} 的 DefaultMQProducer 缓存
  32 + */
  33 + // TODO @puhui999:因为 kafka 之类也存在这个情况,是不是得搞个抽象类。提供一个 initProducer,和 closeProducer 方法
  34 + private final LoadingCache<IotDataBridgeDO.RocketMQConfig, DefaultMQProducer> PRODUCER_CACHE = CacheBuilder.newBuilder()
  35 + .refreshAfterWrite(Duration.ofMinutes(10)) // TODO puhui999:应该是 read 30 分钟哈
  36 + // 增加移除监听器,自动关闭 producer
  37 + .removalListener(notification -> {
  38 + DefaultMQProducer producer = (DefaultMQProducer) notification.getValue();
  39 + // TODO puhui999:if return,更简短哈
  40 + if (producer != null) {
  41 + try {
  42 + producer.shutdown();
  43 + log.info("[PRODUCER_CACHE][配置({}) 对应的 producer 已关闭]", notification.getKey());
  44 + } catch (Exception e) {
  45 + log.error("[PRODUCER_CACHE][配置({}) 对应的 producer 关闭失败]", notification.getKey(), e);
  46 + }
  47 + }
  48 + })
  49 + // TODO @puhui999:就同步哈,不用异步处理。
  50 + // 通过 asyncReloading 实现全异步加载,包括 refreshAfterWrite 被阻塞的加载线程
  51 + .build(CacheLoader.asyncReloading(new CacheLoader<IotDataBridgeDO.RocketMQConfig, DefaultMQProducer>() {
  52 +
  53 + @Override
  54 + public DefaultMQProducer load(IotDataBridgeDO.RocketMQConfig config) throws Exception {
  55 + DefaultMQProducer producer = new DefaultMQProducer(config.getGroup());
  56 + producer.setNamesrvAddr(config.getNameServer());
  57 + producer.start();
  58 + log.info("[PRODUCER_CACHE][配置({}) 对应的 producer 已创建并启动]", config);
  59 + return producer;
  60 + }
  61 +
  62 + }, Executors.newCachedThreadPool()));
  63 +
  64 + @Override
  65 + public void execute(IotDeviceMessage message, IotDataBridgeDO dataBridge) {
  66 + // 1.1 校验数据桥接的类型 == ROCKETMQ
  67 + if (!IotDataBridgTypeEnum.ROCKETMQ.getType().equals(dataBridge.getType())) {
  68 + return;
  69 + }
  70 + // 1.2 执行 RocketMQ 发送消息
  71 + executeRocketMQ(message, (IotDataBridgeDO.RocketMQConfig) dataBridge.getConfig());
  72 + }
  73 +
  74 + private void executeRocketMQ(IotDeviceMessage message, IotDataBridgeDO.RocketMQConfig config) {
  75 + try {
  76 + // 1. 获取或创建 Producer
  77 + DefaultMQProducer producer = PRODUCER_CACHE.get(config);
  78 +
  79 + // 2.1 创建消息对象,指定Topic、Tag和消息体
  80 + Message msg = new Message(
  81 + config.getTopic(),
  82 + config.getTags(),
  83 + message.toString().getBytes(RemotingHelper.DEFAULT_CHARSET)
  84 + );
  85 + // 2.2 发送同步消息并处理结果
  86 + SendResult sendResult = producer.send(msg);
  87 + // 2.3 处理发送结果
  88 + if (SendStatus.SEND_OK.equals(sendResult.getSendStatus())) {
  89 + log.info("[executeRocketMQ][message({}) config({}) 发送成功,结果({})]", message, config, sendResult);
  90 + } else {
  91 + log.error("[executeRocketMQ][message({}) config({}) 发送失败,结果({})]", message, config, sendResult);
  92 + }
  93 + } catch (Exception e) {
  94 + log.error("[executeRocketMQ][message({}) config({}) 发送异常]", message, config, e);
  95 + }
  96 + }
  97 +
  98 + // TODO @芋艿:测试代码,后续清理
  99 + public static void main(String[] args) {
  100 + // 1. 创建一个共享的实例
  101 + IotRocketMQDataBridgeExecute action = new IotRocketMQDataBridgeExecute();
  102 +
  103 + // 2. 创建共享的配置
  104 + IotDataBridgeDO.RocketMQConfig config = new IotDataBridgeDO.RocketMQConfig();
  105 + config.setNameServer("127.0.0.1:9876");
  106 + config.setGroup("test-group");
  107 + config.setTopic("test-topic");
  108 + config.setTags("test-tag");
  109 +
  110 + // 3. 创建共享的消息
  111 + IotDeviceMessage message = IotDeviceMessage.builder()
  112 + .requestId("TEST-001")
  113 + .productKey("testProduct")
  114 + .deviceName("testDevice")
  115 + .deviceKey("testDeviceKey")
  116 + .type("property")
  117 + .identifier("temperature")
  118 + .data("{\"value\": 60}")
  119 + .reportTime(LocalDateTime.now())
  120 + .tenantId(1L)
  121 + .build();
  122 +
  123 + // 4. 执行两次测试,验证缓存
  124 + log.info("[main][第一次执行,应该会创建新的 producer]");
  125 + action.executeRocketMQ(message, config);
  126 +
  127 + log.info("[main][第二次执行,应该会复用缓存的 producer]");
  128 + action.executeRocketMQ(message, config);
  129 + }
  130 +
  131 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/MqttSignUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.util;
  2 +
  3 +import cn.hutool.crypto.digest.HMac;
  4 +import cn.hutool.crypto.digest.HmacAlgorithm;
  5 +import lombok.AllArgsConstructor;
  6 +import lombok.Getter;
  7 +
  8 +import java.nio.charset.StandardCharsets;
  9 +
  10 +/**
  11 + * MQTT 签名工具类
  12 + *
  13 + * 提供静态方法来计算 MQTT 连接参数
  14 + */
  15 +public class MqttSignUtils {
  16 +
  17 + /**
  18 + * 计算 MQTT 连接参数
  19 + *
  20 + * @param productKey 产品密钥
  21 + * @param deviceName 设备名称
  22 + * @param deviceSecret 设备密钥
  23 + * @return 包含 clientId, username, password 的结果对象
  24 + */
  25 + public static MqttSignResult calculate(String productKey, String deviceName, String deviceSecret) {
  26 + return calculate(productKey, deviceName, deviceSecret, productKey + "." + deviceName);
  27 + }
  28 +
  29 + /**
  30 + * 计算 MQTT 连接参数
  31 + *
  32 + * @param productKey 产品密钥
  33 + * @param deviceName 设备名称
  34 + * @param deviceSecret 设备密钥
  35 + * @param clientId 客户端 ID
  36 + * @return 包含 clientId, username, password 的结果对象
  37 + */
  38 + public static MqttSignResult calculate(String productKey, String deviceName, String deviceSecret, String clientId) {
  39 + String username = deviceName + "&" + productKey;
  40 + // 构建签名内容
  41 + StringBuilder signContentBuilder = new StringBuilder()
  42 + .append("clientId").append(clientId)
  43 + .append("deviceName").append(deviceName)
  44 + .append("deviceSecret").append(deviceSecret)
  45 + .append("productKey").append(productKey);
  46 +
  47 + // 使用 HMac 计算签名
  48 + byte[] key = deviceSecret.getBytes(StandardCharsets.UTF_8);
  49 + String signContent = signContentBuilder.toString();
  50 + HMac mac = new HMac(HmacAlgorithm.HmacSHA256, key);
  51 + String password = mac.digestHex(signContent);
  52 +
  53 + return new MqttSignResult(clientId, username, password);
  54 + }
  55 +
  56 + /**
  57 + * MQTT 签名结果类
  58 + */
  59 + @Getter
  60 + @AllArgsConstructor
  61 + public static class MqttSignResult {
  62 + private final String clientId;
  63 + private final String username;
  64 + private final String password;
  65 + }
  66 +
  67 +}
0 \ No newline at end of file 68 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/ota/IotOtaUpgradeRecordMapper.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<!DOCTYPE mapper
  3 + PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  4 + "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  5 +<mapper namespace="cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaUpgradeRecordMapper">
  6 +
  7 + <!-- TODO @li:看看是不是可以通过 mybatis plus 写哈,更好适配多 db -->
  8 + <select id="getOtaUpgradeRecordCount" resultType="java.lang.Long">
  9 + select count(*)
  10 + from iot_ota_upgrade_record
  11 + where task_id = #{taskId}
  12 + and device_name like concat('%', #{deviceName}, '%')
  13 + and status = #{status}
  14 + </select>
  15 +
  16 + <!-- TODO @li:看看是不是可以通过 mybatis plus 写哈,更好适配多 db -->
  17 + <select id="getOtaUpgradeRecordStatistics" resultType="java.lang.Long">
  18 + select count(*)
  19 + from iot_ota_upgrade_record
  20 + where firmware_id = #{firmwareId}
  21 + and status = #{status}
  22 + </select>
  23 +
  24 +</mapper>
0 \ No newline at end of file 25 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/IotPluginCommonAutoConfiguration.java
@@ -44,8 +44,8 @@ public class IotPluginCommonAutoConfiguration { @@ -44,8 +44,8 @@ public class IotPluginCommonAutoConfiguration {
44 44
45 @Bean(initMethod = "init", destroyMethod = "stop") 45 @Bean(initMethod = "init", destroyMethod = "stop")
46 public IotPluginInstanceHeartbeatJob pluginInstanceHeartbeatJob( 46 public IotPluginInstanceHeartbeatJob pluginInstanceHeartbeatJob(
47 - IotDeviceUpstreamApi deviceDataApi, IotDeviceDownstreamServer deviceDownstreamServer) {  
48 - return new IotPluginInstanceHeartbeatJob(deviceDataApi, deviceDownstreamServer); 47 + IotDeviceUpstreamApi deviceDataApi, IotDeviceDownstreamServer deviceDownstreamServer, IotPluginCommonProperties commonProperties) {
  48 + return new IotPluginInstanceHeartbeatJob(deviceDataApi, deviceDownstreamServer, commonProperties);
49 } 49 }
50 50
51 } 51 }
52 \ No newline at end of file 52 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/IotPluginCommonProperties.java
@@ -7,6 +7,11 @@ import org.springframework.validation.annotation.Validated; @@ -7,6 +7,11 @@ import org.springframework.validation.annotation.Validated;
7 7
8 import java.time.Duration; 8 import java.time.Duration;
9 9
  10 +/**
  11 + * IoT 插件的通用配置类
  12 + *
  13 + * @author haohao
  14 + */
10 @ConfigurationProperties(prefix = "yudao.iot.plugin.common") 15 @ConfigurationProperties(prefix = "yudao.iot.plugin.common")
11 @Validated 16 @Validated
12 @Data 17 @Data
@@ -45,4 +50,10 @@ public class IotPluginCommonProperties { @@ -45,4 +50,10 @@ public class IotPluginCommonProperties {
45 */ 50 */
46 private Integer downstreamPort = DOWNSTREAM_PORT_RANDOM; 51 private Integer downstreamPort = DOWNSTREAM_PORT_RANDOM;
47 52
  53 + /**
  54 + * 插件包标识符
  55 + */
  56 + @NotEmpty(message = "插件包标识符不能为空")
  57 + private String pluginKey;
  58 +
48 } 59 }
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDeviceConfigSetVertxHandler.java
@@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC @@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; 16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
17 17
18 /** 18 /**
19 - * IOT 设备配置设置 Vertx Handler 19 + * IoT 设备配置设置 Vertx Handler
20 * 20 *
21 * 芋道源码 21 * 芋道源码
22 */ 22 */
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDeviceOtaUpgradeVertxHandler.java
@@ -5,14 +5,19 @@ import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceOt @@ -5,14 +5,19 @@ import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceOt
5 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler; 5 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
6 import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils; 6 import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
7 import io.vertx.core.Handler; 7 import io.vertx.core.Handler;
  8 +import io.vertx.core.json.JsonObject;
8 import io.vertx.ext.web.RoutingContext; 9 import io.vertx.ext.web.RoutingContext;
9 import lombok.RequiredArgsConstructor; 10 import lombok.RequiredArgsConstructor;
10 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
11 -import io.vertx.core.json.JsonObject;  
12 12
13 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; 13 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
14 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; 14 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
15 15
  16 +/**
  17 + * IoT 设备 OTA 升级 Vertx Handler
  18 + * <p>
  19 + * 芋道源码
  20 + */
16 @Slf4j 21 @Slf4j
17 @RequiredArgsConstructor 22 @RequiredArgsConstructor
18 public class IotDeviceOtaUpgradeVertxHandler implements Handler<RoutingContext> { 23 public class IotDeviceOtaUpgradeVertxHandler implements Handler<RoutingContext> {
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDevicePropertyGetVertxHandler.java
@@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC @@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; 16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
17 17
18 /** 18 /**
19 - * IOT 设备服务获取 Vertx Handler 19 + * IoT 设备服务获取 Vertx Handler
20 * 20 *
21 * 芋道源码 21 * 芋道源码
22 */ 22 */
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDevicePropertySetVertxHandler.java
@@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC @@ -16,7 +16,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; 16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
17 17
18 /** 18 /**
19 - * IOT 设备服务设置 Vertx Handler 19 + * IoT 设置设备属性 Vertx Handler
20 * 20 *
21 * 芋道源码 21 * 芋道源码
22 */ 22 */
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/downstream/router/IotDeviceServiceInvokeVertxHandler.java
@@ -5,17 +5,18 @@ import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceSe @@ -5,17 +5,18 @@ import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.IotDeviceSe
5 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler; 5 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
6 import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils; 6 import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
7 import io.vertx.core.Handler; 7 import io.vertx.core.Handler;
  8 +import io.vertx.core.json.JsonObject;
8 import io.vertx.ext.web.RoutingContext; 9 import io.vertx.ext.web.RoutingContext;
9 import lombok.RequiredArgsConstructor; 10 import lombok.RequiredArgsConstructor;
10 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
11 -import io.vertx.core.json.JsonObject; 12 +
12 import java.util.Map; 13 import java.util.Map;
13 14
14 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; 15 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
15 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; 16 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
16 17
17 /** 18 /**
18 - * IOT 设备服务调用 Vertx Handler 19 + * IoT 设备服务调用 Vertx Handler
19 * 20 *
20 * 芋道源码 21 * 芋道源码
21 */ 22 */
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/heartbeat/IotPluginInstanceHeartbeatJob.java
@@ -4,6 +4,7 @@ import cn.hutool.system.SystemUtil; @@ -4,6 +4,7 @@ import cn.hutool.system.SystemUtil;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi; 5 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
6 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO; 6 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
  7 +import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
7 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer; 8 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer;
8 import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils; 9 import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
9 import lombok.RequiredArgsConstructor; 10 import lombok.RequiredArgsConstructor;
@@ -23,6 +24,7 @@ public class IotPluginInstanceHeartbeatJob { @@ -23,6 +24,7 @@ public class IotPluginInstanceHeartbeatJob {
23 24
24 private final IotDeviceUpstreamApi deviceUpstreamApi; 25 private final IotDeviceUpstreamApi deviceUpstreamApi;
25 private final IotDeviceDownstreamServer deviceDownstreamServer; 26 private final IotDeviceDownstreamServer deviceDownstreamServer;
  27 + private final IotPluginCommonProperties commonProperties;
26 28
27 public void init() { 29 public void init() {
28 CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(buildPluginInstanceHeartbeatReqDTO(true)); 30 CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(buildPluginInstanceHeartbeatReqDTO(true));
@@ -41,9 +43,8 @@ public class IotPluginInstanceHeartbeatJob { @@ -41,9 +43,8 @@ public class IotPluginInstanceHeartbeatJob {
41 } 43 }
42 44
43 private IotPluginInstanceHeartbeatReqDTO buildPluginInstanceHeartbeatReqDTO(Boolean online) { 45 private IotPluginInstanceHeartbeatReqDTO buildPluginInstanceHeartbeatReqDTO(Boolean online) {
44 - // TODO @haohao:pluginKey 的获取???  
45 return new IotPluginInstanceHeartbeatReqDTO() 46 return new IotPluginInstanceHeartbeatReqDTO()
46 - .setPluginKey("yudao-module-iot-plugin-http").setProcessId(IotPluginCommonUtils.getProcessId()) 47 + .setPluginKey(commonProperties.getPluginKey()).setProcessId(IotPluginCommonUtils.getProcessId())
47 .setHostIp(SystemUtil.getHostInfo().getAddress()).setDownstreamPort(deviceDownstreamServer.getPort()) 48 .setHostIp(SystemUtil.getHostInfo().getAddress()).setDownstreamPort(deviceDownstreamServer.getPort())
48 .setOnline(online); 49 .setOnline(online);
49 } 50 }
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/upstream/IotDeviceUpstreamClient.java
@@ -58,6 +58,12 @@ public class IotDeviceUpstreamClient implements IotDeviceUpstreamApi { @@ -58,6 +58,12 @@ public class IotDeviceUpstreamClient implements IotDeviceUpstreamApi {
58 } 58 }
59 59
60 @Override 60 @Override
  61 + public CommonResult<Boolean> authenticateEmqxConnection(IotDeviceEmqxAuthReqDTO authReqDTO) {
  62 + String url = properties.getUpstreamUrl() + URL_PREFIX + "/authenticate-emqx-connection";
  63 + return doPost(url, authReqDTO);
  64 + }
  65 +
  66 + @Override
61 public CommonResult<Boolean> reportDeviceProperty(IotDevicePropertyReportReqDTO reportReqDTO) { 67 public CommonResult<Boolean> reportDeviceProperty(IotDevicePropertyReportReqDTO reportReqDTO) {
62 String url = properties.getUpstreamUrl() + URL_PREFIX + "/report-property"; 68 String url = properties.getUpstreamUrl() + URL_PREFIX + "/report-property";
63 return doPost(url, reportReqDTO); 69 return doPost(url, reportReqDTO);
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/util/IotPluginCommonUtils.java
@@ -41,4 +41,12 @@ public class IotPluginCommonUtils { @@ -41,4 +41,12 @@ public class IotPluginCommonUtils {
41 .end(JsonUtils.toJsonString(result)); 41 .end(JsonUtils.toJsonString(result));
42 } 42 }
43 43
  44 + @SuppressWarnings("deprecation")
  45 + public static void writeJson(RoutingContext routingContext, String result) {
  46 + routingContext.response()
  47 + .setStatusCode(200)
  48 + .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
  49 + .end(result);
  50 + }
  51 +
44 } 52 }
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/plugin.properties
1 -plugin.id=plugin-emqx  
2 -plugin.class=cn.iocoder.yudao.module.iot.plugin.EmqxPlugin 1 +plugin.id=yudao-module-iot-plugin-emqx
  2 +plugin.class=cn.iocoder.yudao.module.iot.plugin.emqx.config.IotEmqxPlugin
3 plugin.version=1.0.0 3 plugin.version=1.0.0
4 -plugin.provider=ahh 4 +plugin.provider=yudao
5 plugin.dependencies= 5 plugin.dependencies=
6 -plugin.description=plugin-emqx-1.0.0 6 +plugin.description=yudao-module-iot-plugin-emqx-1.0.0
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/pom.xml
@@ -12,6 +12,7 @@ @@ -12,6 +12,7 @@
12 <packaging>jar</packaging> 12 <packaging>jar</packaging>
13 13
14 <artifactId>yudao-module-iot-plugin-emqx</artifactId> 14 <artifactId>yudao-module-iot-plugin-emqx</artifactId>
  15 + <version>1.0.0</version>
15 16
16 <name>${project.artifactId}</name> 17 <name>${project.artifactId}</name>
17 <description> 18 <description>
@@ -21,36 +22,16 @@ @@ -21,36 +22,16 @@
21 <properties> 22 <properties>
22 <!-- 插件相关 --> 23 <!-- 插件相关 -->
23 <plugin.id>emqx-plugin</plugin.id> 24 <plugin.id>emqx-plugin</plugin.id>
24 - <plugin.class>cn.iocoder.yudao.module.iot.plugin.EmqxPlugin</plugin.class>  
25 - <plugin.version>0.0.1</plugin.version>  
26 - <plugin.provider>ahh</plugin.provider>  
27 - <plugin.description>emqx-plugin-0.0.1</plugin.description> 25 + <plugin.class>cn.iocoder.yudao.module.iot.plugin.emqx.config.IotEmqxPlugin</plugin.class>
  26 + <plugin.version>${project.version}</plugin.version>
  27 + <plugin.provider>yudao</plugin.provider>
  28 + <plugin.description>${project.artifactId}-${project.version}</plugin.description>
28 <plugin.dependencies/> 29 <plugin.dependencies/>
29 </properties> 30 </properties>
30 31
31 <build> 32 <build>
32 <plugins> 33 <plugins>
33 - <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)  
34 - <plugin>  
35 - <groupId>org.codehaus.mojo</groupId>  
36 - <artifactId>properties-maven-plugin</artifactId>  
37 - <version>1.0-alpha-2</version>  
38 - <executions>  
39 - <execution>  
40 - <phase>initialize</phase>  
41 - <goals>  
42 - <goal>read-project-properties</goal>  
43 - </goals>  
44 - <configuration>  
45 - <files>  
46 - <file>plugin.properties</file>  
47 - </files>  
48 - </configuration>  
49 - </execution>  
50 - </executions>  
51 - </plugin>  
52 - -->  
53 - 34 + <!-- 插件模式 zip -->
54 <plugin> 35 <plugin>
55 <groupId>org.apache.maven.plugins</groupId> 36 <groupId>org.apache.maven.plugins</groupId>
56 <artifactId>maven-antrun-plugin</artifactId> 37 <artifactId>maven-antrun-plugin</artifactId>
@@ -94,6 +75,7 @@ @@ -94,6 +75,7 @@
94 </executions> 75 </executions>
95 </plugin> 76 </plugin>
96 77
  78 + <!-- 插件模式 jar -->
97 <plugin> 79 <plugin>
98 <groupId>org.apache.maven.plugins</groupId> 80 <groupId>org.apache.maven.plugins</groupId>
99 <artifactId>maven-jar-plugin</artifactId> 81 <artifactId>maven-jar-plugin</artifactId>
@@ -111,54 +93,76 @@ @@ -111,54 +93,76 @@
111 </archive> 93 </archive>
112 </configuration> 94 </configuration>
113 </plugin> 95 </plugin>
  96 + <!-- <plugin>-->
  97 + <!-- <groupId>org.apache.maven.plugins</groupId>-->
  98 + <!-- <artifactId>maven-shade-plugin</artifactId>-->
  99 + <!-- <version>3.6.0</version>-->
  100 + <!-- <executions>-->
  101 + <!-- <execution>-->
  102 + <!-- <phase>package</phase>-->
  103 + <!-- <goals>-->
  104 + <!-- <goal>shade</goal>-->
  105 + <!-- </goals>-->
  106 + <!-- <configuration>-->
  107 + <!-- <minimizeJar>true</minimizeJar>-->
  108 + <!-- </configuration>-->
  109 + <!-- </execution>-->
  110 + <!-- </executions>-->
  111 + <!-- <configuration>-->
  112 + <!-- <archive>-->
  113 + <!-- <manifestEntries>-->
  114 + <!-- <Plugin-Id>${plugin.id}</Plugin-Id>-->
  115 + <!-- <Plugin-Class>${plugin.class}</Plugin-Class>-->
  116 + <!-- <Plugin-Version>${plugin.version}</Plugin-Version>-->
  117 + <!-- <Plugin-Provider>${plugin.provider}</Plugin-Provider>-->
  118 + <!-- <Plugin-Description>${plugin.description}</Plugin-Description>-->
  119 + <!-- <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>-->
  120 + <!-- </manifestEntries>-->
  121 + <!-- </archive>-->
  122 + <!-- </configuration>-->
  123 + <!-- </plugin>-->
114 124
  125 + <!-- 独立模式 -->
115 <plugin> 126 <plugin>
116 - <artifactId>maven-deploy-plugin</artifactId>  
117 - <configuration>  
118 - <skip>true</skip>  
119 - </configuration> 127 + <groupId>org.springframework.boot</groupId>
  128 + <artifactId>spring-boot-maven-plugin</artifactId>
  129 + <version>${spring.boot.version}</version>
  130 + <executions>
  131 + <execution>
  132 + <goals>
  133 + <goal>repackage</goal>
  134 + </goals>
  135 + <configuration>
  136 + <classifier>-standalone</classifier>
  137 + </configuration>
  138 + </execution>
  139 + </executions>
120 </plugin> 140 </plugin>
  141 +
121 </plugins> 142 </plugins>
122 </build> 143 </build>
123 144
124 <dependencies> 145 <dependencies>
125 - <!-- 其他依赖项 -->  
126 - <dependency>  
127 - <groupId>org.springframework.boot</groupId>  
128 - <artifactId>spring-boot-starter-web</artifactId>  
129 - </dependency>  
130 - <!-- PF4J Spring 集成 -->  
131 - <dependency>  
132 - <groupId>org.pf4j</groupId>  
133 - <artifactId>pf4j-spring</artifactId>  
134 - <scope>provided</scope>  
135 - </dependency>  
136 - <!-- 项目依赖 -->  
137 <dependency> 146 <dependency>
138 <groupId>cn.iocoder.boot</groupId> 147 <groupId>cn.iocoder.boot</groupId>
139 - <artifactId>yudao-module-iot-api</artifactId> 148 + <artifactId>yudao-module-iot-plugin-common</artifactId>
140 <version>${revision}</version> 149 <version>${revision}</version>
141 </dependency> 150 </dependency>
  151 +
  152 + <!-- Web 相关 -->
142 <dependency> 153 <dependency>
143 - <groupId>org.projectlombok</groupId>  
144 - <artifactId>lombok</artifactId>  
145 - <version>${lombok.version}</version>  
146 - <scope>provided</scope>  
147 - </dependency>  
148 - <!-- Vert.x 核心依赖 -->  
149 - <dependency>  
150 - <groupId>io.vertx</groupId>  
151 - <artifactId>vertx-core</artifactId> 154 + <groupId>org.springframework.boot</groupId>
  155 + <artifactId>spring-boot-starter-web</artifactId>
152 </dependency> 156 </dependency>
153 - <!-- Vert.x Web 模块 --> 157 +
  158 + <!-- 工具类相关 -->
154 <dependency> 159 <dependency>
155 <groupId>io.vertx</groupId> 160 <groupId>io.vertx</groupId>
156 <artifactId>vertx-web</artifactId> 161 <artifactId>vertx-web</artifactId>
157 </dependency> 162 </dependency>
158 - <!-- MQTT -->  
159 <dependency> 163 <dependency>
160 - <groupId>org.eclipse.paho</groupId>  
161 - <artifactId>org.eclipse.paho.client.mqttv3</artifactId> 164 + <groupId>io.vertx</groupId>
  165 + <artifactId>vertx-mqtt</artifactId>
162 </dependency> 166 </dependency>
163 </dependencies> 167 </dependencies>
164 </project> 168 </project>
165 \ No newline at end of file 169 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/EmqxPlugin.java deleted
1 -package cn.iocoder.yudao.module.iot.plugin;  
2 -  
3 -import cn.hutool.extra.spring.SpringUtil;  
4 -import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;  
5 -import lombok.extern.slf4j.Slf4j;  
6 -import org.pf4j.Plugin;  
7 -import org.pf4j.PluginWrapper;  
8 -  
9 -import java.util.concurrent.ExecutorService;  
10 -import java.util.concurrent.Executors;  
11 -  
12 -@Slf4j  
13 -public class EmqxPlugin extends Plugin {  
14 -  
15 - private ExecutorService executorService;  
16 -  
17 - public EmqxPlugin(PluginWrapper wrapper) {  
18 - super(wrapper);  
19 - this.executorService = Executors.newSingleThreadExecutor();  
20 - }  
21 -  
22 - @Override  
23 - public void start() {  
24 - log.info("EmqxPlugin.start()");  
25 -  
26 - if (executorService.isShutdown() || executorService.isTerminated()) {  
27 - executorService = Executors.newSingleThreadExecutor();  
28 - }  
29 -  
30 - IotDeviceUpstreamApi deviceDataApi = SpringUtil.getBean(IotDeviceUpstreamApi.class);  
31 - if (deviceDataApi == null) {  
32 - log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!");  
33 - return;  
34 - }  
35 -  
36 - }  
37 -  
38 - @Override  
39 - public void stop() {  
40 - log.info("EmqxPlugin.stop()");  
41 - }  
42 -}  
43 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/IotEmqxPluginApplication.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx;
  2 +
  3 +import lombok.extern.slf4j.Slf4j;
  4 +import org.springframework.boot.SpringApplication;
  5 +import org.springframework.boot.WebApplicationType;
  6 +import org.springframework.boot.autoconfigure.SpringBootApplication;
  7 +
  8 +/**
  9 + * IoT Emqx 插件的独立运行入口
  10 + */
  11 +@Slf4j
  12 +@SpringBootApplication
  13 +public class IotEmqxPluginApplication {
  14 +
  15 + public static void main(String[] args) {
  16 + SpringApplication application = new SpringApplication(IotEmqxPluginApplication.class);
  17 + application.setWebApplicationType(WebApplicationType.NONE);
  18 + application.run(args);
  19 + log.info("[main][独立模式启动完成]");
  20 + }
  21 +
  22 +}
0 \ No newline at end of file 23 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/config/IotEmqxPlugin.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx.config;
  2 +
  3 +import cn.hutool.extra.spring.SpringUtil;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.pf4j.PluginWrapper;
  6 +import org.pf4j.spring.SpringPlugin;
  7 +import org.springframework.context.ApplicationContext;
  8 +import org.springframework.context.annotation.AnnotationConfigApplicationContext;
  9 +
  10 +// TODO @芋艿:完善注释
  11 +
  12 +/**
  13 + * 负责插件的启动和停止
  14 + */
  15 +@Slf4j
  16 +public class IotEmqxPlugin extends SpringPlugin {
  17 +
  18 + public IotEmqxPlugin(PluginWrapper wrapper) {
  19 + super(wrapper);
  20 + }
  21 +
  22 + @Override
  23 + public void start() {
  24 + log.info("[EmqxPlugin][EmqxPlugin 插件启动开始...]");
  25 + try {
  26 +
  27 + log.info("[EmqxPlugin][EmqxPlugin 插件启动成功...]");
  28 + } catch (Exception e) {
  29 + log.error("[EmqxPlugin][EmqxPlugin 插件开启动异常...]", e);
  30 + }
  31 + }
  32 +
  33 + @Override
  34 + public void stop() {
  35 + log.info("[EmqxPlugin][EmqxPlugin 插件停止开始...]");
  36 + try {
  37 + log.info("[EmqxPlugin][EmqxPlugin 插件停止成功...]");
  38 + } catch (Exception e) {
  39 + log.error("[EmqxPlugin][EmqxPlugin 插件停止异常...]", e);
  40 + }
  41 + }
  42 +
  43 + @Override
  44 + protected ApplicationContext createApplicationContext() {
  45 + // 创建插件自己的 ApplicationContext
  46 + AnnotationConfigApplicationContext pluginContext = new AnnotationConfigApplicationContext();
  47 + // 设置父容器为主应用的 ApplicationContext (确保主应用中提供的类可用)
  48 + pluginContext.setParent(SpringUtil.getApplicationContext());
  49 + // 继续使用插件自己的 ClassLoader 以加载插件内部的类
  50 + pluginContext.setClassLoader(getWrapper().getPluginClassLoader());
  51 + // 扫描当前插件的自动配置包
  52 + pluginContext.scan("cn.iocoder.yudao.module.iot.plugin.emqx.config");
  53 + pluginContext.refresh();
  54 + return pluginContext;
  55 + }
  56 +
  57 +}
0 \ No newline at end of file 58 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/config/IotPluginEmqxAutoConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx.config;
  2 +
  3 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  4 +import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
  5 +import cn.iocoder.yudao.module.iot.plugin.emqx.downstream.IotDeviceDownstreamHandlerImpl;
  6 +import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.IotDeviceUpstreamServer;
  7 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
  8 +import org.springframework.context.annotation.Bean;
  9 +import org.springframework.context.annotation.Configuration;
  10 +
  11 +/**
  12 + * IoT 插件 Emqx 的专用自动配置类
  13 + *
  14 + * @author haohao
  15 + */
  16 +@Configuration
  17 +@EnableConfigurationProperties(IotPluginEmqxProperties.class)
  18 +public class IotPluginEmqxAutoConfiguration {
  19 +
  20 + @Bean(initMethod = "start", destroyMethod = "stop")
  21 + public IotDeviceUpstreamServer deviceUpstreamServer(IotDeviceUpstreamApi deviceUpstreamApi,
  22 + IotPluginEmqxProperties emqxProperties) {
  23 + return new IotDeviceUpstreamServer(emqxProperties, deviceUpstreamApi);
  24 + }
  25 +
  26 + @Bean
  27 + public IotDeviceDownstreamHandler deviceDownstreamHandler() {
  28 + return new IotDeviceDownstreamHandlerImpl();
  29 + }
  30 +
  31 +}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/config/IotPluginEmqxProperties.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx.config;
  2 +
  3 +import lombok.Data;
  4 +import org.springframework.boot.context.properties.ConfigurationProperties;
  5 +import org.springframework.validation.annotation.Validated;
  6 +
  7 +/**
  8 + * 物联网插件 - EMQX 配置
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@ConfigurationProperties(prefix = "yudao.iot.plugin.emqx")
  13 +@Validated
  14 +@Data
  15 +public class IotPluginEmqxProperties {
  16 +
  17 + /**
  18 + * 服务主机
  19 + */
  20 + private String mqttHost;
  21 + /**
  22 + * 服务端口
  23 + */
  24 + private int mqttPort;
  25 + /**
  26 + * 服务用户名
  27 + */
  28 + private String mqttUsername;
  29 +
  30 + /**
  31 + * 服务密码
  32 + */
  33 + private String mqttPassword;
  34 + /**
  35 + * 是否启用 SSL
  36 + */
  37 + private boolean mqttSsl;
  38 +
  39 + /**
  40 + * 订阅的主题
  41 + */
  42 + private String mqttTopics;
  43 +
  44 + /**
  45 + * 认证端口
  46 + */
  47 + private int authPort;
  48 +
  49 +}
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/downstream/IotDeviceDownstreamHandlerImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx.downstream;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.iot.api.device.dto.control.downstream.*;
  5 +import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
  6 +
  7 +/**
  8 + * EMQX 插件的 {@link IotDeviceDownstreamHandler} 实现类
  9 + * <p>
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +public class IotDeviceDownstreamHandlerImpl implements IotDeviceDownstreamHandler {
  14 +
  15 + @Override
  16 + public CommonResult<Boolean> invokeDeviceService(IotDeviceServiceInvokeReqDTO invokeReqDTO) {
  17 + // 设备服务调用
  18 + // 请求Topic:/sys/${productKey}/${deviceName}/thing/service/${tsl.service.identifier}
  19 + // 响应Topic:/sys/${productKey}/${deviceName}/thing/service/${tsl.service.identifier}_reply
  20 + return CommonResult.success(true);
  21 + }
  22 +
  23 + @Override
  24 + public CommonResult<Boolean> getDeviceProperty(IotDevicePropertyGetReqDTO getReqDTO) {
  25 + return CommonResult.success(true);
  26 + }
  27 +
  28 + @Override
  29 + public CommonResult<Boolean> setDeviceProperty(IotDevicePropertySetReqDTO setReqDTO) {
  30 + // 设置设备属性 标准 JSON
  31 + // 请求Topic:/sys/${productKey}/${deviceName}/thing/service/property/set
  32 + // 响应Topic:/sys/${productKey}/${deviceName}/thing/service/property/set_reply
  33 + return CommonResult.success(true);
  34 + }
  35 +
  36 + @Override
  37 + public CommonResult<Boolean> setDeviceConfig(IotDeviceConfigSetReqDTO setReqDTO) {
  38 + return CommonResult.success(true);
  39 + }
  40 +
  41 + @Override
  42 + public CommonResult<Boolean> upgradeDeviceOta(IotDeviceOtaUpgradeReqDTO upgradeReqDTO) {
  43 + return CommonResult.success(true);
  44 + }
  45 +
  46 +}
0 \ No newline at end of file 47 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/IotDeviceUpstreamServer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx.upstream;
  2 +
  3 +import cn.hutool.core.util.IdUtil;
  4 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  5 +import cn.iocoder.yudao.module.iot.plugin.emqx.config.IotPluginEmqxProperties;
  6 +import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceAuthVertxHandler;
  7 +import cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router.IotDeviceMqttMessageHandler;
  8 +import io.netty.handler.codec.mqtt.MqttQoS;
  9 +import io.vertx.core.Vertx;
  10 +import io.vertx.core.http.HttpServer;
  11 +import io.vertx.ext.web.Router;
  12 +import io.vertx.ext.web.handler.BodyHandler;
  13 +import io.vertx.mqtt.MqttClient;
  14 +import io.vertx.mqtt.MqttClientOptions;
  15 +import lombok.extern.slf4j.Slf4j;
  16 +
  17 +/**
  18 + * IoT 设备下行服务端,接收来自 device 设备的请求,转发给 server 服务器
  19 + * <p>
  20 + * 协议:HTTP、MQTT
  21 + *
  22 + * @author haohao
  23 + */
  24 +@Slf4j
  25 +public class IotDeviceUpstreamServer {
  26 +
  27 + private static final int RECONNECT_DELAY = 5000; // 重连延迟时间(毫秒)
  28 +
  29 + private final Vertx vertx;
  30 + private final HttpServer server;
  31 + private final MqttClient client;
  32 + private final IotPluginEmqxProperties emqxProperties;
  33 + private final IotDeviceMqttMessageHandler mqttMessageHandler;
  34 +
  35 + public IotDeviceUpstreamServer(IotPluginEmqxProperties emqxProperties,
  36 + IotDeviceUpstreamApi deviceUpstreamApi) {
  37 + this.emqxProperties = emqxProperties;
  38 +
  39 + // 创建 Vertx 实例
  40 + this.vertx = Vertx.vertx();
  41 + // 创建 Router 实例
  42 + Router router = Router.router(vertx);
  43 + router.route().handler(BodyHandler.create()); // 处理 Body
  44 + router.post(IotDeviceAuthVertxHandler.PATH)
  45 + .handler(new IotDeviceAuthVertxHandler(deviceUpstreamApi));
  46 + // 创建 HttpServer 实例
  47 + this.server = vertx.createHttpServer().requestHandler(router);
  48 +
  49 + // 创建 MQTT 客户端
  50 + MqttClientOptions options = new MqttClientOptions()
  51 + .setClientId("yudao-iot-server-" + IdUtil.fastSimpleUUID())
  52 + .setUsername(emqxProperties.getMqttUsername())
  53 + .setPassword(emqxProperties.getMqttPassword())
  54 + .setSsl(emqxProperties.isMqttSsl());
  55 + client = MqttClient.create(vertx, options);
  56 + this.mqttMessageHandler = new IotDeviceMqttMessageHandler(deviceUpstreamApi, client);
  57 + }
  58 +
  59 + /**
  60 + * 启动 HTTP 服务器、MQTT 客户端
  61 + */
  62 + public void start() {
  63 + // 1. 启动 HTTP 服务器
  64 + log.info("[start][开始启动]");
  65 + server.listen(emqxProperties.getAuthPort())
  66 + .toCompletionStage()
  67 + .toCompletableFuture()
  68 + .join();
  69 + log.info("[start][HTTP服务器启动完成,端口({})]", this.server.actualPort());
  70 +
  71 + // 2. 连接 MQTT Broker
  72 + connectMqtt();
  73 +
  74 + // 3. 添加 MQTT 断开重连监听器
  75 + client.closeHandler(v -> {
  76 + log.warn("[closeHandler][MQTT 连接已断开,准备重连]");
  77 + reconnectWithDelay();
  78 + });
  79 +
  80 + // 4. 设置 MQTT 消息处理器
  81 + setupMessageHandler();
  82 + }
  83 +
  84 + /**
  85 + * 设置 MQTT 消息处理器
  86 + */
  87 + private void setupMessageHandler() {
  88 + client.publishHandler(mqttMessageHandler::handle);
  89 + }
  90 +
  91 + /**
  92 + * 重连 MQTT 客户端
  93 + */
  94 + private void reconnectWithDelay() {
  95 + vertx.setTimer(RECONNECT_DELAY, id -> {
  96 + log.info("[reconnectWithDelay][开始重新连接 MQTT]");
  97 + connectMqtt();
  98 + });
  99 + }
  100 +
  101 + /**
  102 + * 连接 MQTT Broker 并订阅主题
  103 + */
  104 + private void connectMqtt() {
  105 + client.connect(emqxProperties.getMqttPort(), emqxProperties.getMqttHost())
  106 + .onSuccess(connAck -> {
  107 + log.info("[connectMqtt][MQTT客户端连接成功]");
  108 + subscribeToTopics();
  109 + })
  110 + .onFailure(err -> {
  111 + log.error("[connectMqtt][连接 MQTT Broker 失败]", err);
  112 + reconnectWithDelay();
  113 + });
  114 + }
  115 +
  116 + /**
  117 + * 订阅设备上行消息主题
  118 + */
  119 + private void subscribeToTopics() {
  120 + String[] topics = emqxProperties.getMqttTopics().split(",");
  121 + for (String topic : topics) {
  122 + client.subscribe(topic, MqttQoS.AT_LEAST_ONCE.value())
  123 + .onSuccess(v -> log.info("[subscribeToTopics][成功订阅主题: {}]", topic))
  124 + .onFailure(err -> log.error("[subscribeToTopics][订阅主题失败: {}]", topic, err));
  125 + }
  126 + log.info("[subscribeToTopics][开始订阅设备上行消息主题]");
  127 + }
  128 +
  129 + /**
  130 + * 停止所有
  131 + */
  132 + public void stop() {
  133 + log.info("[stop][开始关闭]");
  134 + try {
  135 + // 关闭 HTTP 服务器
  136 + if (server != null) {
  137 + server.close()
  138 + .toCompletionStage()
  139 + .toCompletableFuture()
  140 + .join();
  141 + }
  142 +
  143 + // 关闭 MQTT 客户端
  144 + if (client != null) {
  145 + client.disconnect()
  146 + .toCompletionStage()
  147 + .toCompletableFuture()
  148 + .join();
  149 + }
  150 +
  151 + // 关闭 Vertx 实例
  152 + if (vertx != null) {
  153 + vertx.close()
  154 + .toCompletionStage()
  155 + .toCompletableFuture()
  156 + .join();
  157 + }
  158 + log.info("[stop][关闭完成]");
  159 + } catch (Exception e) {
  160 + log.error("[stop][关闭异常]", e);
  161 + throw new RuntimeException(e);
  162 + }
  163 + }
  164 +}
0 \ No newline at end of file 165 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/router/IotDeviceAuthVertxHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  5 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEmqxAuthReqDTO;
  6 +import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
  7 +import io.vertx.core.Handler;
  8 +import io.vertx.core.json.JsonObject;
  9 +import io.vertx.ext.web.RoutingContext;
  10 +import lombok.RequiredArgsConstructor;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +
  13 +/**
  14 + * IoT Emqx 连接认证的 Vert.x Handler
  15 + * <a href="https://docs.emqx.com/zh/emqx/latest/access-control/authn/http.html">...</a>
  16 + *
  17 + * @author haohao
  18 + */
  19 +@RequiredArgsConstructor
  20 +@Slf4j
  21 +public class IotDeviceAuthVertxHandler implements Handler<RoutingContext> {
  22 +
  23 + public static final String PATH = "/mqtt/auth";
  24 +
  25 + private final IotDeviceUpstreamApi deviceUpstreamApi;
  26 +
  27 + @Override
  28 + @SuppressWarnings("unchecked")
  29 + public void handle(RoutingContext routingContext) {
  30 +
  31 + JsonObject json = routingContext.body().asJsonObject();
  32 + String clientId = json.getString("clientid");
  33 + String username = json.getString("username");
  34 + String password = json.getString("password");
  35 +
  36 + IotDeviceEmqxAuthReqDTO authReqDTO = buildDeviceEmqxAuthReqDTO(clientId, username, password);
  37 +
  38 + CommonResult<Boolean> authResult = deviceUpstreamApi.authenticateEmqxConnection(authReqDTO);
  39 + if (authResult.getCode() != 0 || !authResult.getData()) {
  40 + denyAccess(routingContext);
  41 + return;
  42 + }
  43 + IotPluginCommonUtils.writeJson(routingContext, "{\"result\": \"allow\"}");
  44 + }
  45 +
  46 + private void denyAccess(RoutingContext routingContext) {
  47 + IotPluginCommonUtils.writeJson(routingContext, "{\"result\": \"deny\"}");
  48 + }
  49 +
  50 + private IotDeviceEmqxAuthReqDTO buildDeviceEmqxAuthReqDTO(String clientId, String username, String password) {
  51 + return new IotDeviceEmqxAuthReqDTO().setClientId(clientId).setUsername(username).setPassword(password);
  52 + }
  53 +
  54 +}
0 \ No newline at end of file 55 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/java/cn/iocoder/yudao/module/iot/plugin/emqx/upstream/router/IotDeviceMqttMessageHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.emqx.upstream.router;
  2 +
  3 +import cn.hutool.json.JSONObject;
  4 +import cn.hutool.json.JSONUtil;
  5 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  6 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
  7 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
  8 +import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
  9 +import io.netty.handler.codec.mqtt.MqttQoS;
  10 +import io.vertx.core.buffer.Buffer;
  11 +import io.vertx.mqtt.MqttClient;
  12 +import io.vertx.mqtt.messages.MqttPublishMessage;
  13 +import lombok.extern.slf4j.Slf4j;
  14 +
  15 +import java.time.LocalDateTime;
  16 +
  17 +/**
  18 + * IoT 设备 MQTT 消息处理器
  19 + * <p>
  20 + * 参考:
  21 + * <p>
  22 + * "<a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services?spm=a2c4g.11186623.0.0.97a72915vRck44#section-g4j-5zg-12b">...</a>">
  23 + */
  24 +@Slf4j
  25 +public class IotDeviceMqttMessageHandler {
  26 +
  27 + // 设备上报属性 标准 JSON
  28 + // 请求Topic:/sys/${productKey}/${deviceName}/thing/event/property/post
  29 + // 响应Topic:/sys/${productKey}/${deviceName}/thing/event/property/post_reply
  30 + // 设备上报事件 标准 JSON
  31 + // 请求Topic:/sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post
  32 + // 响应Topic:/sys/${productKey}/${deviceName}/thing/event/${tsl.event.identifier}/post_reply
  33 +
  34 + private static final String SYS_TOPIC_PREFIX = "/sys/";
  35 + private static final String PROPERTY_POST_TOPIC = "/thing/event/property/post";
  36 + private static final String EVENT_POST_TOPIC_PREFIX = "/thing/event/";
  37 + private static final String EVENT_POST_TOPIC_SUFFIX = "/post";
  38 +
  39 + private final IotDeviceUpstreamApi deviceUpstreamApi;
  40 + private final MqttClient mqttClient;
  41 +
  42 + public IotDeviceMqttMessageHandler(IotDeviceUpstreamApi deviceUpstreamApi, MqttClient mqttClient) {
  43 + this.deviceUpstreamApi = deviceUpstreamApi;
  44 + this.mqttClient = mqttClient;
  45 + }
  46 +
  47 + public void handle(MqttPublishMessage message) {
  48 + String topic = message.topicName();
  49 + String payload = message.payload().toString();
  50 + log.info("[messageHandler][接收到消息][topic: {}][payload: {}]", topic, payload);
  51 +
  52 + try {
  53 + handleMessage(topic, payload);
  54 + } catch (Exception e) {
  55 + log.error("[messageHandler][处理消息失败][topic: {}][payload: {}]", topic, payload, e);
  56 + }
  57 + }
  58 +
  59 + private void handleMessage(String topic, String payload) {
  60 + // 校验前缀
  61 + if (!topic.startsWith(SYS_TOPIC_PREFIX)) {
  62 + log.warn("[handleMessage][未知的消息类型][topic: {}]", topic);
  63 + return;
  64 + }
  65 +
  66 + // 处理设备属性上报消息
  67 + if (topic.endsWith(PROPERTY_POST_TOPIC)) {
  68 + log.info("[handleMessage][接收到设备属性上报][topic: {}]", topic);
  69 + handlePropertyPost(topic, payload);
  70 + return;
  71 + }
  72 +
  73 + // 处理设备事件上报消息
  74 + if (topic.contains(EVENT_POST_TOPIC_PREFIX) && topic.endsWith(EVENT_POST_TOPIC_SUFFIX)) {
  75 + log.info("[handleMessage][接收到设备事件上报][topic: {}]", topic);
  76 + handleEventPost(topic, payload);
  77 + return;
  78 + }
  79 +
  80 + // 未知消息类型
  81 + log.warn("[handleMessage][未知的消息类型][topic: {}]", topic);
  82 + }
  83 +
  84 + /**
  85 + * 处理设备属性上报消息
  86 + *
  87 + * @param topic 主题
  88 + * @param payload 消息内容
  89 + */
  90 + private void handlePropertyPost(String topic, String payload) {
  91 + // 解析消息内容
  92 + JSONObject jsonObject = JSONUtil.parseObj(payload);
  93 + String[] topicParts = topic.split("/");
  94 +
  95 + // 构建设备属性上报请求对象
  96 + IotDevicePropertyReportReqDTO reportReqDTO = buildPropertyReportDTO(jsonObject, topicParts);
  97 +
  98 + // 调用上游 API 处理设备上报数据
  99 + deviceUpstreamApi.reportDeviceProperty(reportReqDTO);
  100 + log.info("[handlePropertyPost][处理设备上行消息成功][topic: {}][reportReqDTO: {}]",
  101 + topic, JSONUtil.toJsonStr(reportReqDTO));
  102 +
  103 + // 发送响应消息
  104 + String replyTopic = topic + "_reply";
  105 + JSONObject response = new JSONObject()
  106 + .set("id", jsonObject.getStr("id"))
  107 + .set("code", 200)
  108 + .set("data", new JSONObject())
  109 + .set("message", "success")
  110 + .set("method", "thing.event.property.post");
  111 +
  112 + mqttClient.publish(replyTopic,
  113 + Buffer.buffer(response.toString()),
  114 + MqttQoS.AT_LEAST_ONCE,
  115 + false,
  116 + false);
  117 + log.info("[handlePropertyPost][发送响应消息成功][topic: {}][response: {}]",
  118 + replyTopic, response.toString());
  119 + }
  120 +
  121 + /**
  122 + * 处理设备事件上报消息
  123 + *
  124 + * @param topic 主题
  125 + * @param payload 消息内容
  126 + */
  127 + private void handleEventPost(String topic, String payload) {
  128 + // 解析消息内容
  129 + JSONObject jsonObject = JSONUtil.parseObj(payload);
  130 + String[] topicParts = topic.split("/");
  131 +
  132 + // 构建设备事件上报请求对象
  133 + IotDeviceEventReportReqDTO reportReqDTO = buildEventReportDTO(jsonObject, topicParts);
  134 +
  135 + // 调用上游 API 处理设备上报数据
  136 + deviceUpstreamApi.reportDeviceEvent(reportReqDTO);
  137 + log.info("[handleEventPost][处理设备上行消息成功][topic: {}][reportReqDTO: {}]",
  138 + topic, JSONUtil.toJsonStr(reportReqDTO));
  139 +
  140 + // 发送响应消息
  141 + String replyTopic = topic + "_reply";
  142 + String eventIdentifier = topicParts[6]; // 从 topic 中获取事件标识符
  143 + JSONObject response = new JSONObject()
  144 + .set("id", jsonObject.getStr("id"))
  145 + .set("code", 200)
  146 + .set("data", new JSONObject())
  147 + .set("message", "success")
  148 + .set("method", "thing.event." + eventIdentifier + ".post");
  149 +
  150 + mqttClient.publish(replyTopic,
  151 + Buffer.buffer(response.toString()),
  152 + MqttQoS.AT_LEAST_ONCE,
  153 + false,
  154 + false);
  155 + log.info("[handleEventPost][发送响应消息成功][topic: {}][response: {}]",
  156 + replyTopic, response.toString());
  157 + }
  158 +
  159 + /**
  160 + * 构建设备属性上报请求对象
  161 + *
  162 + * @param jsonObject 消息内容
  163 + * @param topicParts 主题部分
  164 + * @return 设备属性上报请求对象
  165 + */
  166 + private IotDevicePropertyReportReqDTO buildPropertyReportDTO(JSONObject jsonObject,
  167 + String[] topicParts) {
  168 + return ((IotDevicePropertyReportReqDTO) new IotDevicePropertyReportReqDTO()
  169 + .setRequestId(jsonObject.getStr("id"))
  170 + .setProcessId(IotPluginCommonUtils.getProcessId())
  171 + .setReportTime(LocalDateTime.now())
  172 + .setProductKey(topicParts[2])
  173 + .setDeviceName(topicParts[3]))
  174 + .setProperties(jsonObject.getJSONObject("params"));
  175 + }
  176 +
  177 + /**
  178 + * 构建设备事件上报请求对象
  179 + *
  180 + * @param jsonObject 消息内容
  181 + * @param topicParts 主题部分
  182 + * @return 设备事件上报请求对象
  183 + */
  184 + private IotDeviceEventReportReqDTO buildEventReportDTO(JSONObject jsonObject, String[] topicParts) {
  185 + return ((IotDeviceEventReportReqDTO) new IotDeviceEventReportReqDTO()
  186 + .setRequestId(jsonObject.getStr("id"))
  187 + .setProcessId(IotPluginCommonUtils.getProcessId())
  188 + .setReportTime(LocalDateTime.now())
  189 + .setProductKey(topicParts[2])
  190 + .setDeviceName(topicParts[3]))
  191 + .setIdentifier(topicParts[4])
  192 + .setParams(jsonObject.getJSONObject("params"));
  193 + }
  194 +}
0 \ No newline at end of file 195 \ No newline at end of file
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-emqx/src/main/resources/application.yml 0 → 100644
  1 +spring:
  2 + application:
  3 + name: yudao-module-iot-plugin-emqx
  4 +
  5 +yudao:
  6 + iot:
  7 + plugin:
  8 + common:
  9 + upstream-url: http://127.0.0.1:48080
  10 + downstream-port: 8100
  11 + plugin-key: yudao-module-iot-plugin-emqx
  12 + emqx:
  13 + mqtt-host: 127.0.0.1
  14 + mqtt-port: 1883
  15 + mqtt-ssl: false
  16 + mqtt-username: yudao
  17 + mqtt-password: 123456
  18 + mqtt-topics: "/sys/#"
  19 + auth-port: 8101
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/java/cn/iocoder/yudao/module/iot/plugin/http/upstream/router/IotDeviceEventReportVertxHandler.java
@@ -21,7 +21,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC @@ -21,7 +21,7 @@ import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeC
21 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; 21 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR;
22 22
23 /** 23 /**
24 - * IoT 设备设备上报的 Vert.x Handler 24 + * IoT 设备事件上报的 Vert.x Handler
25 */ 25 */
26 @RequiredArgsConstructor 26 @RequiredArgsConstructor
27 @Slf4j 27 @Slf4j
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-http/src/main/resources/application.yml
@@ -8,5 +8,6 @@ yudao: @@ -8,5 +8,6 @@ yudao:
8 common: 8 common:
9 upstream-url: http://127.0.0.1:48080 9 upstream-url: http://127.0.0.1:48080
10 downstream-port: 8093 10 downstream-port: 8093
  11 + plugin-key: yudao-module-iot-plugin-http
11 http: 12 http:
12 server-port: 8092 13 server-port: 8092
yudao-server/src/main/resources/application-local.yaml
@@ -267,23 +267,6 @@ justauth: @@ -267,23 +267,6 @@ justauth:
267 timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟 267 timeout: 24h # 超时时长,目前只对 Redis 缓存生效,默认 3 分钟
268 268
269 --- #################### iot相关配置 TODO 芋艿:再瞅瞅 #################### 269 --- #################### iot相关配置 TODO 芋艿:再瞅瞅 ####################
270 -iot:  
271 - emq:  
272 - # 账号  
273 - username: haohao  
274 - # 密码  
275 - password: ahh@123456  
276 - # 主机地址  
277 - hostUrl: tcp://chaojiniu.top:1883  
278 - # 客户端Id,不能相同,采用随机数 ${random.value}  
279 - client-id: ${random.int}  
280 - # 默认主题  
281 - default-topic: test  
282 - # 保持连接  
283 - keepalive: 60  
284 - # 清除会话(设置为false,断开连接,重连后使用原来的会话 保留订阅的主题,能接收离线期间的消息)  
285 - clearSession: true  
286 -  
287 pf4j: 270 pf4j:
288 # pluginsDir: /tmp/ 271 # pluginsDir: /tmp/
289 pluginsDir: ../plugins 272 pluginsDir: ../plugins
290 \ No newline at end of file 273 \ No newline at end of file