瀏覽代碼

refactor(iot): 重构 OTA 升级模块

- 更新错误码定义,调整 OTA 相关错误码的编号和描述
- 移除未使用的 IotOtaFirmwareCommonReqVO 类- 优化 IotOtaFirmwareCreateReqVO 和 IotOtaFirmwareUpdateReqVO 的结构
- 删除冗余的 IotOtaUpgradeRecordCreateReqBO 类
- 重构 IotOtaUpgradeRecordMapper 的查询方法
- 更新 IotOtaUpgradeRecordService 接口,简化升级记录创建方法
- 删除未使用的 IotOtaUpgradeRecordJob 类
- 优化 IotOtaUpgradeRecordController 的重试接口,使用 PUT 方法
陈玄礼 5 月之前
父節點
當前提交
b87a583842
共有 21 個文件被更改,包括 201 次插入600 次删除
  1. 8 9
      yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
  2. 1 2
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaUpgradeRecordController.java
  3. 1 2
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaUpgradeTaskController.java
  4. 0 27
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCommonReqVO.java
  5. 12 52
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCreateReqVO.java
  6. 11 6
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareUpdateReqVO.java
  7. 0 26
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/record/IotOtaUpgradeRecordPageReqVO.java
  8. 0 9
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskSaveReqVO.java
  9. 0 24
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/ota/IotOtaUpgradeRecordConvert.java
  10. 0 9
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaUpgradeTaskDO.java
  11. 32 24
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaUpgradeRecordMapper.java
  12. 0 35
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeRecordJob.java
  13. 0 72
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeTaskJob.java
  14. 3 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaFirmwareService.java
  15. 16 7
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaFirmwareServiceImpl.java
  16. 6 18
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeRecordService.java
  17. 63 58
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeRecordServiceImpl.java
  18. 3 3
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeTaskService.java
  19. 45 89
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeTaskServiceImpl.java
  20. 0 79
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/bo/IotOtaUpgradeRecordCreateReqBO.java
  21. 0 46
      yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/bo/IotOtaUpgradeRecordUpdateReqBO.java

+ 8 - 9
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java

