Commit b87a5838424e4e091e2df22c74b69722d3adfd3a

Authored by 陈玄礼
1 parent 4e33cd2b

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

- 更新错误码定义,调整 OTA 相关错误码的编号和描述
- 移除未使用的 IotOtaFirmwareCommonReqVO 类- 优化 IotOtaFirmwareCreateReqVO 和 IotOtaFirmwareUpdateReqVO 的结构
- 删除冗余的 IotOtaUpgradeRecordCreateReqBO 类
- 重构 IotOtaUpgradeRecordMapper 的查询方法
- 更新 IotOtaUpgradeRecordService 接口,简化升级记录创建方法
- 删除未使用的 IotOtaUpgradeRecordJob 类
- 优化 IotOtaUpgradeRecordController 的重试接口,使用 PUT 方法
Showing 21 changed files with 201 additions and 600 deletions
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 {
56 56 ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在");
57 57 ErrorCode OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE = new ErrorCode(1_050_008_001, "产品版本号重复");
58 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, "升级任务不能取消");
  59 + ErrorCode OTA_UPGRADE_TASK_NOT_EXISTS = new ErrorCode(1_050_008_100, "升级任务不存在");
  60 + ErrorCode OTA_UPGRADE_TASK_NAME_DUPLICATE = new ErrorCode(1_050_008_101, "升级任务名称重复");
  61 + ErrorCode OTA_UPGRADE_TASK_DEVICE_IDS_EMPTY = new ErrorCode(1_050_008_102, "设备编号列表不能为空");
  62 + ErrorCode OTA_UPGRADE_TASK_DEVICE_LIST_EMPTY = new ErrorCode(1_050_008_103, "设备列表不能为空");
  63 + ErrorCode OTA_UPGRADE_TASK_CANNOT_CANCEL = new ErrorCode(1_050_008_104, "升级任务不能取消");
64 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, "升级记录不能重试");
  65 + ErrorCode OTA_UPGRADE_RECORD_NOT_EXISTS = new ErrorCode(1_050_008_200, "升级记录不存在");
  66 + ErrorCode OTA_UPGRADE_RECORD_DUPLICATE = new ErrorCode(1_050_008_201, "升级记录重复");
  67 + ErrorCode OTA_UPGRADE_RECORD_CANNOT_RETRY = new ErrorCode(1_050_008_202, "升级记录不能重试");
69 68  
70 69 // ========== MQTT 通信相关 1-050-009-000 ==========
71 70 ErrorCode MQTT_TOPIC_ILLEGAL = new ErrorCode(1_050_009_000, "topic illegal");
... ...
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 {
64 64 return success(BeanUtils.toBean(upgradeRecord, IotOtaUpgradeRecordRespVO.class));
65 65 }
66 66  
67   - // TODO @li:使用 Putmapping
68   - @PostMapping("/retry")
  67 + @PutMapping("/retry")
