Commit f9d782c701def246dede38b5e0b0a240ff16f4c3

Authored by YunaiV
1 parent 3ca4cf26

feat:【IoT 物联网】优化固件相关请求和响应对象,添加文件 URL 格式校验,更新固件 ID 类型为 Long

Showing 12 changed files with 131 additions and 225 deletions
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareCreateReqVO.java
... ... @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
4 4 import jakarta.validation.constraints.NotEmpty;
5 5 import jakarta.validation.constraints.NotNull;
6 6 import lombok.Data;
  7 +import org.hibernate.validator.constraints.URL;
7 8  
8 9 @Schema(description = "管理后台 - IoT OTA 固件创建 Request VO")
9 10 @Data
... ... @@ -22,17 +23,11 @@ public class IotOtaFirmwareCreateReqVO {
22 23  
23 24 @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
24 25 @NotNull(message = "产品编号不能为空")
25   - private String productId;
  26 + private Long productId;
26 27  
27   - @Schema(description = "签名方式", example = "MD5")
28   - // TODO @li:是不是必传哈
29   - private String signMethod;
30   -
31   - @Schema(description = "固件文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao-firmware.zip")
  28 + @Schema(description = "固件文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.zip")
32 29 @NotEmpty(message = "固件文件 URL 不能为空")
  30 + @URL(message = "固件文件 URL 格式错误")
33 31 private String fileUrl;
34 32  
35   - @Schema(description = "自定义信息,建议使用 JSON 格式", example = "{\"key1\":\"value1\",\"key2\":\"value2\"}")
36   - private String information;
37   -
38 33 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwarePageReqVO.java
... ... @@ -3,21 +3,24 @@ package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 4 import io.swagger.v3.oas.annotations.media.Schema;
5 5 import lombok.Data;
  6 +import org.springframework.format.annotation.DateTimeFormat;
  7 +
  8 +import java.time.LocalDateTime;
  9 +
  10 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
6 11  
7   -@Data
8 12 @Schema(description = "管理后台 - IoT OTA 固件分页 Request VO")
  13 +@Data
9 14 public class IotOtaFirmwarePageReqVO extends PageParam {
10 15  
11   - /**
12   - * 固件名称
13   - */
14 16 @Schema(description = "固件名称", example = "智能开关固件")
15 17 private String name;
16 18  
17   - /**
18   - * 产品标识
19   - */
20 19 @Schema(description = "产品标识", example = "1024")
21 20 private String productId;
22 21  
  22 + @Schema(description = "创建时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
  23 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  24 + private LocalDateTime[] createTime;
  25 +
23 26 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/vo/firmware/IotOtaFirmwareRespVO.java
1 1 package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
2 2  
3   -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
4   -import com.fhs.core.trans.anno.Trans;
5   -import com.fhs.core.trans.constant.TransType;
6 3 import com.fhs.core.trans.vo.VO;
7 4 import io.swagger.v3.oas.annotations.media.Schema;
8 5 import lombok.Data;
9 6  
10   -@Data
  7 +import java.time.LocalDateTime;
  8 +
11 9 @Schema(description = "管理后台 - IoT OTA 固件 Response VO")
  10 +@Data
12 11 public class IotOtaFirmwareRespVO implements VO {
13 12  
14   - /**
15   - * 固件编号
16   - */
17 13 @Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
18 14 private Long id;
19   - /**
20   - * 固件名称
21   - */
22   - @Schema(description = "固件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "OTA固件")
  15 +
  16 + @Schema(description = "固件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "OTA 固件")
23 17 private String name;
24   - /**
25   - * 固件描述
26   - */
  18 +
27 19 @Schema(description = "固件描述")
28 20 private String description;
29   - /**
30   - * 版本号
31   - */
  21 +
32 22 @Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0.0")
33 23 private String version;
34 24  
35   - /**
36   - * 产品编号
37   - * <p>
38   - * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
39   - */
40 25 @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
41   - @Trans(type = TransType.SIMPLE, target = IotProductDO.class, fields = {"name"}, refs = {"productName"})
42   - private String productId;
43   - /**
44   - * 产品标识
45   - * <p>
46   - * 冗余 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getProductKey()}
47   - */
48   - @Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "iot-product-key")
49   - private String productKey;
50   - /**
51   - * 产品名称
52   - */
53   - @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "OTA产品")
54   - private String productName;
55   - /**
56   - * 签名方式
57   - * <p>
58   - * 例如说:MD5、SHA256
59   - */
60   - @Schema(description = "签名方式", example = "MD5")
61   - private String signMethod;
62   - /**
63   - * 固件文件签名
64   - */
65   - @Schema(description = "固件文件签名", example = "1024")
66   - private String fileSign;
67   - /**
68   - * 固件文件大小
69   - */
  26 + private Long productId;
  27 +
  28 + @Schema(description = "固件文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/firmware.bin")
  29 + private String fileUrl;
  30 +
70 31 @Schema(description = "固件文件大小", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
71 32 private Long fileSize;
72   - /**
73   - * 固件文件 URL
74   - */
75   - @Schema(description = "固件文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
76   - private String fileUrl;
77   - /**
78   - * 自定义信息,建议使用 JSON 格式
79   - */
80   - @Schema(description = "自定义信息,建议使用 JSON 格式")
81   - private String information;
  33 +
  34 + @Schema(description = "固件文件签名算法", example = "MD5")
  35 + private String fileDigestAlgorithm;
  36 +
  37 + @Schema(description = "固件文件签名结果", example = "d41d8cd98f00b204e9800998ecf8427e")
  38 + private String fileDigestValue;
  39 +
  40 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  41 + private LocalDateTime createTime;
  42 +
  43 + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
  44 + private LocalDateTime updateTime;
82 45  
83 46 }
... ...
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;
5 4 import jakarta.validation.constraints.NotNull;
6 5 import lombok.Data;
7 6  
... ... @@ -13,9 +12,7 @@ public class IotOtaFirmwareUpdateReqVO {
13 12 @NotNull(message = "固件编号不能为空")
14 13 private Long id;
15 14  
16   - // TODO @li:name 是不是可以飞必传哈
17   - @Schema(description = "固件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "智能开关固件")
18   - @NotEmpty(message = "固件名称不能为空")
  15 + @Schema(description = "固件名称", example = "智能开关固件")
19 16 private String name;
20 17  
21 18 @Schema(description = "固件描述", example = "某品牌型号固件,测试用")
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaFirmwareDO.java
1 1 package cn.iocoder.yudao.module.iot.dal.dataobject.ota;
2 2  
  3 +import cn.hutool.crypto.digest.DigestAlgorithm;
3 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 5 import com.baomidou.mybatisplus.annotation.KeySequence;
5 6 import com.baomidou.mybatisplus.annotation.TableId;
... ... @@ -34,7 +35,7 @@ public class IotOtaFirmwareDO extends BaseDO {
34 35 */
35 36 private String name;
36 37 /**
37   - * 固件版本
  38 + * 固件描述
38 39 */
39 40 private String description;
40 41 /**
... ... @@ -47,37 +48,25 @@ public class IotOtaFirmwareDO extends BaseDO {
47 48 *
48 49 * 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
49 50 */
50   - // TODO @li:帮我改成 Long 哈,写错了
51   - private String productId;
52   - /**
53   - * 产品标识
54   - *
55   - * 冗余 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getProductKey()}
56   - */
57   - private String productKey;
  51 + private Long productId;
58 52  
59 53 /**
60   - * 签名方式
61   - *
62   - * 例如说:MD5、SHA256
63   - */
64   - private String signMethod;
65   - /**
66   - * 固件文件签名
  54 + * 固件文件 URL
67 55 */
68   - private String fileSign;
  56 + private String fileUrl;
69 57 /**
70 58 * 固件文件大小
71 59 */
72 60 private Long fileSize;
73 61 /**
74   - * 固件文件 URL
  62 + * 固件文件签名算法
  63 + *
  64 + * 枚举 {@link DigestAlgorithm},目前只使用 MD5
75 65 */
76   - private String fileUrl;
77   -
  66 + private String fileDigestAlgorithm;
78 67 /**
79   - * 自定义信息,建议使用 JSON 格式
  68 + * 固件文件签名结果
80 69 */
81   - private String information;
  70 + private String fileDigestValue;
82 71  
83 72 }
84 73 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaFirmwareMapper.java
... ... @@ -9,7 +9,6 @@ import org.apache.ibatis.annotations.Mapper;
9 9  
10 10 import java.util.List;
11 11  
12   -// TODO @li:参考 IotOtaUpgradeRecordMapper 的写法
13 12 @Mapper
14 13 public interface IotOtaFirmwareMapper extends BaseMapperX<IotOtaFirmwareDO> {
15 14  
... ... @@ -20,21 +19,16 @@ public interface IotOtaFirmwareMapper extends BaseMapperX&lt;IotOtaFirmwareDO&gt; {
20 19 * @param version 固件版本号,用于筛选固件信息。
21 20 * @return 返回符合条件的固件信息列表。
22 21 */
23   - default List<IotOtaFirmwareDO> selectByProductIdAndVersion(String productId, String version) {
  22 + default List<IotOtaFirmwareDO> selectByProductIdAndVersion(Long productId, String version) {
24 23 return selectList(IotOtaFirmwareDO::getProductId, productId,
25 24 IotOtaFirmwareDO::getVersion, version);
26 25 }
27 26  
28   - /**
29   - * 分页查询固件信息,支持根据名称和产品ID进行筛选,并按创建时间降序排列。
30   - *
31   - * @param pageReqVO 分页查询请求对象,包含分页参数和筛选条件。
32   - * @return 返回分页查询结果,包含符合条件的固件信息列表。
33   - */
34 27 default PageResult<IotOtaFirmwareDO> selectPage(IotOtaFirmwarePageReqVO pageReqVO) {
35 28 return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaFirmwareDO>()
36 29 .likeIfPresent(IotOtaFirmwareDO::getName, pageReqVO.getName())
37 30 .eqIfPresent(IotOtaFirmwareDO::getProductId, pageReqVO.getProductId())
  31 + .betweenIfPresent(IotOtaFirmwareDO::getCreateTime, pageReqVO.getCreateTime())
38 32 .orderByDesc(IotOtaFirmwareDO::getCreateTime));
39 33 }
40 34  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaFirmwareService.java
... ... @@ -7,7 +7,6 @@ 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:注释写的有点冗余,可以看看别的模块哈。= = AI 生成的注释,有的时候太啰嗦了,需要处理下的哈
11 10 /**
12 11 * OTA 固件管理 Service
13 12 *
... ... @@ -18,41 +17,39 @@ public interface IotOtaFirmwareService {
18 17 /**
19 18 * 创建 OTA 固件
20 19 *
21   - * @param saveReqVO OTA固件保存请求对象,包含固件的相关信息
22   - * @return 返回新创建的固件的ID
  20 + * @param saveReqVO 固件信息
  21 + * @return 固件编号
23 22 */
24 23 Long createOtaFirmware(@Valid IotOtaFirmwareCreateReqVO saveReqVO);
25 24  
26 25 /**
27 26 * 更新 OTA 固件信息
28 27 *
29   - * @param updateReqVO OTA固件保存请求对象,包含需要更新的固件信息
  28 + * @param updateReqVO 固件信息
30 29 */
31 30 void updateOtaFirmware(@Valid IotOtaFirmwareUpdateReqVO updateReqVO);
32 31  
33 32 /**
34 33 * 根据 ID 获取 OTA 固件信息
35 34 *
36   - * @param id OTA固件的唯一标识符
37   - * @return 返回OTA固件的详细信息对象
  35 + * @param id OTA 固件编号
  36 + * @return 固件信息
38 37 */
39 38 IotOtaFirmwareDO getOtaFirmware(Long id);
40 39  
41 40 /**
42 41 * 分页查询 OTA 固件信息
43 42 *
44   - * @param pageReqVO 包含分页查询条件的请求对象
45   - * @return 返回分页查询结果,包含固件信息列表和分页信息
  43 + * @param pageReqVO 分页查询条件
  44 + * @return 分页结果
46 45 */
47 46 PageResult<IotOtaFirmwareDO> getOtaFirmwarePage(@Valid IotOtaFirmwarePageReqVO pageReqVO);
48 47  
49 48 /**
50 49 * 验证物联网 OTA 固件是否存在
51 50 *
52   - * @param id 固件的唯一标识符
53   - * 该方法用于检查系统中是否存在与给定ID关联的物联网OTA固件信息
54   - * 主要目的是在进行固件更新操作前,确保目标固件已经存在并可以被访问
55   - * 如果固件不存在,该方法可能抛出异常或返回错误信息,具体行为未定义
  51 + * @param id 物联网 OTA 固件编号
  52 + * @return OTA 固件
56 53 */
57 54 IotOtaFirmwareDO validateFirmwareExists(Long id);
58 55  
... ...
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 +import cn.hutool.crypto.digest.DigestAlgorithm;
  4 +import cn.hutool.crypto.digest.DigestUtil;
  5 +import cn.hutool.http.HttpUtil;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 8 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareCreateReqVO;
8 9 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwarePageReqVO;
9 10 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwareUpdateReqVO;
10 11 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
11   -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
12 12 import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaFirmwareMapper;
13 13 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
14 14 import jakarta.annotation.Resource;
... ... @@ -17,8 +17,7 @@ import org.springframework.context.annotation.Lazy;
17 17 import org.springframework.stereotype.Service;
18 18 import org.springframework.validation.annotation.Validated;
19 19  
20   -import java.util.List;
21   -import java.util.Objects;
  20 +import java.io.ByteArrayInputStream;
22 21  
23 22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
24 23 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_FIRMWARE_NOT_EXISTS;
... ... @@ -37,16 +36,20 @@ public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService {
37 36  
38 37 @Override
39 38 public Long createOtaFirmware(IotOtaFirmwareCreateReqVO saveReqVO) {
40   - // 1. 校验固件产品 + 版本号不能重复
  39 + // 1.1 校验固件产品 + 版本号不能重复
41 40 validateProductAndVersionDuplicate(saveReqVO.getProductId(), saveReqVO.getVersion());
  41 + // 1.2 校验产品存在
  42 + productService.validateProductExists(saveReqVO.getProductId());
42 43  
43   - // 2.1.转化数据格式,准备存储到数据库中
  44 + // 2. 构建对象 + 存储
44 45 IotOtaFirmwareDO firmware = BeanUtils.toBean(saveReqVO, IotOtaFirmwareDO.class);
45   - // 2.2.查询ProductKey
46   - // TODO @li:productService.getProduct(Convert.toLong(firmware.getProductId())) 放到 1. 后面,先做参考校验。逻辑两段:1)先参数校验;2)构建对象 + 存储
47   - IotProductDO product = productService.getProduct(Convert.toLong(firmware.getProductId()));
48   - firmware.setProductKey(Objects.requireNonNull(product).getProductKey());
49   - // TODO @芋艿: 附件、附件签名等属性的计算
  46 + // 2.1 计算文件签名等属性
  47 + try {
  48 + calculateFileDigest(firmware);
  49 + } catch (Exception e) {
  50 + log.error("[createOtaFirmware][url({}) 计算文件签名失败]", firmware.getFileUrl(), e);
  51 + throw new RuntimeException("计算文件签名失败: " + e.getMessage());
  52 + }
50 53 otaFirmwareMapper.insert(firmware);
51 54 return firmware.getId();
52 55 }
... ... @@ -80,25 +83,34 @@ public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService {
80 83 return firmware;
81 84 }
82 85  
83   - // TODO @li:注释有点冗余
84 86 /**
85 87 * 验证产品和版本号是否重复
86   - * <p>
87   - * 该方法用于确保在系统中不存在具有相同产品ID和版本号的固件条目
88   - * 它通过调用otaFirmwareMapper的selectByProductIdAndVersion方法来查询数据库中是否存在匹配的产品ID和版本号的固件信息
89   - * 如果查询结果非空且不为null,则抛出异常,提示固件信息已存在,从而避免数据重复
90   - *
91   - * @param productId 产品ID,用于数据库查询
92   - * @param version 版本号,用于数据库查询
93   - * @throws cn.iocoder.yudao.framework.common.exception.ServiceException,则抛出异常,提示固件信息已存在
94 88 */
95   - private void validateProductAndVersionDuplicate(String productId, String version) {
96   - // 查询数据库中是否存在具有相同产品ID和版本号的固件信息
97   - List<IotOtaFirmwareDO> list = otaFirmwareMapper.selectByProductIdAndVersion(productId, version);
98   - // 如果查询结果非空且不为null,则抛出异常,提示固件信息已存在
99   - if (CollUtil.isNotEmpty(list)) {
  89 + private void validateProductAndVersionDuplicate(Long productId, String version) {
  90 + // 只查询1条记录检查是否存在
  91 + IotOtaFirmwareDO firmware = otaFirmwareMapper.selectOne(IotOtaFirmwareDO::getProductId, productId,
  92 + IotOtaFirmwareDO::getVersion, version);
  93 + if (firmware != null) {
100 94 throw exception(OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE);
101 95 }
102 96 }
103 97  
  98 + /**
  99 + * 计算文件签名
  100 + *
  101 + * @param firmware 固件对象
  102 + * @throws Exception 下载或计算签名失败时抛出异常
  103 + */
  104 + private void calculateFileDigest(IotOtaFirmwareDO firmware) throws Exception {
  105 + String fileUrl = firmware.getFileUrl();
  106 + // 下载文件并计算签名
  107 + byte[] fileBytes = HttpUtil.downloadBytes(fileUrl);
  108 + // 设置文件大小
  109 + firmware.setFileSize((long) fileBytes.length);
  110 + // 计算 MD5 签名
  111 + firmware.setFileDigestAlgorithm(DigestAlgorithm.MD5.getValue());
  112 + String md5Hex = DigestUtil.digester(firmware.getFileDigestAlgorithm()).digestHex(new ByteArrayInputStream(fileBytes));
  113 + firmware.setFileDigestValue(md5Hex);
  114 + }
  115 +
104 116 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeRecordServiceImpl.java
... ... @@ -34,24 +34,19 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
34 34  
35 35 @Resource
36 36 private IotOtaUpgradeRecordMapper upgradeRecordMapper;
37   - // TODO @li:1)@Resource 写在 @Lazy 之前,先用关键注解;2)有必要的情况下,在写 @Lazy 注解。
38   - @Lazy
39 37 @Resource
40 38 private IotDeviceService deviceService;
41   - @Lazy
42 39 @Resource
43 40 private IotOtaFirmwareService firmwareService;
44   - @Lazy
45 41 @Resource
46 42 private IotOtaUpgradeTaskService upgradeTaskService;
47 43  
48 44 @Override
49 45 public void createOtaUpgradeRecordBatch(List<Long> deviceIds, Long firmwareId, Long upgradeTaskId) {
50 46 // 1. 校验升级记录信息是否存在,并且已经取消的任务可以重新开始
51   - // TODO @li:批量查询。。
52 47 deviceIds.forEach(deviceId -> validateUpgradeRecordDuplicate(firmwareId, upgradeTaskId, String.valueOf(deviceId)));
53 48  
54   - // 2.初始化OTA升级记录列表信息
  49 + // 2. 初始化OTA升级记录列表信息
55 50 IotOtaUpgradeTaskDO upgradeTask = upgradeTaskService.getUpgradeTask(upgradeTaskId);
56 51 IotOtaFirmwareDO firmware = firmwareService.getOtaFirmware(firmwareId);
57 52 List<IotDeviceDO> deviceList = deviceService.getDeviceListByIdList(deviceIds);
... ... @@ -67,10 +62,9 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
67 62 upgradeRecord.setProgress(0);
68 63 return upgradeRecord;
69 64 }).toList();
70   - // 3.保存数据
  65 + // 3. 保存数据
71 66 upgradeRecordMapper.insertBatch(upgradeRecordList);
72 67 // TODO @芋艿:在这里需要处理推送升级任务的逻辑
73   -
74 68 }
75 69  
76 70 // TODO @li:1)方法注释,简单写;2)父类写了注释,子类就不用写了。。。
... ... @@ -116,9 +110,9 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
116 110  
117 111 @Override
118 112 public void retryUpgradeRecord(Long id) {
119   - // 1.1.校验升级记录信息是否存在
  113 + // 1.1 校验升级记录信息是否存在
120 114 IotOtaUpgradeRecordDO upgradeRecord = validateUpgradeRecordExists(id);
121   - // 1.2.校验升级记录是否可以重新升级
  115 + // 1.2 校验升级记录是否可以重新升级
122 116 validateUpgradeRecordCanRetry(upgradeRecord);
123 117  
124 118 // 2. 将一些数据重置,这样定时任务轮询就可以重启任务
... ... @@ -191,16 +185,12 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
191 185 * @param deviceId 设备ID,用于标识特定的设备
192 186 */
193 187 private void validateUpgradeRecordDuplicate(Long firmwareId, Long taskId, String deviceId) {
194   - // 根据条件查询升级记录
195 188 IotOtaUpgradeRecordDO upgradeRecord = upgradeRecordMapper.selectByConditions(firmwareId, taskId, deviceId);
196   - // 如果查询到升级记录且状态不是已取消,则抛出异常
197   - // TODO @li:if return,减少括号层级;
198   - // TODO @li:ObjUtil.notEquals,尽量不用 !取否逻辑;
199   - if (upgradeRecord != null) {
200   - if (!IotOtaUpgradeRecordStatusEnum.CANCELED.getStatus().equals(upgradeRecord.getStatus())) {
201   - // TODO @li:提示的时候,需要把 deviceName 给提示出来,不然用户不知道哪个重复啦。
202   - throw exception(OTA_UPGRADE_RECORD_DUPLICATE);
203   - }
  189 + if (upgradeRecord == null) {
  190 + return;
  191 + }
  192 + if (!Objects.equals(upgradeRecord.getStatus(), IotOtaUpgradeRecordStatusEnum.CANCELED.getStatus())) {
  193 + throw exception(OTA_UPGRADE_RECORD_DUPLICATE);
204 194 }
205 195 }
206 196  
... ... @@ -216,12 +206,10 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
216 206 */
217 207 // TODO @li:这种一次性的方法(不复用的),其实一步一定要抽成小方法;
218 208 private void validateUpgradeRecordCanRetry(IotOtaUpgradeRecordDO upgradeRecord) {
219   - // 检查升级记录的状态是否为 PENDING、PUSHED 或 UPGRADING
220 209 if (ObjectUtils.equalsAny(upgradeRecord.getStatus(),
221 210 IotOtaUpgradeRecordStatusEnum.PENDING.getStatus(),
222 211 IotOtaUpgradeRecordStatusEnum.PUSHED.getStatus(),
223 212 IotOtaUpgradeRecordStatusEnum.UPGRADING.getStatus())) {
224   - // 如果升级记录处于上述状态之一,则抛出异常,表示不允许重试
225 213 throw exception(OTA_UPGRADE_RECORD_CANNOT_RETRY);
226 214 }
227 215 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaUpgradeTaskServiceImpl.java
... ... @@ -27,10 +27,14 @@ import java.util.stream.Collectors;
27 27 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
28 28 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
29 29  
30   -// TODO @li:完善注释、注解顺序
31   -@Slf4j
  30 +/**
  31 + * IoT OTA升级任务 Service 实现类
  32 + *
  33 + * @author Shelly Chan
  34 + */
32 35 @Service
33 36 @Validated
  37 +@Slf4j
34 38 public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
35 39  
36 40 @Resource
... ... @@ -105,102 +109,65 @@ public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
105 109 upgradeTaskMapper.updateById(IotOtaUpgradeTaskDO.builder().id(id).status(status).build());
106 110 }
107 111  
108   - // TODO @li:注释有点冗余
109 112 /**
110 113 * 校验固件升级任务是否重复
111   - * <p>
112   - * 该方法用于检查给定固件ID和任务名称组合是否已存在于数据库中,如果存在则抛出异常,
113   - * 表示任务名称对于该固件而言是重复的此检查确保用户不能创建具有相同名称的任务,
114   - * 从而避免数据重复和混淆
115   - *
116   - * @param firmwareId 固件的唯一标识符,用于区分不同的固件
117   - * @param taskName 升级任务的名称,用于与固件ID一起检查重复性
118   - * @throws cn.iocoder.yudao.framework.common.exception.ServerException 则抛出预定义的异常
119 114 */
120 115 private void validateFirmwareTaskDuplicate(Long firmwareId, String taskName) {
121   - // 查询数据库中是否有相同固件ID和任务名称的升级任务存在
122 116 List<IotOtaUpgradeTaskDO> upgradeTaskList = upgradeTaskMapper.selectByFirmwareIdAndName(firmwareId, taskName);
123   - // 如果查询结果不为空,说明存在重复的任务名称,抛出异常
124 117 if (CollUtil.isNotEmpty(upgradeTaskList)) {
125 118 throw exception(OTA_UPGRADE_TASK_NAME_DUPLICATE);
126 119 }
127 120 }
128 121  
129   - // TODO @li:注释有点冗余
130 122 /**
131 123 * 验证升级任务的范围和设备列表的有效性。
132   - * <p>
133   - * 根据升级任务的范围(scope),验证设备列表(deviceIds)或产品ID(productId)是否有效。
134   - * 如果范围是“选择设备”(SELECT),则必须提供设备列表;如果范围是“所有设备”(ALL),则必须根据产品ID获取设备列表,并确保列表不为空。
135 124 *
136 125 * @param scope 升级任务的范围,参考 IotOtaUpgradeTaskScopeEnum 枚举值
137   - * @param deviceIds 设备ID列表,当范围为“选择设备”时,该列表不能为空
138   - * @param productId 产品ID,当范围为“所有设备”时,用于获取设备列表
  126 + * @param deviceIds 设备ID列表,当范围为"选择设备"时,该列表不能为空
  127 + * @param productId 产品ID,当范围为"所有设备"时,用于获取设备列表
139 128 * @throws cn.iocoder.yudao.framework.common.exception.ServiceException,抛出相应的异常
140 129 */
141   - private void validateScopeAndDevice(Integer scope, List<Long> deviceIds, String productId) {
142   - // TODO @li:if return
143   - // 验证范围为“选择设备”时,设备列表不能为空
  130 + private void validateScopeAndDevice(Integer scope, List<Long> deviceIds, Long productId) {
144 131 if (Objects.equals(scope, IotOtaUpgradeTaskScopeEnum.SELECT.getScope())) {
145 132 if (CollUtil.isEmpty(deviceIds)) {
146 133 throw exception(OTA_UPGRADE_TASK_DEVICE_IDS_EMPTY);
147 134 }
148   - } else if (Objects.equals(scope, IotOtaUpgradeTaskScopeEnum.ALL.getScope())) {
149   - // 验证范围为“所有设备”时,根据产品ID获取的设备列表不能为空
150   - List<IotDeviceDO> deviceList = deviceService.getDeviceListByProductId(Convert.toLong(productId));
  135 + return;
  136 + }
  137 +
  138 + if (Objects.equals(scope, IotOtaUpgradeTaskScopeEnum.ALL.getScope())) {
  139 + List<IotDeviceDO> deviceList = deviceService.getDeviceListByProductId(productId);
151 140 if (CollUtil.isEmpty(deviceList)) {
152 141 throw exception(OTA_UPGRADE_TASK_DEVICE_LIST_EMPTY);
153 142 }
154 143 }
155 144 }
156 145  
157   - // TODO @li:注释有点冗余
158 146 /**
159 147 * 验证升级任务是否存在
160   - * <p>
161   - * 通过查询数据库来验证给定ID的升级任务是否存在此方法主要用于确保后续操作所针对的升级任务是有效的
162   - *
163   - * @param id 升级任务的唯一标识符如果为null或数据库中不存在对应的记录,则认为任务不存在
164   - * @throws cn.iocoder.yudao.framework.common.exception.ServiceException 如果升级任务不存在,则抛出异常提示任务不存在
165 148 */
166 149 private IotOtaUpgradeTaskDO validateUpgradeTaskExists(Long id) {
167   - // 查询数据库中是否有相同固件ID和任务名称的升级任务存在
168 150 IotOtaUpgradeTaskDO upgradeTask = upgradeTaskMapper.selectById(id);
169   - // 如果查询结果不为空,说明存在重复的任务名称,抛出异常
170 151 if (Objects.isNull(upgradeTask)) {
171 152 throw exception(OTA_UPGRADE_TASK_NOT_EXISTS);
172 153 }
173 154 return upgradeTask;
174 155 }
175 156  
176   - // TODO @li:注释有点冗余
177 157 /**
178 158 * 初始化升级任务
179   - * <p>
180   - * 根据请求参数创建升级任务对象,并根据选择的范围初始化设备数量
181   - * 如果选择特定设备进行升级,则设备数量为所选设备的总数
182   - * 如果选择全部设备进行升级,则设备数量为该固件对应产品下的所有设备总数
183   - *
184   - * @param createReqVO 升级任务保存请求对象,包含创建升级任务所需的信息
185   - * @return 返回初始化后的升级任务对象
186 159 */
187   - // TODO @li:一次性的方法,不用特别抽小方法
188   - private IotOtaUpgradeTaskDO initOtaUpgradeTask(IotOtaUpgradeTaskSaveReqVO createReqVO, String productId) {
189   - // 将请求参数转换为升级任务对象
  160 + private IotOtaUpgradeTaskDO initOtaUpgradeTask(IotOtaUpgradeTaskSaveReqVO createReqVO, Long productId) {
190 161 IotOtaUpgradeTaskDO upgradeTask = BeanUtils.toBean(createReqVO, IotOtaUpgradeTaskDO.class);
191   - // 初始化的时候,设置设备数量和状态
192 162 upgradeTask.setDeviceCount(Convert.toLong(CollUtil.size(createReqVO.getDeviceIds())))
193 163 .setStatus(IotOtaUpgradeTaskStatusEnum.IN_PROGRESS.getStatus());
194   - // 如果选择全选,则需要查询设备数量
  164 +
195 165 if (Objects.equals(createReqVO.getScope(), IotOtaUpgradeTaskScopeEnum.ALL.getScope())) {
196   - // 根据产品ID查询设备数量
197   - List<IotDeviceDO> deviceList = deviceService.getDeviceListByProductId(Convert.toLong(productId));
198   - // 设置升级任务的设备数量
  166 + List<IotDeviceDO> deviceList = deviceService.getDeviceListByProductId(productId);
199 167 upgradeTask.setDeviceCount((long) deviceList.size());
200 168 upgradeTask.setDeviceIds(
201 169 deviceList.stream().map(IotDeviceDO::getId).collect(Collectors.toList()));
202 170 }
203   - // 返回初始化后的升级任务对象
204 171 return upgradeTask;
205 172 }
206 173  
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/router/IotEmqxDownstreamHandler.java
... ... @@ -70,7 +70,6 @@ public class IotEmqxDownstreamHandler {
70 70 private String buildTopicByMethod(IotDeviceMessage message, String productKey, String deviceName) {
71 71 // 1. 判断是否为回复消息
72 72 boolean isReply = IotDeviceMessageUtils.isReplyMessage(message);
73   -
74 73 // 2. 根据消息方法类型构建对应的主题
75 74 return IotMqttTopicUtils.buildTopicByMethod(message.getMethod(), productKey, deviceName, isReply);
76 75 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/util/IotMqttTopicUtils.java
... ... @@ -18,6 +18,11 @@ public final class IotMqttTopicUtils {
18 18 */
19 19 private static final String SYS_TOPIC_PREFIX = "/sys/";
20 20  
  21 + /**
  22 + * 回复主题后缀
  23 + */
  24 + private static final String REPLY_TOPIC_SUFFIX = "_reply";
  25 +
21 26 // ========== MQTT HTTP 接口路径常量 ==========
22 27  
23 28 /**
... ... @@ -48,15 +53,12 @@ public final class IotMqttTopicUtils {
48 53 if (StrUtil.isBlank(method)) {
49 54 return null;
50 55 }
51   -
52 56 // 1. 将点分隔符转换为斜杠
53 57 String topicSuffix = method.replace('.', '/');
54   -
55 58 // 2. 对于回复消息,添加 _reply 后缀
56 59 if (isReply) {
57   - topicSuffix += "_reply";
  60 + topicSuffix += REPLY_TOPIC_SUFFIX;
58 61 }
59   -
60 62 // 3. 构建完整主题
61 63 return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + "/" + topicSuffix;
62 64 }
... ...