feat:【IoT 物联网】优化固件相关请求和响应对象,添加文件 URL 格式校验,更新固件 ID 类型为 Long
This commit is contained in:
@@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.hibernate.validator.constraints.URL;
|
||||
|
||||
@Schema(description = "管理后台 - IoT OTA 固件创建 Request VO")
|
||||
@Data
|
||||
@@ -22,17 +23,11 @@ public class IotOtaFirmwareCreateReqVO {
|
||||
|
||||
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@NotNull(message = "产品编号不能为空")
|
||||
private String productId;
|
||||
private Long productId;
|
||||
|
||||
@Schema(description = "签名方式", example = "MD5")
|
||||
// TODO @li:是不是必传哈
|
||||
private String signMethod;
|
||||
|
||||
@Schema(description = "固件文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/yudao-firmware.zip")
|
||||
@Schema(description = "固件文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.zip")
|
||||
@NotEmpty(message = "固件文件 URL 不能为空")
|
||||
@URL(message = "固件文件 URL 格式错误")
|
||||
private String fileUrl;
|
||||
|
||||
@Schema(description = "自定义信息,建议使用 JSON 格式", example = "{\"key1\":\"value1\",\"key2\":\"value2\"}")
|
||||
private String information;
|
||||
|
||||
}
|
||||
|
||||
@@ -3,21 +3,24 @@ package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageParam;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
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;
|
||||
|
||||
@Data
|
||||
@Schema(description = "管理后台 - IoT OTA 固件分页 Request VO")
|
||||
@Data
|
||||
public class IotOtaFirmwarePageReqVO extends PageParam {
|
||||
|
||||
/**
|
||||
* 固件名称
|
||||
*/
|
||||
@Schema(description = "固件名称", example = "智能开关固件")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 产品标识
|
||||
*/
|
||||
@Schema(description = "产品标识", example = "1024")
|
||||
private String productId;
|
||||
|
||||
@Schema(description = "创建时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
|
||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||
private LocalDateTime[] createTime;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,83 +1,46 @@
|
||||
package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware;
|
||||
|
||||
import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
|
||||
import com.fhs.core.trans.anno.Trans;
|
||||
import com.fhs.core.trans.constant.TransType;
|
||||
import com.fhs.core.trans.vo.VO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Schema(description = "管理后台 - IoT OTA 固件 Response VO")
|
||||
@Data
|
||||
public class IotOtaFirmwareRespVO implements VO {
|
||||
|
||||
/**
|
||||
* 固件编号
|
||||
*/
|
||||
@Schema(description = "固件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long id;
|
||||
/**
|
||||
* 固件名称
|
||||
*/
|
||||
@Schema(description = "固件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "OTA固件")
|
||||
|
||||
@Schema(description = "固件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "OTA 固件")
|
||||
private String name;
|
||||
/**
|
||||
* 固件描述
|
||||
*/
|
||||
|
||||
@Schema(description = "固件描述")
|
||||
private String description;
|
||||
/**
|
||||
* 版本号
|
||||
*/
|
||||
|
||||
@Schema(description = "版本号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0.0")
|
||||
private String version;
|
||||
|
||||
/**
|
||||
* 产品编号
|
||||
* <p>
|
||||
* 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
|
||||
*/
|
||||
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
@Trans(type = TransType.SIMPLE, target = IotProductDO.class, fields = {"name"}, refs = {"productName"})
|
||||
private String productId;
|
||||
/**
|
||||
* 产品标识
|
||||
* <p>
|
||||
* 冗余 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getProductKey()}
|
||||
*/
|
||||
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "iot-product-key")
|
||||
private String productKey;
|
||||
/**
|
||||
* 产品名称
|
||||
*/
|
||||
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "OTA产品")
|
||||
private String productName;
|
||||
/**
|
||||
* 签名方式
|
||||
* <p>
|
||||
* 例如说:MD5、SHA256
|
||||
*/
|
||||
@Schema(description = "签名方式", example = "MD5")
|
||||
private String signMethod;
|
||||
/**
|
||||
* 固件文件签名
|
||||
*/
|
||||
@Schema(description = "固件文件签名", example = "1024")
|
||||
private String fileSign;
|
||||
/**
|
||||
* 固件文件大小
|
||||
*/
|
||||
private Long productId;
|
||||
|
||||
@Schema(description = "固件文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/firmware.bin")
|
||||
private String fileUrl;
|
||||
|
||||
@Schema(description = "固件文件大小", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
|
||||
private Long fileSize;
|
||||
/**
|
||||
* 固件文件 URL
|
||||
*/
|
||||
@Schema(description = "固件文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn")
|
||||
private String fileUrl;
|
||||
/**
|
||||
* 自定义信息,建议使用 JSON 格式
|
||||
*/
|
||||
@Schema(description = "自定义信息,建议使用 JSON 格式")
|
||||
private String information;
|
||||
|
||||
@Schema(description = "固件文件签名算法", example = "MD5")
|
||||
private String fileDigestAlgorithm;
|
||||
|
||||
@Schema(description = "固件文件签名结果", example = "d41d8cd98f00b204e9800998ecf8427e")
|
||||
private String fileDigestValue;
|
||||
|
||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
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;
|
||||
|
||||
@@ -13,9 +12,7 @@ public class IotOtaFirmwareUpdateReqVO {
|
||||
@NotNull(message = "固件编号不能为空")
|
||||
private Long id;
|
||||
|
||||
// TODO @li:name 是不是可以飞必传哈
|
||||
@Schema(description = "固件名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "智能开关固件")
|
||||
@NotEmpty(message = "固件名称不能为空")
|
||||
@Schema(description = "固件名称", example = "智能开关固件")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "固件描述", example = "某品牌型号固件,测试用")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.iocoder.yudao.module.iot.dal.dataobject.ota;
|
||||
|
||||
import cn.hutool.crypto.digest.DigestAlgorithm;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@@ -34,7 +35,7 @@ public class IotOtaFirmwareDO extends BaseDO {
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 固件版本
|
||||
* 固件描述
|
||||
*/
|
||||
private String description;
|
||||
/**
|
||||
@@ -47,37 +48,25 @@ public class IotOtaFirmwareDO extends BaseDO {
|
||||
*
|
||||
* 关联 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getId()}
|
||||
*/
|
||||
// TODO @li:帮我改成 Long 哈,写错了
|
||||
private String productId;
|
||||
/**
|
||||
* 产品标识
|
||||
*
|
||||
* 冗余 {@link cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO#getProductKey()}
|
||||
*/
|
||||
private String productKey;
|
||||
private Long productId;
|
||||
|
||||
/**
|
||||
* 签名方式
|
||||
*
|
||||
* 例如说:MD5、SHA256
|
||||
* 固件文件 URL
|
||||
*/
|
||||
private String signMethod;
|
||||
/**
|
||||
* 固件文件签名
|
||||
*/
|
||||
private String fileSign;
|
||||
private String fileUrl;
|
||||
/**
|
||||
* 固件文件大小
|
||||
*/
|
||||
private Long fileSize;
|
||||
/**
|
||||
* 固件文件 URL
|
||||
* 固件文件签名算法
|
||||
*
|
||||
* 枚举 {@link DigestAlgorithm},目前只使用 MD5
|
||||
*/
|
||||
private String fileUrl;
|
||||
|
||||
private String fileDigestAlgorithm;
|
||||
/**
|
||||
* 自定义信息,建议使用 JSON 格式
|
||||
* 固件文件签名结果
|
||||
*/
|
||||
private String information;
|
||||
private String fileDigestValue;
|
||||
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
// TODO @li:参考 IotOtaUpgradeRecordMapper 的写法
|
||||
@Mapper
|
||||
public interface IotOtaFirmwareMapper extends BaseMapperX<IotOtaFirmwareDO> {
|
||||
|
||||
@@ -20,21 +19,16 @@ public interface IotOtaFirmwareMapper extends BaseMapperX<IotOtaFirmwareDO> {
|
||||
* @param version 固件版本号,用于筛选固件信息。
|
||||
* @return 返回符合条件的固件信息列表。
|
||||
*/
|
||||
default List<IotOtaFirmwareDO> selectByProductIdAndVersion(String productId, String version) {
|
||||
default List<IotOtaFirmwareDO> selectByProductIdAndVersion(Long productId, String version) {
|
||||
return selectList(IotOtaFirmwareDO::getProductId, productId,
|
||||
IotOtaFirmwareDO::getVersion, version);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询固件信息,支持根据名称和产品ID进行筛选,并按创建时间降序排列。
|
||||
*
|
||||
* @param pageReqVO 分页查询请求对象,包含分页参数和筛选条件。
|
||||
* @return 返回分页查询结果,包含符合条件的固件信息列表。
|
||||
*/
|
||||
default PageResult<IotOtaFirmwareDO> selectPage(IotOtaFirmwarePageReqVO pageReqVO) {
|
||||
return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaFirmwareDO>()
|
||||
.likeIfPresent(IotOtaFirmwareDO::getName, pageReqVO.getName())
|
||||
.eqIfPresent(IotOtaFirmwareDO::getProductId, pageReqVO.getProductId())
|
||||
.betweenIfPresent(IotOtaFirmwareDO::getCreateTime, pageReqVO.getCreateTime())
|
||||
.orderByDesc(IotOtaFirmwareDO::getCreateTime));
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ 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:注释写的有点冗余,可以看看别的模块哈。= = AI 生成的注释,有的时候太啰嗦了,需要处理下的哈
|
||||
/**
|
||||
* OTA 固件管理 Service
|
||||
*
|
||||
@@ -18,41 +17,39 @@ public interface IotOtaFirmwareService {
|
||||
/**
|
||||
* 创建 OTA 固件
|
||||
*
|
||||
* @param saveReqVO OTA固件保存请求对象,包含固件的相关信息
|
||||
* @return 返回新创建的固件的ID
|
||||
* @param saveReqVO 固件信息
|
||||
* @return 固件编号
|
||||
*/
|
||||
Long createOtaFirmware(@Valid IotOtaFirmwareCreateReqVO saveReqVO);
|
||||
|
||||
/**
|
||||
* 更新 OTA 固件信息
|
||||
*
|
||||
* @param updateReqVO OTA固件保存请求对象,包含需要更新的固件信息
|
||||
* @param updateReqVO 固件信息
|
||||
*/
|
||||
void updateOtaFirmware(@Valid IotOtaFirmwareUpdateReqVO updateReqVO);
|
||||
|
||||
/**
|
||||
* 根据 ID 获取 OTA 固件信息
|
||||
*
|
||||
* @param id OTA固件的唯一标识符
|
||||
* @return 返回OTA固件的详细信息对象
|
||||
* @param id OTA 固件编号
|
||||
* @return 固件信息
|
||||
*/
|
||||
IotOtaFirmwareDO getOtaFirmware(Long id);
|
||||
|
||||
/**
|
||||
* 分页查询 OTA 固件信息
|
||||
*
|
||||
* @param pageReqVO 包含分页查询条件的请求对象
|
||||
* @return 返回分页查询结果,包含固件信息列表和分页信息
|
||||
* @param pageReqVO 分页查询条件
|
||||
* @return 分页结果
|
||||
*/
|
||||
PageResult<IotOtaFirmwareDO> getOtaFirmwarePage(@Valid IotOtaFirmwarePageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 验证物联网 OTA 固件是否存在
|
||||
*
|
||||
* @param id 固件的唯一标识符
|
||||
* 该方法用于检查系统中是否存在与给定ID关联的物联网OTA固件信息
|
||||
* 主要目的是在进行固件更新操作前,确保目标固件已经存在并可以被访问
|
||||
* 如果固件不存在,该方法可能抛出异常或返回错误信息,具体行为未定义
|
||||
* @param id 物联网 OTA 固件编号
|
||||
* @return OTA 固件
|
||||
*/
|
||||
IotOtaFirmwareDO validateFirmwareExists(Long id);
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package cn.iocoder.yudao.module.iot.service.ota;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.crypto.digest.DigestAlgorithm;
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
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;
|
||||
@@ -17,8 +17,7 @@ import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_FIRMWARE_NOT_EXISTS;
|
||||
@@ -37,16 +36,20 @@ public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService {
|
||||
|
||||
@Override
|
||||
public Long createOtaFirmware(IotOtaFirmwareCreateReqVO saveReqVO) {
|
||||
// 1. 校验固件产品 + 版本号不能重复
|
||||
// 1.1 校验固件产品 + 版本号不能重复
|
||||
validateProductAndVersionDuplicate(saveReqVO.getProductId(), saveReqVO.getVersion());
|
||||
// 1.2 校验产品存在
|
||||
productService.validateProductExists(saveReqVO.getProductId());
|
||||
|
||||
// 2.1.转化数据格式,准备存储到数据库中
|
||||
// 2. 构建对象 + 存储
|
||||
IotOtaFirmwareDO firmware = BeanUtils.toBean(saveReqVO, IotOtaFirmwareDO.class);
|
||||
// 2.2.查询ProductKey
|
||||
// TODO @li:productService.getProduct(Convert.toLong(firmware.getProductId())) 放到 1. 后面,先做参考校验。逻辑两段:1)先参数校验;2)构建对象 + 存储
|
||||
IotProductDO product = productService.getProduct(Convert.toLong(firmware.getProductId()));
|
||||
firmware.setProductKey(Objects.requireNonNull(product).getProductKey());
|
||||
// TODO @芋艿: 附件、附件签名等属性的计算
|
||||
// 2.1 计算文件签名等属性
|
||||
try {
|
||||
calculateFileDigest(firmware);
|
||||
} catch (Exception e) {
|
||||
log.error("[createOtaFirmware][url({}) 计算文件签名失败]", firmware.getFileUrl(), e);
|
||||
throw new RuntimeException("计算文件签名失败: " + e.getMessage());
|
||||
}
|
||||
otaFirmwareMapper.insert(firmware);
|
||||
return firmware.getId();
|
||||
}
|
||||
@@ -80,25 +83,34 @@ public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService {
|
||||
return firmware;
|
||||
}
|
||||
|
||||
// TODO @li:注释有点冗余
|
||||
/**
|
||||
* 验证产品和版本号是否重复
|
||||
* <p>
|
||||
* 该方法用于确保在系统中不存在具有相同产品ID和版本号的固件条目
|
||||
* 它通过调用otaFirmwareMapper的selectByProductIdAndVersion方法来查询数据库中是否存在匹配的产品ID和版本号的固件信息
|
||||
* 如果查询结果非空且不为null,则抛出异常,提示固件信息已存在,从而避免数据重复
|
||||
*
|
||||
* @param productId 产品ID,用于数据库查询
|
||||
* @param version 版本号,用于数据库查询
|
||||
* @throws cn.iocoder.yudao.framework.common.exception.ServiceException,则抛出异常,提示固件信息已存在
|
||||
*/
|
||||
private void validateProductAndVersionDuplicate(String productId, String version) {
|
||||
// 查询数据库中是否存在具有相同产品ID和版本号的固件信息
|
||||
List<IotOtaFirmwareDO> list = otaFirmwareMapper.selectByProductIdAndVersion(productId, version);
|
||||
// 如果查询结果非空且不为null,则抛出异常,提示固件信息已存在
|
||||
if (CollUtil.isNotEmpty(list)) {
|
||||
private void validateProductAndVersionDuplicate(Long productId, String version) {
|
||||
// 只查询1条记录检查是否存在
|
||||
IotOtaFirmwareDO firmware = otaFirmwareMapper.selectOne(IotOtaFirmwareDO::getProductId, productId,
|
||||
IotOtaFirmwareDO::getVersion, version);
|
||||
if (firmware != null) {
|
||||
throw exception(OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算文件签名
|
||||
*
|
||||
* @param firmware 固件对象
|
||||
* @throws Exception 下载或计算签名失败时抛出异常
|
||||
*/
|
||||
private void calculateFileDigest(IotOtaFirmwareDO firmware) throws Exception {
|
||||
String fileUrl = firmware.getFileUrl();
|
||||
// 下载文件并计算签名
|
||||
byte[] fileBytes = HttpUtil.downloadBytes(fileUrl);
|
||||
// 设置文件大小
|
||||
firmware.setFileSize((long) fileBytes.length);
|
||||
// 计算 MD5 签名
|
||||
firmware.setFileDigestAlgorithm(DigestAlgorithm.MD5.getValue());
|
||||
String md5Hex = DigestUtil.digester(firmware.getFileDigestAlgorithm()).digestHex(new ByteArrayInputStream(fileBytes));
|
||||
firmware.setFileDigestValue(md5Hex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,24 +34,19 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
|
||||
|
||||
@Resource
|
||||
private IotOtaUpgradeRecordMapper upgradeRecordMapper;
|
||||
// TODO @li:1)@Resource 写在 @Lazy 之前,先用关键注解;2)有必要的情况下,在写 @Lazy 注解。
|
||||
@Lazy
|
||||
@Resource
|
||||
private IotDeviceService deviceService;
|
||||
@Lazy
|
||||
@Resource
|
||||
private IotOtaFirmwareService firmwareService;
|
||||
@Lazy
|
||||
@Resource
|
||||
private IotOtaUpgradeTaskService upgradeTaskService;
|
||||
|
||||
@Override
|
||||
public void createOtaUpgradeRecordBatch(List<Long> deviceIds, Long firmwareId, Long upgradeTaskId) {
|
||||
// 1. 校验升级记录信息是否存在,并且已经取消的任务可以重新开始
|
||||
// TODO @li:批量查询。。
|
||||
deviceIds.forEach(deviceId -> validateUpgradeRecordDuplicate(firmwareId, upgradeTaskId, String.valueOf(deviceId)));
|
||||
|
||||
// 2.初始化OTA升级记录列表信息
|
||||
// 2. 初始化OTA升级记录列表信息
|
||||
IotOtaUpgradeTaskDO upgradeTask = upgradeTaskService.getUpgradeTask(upgradeTaskId);
|
||||
IotOtaFirmwareDO firmware = firmwareService.getOtaFirmware(firmwareId);
|
||||
List<IotDeviceDO> deviceList = deviceService.getDeviceListByIdList(deviceIds);
|
||||
@@ -67,10 +62,9 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
|
||||
upgradeRecord.setProgress(0);
|
||||
return upgradeRecord;
|
||||
}).toList();
|
||||
// 3.保存数据
|
||||
// 3. 保存数据
|
||||
upgradeRecordMapper.insertBatch(upgradeRecordList);
|
||||
// TODO @芋艿:在这里需要处理推送升级任务的逻辑
|
||||
|
||||
}
|
||||
|
||||
// TODO @li:1)方法注释,简单写;2)父类写了注释,子类就不用写了。。。
|
||||
@@ -116,9 +110,9 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
|
||||
|
||||
@Override
|
||||
public void retryUpgradeRecord(Long id) {
|
||||
// 1.1.校验升级记录信息是否存在
|
||||
// 1.1 校验升级记录信息是否存在
|
||||
IotOtaUpgradeRecordDO upgradeRecord = validateUpgradeRecordExists(id);
|
||||
// 1.2.校验升级记录是否可以重新升级
|
||||
// 1.2 校验升级记录是否可以重新升级
|
||||
validateUpgradeRecordCanRetry(upgradeRecord);
|
||||
|
||||
// 2. 将一些数据重置,这样定时任务轮询就可以重启任务
|
||||
@@ -191,16 +185,12 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
|
||||
* @param deviceId 设备ID,用于标识特定的设备
|
||||
*/
|
||||
private void validateUpgradeRecordDuplicate(Long firmwareId, Long taskId, String deviceId) {
|
||||
// 根据条件查询升级记录
|
||||
IotOtaUpgradeRecordDO upgradeRecord = upgradeRecordMapper.selectByConditions(firmwareId, taskId, deviceId);
|
||||
// 如果查询到升级记录且状态不是已取消,则抛出异常
|
||||
// TODO @li:if return,减少括号层级;
|
||||
// TODO @li:ObjUtil.notEquals,尽量不用 !取否逻辑;
|
||||
if (upgradeRecord != null) {
|
||||
if (!IotOtaUpgradeRecordStatusEnum.CANCELED.getStatus().equals(upgradeRecord.getStatus())) {
|
||||
// TODO @li:提示的时候,需要把 deviceName 给提示出来,不然用户不知道哪个重复啦。
|
||||
throw exception(OTA_UPGRADE_RECORD_DUPLICATE);
|
||||
}
|
||||
if (upgradeRecord == null) {
|
||||
return;
|
||||
}
|
||||
if (!Objects.equals(upgradeRecord.getStatus(), IotOtaUpgradeRecordStatusEnum.CANCELED.getStatus())) {
|
||||
throw exception(OTA_UPGRADE_RECORD_DUPLICATE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,12 +206,10 @@ public class IotOtaUpgradeRecordServiceImpl implements IotOtaUpgradeRecordServic
|
||||
*/
|
||||
// TODO @li:这种一次性的方法(不复用的),其实一步一定要抽成小方法;
|
||||
private void validateUpgradeRecordCanRetry(IotOtaUpgradeRecordDO upgradeRecord) {
|
||||
// 检查升级记录的状态是否为 PENDING、PUSHED 或 UPGRADING
|
||||
if (ObjectUtils.equalsAny(upgradeRecord.getStatus(),
|
||||
IotOtaUpgradeRecordStatusEnum.PENDING.getStatus(),
|
||||
IotOtaUpgradeRecordStatusEnum.PUSHED.getStatus(),
|
||||
IotOtaUpgradeRecordStatusEnum.UPGRADING.getStatus())) {
|
||||
// 如果升级记录处于上述状态之一,则抛出异常,表示不允许重试
|
||||
throw exception(OTA_UPGRADE_RECORD_CANNOT_RETRY);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,10 +27,14 @@ 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.*;
|
||||
|
||||
// TODO @li:完善注释、注解顺序
|
||||
@Slf4j
|
||||
/**
|
||||
* IoT OTA升级任务 Service 实现类
|
||||
*
|
||||
* @author Shelly Chan
|
||||
*/
|
||||
@Service
|
||||
@Validated
|
||||
@Slf4j
|
||||
public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
|
||||
|
||||
@Resource
|
||||
@@ -105,102 +109,65 @@ public class IotOtaUpgradeTaskServiceImpl implements IotOtaUpgradeTaskService {
|
||||
upgradeTaskMapper.updateById(IotOtaUpgradeTaskDO.builder().id(id).status(status).build());
|
||||
}
|
||||
|
||||
// TODO @li:注释有点冗余
|
||||
/**
|
||||
* 校验固件升级任务是否重复
|
||||
* <p>
|
||||
* 该方法用于检查给定固件ID和任务名称组合是否已存在于数据库中,如果存在则抛出异常,
|
||||
* 表示任务名称对于该固件而言是重复的此检查确保用户不能创建具有相同名称的任务,
|
||||
* 从而避免数据重复和混淆
|
||||
*
|
||||
* @param firmwareId 固件的唯一标识符,用于区分不同的固件
|
||||
* @param taskName 升级任务的名称,用于与固件ID一起检查重复性
|
||||
* @throws cn.iocoder.yudao.framework.common.exception.ServerException 则抛出预定义的异常
|
||||
*/
|
||||
private void validateFirmwareTaskDuplicate(Long firmwareId, String taskName) {
|
||||
// 查询数据库中是否有相同固件ID和任务名称的升级任务存在
|
||||
List<IotOtaUpgradeTaskDO> upgradeTaskList = upgradeTaskMapper.selectByFirmwareIdAndName(firmwareId, taskName);
|
||||
// 如果查询结果不为空,说明存在重复的任务名称,抛出异常
|
||||
if (CollUtil.isNotEmpty(upgradeTaskList)) {
|
||||
throw exception(OTA_UPGRADE_TASK_NAME_DUPLICATE);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @li:注释有点冗余
|
||||
/**
|
||||
* 验证升级任务的范围和设备列表的有效性。
|
||||
* <p>
|
||||
* 根据升级任务的范围(scope),验证设备列表(deviceIds)或产品ID(productId)是否有效。
|
||||
* 如果范围是“选择设备”(SELECT),则必须提供设备列表;如果范围是“所有设备”(ALL),则必须根据产品ID获取设备列表,并确保列表不为空。
|
||||
*
|
||||
* @param scope 升级任务的范围,参考 IotOtaUpgradeTaskScopeEnum 枚举值
|
||||
* @param deviceIds 设备ID列表,当范围为“选择设备”时,该列表不能为空
|
||||
* @param productId 产品ID,当范围为“所有设备”时,用于获取设备列表
|
||||
* @param deviceIds 设备ID列表,当范围为"选择设备"时,该列表不能为空
|
||||
* @param productId 产品ID,当范围为"所有设备"时,用于获取设备列表
|
||||
* @throws cn.iocoder.yudao.framework.common.exception.ServiceException,抛出相应的异常
|
||||
*/
|
||||
private void validateScopeAndDevice(Integer scope, List<Long> deviceIds, String productId) {
|
||||
// TODO @li:if return
|
||||
// 验证范围为“选择设备”时,设备列表不能为空
|
||||
private void validateScopeAndDevice(Integer scope, List<Long> deviceIds, Long productId) {
|
||||
if (Objects.equals(scope, IotOtaUpgradeTaskScopeEnum.SELECT.getScope())) {
|
||||
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));
|
||||
return;
|
||||
}
|
||||
|
||||
if (Objects.equals(scope, IotOtaUpgradeTaskScopeEnum.ALL.getScope())) {
|
||||
List<IotDeviceDO> deviceList = deviceService.getDeviceListByProductId(productId);
|
||||
if (CollUtil.isEmpty(deviceList)) {
|
||||
throw exception(OTA_UPGRADE_TASK_DEVICE_LIST_EMPTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO @li:注释有点冗余
|
||||
/**
|
||||
* 验证升级任务是否存在
|
||||
* <p>
|
||||
* 通过查询数据库来验证给定ID的升级任务是否存在此方法主要用于确保后续操作所针对的升级任务是有效的
|
||||
*
|
||||
* @param id 升级任务的唯一标识符如果为null或数据库中不存在对应的记录,则认为任务不存在
|
||||
* @throws cn.iocoder.yudao.framework.common.exception.ServiceException 如果升级任务不存在,则抛出异常提示任务不存在
|
||||
*/
|
||||
private IotOtaUpgradeTaskDO validateUpgradeTaskExists(Long id) {
|
||||
// 查询数据库中是否有相同固件ID和任务名称的升级任务存在
|
||||
IotOtaUpgradeTaskDO upgradeTask = upgradeTaskMapper.selectById(id);
|
||||
// 如果查询结果不为空,说明存在重复的任务名称,抛出异常
|
||||
if (Objects.isNull(upgradeTask)) {
|
||||
throw exception(OTA_UPGRADE_TASK_NOT_EXISTS);
|
||||
}
|
||||
return upgradeTask;
|
||||
}
|
||||
|
||||
// TODO @li:注释有点冗余
|
||||
/**
|
||||
* 初始化升级任务
|
||||
* <p>
|
||||
* 根据请求参数创建升级任务对象,并根据选择的范围初始化设备数量
|
||||
* 如果选择特定设备进行升级,则设备数量为所选设备的总数
|
||||
* 如果选择全部设备进行升级,则设备数量为该固件对应产品下的所有设备总数
|
||||
*
|
||||
* @param createReqVO 升级任务保存请求对象,包含创建升级任务所需的信息
|
||||
* @return 返回初始化后的升级任务对象
|
||||
*/
|
||||
// TODO @li:一次性的方法,不用特别抽小方法
|
||||
private IotOtaUpgradeTaskDO initOtaUpgradeTask(IotOtaUpgradeTaskSaveReqVO createReqVO, String productId) {
|
||||
// 将请求参数转换为升级任务对象
|
||||
private IotOtaUpgradeTaskDO initOtaUpgradeTask(IotOtaUpgradeTaskSaveReqVO createReqVO, Long 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查询设备数量
|
||||
List<IotDeviceDO> deviceList = deviceService.getDeviceListByProductId(Convert.toLong(productId));
|
||||
// 设置升级任务的设备数量
|
||||
List<IotDeviceDO> deviceList = deviceService.getDeviceListByProductId(productId);
|
||||
upgradeTask.setDeviceCount((long) deviceList.size());
|
||||
upgradeTask.setDeviceIds(
|
||||
deviceList.stream().map(IotDeviceDO::getId).collect(Collectors.toList()));
|
||||
}
|
||||
// 返回初始化后的升级任务对象
|
||||
return upgradeTask;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,7 +70,6 @@ public class IotEmqxDownstreamHandler {
|
||||
private String buildTopicByMethod(IotDeviceMessage message, String productKey, String deviceName) {
|
||||
// 1. 判断是否为回复消息
|
||||
boolean isReply = IotDeviceMessageUtils.isReplyMessage(message);
|
||||
|
||||
// 2. 根据消息方法类型构建对应的主题
|
||||
return IotMqttTopicUtils.buildTopicByMethod(message.getMethod(), productKey, deviceName, isReply);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,11 @@ public final class IotMqttTopicUtils {
|
||||
*/
|
||||
private static final String SYS_TOPIC_PREFIX = "/sys/";
|
||||
|
||||
/**
|
||||
* 回复主题后缀
|
||||
*/
|
||||
private static final String REPLY_TOPIC_SUFFIX = "_reply";
|
||||
|
||||
// ========== MQTT HTTP 接口路径常量 ==========
|
||||
|
||||
/**
|
||||
@@ -48,15 +53,12 @@ public final class IotMqttTopicUtils {
|
||||
if (StrUtil.isBlank(method)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 1. 将点分隔符转换为斜杠
|
||||
String topicSuffix = method.replace('.', '/');
|
||||
|
||||
// 2. 对于回复消息,添加 _reply 后缀
|
||||
if (isReply) {
|
||||
topicSuffix += "_reply";
|
||||
topicSuffix += REPLY_TOPIC_SUFFIX;
|
||||
}
|
||||
|
||||
// 3. 构建完整主题
|
||||
return SYS_TOPIC_PREFIX + productKey + "/" + deviceName + "/" + topicSuffix;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user