@@ -56,16 +56,15 @@ public interface ErrorCodeConstants {
     ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在");
     ErrorCode OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE = new ErrorCode(1_050_008_001, "产品版本号重复");
 
-    // TODO @li:1_050_008_100,这样有点间隔?
-    ErrorCode OTA_UPGRADE_TASK_NOT_EXISTS = new ErrorCode(1_050_008_002, "升级任务不存在");
-    ErrorCode OTA_UPGRADE_TASK_NAME_DUPLICATE = new ErrorCode(1_050_008_003, "升级任务名称重复");
-    ErrorCode OTA_UPGRADE_TASK_PARAMS_INVALID = new ErrorCode(1_050_008_004, "升级任务参数无效");
-    ErrorCode OTA_UPGRADE_TASK_CANNOT_CANCEL = new ErrorCode(1_050_008_005, "升级任务不能取消");
+    ErrorCode OTA_UPGRADE_TASK_NOT_EXISTS = new ErrorCode(1_050_008_100, "升级任务不存在");
+    ErrorCode OTA_UPGRADE_TASK_NAME_DUPLICATE = new ErrorCode(1_050_008_101, "升级任务名称重复");
+    ErrorCode OTA_UPGRADE_TASK_DEVICE_IDS_EMPTY = new ErrorCode(1_050_008_102, "设备编号列表不能为空");
+    ErrorCode OTA_UPGRADE_TASK_DEVICE_LIST_EMPTY = new ErrorCode(1_050_008_103, "设备列表不能为空");
+    ErrorCode OTA_UPGRADE_TASK_CANNOT_CANCEL = new ErrorCode(1_050_008_104, "升级任务不能取消");
 
-    // TODO @li:1_050_008_200
-    ErrorCode OTA_UPGRADE_RECORD_NOT_EXISTS = new ErrorCode(1_050_008_006, "升级记录不存在");
-    ErrorCode OTA_UPGRADE_RECORD_DUPLICATE = new ErrorCode(1_050_008_007, "升级记录重复");
-    ErrorCode OTA_UPGRADE_RECORD_CANNOT_RETRY = new ErrorCode(1_050_008_008, "升级记录不能重试");
+    ErrorCode OTA_UPGRADE_RECORD_NOT_EXISTS = new ErrorCode(1_050_008_200, "升级记录不存在");
+    ErrorCode OTA_UPGRADE_RECORD_DUPLICATE = new ErrorCode(1_050_008_201, "升级记录重复");
+    ErrorCode OTA_UPGRADE_RECORD_CANNOT_RETRY = new ErrorCode(1_050_008_202, "升级记录不能重试");
 
     // ========== MQTT 通信相关 1-050-009-000 ==========
     ErrorCode MQTT_TOPIC_ILLEGAL = new ErrorCode(1_050_009_000, "topic illegal");

+ 1 - 2
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaUpgradeRecordController.java

@@ -64,8 +64,7 @@ public class IotOtaUpgradeRecordController {
         return success(BeanUtils.toBean(upgradeRecord, IotOtaUpgradeRecordRespVO.class));
     }
 
-    // TODO @li:使用 Putmapping
-    @PostMapping("/retry")
+    @PutMapping("/retry")
     @Operation(summary = "重试升级记录")
     @PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:retry')")
     @Parameter(name = "id", description = "升级记录编号", required = true, example = "1024")

+ 1 - 2
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaUpgradeTaskController.java

@@ -44,11 +44,10 @@ public class IotOtaUpgradeTaskController {
         return success(true);
     }
 
-    // TODO @li:get 接口,不是 @RequestBody 哈
     @GetMapping("/page")
     @Operation(summary = "获得升级任务分页")
     @PreAuthorize(value = "@ss.hasPermission('iot:ota-upgrade-task:query')")
-    public CommonResult<PageResult<IotOtaUpgradeTaskRespVO>> getUpgradeTaskPage(@Valid @RequestBody IotOtaUpgradeTaskPageReqVO pageReqVO) {
+    public CommonResult<PageResult<IotOtaUpgradeTaskRespVO>> getUpgradeTaskPage(@Valid IotOtaUpgradeTaskPageReqVO pageReqVO) {
         PageResult<IotOtaUpgradeTaskDO> pageResult = upgradeTaskService.getUpgradeTaskPage(pageReqVO);
         return success(BeanUtils.toBean(pageResult, IotOtaUpgradeTaskRespVO.class));
     }

+ 0 - 27
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCommonReqVO.java

@@ -1,27 +0,0 @@
-package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotEmpty;
-import lombok.Data;
-
-import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
-
-// TODO @li:因为 create 和 update 可以公用的字段比较少,建议不用 IotOtaFirmwareCommonReqVO
-@Data
-@Schema(description = "管理后台 - OTA固件信息 Request VO")
-public class IotOtaFirmwareCommonReqVO {
-
-    /**
-     * 固件名称
-     */
-    @NotEmpty(message = "固件名称不能为空")
-    @Schema(description = "固件名称", requiredMode = REQUIRED, example = "智能开关固件")
-    private String name;
-
-    /**
-     * 固件描述
-     */
-    @Schema(description = "固件描述", example = "某品牌型号固件,测试用")
-    private String description;
-
-}

+ 12 - 52
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCreateReqVO.java

@@ -7,72 +7,32 @@ import lombok.Data;
 
 import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
 
-// TODO @li:中英文之间,有个空格。中文写作习惯哈。
-@Schema(description = "管理后台 - OTA固件创建 Request VO")
+@Schema(description = "管理后台 - OTA 固件创建 Request VO")
 @Data
-public class IotOtaFirmwareCreateReqVO extends IotOtaFirmwareCommonReqVO {
+public class IotOtaFirmwareCreateReqVO {
+
+    @Schema(description = "固件名称", requiredMode = REQUIRED, example = "智能开关固件")
+    @NotEmpty(message = "固件名称不能为空")
+    private String name;
+
+    @Schema(description = "固件描述", example = "某品牌型号固件,测试用")
+    private String description;
 
-    // TODO @li:因为有了注解,注释可以不写哈
-    // TODO @li:swagger 注解,写在 validator 注解之前,保持项目统一哈。
-    /**
-     * 版本号
-     */
-    @NotEmpty(message = "版本号不能为空")
     @Schema(description = "版本号", requiredMode = REQUIRED, example = "1.0.0")
+    @NotEmpty(message = "版本号不能为空")
     private String version;
 
-    /**
-     * 产品编号
-     * <p>
-     * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
-     */
-    @NotNull(message = "产品编号不能为空")
     @Schema(description = "产品编号", requiredMode = REQUIRED, example = "1024")
+    @NotNull(message = "产品编号不能为空")
     private String productId;
 
-    // TODO @li:productId 即可,而 productKey 通过 productId 查询
-    /**
-     * 产品标识
-     * <p>
-     * 冗余 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getProductKey()}
-     */
-    @NotEmpty(message = "产品标识不能为空")
-    @Schema(description = "产品标识", requiredMode = REQUIRED, example = "yudao")
-    private String productKey;
-
-    /**
-     * 签名方式
-     * <p>
-     * 例如说:MD5、SHA256
-     */
     @Schema(description = "签名方式", example = "MD5")
     private String signMethod;
 
-    // TODO @li:fileSign、fileSize 通过后端下载文件,计算出来。对前端屏蔽这个细节。
-
-    /**
-     * 固件文件签名
-     */
-    @Schema(description = "固件文件签名", example = "d41d8cd98f00b204e9800998ecf8427e")
-    private String fileSign;
-
-    /**
-     * 固件文件大小
-     */
-    @NotNull(message = "固件文件大小不能为空")
-    @Schema(description = "固件文件大小(单位:byte)", example = "1024")
-    private Long fileSize;
-
-    /**
-     * 固件文件 URL
-     */
-    @NotEmpty(message = "固件文件 URL 不能为空")
     @Schema(description = "固件文件 URL", requiredMode = REQUIRED, example = "https://www.iocoder.cn/yudao-firmware.zip")
+    @NotEmpty(message = "固件文件 URL 不能为空")
     private String fileUrl;
 
-    /**
-     * 自定义信息,建议使用 JSON 格式
-     */
     @Schema(description = "自定义信息,建议使用 JSON 格式", example = "{\"key1\":\"value1\",\"key2\":\"value2\"}")
     private String information;
 

+ 11 - 6
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareUpdateReqVO.java

@@ -1,20 +1,25 @@
 package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
 
 import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
 import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
 
-@Data
 @Schema(description = "管理后台 - OTA固件更新 Request VO")
-public class IotOtaFirmwareUpdateReqVO extends IotOtaFirmwareCommonReqVO {
+@Data
+public class IotOtaFirmwareUpdateReqVO {
 
-    /**
-     * 固件编号
-     */
-    @NotNull(message = "固件编号不能为空")
     @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
+    @NotNull(message = "固件编号不能为空")
     private Long id;
 
+    @Schema(description = "固件名称", requiredMode = REQUIRED, example = "智能开关固件")
+    @NotEmpty(message = "固件名称不能为空")
+    private String name;
+
+    @Schema(description = "固件描述", example = "某品牌型号固件,测试用")
+    private String description;
+
 }

+ 0 - 26
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/record/IotOtaUpgradeRecordPageReqVO.java

@@ -11,32 +11,6 @@ import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
 @Schema(description = "管理后台 - OTA升级记录分页 Request VO")
 public class IotOtaUpgradeRecordPageReqVO extends PageParam {
 
-    // TODO @li:使用 IotOtaUpgradeRecordStatusEnum 枚举哈
-    /**
-     * 待处理状态
-     */
-    public static final Integer PENDING = 0;
-    /**
-     * 已推送状态
-     */
-    public static final Integer PUSHED = 10;
-    /**
-     * 正在升级状态
-     */
-    public static final Integer UPGRADING = 20;
-    /**
-     * 升级成功状态
-     */
-    public static final Integer SUCCESS = 30;
-    /**
-     * 升级失败状态
-     */
-    public static final Integer FAILURE = 40;
-    /**
-     * 升级已取消状态
-     */
-    public static final Integer CANCELED = 50;
-
     /**
      * 升级任务编号字段。
      * <p>

+ 0 - 9
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/upgrade/task/IotOtaUpgradeTaskSaveReqVO.java

@@ -57,13 +57,4 @@ public class IotOtaUpgradeTaskSaveReqVO {
     @Schema(description = "选中的设备编号数组", requiredMode = REQUIRED, example = "[1,2,3,4]")
     private List<Long> deviceIds;
 
-    // TODO @li:通过 deviceIds 查询 deviceNames,前端不传递哈
-    /**
-     * 选中的设备名字数组
-     * <p>
-     * 关联 {@link IotDeviceDO#getDeviceName()}
-     */
-    @Schema(description = "选中的设备名字数组", requiredMode = REQUIRED, example = "[设备1,设备2,设备3,设备4]")
-    private List<String> deviceNames;
-
 }

+ 0 - 24
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/ota/IotOtaUpgradeRecordConvert.java

@@ -1,35 +1,11 @@
 package cn.iocoder.yudao.module.iot.convert.ota;
 
-import cn.hutool.core.convert.Convert;
-import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
-import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum;
-import cn.iocoder.yudao.module.iot.service.ota.bo.IotOtaUpgradeRecordCreateReqBO;
 import org.mapstruct.Mapper;
 import org.mapstruct.factory.Mappers;
 
-import java.util.List;
-
 @Mapper
 public interface IotOtaUpgradeRecordConvert {
 
     IotOtaUpgradeRecordConvert INSTANCE = Mappers.getMapper(IotOtaUpgradeRecordConvert.class);
 
-    // TODO @li:一般情况下,这种 convert 直接写 service 就好啦。不用特别写一个哈
-    default List<IotOtaUpgradeRecordCreateReqBO> convertBOList(IotOtaUpgradeTaskDO upgradeTask, IotOtaFirmwareDO firmware, List<IotDeviceDO> deviceList) {
-        return deviceList.stream().map(device -> {
-            IotOtaUpgradeRecordCreateReqBO createReqBO = new IotOtaUpgradeRecordCreateReqBO();
-            createReqBO.setFirmwareId(firmware.getId());
-            createReqBO.setTaskId(upgradeTask.getId());
-            createReqBO.setProductKey(device.getProductKey());
-            createReqBO.setDeviceName(device.getDeviceName());
-            createReqBO.setDeviceId(Convert.toStr(device.getId()));
-            createReqBO.setFromFirmwareId(Convert.toLong(device.getFirmwareId()));
-            createReqBO.setStatus(IotOtaUpgradeRecordStatusEnum.PENDING.getStatus());
-            createReqBO.setProgress(0);
-            return createReqBO;
-        }).toList();
-    }
-
 }

+ 0 - 9
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaUpgradeTaskDO.java

@@ -71,13 +71,4 @@ public class IotOtaUpgradeTaskDO extends BaseDO {
     @TableField(typeHandler = JacksonTypeHandler.class)
     private List<Long> deviceIds;
 
-    // TODO @li:这个通过查询,不用冗余
-    /**
-     * 选中的设备名字数组
-     * <p>
-     * 关联 {@link IotDeviceDO#getDeviceName()}
-     */
-    @TableField(typeHandler = JacksonTypeHandler.class)
-    private List<String> deviceNames;
-
 }

+ 32 - 24
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaUpgradeRecordMapper.java

@@ -8,8 +8,10 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeRecordDO;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * OTA 升级记录 Mapper
@@ -36,36 +38,42 @@ public interface IotOtaUpgradeRecordMapper extends BaseMapperX<IotOtaUpgradeReco
     }
 
     /**
-     * 获取OTA升级记录的数量
+     * 根据任务ID和设备名称查询OTA升级记录的状态统计信息。
+     * 该函数通过SQL查询统计不同状态(0到5)的记录数量,并返回一个包含统计结果的Map列表。
      *
-     * @param taskId     任务ID,用于筛选特定任务的升级记录
-     * @param deviceName 设备名称,用于筛选特定设备的升级记录
-     * @param status     状态,用于筛选特定状态的升级记录
-     * @return 返回符合条件的OTA升级记录的数量
+     * @param taskId     任务ID,用于筛选特定任务的OTA升级记录。
+     * @param deviceName 设备名称,支持模糊查询,用于筛选特定设备的OTA升级记录。
+     * @return 返回一个Map列表,每个Map包含不同状态(0到5)的记录数量。
      */
-    default Long getOtaUpgradeRecordCount(@Param("taskId") Long taskId,
-                                          @Param("deviceName") String deviceName,
-                                          @Param("status") Integer status) {
-        return selectCount(new LambdaQueryWrapperX<IotOtaUpgradeRecordDO>()
-                .eqIfPresent(IotOtaUpgradeRecordDO::getTaskId, taskId)
-                .likeIfPresent(IotOtaUpgradeRecordDO::getDeviceId, deviceName)
-                .eqIfPresent(IotOtaUpgradeRecordDO::getStatus, status));
-    }
+    @Select("select count(case when status = 0 then 1 else 0) as `0` " +
+            "count(case when status = 1 then 1 else 0) as `1` " +
+            "count(case when status = 2 then 1 else 0) as `2` " +
+            "count(case when status = 3 then 1 else 0) as `3` " +
+            "count(case when status = 4 then 1 else 0) as `4` " +
+            "count(case when status = 5 then 1 else 0) as `5` " +
+            "from iot_ota_upgrade_record " +
+            "where task_id = #{taskId} " +
+            "and device_name like concat('%', #{deviceName}, '%') " +
+            "and status = #{status}")
+    List<Map<String, Object>> selectOtaUpgradeRecordCount(@Param("taskId") Long taskId,
+                                                          @Param("deviceName") String deviceName);
 
     /**
-     * 获取OTA升级记录的统计信息
+     * 根据固件ID查询OTA升级记录的状态统计信息。
+     * 该函数通过SQL查询统计不同状态(0到5)的记录数量,并返回一个包含统计结果的Map列表。
      *
-     * @param firmwareId 固件ID,用于筛选特定固件的升级记录
-     * @param status     状态,用于筛选特定状态的升级记录
-     * @return 返回符合条件的OTA升级记录的统计信息
+     * @param firmwareId 固件ID,用于筛选特定固件的OTA升级记录。
+     * @return 返回一个Map列表,每个Map包含不同状态(0到5)的记录数量。
      */
-    default Long getOtaUpgradeRecordStatistics(@Param("firmwareId") Long firmwareId,
-                                               @Param("status") Integer status) {
-        return selectCount(new LambdaQueryWrapperX<IotOtaUpgradeRecordDO>()
-                .eqIfPresent(IotOtaUpgradeRecordDO::getFirmwareId, firmwareId)
-                .eqIfPresent(IotOtaUpgradeRecordDO::getStatus, status));
-    }
-
+    @Select("select count(case when status = 0 then 1 else 0) as `0` " +
+            "count(case when status = 1 then 1 else 0) as `1` " +
+            "count(case when status = 2 then 1 else 0) as `2` " +
+            "count(case when status = 3 then 1 else 0) as `3` " +
+            "count(case when status = 4 then 1 else 0) as `4` " +
+            "count(case when status = 5 then 1 else 0) as `5` " +
+            "from iot_ota_upgrade_record " +
+            "where firmware_id = #{firmwareId}")
+    List<Map<String, Object>> selectOtaUpgradeRecordStatistics(Long firmwareId);
 
     /**
      * 根据分页查询条件获取IOT OTA升级记录的分页结果

+ 0 - 35
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeRecordJob.java

@@ -1,35 +0,0 @@
-package cn.iocoder.yudao.module.iot.job.ota;
-
-import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
-import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
-import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeRecordDO;
-import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum;
-import cn.iocoder.yudao.module.iot.service.ota.IotOtaUpgradeRecordService;
-import jakarta.annotation.Resource;
-import org.springframework.stereotype.Component;
-
-import java.util.List;
-
-@Component
-public class IotOtaUpgradeRecordJob implements JobHandler {
-
-    @Resource
-    private IotOtaUpgradeRecordService upgradeRecordService;
-
-    @Override
-    @TenantJob
-    public String execute(String param) throws Exception {
-        // 1. 查询待处理的升级记录
-        List<IotOtaUpgradeRecordDO> upgradeRecords = upgradeRecordService
-                .getUpgradeRecordListByState(IotOtaUpgradeRecordStatusEnum.PENDING.getStatus());
-
-        // TODO @芋艿 2.执行升级动作
-        // TODO @li:应该是逐条 push,逐条更新。不用批量哈
-
-        // 3. 最终,更新升级记录状态
-        List<Long> ids = upgradeRecords.stream().map(IotOtaUpgradeRecordDO::getId).toList();
-        upgradeRecordService.updateUpgradeRecordStatus(ids, IotOtaUpgradeRecordStatusEnum.PUSHED.getStatus());
-        return "";
-    }
-
-}

+ 0 - 72
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeTaskJob.java

@@ -1,72 +0,0 @@
-package cn.iocoder.yudao.module.iot.job.ota;
-
-import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
-import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
-import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
-import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeRecordDO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
-import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum;
-import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskStatusEnum;
-import cn.iocoder.yudao.module.iot.service.ota.IotOtaUpgradeRecordService;
-import cn.iocoder.yudao.module.iot.service.ota.IotOtaUpgradeTaskService;
-import jakarta.annotation.Resource;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import java.util.List;
-
-// TODO @li:也不用通过 job 去统计。可以通过 record update status 后,主动去更新 task 的状态。
-@Slf4j
-@Component
-public class IotOtaUpgradeTaskJob implements JobHandler {
-
-    @Resource
-    private IotOtaUpgradeTaskService upgradeTaskService;
-    @Resource
-    private IotOtaUpgradeRecordService upgradeRecordService;
-
-    @Override
-    @TenantJob
-    public String execute(String param) throws Exception {
-        // 1.这个任务主要是为了检查并更新升级任务的状态
-        List<IotOtaUpgradeTaskDO> upgradeTasks = upgradeTaskService
-                .getUpgradeTaskByState(IotOtaUpgradeTaskStatusEnum.IN_PROGRESS.getStatus());
-        // 2.遍历并且确定升级任务的状态
-        upgradeTasks.forEach(this::checkUpgradeTaskState);
-        // TODO @芋艿: 其他的一些业务逻辑
-        return "";
-    }
-
-    private void checkUpgradeTaskState(IotOtaUpgradeTaskDO upgradeTask) {
-        // 1.查询任务所有的升级记录
-        List<IotOtaUpgradeRecordDO> upgradeRecords =
-                upgradeRecordService.getUpgradeRecordListByTaskId(upgradeTask.getId());
-        if (upgradeRecords.stream().anyMatch(upgradeRecord ->
-                ObjectUtils.equalsAny(upgradeRecord.getStatus(),
-                        IotOtaUpgradeRecordStatusEnum.PENDING.getStatus(),
-                        IotOtaUpgradeRecordStatusEnum.PUSHED.getStatus(),
-                        IotOtaUpgradeRecordStatusEnum.UPGRADING.getStatus()))) {
-            // 如果存在正在升级的升级记录,则升级任务的状态为进行中
-            log.debug("升级任务 {} 状态为进行中", upgradeTask.getId());
-        } else if (upgradeRecords.stream().allMatch(upgradeRecord ->
-                ObjectUtils.equalsAny(upgradeRecord.getStatus(),
-                        IotOtaUpgradeRecordStatusEnum.SUCCESS.getStatus()))) {
-            // 如果全部升级成功,则升级任务的状态为已完成
-            upgradeTaskService.updateUpgradeTaskStatus(upgradeTask.getId(),
-                    IotOtaUpgradeTaskStatusEnum.COMPLETED.getStatus());
-        } else if (upgradeRecords.stream().noneMatch(upgradeRecord ->
-                ObjectUtils.equalsAny(upgradeRecord.getStatus(),
-                        IotOtaUpgradeRecordStatusEnum.PENDING.getStatus(),
-                        IotOtaUpgradeRecordStatusEnum.PUSHED.getStatus(),
-                        IotOtaUpgradeRecordStatusEnum.UPGRADING.getStatus())) &&
-                upgradeRecords.stream().anyMatch(upgradeRecord ->
-                        ObjectUtils.equalsAny(upgradeRecord.getStatus(),
-                                IotOtaUpgradeRecordStatusEnum.FAILURE.getStatus(),
-                                IotOtaUpgradeRecordStatusEnum.CANCELED.getStatus()))) {
-            // 如果全部升级完毕,但是存在升级失败或者取消的升级记录,则升级任务的状态为失败
-            upgradeTaskService.updateUpgradeTaskStatus(upgradeTask.getId(),
-                    IotOtaUpgradeTaskStatusEnum.INCOMPLETE.getStatus());
-        }
-    }
-
-}

+ 3 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaFirmwareService.java

@@ -7,10 +7,10 @@ import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwa
 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
 import jakarta.validation.Valid;
 
-// TODO @li:类、方法注释有点冗余,可以参考别的模块哈
 /**
- * OTA固件管理服务接口
- * 提供OTA固件的创建、更新和查询等功能
+ * OTA 固件管理 Service
+ *
+ * @author Shelly Chan
  */
 public interface IotOtaFirmwareService {
 

+ 16 - 7
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaFirmwareServiceImpl.java

@@ -1,14 +1,19 @@
 package cn.iocoder.yudao.module.iot.service.ota;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.convert.Convert;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareCreateReqVO;
 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwarePageReqVO;
 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareUpdateReqVO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
 import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaFirmwareMapper;
+import cn.iocoder.yudao.module.iot.service.product.IotProductService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
@@ -26,23 +31,28 @@ public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService {
 
     @Resource
     private IotOtaFirmwareMapper otaFirmwareMapper;
+    @Lazy
+    @Resource
+    private IotProductService productService;
 
     @Override
     public Long createOtaFirmware(IotOtaFirmwareCreateReqVO saveReqVO) {
         // 1. 校验固件产品 + 版本号不能重复
-        // TODO @li:需要考虑设备也存在
         validateProductAndVersionDuplicate(saveReqVO.getProductId(), saveReqVO.getVersion());
-
-        // 2.转化数据格式,准备存储到数据库中
+        // 2.1.转化数据格式,准备存储到数据库中
         IotOtaFirmwareDO firmware = BeanUtils.toBean(saveReqVO, IotOtaFirmwareDO.class);
+        // 2.2.查询ProductKey
+        IotProductDO product = productService.getProduct(Convert.toLong(firmware.getProductId()));
+        firmware.setProductKey(Objects.requireNonNull(product).getProductKey());
+        // TODO @芋艿: 附件、附件签名等属性的计算
+
         otaFirmwareMapper.insert(firmware);
         return firmware.getId();
     }
 
     @Override
     public void updateOtaFirmware(IotOtaFirmwareUpdateReqVO updateReqVO) {
-        // TODO @li:如果序号只有一个,直接写 1. 更好哈
-        // 1.1. 校验存在
+        // 1. 校验存在
         validateFirmwareExists(updateReqVO.getId());
 
         // 2. 更新数据
@@ -84,8 +94,7 @@ public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService {
         // 查询数据库中是否存在具有相同产品ID和版本号的固件信息
         List<IotOtaFirmwareDO> list = otaFirmwareMapper.selectByProductIdAndVersion(productId, version);
         // 如果查询结果非空且不为null,则抛出异常,提示固件信息已存在
-        // TODO @li:使用 isNotEmpty 这种 方法,简化
-        if (Objects.nonNull(list) && !list.isEmpty()) {
+        if (CollUtil.isNotEmpty(list)) {
             throw exception(OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE);
         }
     }

+ 6 - 18
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeRecordService.java

@@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.iot.service.ota;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record.IotOtaUpgradeRecordPageReqVO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeRecordDO;
-import cn.iocoder.yudao.module.iot.service.ota.bo.IotOtaUpgradeRecordCreateReqBO;
-import cn.iocoder.yudao.module.iot.service.ota.bo.IotOtaUpgradeRecordUpdateReqBO;
 import jakarta.validation.Valid;
 
 import java.util.List;
@@ -16,25 +14,15 @@ import java.util.Map;
  */
 public interface IotOtaUpgradeRecordService {
 
-    // TODO @createOtaUpgradeRecordBatch 哈,需要补充方法里,缺少 Ota 关键字的
-
-    /**
-     * 批量创建物联网OTA升级记录
-     * <p>
-     * 该函数用于处理一组物联网OTA升级记录的创建请求,并将这些记录批量保存到系统中。
-     *
-     * @param createList 包含多个物联网OTA升级记录创建请求的列表,每个请求对象都经过校验(@Valid注解确保)
-     *                 列表中的每个元素都是IotOtaUpgradeRecordCreateReqBO类型的对象,表示一个独立的升级记录创建请求。
-     */
-    void createUpgradeRecordBatch(@Valid List<IotOtaUpgradeRecordCreateReqBO> createList);
-
-    // TODO @li:尽量避免写比较大的通用 update。而是根据场景提供,这样才能收敛
     /**
-     * 更新现有的 OTA 升级记录
+     * 批量创建OTA升级记录。
+     * 该函数用于为指定的设备列表、固件ID和升级任务ID创建OTA升级记录。
      *
-     * @param updateReqBO 包含更新升级记录所需信息的请求对象,必须经过验证。
+     * @param deviceIds     设备ID列表,表示需要升级的设备集合。
+     * @param firmwareId    固件ID,表示要升级到的固件版本。
+     * @param upgradeTaskId 升级任务ID,表示此次升级任务的唯一标识。
      */
-    void updateUpgradeRecord(@Valid IotOtaUpgradeRecordUpdateReqBO updateReqBO);
+    void createOtaUpgradeRecordBatch(List<Long> deviceIds, Long firmwareId, Long upgradeTaskId);
 
     /**
      * 获取OTA升级记录的数量统计。

+ 63 - 58
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeRecordServiceImpl.java

@@ -1,25 +1,31 @@
 package cn.iocoder.yudao.module.iot.service.ota;
 
+import cn.hutool.core.convert.Convert;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record.IotOtaUpgradeRecordPageReqVO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeRecordDO;
+import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
 import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaUpgradeRecordMapper;
 import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum;
-import cn.iocoder.yudao.module.iot.service.ota.bo.IotOtaUpgradeRecordCreateReqBO;
-import cn.iocoder.yudao.module.iot.service.ota.bo.IotOtaUpgradeRecordUpdateReqBO;
+import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
+import static cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum.CANCELED;
 
 @Slf4j
 @Service
@@ -28,27 +34,40 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
 
     @Resource
     private IotOtaUpgradeRecordMapper upgradeRecordMapper;
+    @Lazy
+    @Resource
+    private IotDeviceService deviceService;
+    @Lazy
+    @Resource
+    private IotOtaFirmwareService firmwareService;
+    @Lazy
+    @Resource
+    private IotOtaUpgradeTaskService upgradeTaskService;
 
     @Override
-    public void createUpgradeRecordBatch(List<IotOtaUpgradeRecordCreateReqBO> createList) {
-        // 1. 批量校验参数信息
-        createList.forEach(saveBO -> validateUpgradeRecordDuplicate(saveBO.getFirmwareId(), saveBO.getTaskId(), saveBO.getDeviceId()));
-
-        // 2. 将数据批量存储到数据库里
-        List<IotOtaUpgradeRecordDO> upgradeRecords = BeanUtils.toBean(createList, IotOtaUpgradeRecordDO.class);
-        upgradeRecordMapper.insertBatch(upgradeRecords);
-    }
+    public void createOtaUpgradeRecordBatch(List<Long> deviceIds, Long firmwareId, Long upgradeTaskId) {
+        // 1.校验升级记录信息是否存在,并且已经取消的任务可以重新开始
+        deviceIds.forEach(deviceId -> validateUpgradeRecordDuplicate(firmwareId, upgradeTaskId, String.valueOf(deviceId)));
+        // 2.初始化OTA升级记录列表信息
+        IotOtaUpgradeTaskDO upgradeTask = upgradeTaskService.getUpgradeTask(upgradeTaskId);
+        IotOtaFirmwareDO firmware = firmwareService.getOtaFirmware(firmwareId);
+        List<IotDeviceDO> deviceList = deviceService.getDeviceListByIdList(deviceIds);
+        List<IotOtaUpgradeRecordDO> upgradeRecordList = deviceList.stream().map(device -> {
+            IotOtaUpgradeRecordDO upgradeRecord = new IotOtaUpgradeRecordDO();
+            upgradeRecord.setFirmwareId(firmware.getId());
+            upgradeRecord.setTaskId(upgradeTask.getId());
+            upgradeRecord.setProductKey(device.getProductKey());
+            upgradeRecord.setDeviceName(device.getDeviceName());
+            upgradeRecord.setDeviceId(Convert.toStr(device.getId()));
+            upgradeRecord.setFromFirmwareId(Convert.toLong(device.getFirmwareId()));
+            upgradeRecord.setStatus(IotOtaUpgradeRecordStatusEnum.PENDING.getStatus());
+            upgradeRecord.setProgress(0);
+            return upgradeRecord;
+        }).toList();
+        // 3.保存数据
+        upgradeRecordMapper.insertBatch(upgradeRecordList);
+        // TODO @芋艿:在这里需要处理推送升级任务的逻辑
 
-    @Override
-    @Transactional
-    public void updateUpgradeRecord(IotOtaUpgradeRecordUpdateReqBO updateReqBO) {
-        // 1. 校验升级记录信息是否存在
-        validateUpgradeRecordExists(updateReqBO.getId());
-
-        // 2. 将数据转化成数据库存储的格式
-        IotOtaUpgradeRecordDO updateRecord = BeanUtils.toBean(updateReqBO, IotOtaUpgradeRecordDO.class);
-        upgradeRecordMapper.updateById(updateRecord);
-        // TODO @芋艿: 更新升级记录触发的其他Action
     }
 
     /**
@@ -62,21 +81,14 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
     @Transactional
     public Map<Integer, Long> getOtaUpgradeRecordCount(IotOtaUpgradeRecordPageReqVO pageReqVO) {
         // 分别查询不同状态的OTA升级记录数量
-        // TODO @li: 通过 groupby 统计下;
-        Long pending = upgradeRecordMapper.getOtaUpgradeRecordCount(pageReqVO.getTaskId(), pageReqVO.getDeviceName(), IotOtaUpgradeRecordStatusEnum.PENDING.getStatus());
-        Long pushed = upgradeRecordMapper.getOtaUpgradeRecordCount(pageReqVO.getTaskId(), pageReqVO.getDeviceName(), IotOtaUpgradeRecordStatusEnum.PUSHED.getStatus());
-        Long upgrading = upgradeRecordMapper.getOtaUpgradeRecordCount(pageReqVO.getTaskId(), pageReqVO.getDeviceName(), IotOtaUpgradeRecordStatusEnum.UPGRADING.getStatus());
-        Long success = upgradeRecordMapper.getOtaUpgradeRecordCount(pageReqVO.getTaskId(), pageReqVO.getDeviceName(), IotOtaUpgradeRecordStatusEnum.SUCCESS.getStatus());
-        Long failure = upgradeRecordMapper.getOtaUpgradeRecordCount(pageReqVO.getTaskId(), pageReqVO.getDeviceName(), IotOtaUpgradeRecordStatusEnum.FAILURE.getStatus());
-        Long canceled = upgradeRecordMapper.getOtaUpgradeRecordCount(pageReqVO.getTaskId(), pageReqVO.getDeviceName(), IotOtaUpgradeRecordStatusEnum.CANCELED.getStatus());
-        // 将各状态的数量封装到Map中返回
-        // TODO @li:使用 MapUtil,因为 Map.of 是 jdk9 才有,后续不好同步到 master 的 jdk8;
-        return Map.of(IotOtaUpgradeRecordPageReqVO.PENDING, pending,
-                IotOtaUpgradeRecordPageReqVO.PUSHED, pushed,
-                IotOtaUpgradeRecordPageReqVO.UPGRADING, upgrading,
-                IotOtaUpgradeRecordPageReqVO.SUCCESS, success,
-                IotOtaUpgradeRecordPageReqVO.FAILURE, failure,
-                IotOtaUpgradeRecordPageReqVO.CANCELED, canceled);
+        List<Map<String, Object>> upgradeRecordCountList = upgradeRecordMapper.selectOtaUpgradeRecordCount(
+                pageReqVO.getTaskId(), pageReqVO.getDeviceName());
+        Map<String, Object> upgradeRecordCountMap = ObjectUtils.defaultIfNull(upgradeRecordCountList.get(0));
+        Objects.requireNonNull(upgradeRecordCountMap);
+        return upgradeRecordCountMap.entrySet().stream().collect(Collectors.toMap(
+                entry -> Convert.toInt(entry.getKey()),
+                entry -> Convert.toLong(entry.getValue())
+        ));
     }
 
     /**
@@ -90,20 +102,13 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
     @Transactional
     public Map<Integer, Long> getOtaUpgradeRecordStatistics(Long firmwareId) {
         // 查询并统计不同状态的OTA升级记录数量
-        // TODO @li: 通过 groupby 统计下;
-        Long pending = upgradeRecordMapper.getOtaUpgradeRecordStatistics(firmwareId, IotOtaUpgradeRecordStatusEnum.PENDING.getStatus());
-        Long pushed = upgradeRecordMapper.getOtaUpgradeRecordStatistics(firmwareId, IotOtaUpgradeRecordStatusEnum.PUSHED.getStatus());
-        Long upgrading = upgradeRecordMapper.getOtaUpgradeRecordStatistics(firmwareId, IotOtaUpgradeRecordStatusEnum.UPGRADING.getStatus());
-        Long success = upgradeRecordMapper.getOtaUpgradeRecordStatistics(firmwareId, IotOtaUpgradeRecordStatusEnum.SUCCESS.getStatus());
-        Long failure = upgradeRecordMapper.getOtaUpgradeRecordStatistics(firmwareId, IotOtaUpgradeRecordStatusEnum.FAILURE.getStatus());
-        Long canceled = upgradeRecordMapper.getOtaUpgradeRecordStatistics(firmwareId, IotOtaUpgradeRecordStatusEnum.CANCELED.getStatus());
-        // 将统计结果封装为Map并返回
-        return Map.of(IotOtaUpgradeRecordPageReqVO.PENDING, pending,
-                IotOtaUpgradeRecordPageReqVO.PUSHED, pushed,
-                IotOtaUpgradeRecordPageReqVO.UPGRADING, upgrading,
-                IotOtaUpgradeRecordPageReqVO.SUCCESS, success,
-                IotOtaUpgradeRecordPageReqVO.FAILURE, failure,
-                IotOtaUpgradeRecordPageReqVO.CANCELED, canceled);
+        List<Map<String, Object>> upgradeRecordStatisticsList = upgradeRecordMapper.selectOtaUpgradeRecordStatistics(firmwareId);
+        Map<String, Object> upgradeRecordStatisticsMap = ObjectUtils.defaultIfNull(upgradeRecordStatisticsList.get(0));
+        Objects.requireNonNull(upgradeRecordStatisticsMap);
+        return upgradeRecordStatisticsMap.entrySet().stream().collect(Collectors.toMap(
+                entry -> Convert.toInt(entry.getKey()),
+                entry -> Convert.toLong(entry.getValue())
+        ));
     }
 
     @Override
@@ -117,8 +122,6 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
         upgradeRecordMapper.updateById(new IotOtaUpgradeRecordDO()
                 .setId(upgradeRecord.getId()).setProgress(0)
                 .setStatus(IotOtaUpgradeRecordStatusEnum.PENDING.getStatus()));
-        // TODO @芋艿: 重试升级记录触发的其他Action
-        // TODO 如果一个升级记录被取消或者已经执行失败,重试成功,是否会对升级任务的状态有影响?
     }
 
     @Override
@@ -135,7 +138,7 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
     public void cancelUpgradeRecordByTaskId(Long taskId) {
         // 暂定只有待推送的升级记录可以取消
         upgradeRecordMapper.updateUpgradeRecordStatusByTaskIdAndStatus(
-                IotOtaUpgradeRecordStatusEnum.CANCELED.getStatus(), taskId,
+                CANCELED.getStatus(), taskId,
                 IotOtaUpgradeRecordStatusEnum.PENDING.getStatus());
     }
 
@@ -173,21 +176,23 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
     }
 
     /**
-     * 验证固件升级记录是否存在
+     * 校验固件升级记录是否重复
      * <p>
-     * 该函数通过给定的固件ID、任务ID和设备ID查询升级记录,如果查询结果为空,则抛出异常。
+     * 该函数用于检查给定的固件ID、任务ID和设备ID是否已经存在未取消的升级记录。
+     * 如果存在未取消的记录,则抛出异常,提示升级记录重复。
      *
      * @param firmwareId 固件ID,用于标识特定的固件版本
      * @param taskId     任务ID,用于标识特定的升级任务
      * @param deviceId   设备ID,用于标识特定的设备
-     * @throws cn.iocoder.yudao.framework.common.exception.ServiceException,则抛出OTA_UPGRADE_RECORD_NOT_EXISTS异常
      */
     private void validateUpgradeRecordDuplicate(Long firmwareId, Long taskId, String deviceId) {
         // 根据条件查询升级记录
         IotOtaUpgradeRecordDO upgradeRecord = upgradeRecordMapper.selectByConditions(firmwareId, taskId, deviceId);
-        // 如果查询结果为空,抛出异常
+        // 如果查询到升级记录且状态不是已取消,则抛出异常
         if (upgradeRecord != null) {
-            throw exception(OTA_UPGRADE_RECORD_DUPLICATE);
+            if (!CANCELED.getStatus().equals(upgradeRecord.getStatus())) {
+                throw exception(OTA_UPGRADE_RECORD_DUPLICATE);
+            }
         }
     }
 

+ 3 - 3
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeTaskService.java

@@ -8,10 +8,10 @@ import jakarta.validation.Valid;
 
 import java.util.List;
 
-// TODO @li:类、方法注释有点冗余,可以参考别的模块哈
 /**
- * IoT OTA升级任务服务接口
- * 提供OTA升级任务的创建、取消和查询功能
+ * IoT OTA升级任务 Service 接口
+ *
+ * @author Shelly Chan
  */
 public interface IotOtaUpgradeTaskService {
 

+ 45 - 89
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeTaskServiceImpl.java

@@ -3,9 +3,9 @@ package cn.iocoder.yudao.module.iot.service.ota;
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.convert.Convert;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task.IotOtaUpgradeTaskPageReqVO;
 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task.IotOtaUpgradeTaskSaveReqVO;
-import cn.iocoder.yudao.module.iot.convert.ota.IotOtaUpgradeRecordConvert;
 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
@@ -13,7 +13,6 @@ import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaUpgradeTaskMapper;
 import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskScopeEnum;
 import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskStatusEnum;
 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
-import cn.iocoder.yudao.module.iot.service.ota.bo.IotOtaUpgradeRecordCreateReqBO;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.context.annotation.Lazy;
@@ -23,6 +22,7 @@ import org.springframework.validation.annotation.Validated;
 
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
@@ -52,21 +52,13 @@ public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
         validateFirmwareTaskDuplicate(createReqVO.getFirmwareId(), createReqVO.getName());
         // 1.2 校验固件信息是否存在
         IotOtaFirmwareDO firmware = firmwareService.validateFirmwareExists(createReqVO.getFirmwareId());
-        // 1.3 校验升级范围=2(指定设备时),deviceIds deviceNames不为空并且长度相等
-        // TODO @li:deviceNames 应该后端查询
-        validateScopeAndDevice(createReqVO.getScope(), createReqVO.getDeviceIds(), createReqVO.getDeviceNames());
-        // TODO @li:如果全部范围,但是没设备可以升级,需要报错
-
+        // 1.3 补全设备范围信息,并且校验是否又设备可以升级,如果没有设备可以升级,则报错
+        validateScopeAndDevice(createReqVO.getScope(), createReqVO.getDeviceIds(), firmware.getProductId());
         // 2. 保存 OTA 升级任务信息到数据库
-        IotOtaUpgradeTaskDO upgradeTask = initUpgradeTask(createReqVO, firmware.getProductId());
+        IotOtaUpgradeTaskDO upgradeTask = initOtaUpgradeTask(createReqVO, firmware.getProductId());
         upgradeTaskMapper.insert(upgradeTask);
-
         // 3. 生成设备升级记录信息并存储,等待定时任务轮询
-        List<IotOtaUpgradeRecordCreateReqBO> upgradeRecordList = initUpgradeRecordList(
-                upgradeTask, firmware, createReqVO.getDeviceIds());
-        // TODO @li:只需要传递 deviceIds、firewareId、剩余的 upgradeRecordService 里面自己处理;这样,后续 record 加字段,都不需要透传太多;解耦
-        upgradeRecordService.createUpgradeRecordBatch(upgradeRecordList);
-        // TODO @芋艿: 创建任务触发的其他Action
+        upgradeRecordService.createOtaUpgradeRecordBatch(upgradeTask.getDeviceIds(), firmware.getId(), upgradeTask.getId());
         return upgradeTask.getId();
     }
 
@@ -76,16 +68,17 @@ public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
         // 1.1 校验升级任务是否存在
         IotOtaUpgradeTaskDO upgradeTask = validateUpgradeTaskExists(id);
         // 1.2 校验升级任务是否可以取消
-        // TODO @li:这种一次性的,可以不考虑拆分方法
-        validateUpgradeTaskCanCancel(upgradeTask);
-
+        // 检查升级任务的状态是否为进行中,只有此状态下的任务才允许取消
+        if (!Objects.equals(upgradeTask.getStatus(), IotOtaUpgradeTaskStatusEnum.IN_PROGRESS.getStatus())) {
+            // 只有进行中的任务才可以取消
+            throw exception(OTA_UPGRADE_TASK_CANNOT_CANCEL);
+        }
         // 2. 更新 OTA 升级任务状态为已取消
         upgradeTaskMapper.updateById(IotOtaUpgradeTaskDO.builder()
                 .id(id).status(IotOtaUpgradeTaskStatusEnum.CANCELED.getStatus())
                 .build());
         // 3. 更新 OTA 升级记录状态为已取消
         upgradeRecordService.cancelUpgradeRecordByTaskId(id);
-        // TODO @芋艿: 取消任务触发的其他Action
     }
 
     @Override
@@ -131,19 +124,27 @@ public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
     }
 
     /**
-     * 验证升级任务的范围和设备参数是否有效
-     * 当选择特定设备进行升级时,确保提供的设备ID和设备名称列表有效且对应
+     * 验证升级任务的范围和设备列表的有效性。
+     * <p>
+     * 根据升级任务的范围(scope),验证设备列表(deviceIds)或产品ID(productId)是否有效。
+     * 如果范围是“选择设备”(SELECT),则必须提供设备列表;如果范围是“所有设备”(ALL),则必须根据产品ID获取设备列表,并确保列表不为空。
      *
-     * @param scope       升级任务的范围,表示是选择特定设备还是其他范围
-     * @param deviceIds   设备ID列表,用于标识参与升级的设备
-     * @param deviceNames 设备名称列表,与设备ID列表对应
+     * @param scope     升级任务的范围,参考 IotOtaUpgradeTaskScopeEnum 枚举值
+     * @param deviceIds 设备ID列表,当范围为“选择设备”时,该列表不能为空
+     * @param productId 产品ID,当范围为“所有设备”时,用于获取设备列表
+     * @throws cn.iocoder.yudao.framework.common.exception.ServiceException,抛出相应的异常
      */
-    private void validateScopeAndDevice(Integer scope, List<Long> deviceIds, List<String> deviceNames) {
-        // 当升级任务范围为选择特定设备时
+    private void validateScopeAndDevice(Integer scope, List<Long> deviceIds, String productId) {
+        // 验证范围为“选择设备”时,设备列表不能为空
         if (Objects.equals(scope, IotOtaUpgradeTaskScopeEnum.SELECT.getScope())) {
-            // 检查设备ID列表和设备名称列表是否为空或长度不一致,若不符合要求,则抛出异常
-            if (CollUtil.isEmpty(deviceIds) || CollUtil.isEmpty(deviceNames) || deviceIds.size() != deviceNames.size()) {
-                throw exception(OTA_UPGRADE_TASK_PARAMS_INVALID);
+            if (CollUtil.isEmpty(deviceIds)) {
+                throw exception(OTA_UPGRADE_TASK_DEVICE_IDS_EMPTY);
+            }
+        } else if (Objects.equals(scope, IotOtaUpgradeTaskScopeEnum.ALL.getScope())) {
+            // 验证范围为“所有设备”时,根据产品ID获取的设备列表不能为空
+            List<IotDeviceDO> deviceList = deviceService.getDeviceListByProductId(Convert.toLong(productId));
+            if (CollUtil.isEmpty(deviceList)) {
+                throw exception(OTA_UPGRADE_TASK_DEVICE_LIST_EMPTY);
             }
         }
     }
@@ -166,25 +167,6 @@ public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
         return upgradeTask;
     }
 
-    /**
-     * 验证升级任务是否可以被取消
-     * <p>
-     * 此方法旨在确保只有当升级任务处于进行中状态时,才可以执行取消操作
-     * 它通过比较任务的当前状态与预定义的进行中状态来判断是否允许取消操作
-     * 如果任务状态不符合条件,则抛出异常,表明该任务无法取消
-     *
-     * @param upgradeTask 待验证的升级任务对象,包含任务的详细信息,如状态等
-     * @throws cn.iocoder.yudao.framework.common.exception.ServiceException 如果任务状态不是进行中,则抛出此异常,表明任务无法取消
-     */
-    private void validateUpgradeTaskCanCancel(IotOtaUpgradeTaskDO upgradeTask) {
-        // 检查升级任务的状态是否为进行中,只有此状态下的任务才允许取消
-        if (!Objects.equals(upgradeTask.getStatus(), IotOtaUpgradeTaskStatusEnum.IN_PROGRESS.getStatus())) {
-            // 只有进行中的任务才可以取消
-            throw exception(OTA_UPGRADE_TASK_CANNOT_CANCEL);
-        }
-    }
-
-    // TODO @li:一次性,不复用的,可以直接写在对应的逻辑里;
     /**
      * 初始化升级任务
      * <p>
@@ -195,55 +177,29 @@ public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
      * @param createReqVO 升级任务保存请求对象,包含创建升级任务所需的信息
      * @return 返回初始化后的升级任务对象
      */
-    private IotOtaUpgradeTaskDO initUpgradeTask(IotOtaUpgradeTaskSaveReqVO createReqVO, String productId) {
-        // 配置各项参数
-        IotOtaUpgradeTaskDO upgradeTask = IotOtaUpgradeTaskDO.builder()
-                // TODO @li:不用每个占一行。最好相同类型的,放在一行里;
-                .name(createReqVO.getName())
-                .description(createReqVO.getDescription())
-                .firmwareId(createReqVO.getFirmwareId())
-                .scope(createReqVO.getScope())
-                .deviceIds(createReqVO.getDeviceIds())
-                .deviceNames(createReqVO.getDeviceNames())
-                .deviceCount(Convert.toLong(CollUtil.size(createReqVO.getDeviceIds())))
-                .status(IotOtaUpgradeTaskStatusEnum.IN_PROGRESS.getStatus())
-                .build();
+    private IotOtaUpgradeTaskDO initOtaUpgradeTask(IotOtaUpgradeTaskSaveReqVO createReqVO, String productId) {
+        // 将请求参数转换为升级任务对象
+        IotOtaUpgradeTaskDO upgradeTask = BeanUtils.toBean(createReqVO, IotOtaUpgradeTaskDO.class);
+        // 初始化的时候,设置设备数量和状态
+        upgradeTask.setDeviceCount(Convert.toLong(CollUtil.size(createReqVO.getDeviceIds())))
+                .setStatus(IotOtaUpgradeTaskStatusEnum.IN_PROGRESS.getStatus());
         // 如果选择全选,则需要查询设备数量
         if (Objects.equals(createReqVO.getScope(), IotOtaUpgradeTaskScopeEnum.ALL.getScope())) {
             // 根据产品ID查询设备数量
-            Long deviceCount = deviceService.getDeviceCountByProductId(Convert.toLong(productId));
+            List<IotDeviceDO> deviceList = deviceService.getDeviceListByProductId(Convert.toLong(productId));
             // 设置升级任务的设备数量
-            upgradeTask.setDeviceCount(deviceCount);
+            upgradeTask.setDeviceCount((long) deviceList.size());
+            upgradeTask.setDeviceIds(
+                    deviceList.stream().map(IotDeviceDO::getId).collect(Collectors.toList()));
+            upgradeTask.setDeviceNames(
+                    deviceList.stream().map(IotDeviceDO::getDeviceName).collect(Collectors.toList()));
+        } else if (Objects.equals(createReqVO.getScope(), IotOtaUpgradeTaskScopeEnum.SELECT.getScope())) {
+            List<IotDeviceDO> deviceList = deviceService.getDeviceListByIdList(createReqVO.getDeviceIds());
+            upgradeTask.setDeviceNames(
+                    deviceList.stream().map(IotDeviceDO::getDeviceName).collect(Collectors.toList()));
         }
         // 返回初始化后的升级任务对象
         return upgradeTask;
     }
 
-    /**
-     * 初始化升级记录列表
-     * <p>
-     * 根据升级任务的范围(选择设备或按产品ID)获取设备列表,并将其转换为升级记录请求对象列表。
-     *
-     * @param upgradeTask 升级任务对象,包含升级任务的相关信息
-     * @param firmware    固件对象,包含固件的相关信息
-     * @param deviceIds   设备ID列表,仅在升级任务范围为选择设备时使用
-     * @return 升级记录请求对象列表,包含每个设备的升级记录信息
-     */
-    private List<IotOtaUpgradeRecordCreateReqBO> initUpgradeRecordList(
-            IotOtaUpgradeTaskDO upgradeTask, IotOtaFirmwareDO firmware, List<Long> deviceIds) {
-        // TODO @li:需要考虑,如果创建多个任务,相互之间不能重复;
-        // 1)指定设备的时候,进行校验;2)如果是全部,则过滤其它已经发起的;;;;;另外,需要排除掉 cancel 的哈。因为 cancal 之后,还可以发起
-        // 根据升级任务的范围确定设备列表
-        List<IotDeviceDO> deviceList;
-        if (Objects.equals(upgradeTask.getScope(), IotOtaUpgradeTaskScopeEnum.SELECT.getScope())) {
-            // 如果升级任务范围为选择设备,则根据设备ID列表获取设备信息
-            deviceList = deviceService.getDeviceListByIdList(deviceIds);
-        } else {
-            // 如果升级任务范围为按产品ID,则根据固件的产品ID获取设备信息
-            deviceList = deviceService.getDeviceListByProductId(Convert.toLong(firmware.getProductId()));
-        }
-        // 将升级任务、固件和设备列表转换为升级记录请求对象列表
-        return IotOtaUpgradeRecordConvert.INSTANCE.convertBOList(upgradeTask, firmware, deviceList);
-    }
-
 }

+ 0 - 79
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/bo/IotOtaUpgradeRecordCreateReqBO.java

@@ -1,79 +0,0 @@
-package cn.iocoder.yudao.module.iot.service.ota.bo;
-
-import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
-import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
-import jakarta.validation.constraints.NotNull;
-import lombok.Data;
-
-import java.time.LocalDateTime;
-
-@Data
-public class IotOtaUpgradeRecordCreateReqBO {
-
-    /**
-     * 固件编号
-     * <p>
-     * 关联 {@link IotOtaFirmwareDO#getId()}
-     */
-    @NotNull(message = "固件编号不能为空")
-    private Long firmwareId;
-    /**
-     * 任务编号
-     * <p>
-     * 关联 {@link IotOtaUpgradeTaskDO#getId()}
-     */
-    @NotNull(message = "任务编号不能为空")
-    private Long taskId;
-    /**
-     * 产品标识
-     * <p>
-     * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
-     */
-    private String productKey;
-    /**
-     * 设备名称
-     * <p>
-     * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO#getId()}
-     */
-    private String deviceName;
-    /**
-     * 设备编号
-     * <p>
-     * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO#getId()}
-     */
-    @NotNull(message = "设备编号不能为空")
-    private String deviceId;
-    /**
-     * 来源的固件编号
-     * <p>
-     * 关联 {@link IotDeviceDO#getFirmwareId()}
-     */
-    private Long fromFirmwareId;
-    /**
-     * 升级状态
-     * <p>
-     * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum}
-     */
-    private Integer status;
-    /**
-     * 升级进度,百分比
-     */
-    private Integer progress;
-    /**
-     * 升级进度描述
-     * <p>
-     * 注意,只记录设备最后一次的升级进度描述
-     * 如果想看历史记录,可以查看 {@link cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceLogDO} 设备日志
-     */
-    private String description;
-    /**
-     * 升级开始时间
-     */
-    private LocalDateTime startTime;
-    /**
-     * 升级结束时间
-     */
-    private LocalDateTime endTime;
-
-}

+ 0 - 46
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/bo/IotOtaUpgradeRecordUpdateReqBO.java

@@ -1,46 +0,0 @@
-package cn.iocoder.yudao.module.iot.service.ota.bo;
-
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum;
-import jakarta.validation.constraints.NotNull;
-import lombok.Data;
-import org.hibernate.validator.constraints.Range;
-import org.springframework.format.annotation.DateTimeFormat;
-
-import java.time.LocalDateTime;
-
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
-
-// TODO @li:这个类,貌似没用?
-@Data
-public class IotOtaUpgradeRecordUpdateReqBO {
-
-    /**
-     * 升级记录编号
-     */
-    @NotNull(message = "升级记录编号不能为空")
-    private Long id;
-    /**
-     * 升级状态
-     * <p>
-     * 关联 {@link cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum}
-     */
-    @InEnum(IotOtaUpgradeRecordStatusEnum.class)
-    private Integer status;
-    /**
-     * 升级进度,百分比
-     */
-    @Range(min = 0, max = 100, message = "升级进度必须介于 0-100 之间")
-    private Integer progress;
-    /**
-     * 升级开始时间
-     */
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime startTime;
-    /**
-     * 升级结束时间
-     */
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
-    private LocalDateTime endTime;
-
-}