69 68 @Operation(summary = "重试升级记录")
70 69 @PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:retry')")
71 70 @Parameter(name = "id", description = "升级记录编号", required = true, example = "1024")
... ...
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 {
44 44 return success(true);
45 45 }
46 46  
47   - // TODO @li:get 接口,不是 @RequestBody 哈
48 47 @GetMapping("/page")
49 48 @Operation(summary = "获得升级任务分页")
50 49 @PreAuthorize(value = "@ss.hasPermission('iot:ota-upgrade-task:query')")
51   - public CommonResult<PageResult<IotOtaUpgradeTaskRespVO>> getUpgradeTaskPage(@Valid @RequestBody IotOtaUpgradeTaskPageReqVO pageReqVO) {
  50 + public CommonResult<PageResult<IotOtaUpgradeTaskRespVO>> getUpgradeTaskPage(@Valid IotOtaUpgradeTaskPageReqVO pageReqVO) {
52 51 PageResult<IotOtaUpgradeTaskDO> pageResult = upgradeTaskService.getUpgradeTaskPage(pageReqVO);
53 52 return success(BeanUtils.toBean(pageResult, IotOtaUpgradeTaskRespVO.class));
54 53 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCommonReqVO.java deleted
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
... ... @@ -7,72 +7,32 @@ import lombok.Data;
7 7  
8 8 import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
9 9  
10   -// TODO @li:中英文之间,有个空格。中文写作习惯哈。
11   -@Schema(description = "管理后台 - OTA固件创建 Request VO")
  10 +@Schema(description = "管理后台 - OTA 固件创建 Request VO")
12 11 @Data
13   -public class IotOtaFirmwareCreateReqVO extends IotOtaFirmwareCommonReqVO {
  12 +public class IotOtaFirmwareCreateReqVO {
  13 +
  14 + @Schema(description = "固件名称", requiredMode = REQUIRED, example = "智能开关固件")
  15 + @NotEmpty(message = "固件名称不能为空")
  16 + private String name;
  17 +
  18 + @Schema(description = "固件描述", example = "某品牌型号固件,测试用")
  19 + private String description;
14 20  
15   - // TODO @li:因为有了注解,注释可以不写哈
16   - // TODO @li:swagger 注解,写在 validator 注解之前,保持项目统一哈。
17   - /**
18   - * 版本号
19   - */
20   - @NotEmpty(message = "版本号不能为空")
21 21 @Schema(description = "版本号", requiredMode = REQUIRED, example = "1.0.0")
  22 + @NotEmpty(message = "版本号不能为空")
22 23 private String version;
23 24  
24   - /**
25   - * 产品编号
26   - * <p>
27   - * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
28   - */
29   - @NotNull(message = "产品编号不能为空")
30 25 @Schema(description = "产品编号", requiredMode = REQUIRED, example = "1024")
  26 + @NotNull(message = "产品编号不能为空")
31 27 private String productId;
32 28  
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 29 @Schema(description = "签名方式", example = "MD5")
49 30 private String signMethod;
50 31  
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 32 @Schema(description = "固件文件 URL", requiredMode = REQUIRED, example = "https://www.iocoder.cn/yudao-firmware.zip")
  33 + @NotEmpty(message = "固件文件 URL 不能为空")
71 34 private String fileUrl;
72 35  
73   - /**
74   - * 自定义信息,建议使用 JSON 格式
75   - */
76 36 @Schema(description = "自定义信息,建议使用 JSON 格式", example = "{\"key1\":\"value1\",\"key2\":\"value2\"}")
77 37 private String information;
78 38  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareUpdateReqVO.java
1 1 package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
4 5 import jakarta.validation.constraints.NotNull;
5 6 import lombok.Data;
6 7  
7 8 import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
8 9  
9   -@Data
10 10 @Schema(description = "管理后台 - OTA固件更新 Request VO")
11   -public class IotOtaFirmwareUpdateReqVO extends IotOtaFirmwareCommonReqVO {
  11 +@Data
  12 +public class IotOtaFirmwareUpdateReqVO {
12 13  
13   - /**
14   - * 固件编号
15   - */
16   - @NotNull(message = "固件编号不能为空")
17 14 @Schema(description = "固件编号", requiredMode = REQUIRED, example = "1024")
  15 + @NotNull(message = "固件编号不能为空")
18 16 private Long id;
19 17  
  18 + @Schema(description = "固件名称", requiredMode = REQUIRED, example = "智能开关固件")
  19 + @NotEmpty(message = "固件名称不能为空")
  20 + private String name;
  21 +
  22 + @Schema(description = "固件描述", example = "某品牌型号固件,测试用")
  23 + private String description;
  24 +
20 25 }
... ...
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;
11 11 @Schema(description = "管理后台 - OTA升级记录分页 Request VO")
12 12 public class IotOtaUpgradeRecordPageReqVO extends PageParam {
13 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 14 /**
41 15 * 升级任务编号字段。
42 16 * <p>
... ...
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 {
57 57 @Schema(description = "选中的设备编号数组", requiredMode = REQUIRED, example = "[1,2,3,4]")
58 58 private List<Long> deviceIds;
59 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 60 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/convert/ota/IotOtaUpgradeRecordConvert.java
1 1 package cn.iocoder.yudao.module.iot.convert.ota;
2 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 3 import org.mapstruct.Mapper;
10 4 import org.mapstruct.factory.Mappers;
11 5  
12   -import java.util.List;
13   -
14 6 @Mapper
15 7 public interface IotOtaUpgradeRecordConvert {
16 8  
17 9 IotOtaUpgradeRecordConvert INSTANCE = Mappers.getMapper(IotOtaUpgradeRecordConvert.class);
18 10  
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 11 }
... ...
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 {
71 71 @TableField(typeHandler = JacksonTypeHandler.class)
72 72 private List<Long> deviceIds;
73 73  
74   - // TODO @li:这个通过查询,不用冗余
75   - /**
76   - * 选中的设备名字数组
77   - * <p>
78   - * 关联 {@link IotDeviceDO#getDeviceName()}
79   - */
80   - @TableField(typeHandler = JacksonTypeHandler.class)
81   - private List<String> deviceNames;
82   -
83 74 }
... ...
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;
8 8 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
9 9 import org.apache.ibatis.annotations.Mapper;
10 10 import org.apache.ibatis.annotations.Param;
  11 +import org.apache.ibatis.annotations.Select;
11 12  
12 13 import java.util.List;
  14 +import java.util.Map;
13 15  
14 16 /**
15 17 * OTA 升级记录 Mapper
... ... @@ -36,36 +38,42 @@ public interface IotOtaUpgradeRecordMapper extends BaseMapperX&lt;IotOtaUpgradeReco
36 38 }
37 39  
38 40 /**
39   - * 获取OTA升级记录的数量
  41 + * 根据任务ID和设备名称查询OTA升级记录的状态统计信息。
  42 + * 该函数通过SQL查询统计不同状态(0到5)的记录数量,并返回一个包含统计结果的Map列表。
40 43 *
41   - * @param taskId 任务ID,用于筛选特定任务的升级记录
42   - * @param deviceName 设备名称,用于筛选特定设备的升级记录
43   - * @param status 状态,用于筛选特定状态的升级记录
44   - * @return 返回符合条件的OTA升级记录的数量
  44 + * @param taskId 任务ID,用于筛选特定任务的OTA升级记录。
  45 + * @param deviceName 设备名称,支持模糊查询,用于筛选特定设备的OTA升级记录。
  46 + * @return 返回一个Map列表,每个Map包含不同状态(0到5)的记录数量。
45 47 */
46   - default Long getOtaUpgradeRecordCount(@Param("taskId") Long taskId,
47   - @Param("deviceName") String deviceName,
48   - @Param("status") Integer status) {
49   - return selectCount(new LambdaQueryWrapperX<IotOtaUpgradeRecordDO>()
50   - .eqIfPresent(IotOtaUpgradeRecordDO::getTaskId, taskId)
51   - .likeIfPresent(IotOtaUpgradeRecordDO::getDeviceId, deviceName)
52   - .eqIfPresent(IotOtaUpgradeRecordDO::getStatus, status));
53   - }
  48 + @Select("select count(case when status = 0 then 1 else 0) as `0` " +
  49 + "count(case when status = 1 then 1 else 0) as `1` " +
  50 + "count(case when status = 2 then 1 else 0) as `2` " +
  51 + "count(case when status = 3 then 1 else 0) as `3` " +
  52 + "count(case when status = 4 then 1 else 0) as `4` " +
  53 + "count(case when status = 5 then 1 else 0) as `5` " +
  54 + "from iot_ota_upgrade_record " +
  55 + "where task_id = #{taskId} " +
  56 + "and device_name like concat('%', #{deviceName}, '%') " +
  57 + "and status = #{status}")
  58 + List<Map<String, Object>> selectOtaUpgradeRecordCount(@Param("taskId") Long taskId,
  59 + @Param("deviceName") String deviceName);
54 60  
55 61 /**
56   - * 获取OTA升级记录的统计信息
  62 + * 根据固件ID查询OTA升级记录的状态统计信息。
  63 + * 该函数通过SQL查询统计不同状态(0到5)的记录数量,并返回一个包含统计结果的Map列表。
57 64 *
58   - * @param firmwareId 固件ID,用于筛选特定固件的升级记录
59   - * @param status 状态,用于筛选特定状态的升级记录
60   - * @return 返回符合条件的OTA升级记录的统计信息
  65 + * @param firmwareId 固件ID,用于筛选特定固件的OTA升级记录。
  66 + * @return 返回一个Map列表,每个Map包含不同状态(0到5)的记录数量。
61 67 */
62   - default Long getOtaUpgradeRecordStatistics(@Param("firmwareId") Long firmwareId,
63   - @Param("status") Integer status) {
64   - return selectCount(new LambdaQueryWrapperX<IotOtaUpgradeRecordDO>()
65   - .eqIfPresent(IotOtaUpgradeRecordDO::getFirmwareId, firmwareId)
66   - .eqIfPresent(IotOtaUpgradeRecordDO::getStatus, status));
67   - }
68   -
  68 + @Select("select count(case when status = 0 then 1 else 0) as `0` " +
  69 + "count(case when status = 1 then 1 else 0) as `1` " +
  70 + "count(case when status = 2 then 1 else 0) as `2` " +
  71 + "count(case when status = 3 then 1 else 0) as `3` " +
  72 + "count(case when status = 4 then 1 else 0) as `4` " +
  73 + "count(case when status = 5 then 1 else 0) as `5` " +
  74 + "from iot_ota_upgrade_record " +
  75 + "where firmware_id = #{firmwareId}")
  76 + List<Map<String, Object>> selectOtaUpgradeRecordStatistics(Long firmwareId);
69 77  
70 78 /**
71 79 * 根据分页查询条件获取IOT OTA升级记录的分页结果
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeRecordJob.java deleted
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 deleted
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/ota/IotOtaFirmwareService.java
... ... @@ -7,10 +7,10 @@ import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwa
7 7 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
8 8 import jakarta.validation.Valid;
9 9  
10   -// TODO @li:类、方法注释有点冗余,可以参考别的模块哈
11 10 /**
12   - * OTA固件管理服务接口
13   - * 提供OTA固件的创建、更新和查询等功能
  11 + * OTA 固件管理 Service
  12 + *
  13 + * @author Shelly Chan
14 14 */
15 15 public interface IotOtaFirmwareService {
16 16  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaFirmwareServiceImpl.java
1 1 package cn.iocoder.yudao.module.iot.service.ota;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.convert.Convert;
3 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 7 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareCreateReqVO;
6 8 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwarePageReqVO;
7 9 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareUpdateReqVO;
8 10 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
  11 +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
9 12 import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaFirmwareMapper;
  13 +import cn.iocoder.yudao.module.iot.service.product.IotProductService;
10 14 import jakarta.annotation.Resource;
11 15 import lombok.extern.slf4j.Slf4j;
  16 +import org.springframework.context.annotation.Lazy;
12 17 import org.springframework.stereotype.Service;
13 18 import org.springframework.validation.annotation.Validated;
14 19  
... ... @@ -26,23 +31,28 @@ public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService {
26 31  
27 32 @Resource
28 33 private IotOtaFirmwareMapper otaFirmwareMapper;
  34 + @Lazy
  35 + @Resource
  36 + private IotProductService productService;
29 37  
30 38 @Override
31 39 public Long createOtaFirmware(IotOtaFirmwareCreateReqVO saveReqVO) {
32 40 // 1. 校验固件产品 + 版本号不能重复
33   - // TODO @li:需要考虑设备也存在
34 41 validateProductAndVersionDuplicate(saveReqVO.getProductId(), saveReqVO.getVersion());
35   -
36   - // 2.转化数据格式,准备存储到数据库中
  42 + // 2.1.转化数据格式,准备存储到数据库中
37 43 IotOtaFirmwareDO firmware = BeanUtils.toBean(saveReqVO, IotOtaFirmwareDO.class);
  44 + // 2.2.查询ProductKey
  45 + IotProductDO product = productService.getProduct(Convert.toLong(firmware.getProductId()));
  46 + firmware.setProductKey(Objects.requireNonNull(product).getProductKey());
  47 + // TODO @芋艿: 附件、附件签名等属性的计算
  48 +
38 49 otaFirmwareMapper.insert(firmware);
39 50 return firmware.getId();
40 51 }
41 52  
42 53 @Override
43 54 public void updateOtaFirmware(IotOtaFirmwareUpdateReqVO updateReqVO) {
44   - // TODO @li:如果序号只有一个,直接写 1. 更好哈
45   - // 1.1. 校验存在
  55 + // 1. 校验存在
46 56 validateFirmwareExists(updateReqVO.getId());
47 57  
48 58 // 2. 更新数据
... ... @@ -84,8 +94,7 @@ public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService {
84 94 // 查询数据库中是否存在具有相同产品ID和版本号的固件信息
85 95 List<IotOtaFirmwareDO> list = otaFirmwareMapper.selectByProductIdAndVersion(productId, version);
86 96 // 如果查询结果非空且不为null,则抛出异常,提示固件信息已存在
87   - // TODO @li:使用 isNotEmpty 这种 方法,简化
88   - if (Objects.nonNull(list) && !list.isEmpty()) {
  97 + if (CollUtil.isNotEmpty(list)) {
89 98 throw exception(OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE);
90 99 }
91 100 }
... ...
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;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record.IotOtaUpgradeRecordPageReqVO;
5 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 6 import jakarta.validation.Valid;
9 7  
10 8 import java.util.List;
... ... @@ -16,25 +14,15 @@ import java.util.Map;
16 14 */
17 15 public interface IotOtaUpgradeRecordService {
18 16  
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 17 /**
33   - * 更新现有的 OTA 升级记录
  18 + * 批量创建OTA升级记录。
  19 + * 该函数用于为指定的设备列表、固件ID和升级任务ID创建OTA升级记录。
34 20 *
35   - * @param updateReqBO 包含更新升级记录所需信息的请求对象,必须经过验证。
  21 + * @param deviceIds 设备ID列表,表示需要升级的设备集合。
  22 + * @param firmwareId 固件ID,表示要升级到的固件版本。
  23 + * @param upgradeTaskId 升级任务ID,表示此次升级任务的唯一标识。
36 24 */
37   - void updateUpgradeRecord(@Valid IotOtaUpgradeRecordUpdateReqBO updateReqBO);
  25 + void createOtaUpgradeRecordBatch(List<Long> deviceIds, Long firmwareId, Long upgradeTaskId);
38 26  
39 27 /**
40 28 * 获取OTA升级记录的数量统计。
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeRecordServiceImpl.java
1 1 package cn.iocoder.yudao.module.iot.service.ota;
2 2  
  3 +import cn.hutool.core.convert.Convert;
3 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 5 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
6 6 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.record.IotOtaUpgradeRecordPageReqVO;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  8 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
7 9 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeRecordDO;
  10 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
8 11 import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaUpgradeRecordMapper;
9 12 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;
  13 +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
12 14 import jakarta.annotation.Resource;
13 15 import lombok.extern.slf4j.Slf4j;
  16 +import org.springframework.context.annotation.Lazy;
14 17 import org.springframework.stereotype.Service;
15 18 import org.springframework.transaction.annotation.Transactional;
16 19 import org.springframework.validation.annotation.Validated;
17 20  
18 21 import java.util.List;
19 22 import java.util.Map;
  23 +import java.util.Objects;
  24 +import java.util.stream.Collectors;
20 25  
21 26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
22 27 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
  28 +import static cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeRecordStatusEnum.CANCELED;
23 29  
24 30 @Slf4j
25 31 @Service
... ... @@ -28,27 +34,40 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
28 34  
29 35 @Resource
30 36 private IotOtaUpgradeRecordMapper upgradeRecordMapper;
  37 + @Lazy
  38 + @Resource
  39 + private IotDeviceService deviceService;
  40 + @Lazy
  41 + @Resource
  42 + private IotOtaFirmwareService firmwareService;
  43 + @Lazy
  44 + @Resource
  45 + private IotOtaUpgradeTaskService upgradeTaskService;
31 46  
32 47 @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   - }
  48 + public void createOtaUpgradeRecordBatch(List<Long> deviceIds, Long firmwareId, Long upgradeTaskId) {
  49 + // 1.校验升级记录信息是否存在,并且已经取消的任务可以重新开始
  50 + deviceIds.forEach(deviceId -> validateUpgradeRecordDuplicate(firmwareId, upgradeTaskId, String.valueOf(deviceId)));
  51 + // 2.初始化OTA升级记录列表信息
  52 + IotOtaUpgradeTaskDO upgradeTask = upgradeTaskService.getUpgradeTask(upgradeTaskId);
  53 + IotOtaFirmwareDO firmware = firmwareService.getOtaFirmware(firmwareId);
  54 + List<IotDeviceDO> deviceList = deviceService.getDeviceListByIdList(deviceIds);
  55 + List<IotOtaUpgradeRecordDO> upgradeRecordList = deviceList.stream().map(device -> {
  56 + IotOtaUpgradeRecordDO upgradeRecord = new IotOtaUpgradeRecordDO();
  57 + upgradeRecord.setFirmwareId(firmware.getId());
  58 + upgradeRecord.setTaskId(upgradeTask.getId());
  59 + upgradeRecord.setProductKey(device.getProductKey());
  60 + upgradeRecord.setDeviceName(device.getDeviceName());
  61 + upgradeRecord.setDeviceId(Convert.toStr(device.getId()));
  62 + upgradeRecord.setFromFirmwareId(Convert.toLong(device.getFirmwareId()));
  63 + upgradeRecord.setStatus(IotOtaUpgradeRecordStatusEnum.PENDING.getStatus());
  64 + upgradeRecord.setProgress(0);
  65 + return upgradeRecord;
  66 + }).toList();
  67 + // 3.保存数据
  68 + upgradeRecordMapper.insertBatch(upgradeRecordList);
  69 + // TODO @芋艿:在这里需要处理推送升级任务的逻辑
41 70  
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 71 }
53 72  
54 73 /**
... ... @@ -62,21 +81,14 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
62 81 @Transactional
63 82 public Map<Integer, Long> getOtaUpgradeRecordCount(IotOtaUpgradeRecordPageReqVO pageReqVO) {
64 83 // 分别查询不同状态的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);
  84 + List<Map<String, Object>> upgradeRecordCountList = upgradeRecordMapper.selectOtaUpgradeRecordCount(
  85 + pageReqVO.getTaskId(), pageReqVO.getDeviceName());
  86 + Map<String, Object> upgradeRecordCountMap = ObjectUtils.defaultIfNull(upgradeRecordCountList.get(0));
  87 + Objects.requireNonNull(upgradeRecordCountMap);
  88 + return upgradeRecordCountMap.entrySet().stream().collect(Collectors.toMap(
  89 + entry -> Convert.toInt(entry.getKey()),
  90 + entry -> Convert.toLong(entry.getValue())
  91 + ));
80 92 }
81 93  
82 94 /**
... ... @@ -90,20 +102,13 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
90 102 @Transactional
91 103 public Map<Integer, Long> getOtaUpgradeRecordStatistics(Long firmwareId) {
92 104 // 查询并统计不同状态的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);
  105 + List<Map<String, Object>> upgradeRecordStatisticsList = upgradeRecordMapper.selectOtaUpgradeRecordStatistics(firmwareId);
  106 + Map<String, Object> upgradeRecordStatisticsMap = ObjectUtils.defaultIfNull(upgradeRecordStatisticsList.get(0));
  107 + Objects.requireNonNull(upgradeRecordStatisticsMap);
  108 + return upgradeRecordStatisticsMap.entrySet().stream().collect(Collectors.toMap(
  109 + entry -> Convert.toInt(entry.getKey()),
  110 + entry -> Convert.toLong(entry.getValue())
  111 + ));
107 112 }
108 113  
109 114 @Override
... ... @@ -117,8 +122,6 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
117 122 upgradeRecordMapper.updateById(new IotOtaUpgradeRecordDO()
118 123 .setId(upgradeRecord.getId()).setProgress(0)
119 124 .setStatus(IotOtaUpgradeRecordStatusEnum.PENDING.getStatus()));
120   - // TODO @芋艿: 重试升级记录触发的其他Action
121   - // TODO 如果一个升级记录被取消或者已经执行失败,重试成功,是否会对升级任务的状态有影响?
122 125 }
123 126  
124 127 @Override
... ... @@ -135,7 +138,7 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
135 138 public void cancelUpgradeRecordByTaskId(Long taskId) {
136 139 // 暂定只有待推送的升级记录可以取消
137 140 upgradeRecordMapper.updateUpgradeRecordStatusByTaskIdAndStatus(
138   - IotOtaUpgradeRecordStatusEnum.CANCELED.getStatus(), taskId,
  141 + CANCELED.getStatus(), taskId,
139 142 IotOtaUpgradeRecordStatusEnum.PENDING.getStatus());
140 143 }
141 144  
... ... @@ -173,21 +176,23 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
173 176 }
174 177  
175 178 /**
176   - * 验证固件升级记录是否存在。
  179 + * 校验固件升级记录是否重复。
177 180 * <p>
178   - * 该函数通过给定的固件ID、任务ID和设备ID查询升级记录,如果查询结果为空,则抛出异常。
  181 + * 该函数用于检查给定的固件ID、任务ID和设备ID是否已经存在未取消的升级记录。
  182 + * 如果存在未取消的记录,则抛出异常,提示升级记录重复。
179 183 *
180 184 * @param firmwareId 固件ID,用于标识特定的固件版本
181 185 * @param taskId 任务ID,用于标识特定的升级任务
182 186 * @param deviceId 设备ID,用于标识特定的设备
183   - * @throws cn.iocoder.yudao.framework.common.exception.ServiceException,则抛出OTA_UPGRADE_RECORD_NOT_EXISTS异常
184 187 */
185 188 private void validateUpgradeRecordDuplicate(Long firmwareId, Long taskId, String deviceId) {
186 189 // 根据条件查询升级记录
187 190 IotOtaUpgradeRecordDO upgradeRecord = upgradeRecordMapper.selectByConditions(firmwareId, taskId, deviceId);
188   - // 如果查询结果为空,抛出异常
  191 + // 如果查询到升级记录且状态不是已取消,则抛出异常
189 192 if (upgradeRecord != null) {
190   - throw exception(OTA_UPGRADE_RECORD_DUPLICATE);
  193 + if (!CANCELED.getStatus().equals(upgradeRecord.getStatus())) {
  194 + throw exception(OTA_UPGRADE_RECORD_DUPLICATE);
  195 + }
191 196 }
192 197 }
193 198  
... ...
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;
8 8  
9 9 import java.util.List;
10 10  
11   -// TODO @li:类、方法注释有点冗余,可以参考别的模块哈
12 11 /**
13   - * IoT OTA升级任务服务接口
14   - * 提供OTA升级任务的创建、取消和查询功能
  12 + * IoT OTA升级任务 Service 接口
  13 + *
  14 + * @author Shelly Chan
15 15 */
16 16 public interface IotOtaUpgradeTaskService {
17 17  
... ...
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;
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.convert.Convert;
5 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 7 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task.IotOtaUpgradeTaskPageReqVO;
7 8 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.upgrade.task.IotOtaUpgradeTaskSaveReqVO;
8   -import cn.iocoder.yudao.module.iot.convert.ota.IotOtaUpgradeRecordConvert;
9 9 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
10 10 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
11 11 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaUpgradeTaskDO;
... ... @@ -13,7 +13,6 @@ import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaUpgradeTaskMapper;
13 13 import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskScopeEnum;
14 14 import cn.iocoder.yudao.module.iot.enums.ota.IotOtaUpgradeTaskStatusEnum;
15 15 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
16   -import cn.iocoder.yudao.module.iot.service.ota.bo.IotOtaUpgradeRecordCreateReqBO;
17 16 import jakarta.annotation.Resource;
18 17 import lombok.extern.slf4j.Slf4j;
19 18 import org.springframework.context.annotation.Lazy;
... ... @@ -23,6 +22,7 @@ import org.springframework.validation.annotation.Validated;
23 22  
24 23 import java.util.List;
25 24 import java.util.Objects;
  25 +import java.util.stream.Collectors;
26 26  
27 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
28 28 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
... ... @@ -52,21 +52,13 @@ public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
52 52 validateFirmwareTaskDuplicate(createReqVO.getFirmwareId(), createReqVO.getName());
53 53 // 1.2 校验固件信息是否存在
54 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   -
  55 + // 1.3 补全设备范围信息,并且校验是否又设备可以升级,如果没有设备可以升级,则报错
  56 + validateScopeAndDevice(createReqVO.getScope(), createReqVO.getDeviceIds(), firmware.getProductId());
60 57 // 2. 保存 OTA 升级任务信息到数据库
61   - IotOtaUpgradeTaskDO upgradeTask = initUpgradeTask(createReqVO, firmware.getProductId());
  58 + IotOtaUpgradeTaskDO upgradeTask = initOtaUpgradeTask(createReqVO, firmware.getProductId());
62 59 upgradeTaskMapper.insert(upgradeTask);
63   -
64 60 // 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
  61 + upgradeRecordService.createOtaUpgradeRecordBatch(upgradeTask.getDeviceIds(), firmware.getId(), upgradeTask.getId());
70 62 return upgradeTask.getId();
71 63 }
72 64  
... ... @@ -76,16 +68,17 @@ public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
76 68 // 1.1 校验升级任务是否存在
77 69 IotOtaUpgradeTaskDO upgradeTask = validateUpgradeTaskExists(id);
78 70 // 1.2 校验升级任务是否可以取消
79   - // TODO @li:这种一次性的,可以不考虑拆分方法
80   - validateUpgradeTaskCanCancel(upgradeTask);
81   -
  71 + // 检查升级任务的状态是否为进行中,只有此状态下的任务才允许取消
  72 + if (!Objects.equals(upgradeTask.getStatus(), IotOtaUpgradeTaskStatusEnum.IN_PROGRESS.getStatus())) {
  73 + // 只有进行中的任务才可以取消
  74 + throw exception(OTA_UPGRADE_TASK_CANNOT_CANCEL);
  75 + }
82 76 // 2. 更新 OTA 升级任务状态为已取消
83 77 upgradeTaskMapper.updateById(IotOtaUpgradeTaskDO.builder()
84 78 .id(id).status(IotOtaUpgradeTaskStatusEnum.CANCELED.getStatus())
85 79 .build());
86 80 // 3. 更新 OTA 升级记录状态为已取消
87 81 upgradeRecordService.cancelUpgradeRecordByTaskId(id);
88   - // TODO @芋艿: 取消任务触发的其他Action
89 82 }
90 83  
91 84 @Override
... ... @@ -131,19 +124,27 @@ public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
131 124 }
132 125  
133 126 /**
134   - * 验证升级任务的范围和设备参数是否有效
135   - * 当选择特定设备进行升级时,确保提供的设备ID和设备名称列表有效且对应
  127 + * 验证升级任务的范围和设备列表的有效性。
  128 + * <p>
  129 + * 根据升级任务的范围(scope),验证设备列表(deviceIds)或产品ID(productId)是否有效。
  130 + * 如果范围是“选择设备”(SELECT),则必须提供设备列表;如果范围是“所有设备”(ALL),则必须根据产品ID获取设备列表,并确保列表不为空。
136 131 *
137   - * @param scope 升级任务的范围,表示是选择特定设备还是其他范围
138   - * @param deviceIds 设备ID列表,用于标识参与升级的设备
139   - * @param deviceNames 设备名称列表,与设备ID列表对应
  132 + * @param scope 升级任务的范围,参考 IotOtaUpgradeTaskScopeEnum 枚举值
  133 + * @param deviceIds 设备ID列表,当范围为“选择设备”时,该列表不能为空
  134 + * @param productId 产品ID,当范围为“所有设备”时,用于获取设备列表
  135 + * @throws cn.iocoder.yudao.framework.common.exception.ServiceException,抛出相应的异常
140 136 */
141   - private void validateScopeAndDevice(Integer scope, List<Long> deviceIds, List<String> deviceNames) {
142   - // 当升级任务范围为选择特定设备时
  137 + private void validateScopeAndDevice(Integer scope, List<Long> deviceIds, String productId) {
  138 + // 验证范围为“选择设备”时,设备列表不能为空
143 139 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);
  140 + if (CollUtil.isEmpty(deviceIds)) {
  141 + throw exception(OTA_UPGRADE_TASK_DEVICE_IDS_EMPTY);
  142 + }
  143 + } else if (Objects.equals(scope, IotOtaUpgradeTaskScopeEnum.ALL.getScope())) {
  144 + // 验证范围为“所有设备”时,根据产品ID获取的设备列表不能为空
  145 + List<IotDeviceDO> deviceList = deviceService.getDeviceListByProductId(Convert.toLong(productId));
  146 + if (CollUtil.isEmpty(deviceList)) {
  147 + throw exception(OTA_UPGRADE_TASK_DEVICE_LIST_EMPTY);
147 148 }
148 149 }
149 150 }
... ... @@ -167,25 +168,6 @@ public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
167 168 }
168 169  
169 170 /**
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 171 * 初始化升级任务
190 172 * <p>
191 173 * 根据请求参数创建升级任务对象,并根据选择的范围初始化设备数量
... ... @@ -195,55 +177,29 @@ public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
195 177 * @param createReqVO 升级任务保存请求对象,包含创建升级任务所需的信息
196 178 * @return 返回初始化后的升级任务对象
197 179 */
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();
  180 + private IotOtaUpgradeTaskDO initOtaUpgradeTask(IotOtaUpgradeTaskSaveReqVO createReqVO, String productId) {
  181 + // 将请求参数转换为升级任务对象
  182 + IotOtaUpgradeTaskDO upgradeTask = BeanUtils.toBean(createReqVO, IotOtaUpgradeTaskDO.class);
  183 + // 初始化的时候,设置设备数量和状态
  184 + upgradeTask.setDeviceCount(Convert.toLong(CollUtil.size(createReqVO.getDeviceIds())))
  185 + .setStatus(IotOtaUpgradeTaskStatusEnum.IN_PROGRESS.getStatus());
211 186 // 如果选择全选,则需要查询设备数量
212 187 if (Objects.equals(createReqVO.getScope(), IotOtaUpgradeTaskScopeEnum.ALL.getScope())) {
213 188 // 根据产品ID查询设备数量
214   - Long deviceCount = deviceService.getDeviceCountByProductId(Convert.toLong(productId));
  189 + List<IotDeviceDO> deviceList = deviceService.getDeviceListByProductId(Convert.toLong(productId));
215 190 // 设置升级任务的设备数量
216   - upgradeTask.setDeviceCount(deviceCount);
  191 + upgradeTask.setDeviceCount((long) deviceList.size());
  192 + upgradeTask.setDeviceIds(
  193 + deviceList.stream().map(IotDeviceDO::getId).collect(Collectors.toList()));
  194 + upgradeTask.setDeviceNames(
  195 + deviceList.stream().map(IotDeviceDO::getDeviceName).collect(Collectors.toList()));
  196 + } else if (Objects.equals(createReqVO.getScope(), IotOtaUpgradeTaskScopeEnum.SELECT.getScope())) {
  197 + List<IotDeviceDO> deviceList = deviceService.getDeviceListByIdList(createReqVO.getDeviceIds());
  198 + upgradeTask.setDeviceNames(
  199 + deviceList.stream().map(IotDeviceDO::getDeviceName).collect(Collectors.toList()));
217 200 }
218 201 // 返回初始化后的升级任务对象
219 202 return upgradeTask;
220 203 }
221 204  
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 205 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/bo/IotOtaUpgradeRecordCreateReqBO.java deleted
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 deleted
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   -}