feat:【IoT 物联网】增加 OTA 升级任务的实现

This commit is contained in:
YunaiV 2025-07-01 22:10:29 +08:00
parent 4749d93d0e
commit ecf6a4a846
23 changed files with 278 additions and 449 deletions

View File

@ -9,12 +9,16 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService; import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map; import java.util.Map;
@ -22,31 +26,29 @@ import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - IoT OTA 升级任务记录") @Tag(name = "管理后台 - IoT OTA 升级任务记录")
@RestController @RestController
@RequestMapping("/iot/ota/task-record") @RequestMapping("/iot/ota/task/record")
@Validated @Validated
public class IotOtaTaskRecordController { public class IotOtaTaskRecordController {
@Resource @Resource
private IotOtaTaskRecordService otaTaskRecordService; private IotOtaTaskRecordService otaTaskRecordService;
@GetMapping("/get-statistics") @GetMapping("/get-status-count")
@Operation(summary = "固件升级设备统计") @Operation(summary = "获得 OTA 升级记录状态统计")
@Parameters({
@Parameter(name = "firmwareId", description = "固件编号", example = "1024"),
@Parameter(name = "taskId", description = "升级任务编号", example = "2048")
})
@PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')") @PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')")
@Parameter(name = "firmwareId", description = "固件编号", required = true, example = "1024") public CommonResult<Map<Integer, Long>> getOtaTaskRecordStatusCountMap(
public CommonResult<Map<Integer, Long>> getOtaTaskRecordStatistics(@RequestParam(value = "firmwareId") Long firmwareId) { @RequestParam(value = "firmwareId", required = false) Long firmwareId,
return success(otaTaskRecordService.getOtaTaskRecordStatistics(firmwareId)); @RequestParam(value = "taskId", required = false) Long taskId) {
} return success(otaTaskRecordService.getOtaTaskRecordStatusCountMap(firmwareId, taskId));
@GetMapping("/get-count")
@Operation(summary = "获得升级记录分页 tab 数量")
@PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')")
public CommonResult<Map<Integer, Long>> getOtaTaskRecordCount(@Valid IotOtaTaskRecordPageReqVO pageReqVO) {
return success(otaTaskRecordService.getOtaTaskRecordCount(pageReqVO));
} }
@GetMapping("/page") @GetMapping("/page")
@Operation(summary = "获得升级记录分页") @Operation(summary = "获得 OTA 升级记录分页")
@PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:query')") @PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')")
public CommonResult<PageResult<IotOtaTaskRecordRespVO>> getOtaTaskRecordPage( public CommonResult<PageResult<IotOtaTaskRecordRespVO>> getOtaTaskRecordPage(
@Valid IotOtaTaskRecordPageReqVO pageReqVO) { @Valid IotOtaTaskRecordPageReqVO pageReqVO) {
PageResult<IotOtaTaskRecordDO> pageResult = otaTaskRecordService.getOtaTaskRecordPage(pageReqVO); PageResult<IotOtaTaskRecordDO> pageResult = otaTaskRecordService.getOtaTaskRecordPage(pageReqVO);
@ -54,8 +56,8 @@ public class IotOtaTaskRecordController {
} }
@GetMapping("/get") @GetMapping("/get")
@Operation(summary = "获得升级记录") @Operation(summary = "获得 OTA 升级记录")
@PreAuthorize("@ss.hasPermission('iot:ota-upgrade-record:query')") @PreAuthorize("@ss.hasPermission('iot:ota-task-record:query')")
@Parameter(name = "id", description = "升级记录编号", required = true, example = "1024") @Parameter(name = "id", description = "升级记录编号", required = true, example = "1024")
public CommonResult<IotOtaTaskRecordRespVO> getOtaTaskRecord(@RequestParam("id") Long id) { public CommonResult<IotOtaTaskRecordRespVO> getOtaTaskRecord(@RequestParam("id") Long id) {
IotOtaTaskRecordDO upgradeRecord = otaTaskRecordService.getOtaTaskRecord(id); IotOtaTaskRecordDO upgradeRecord = otaTaskRecordService.getOtaTaskRecord(id);

View File

@ -32,4 +32,6 @@ public class IotOtaTaskCreateReqVO {
@Schema(description = "选中的设备编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3") @Schema(description = "选中的设备编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2,3")
private List<Long> deviceIds; private List<Long> deviceIds;
// TODO @li如果 deviceScope 等于 2 deviceIds 校验非空
} }

View File

@ -2,19 +2,16 @@ package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
@Schema(description = "管理后台 - IoT OTA 升级记录分页 Request VO") @Schema(description = "管理后台 - IoT OTA 升级记录分页 Request VO")
@Data @Data
public class IotOtaTaskRecordPageReqVO extends PageParam { public class IotOtaTaskRecordPageReqVO extends PageParam {
// TODO @芋艿分页条件字段梳理 @Schema(description = "升级任务编号", example = "1024")
@Schema(description = "升级任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "升级任务编号不能为空")
private Long taskId; private Long taskId;
@Schema(description = "设备标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "摄像头A1-1") @Schema(description = "设备标识", example = "摄像头A1-1")
private String deviceName; private String deviceName;
} }

View File

@ -3,8 +3,6 @@ package cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - IoT OTA 升级记录 Response VO") @Schema(description = "管理后台 - IoT OTA 升级记录 Response VO")
@Data @Data
public class IotOtaTaskRecordRespVO { public class IotOtaTaskRecordRespVO {
@ -23,12 +21,6 @@ public class IotOtaTaskRecordRespVO {
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long taskId; private Long taskId;
@Schema(description = "产品标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "iot")
private String productKey;
@Schema(description = "设备名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "iot")
private String deviceName;
@Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private String deviceId; private String deviceId;
@ -47,10 +39,4 @@ public class IotOtaTaskRecordRespVO {
@Schema(description = "升级进度描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") @Schema(description = "升级进度描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private String description; private String description;
@Schema(description = "升级开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime startTime;
@Schema(description = "升级结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime endTime;
} }

View File

@ -117,7 +117,7 @@ public class IotDeviceDO extends TenantBaseDO {
* *
* 关联 {@link IotOtaFirmwareDO#getId()} * 关联 {@link IotOtaFirmwareDO#getId()}
*/ */
private String firmwareId; private Long firmwareId;
/** /**
* 设备密钥用于设备认证 * 设备密钥用于设备认证

View File

@ -61,7 +61,7 @@ public class IotOtaTaskDO extends BaseDO {
/** /**
* 设备总数数量 * 设备总数数量
*/ */
private Long deviceTotalCount; private Integer deviceTotalCount;
/** /**
* 设备成功数量 * 设备成功数量
*/ */

View File

@ -7,9 +7,10 @@ import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*; import lombok.AllArgsConstructor;
import lombok.Builder;
import java.time.LocalDateTime; import lombok.Data;
import lombok.NoArgsConstructor;
/** /**
* IoT OTA 升级任务记录 DO * IoT OTA 升级任务记录 DO
@ -70,13 +71,5 @@ public class IotOtaTaskRecordDO extends BaseDO {
* 如果想看历史记录可以查看 {@link IotDeviceMessageDO} 设备日志 * 如果想看历史记录可以查看 {@link IotDeviceMessageDO} 设备日志
*/ */
private String description; private String description;
/**
* 升级开始时间
*/
private LocalDateTime startTime;
/**
* 升级结束时间
*/
private LocalDateTime endTime;
} }

View File

@ -7,20 +7,11 @@ import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.firmware.IotOtaFirmwa
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO; import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper @Mapper
public interface IotOtaFirmwareMapper extends BaseMapperX<IotOtaFirmwareDO> { public interface IotOtaFirmwareMapper extends BaseMapperX<IotOtaFirmwareDO> {
/** default IotOtaFirmwareDO selectByProductIdAndVersion(Long productId, String version) {
* 根据产品ID和固件版本号查询固件信息列表 return selectOne(IotOtaFirmwareDO::getProductId, productId,
*
* @param productId 产品ID用于筛选固件信息
* @param version 固件版本号用于筛选固件信息
* @return 返回符合条件的固件信息列表
*/
default List<IotOtaFirmwareDO> selectByProductIdAndVersion(Long productId, String version) {
return selectList(IotOtaFirmwareDO::getProductId, productId,
IotOtaFirmwareDO::getVersion, version); IotOtaFirmwareDO::getVersion, version);
} }

View File

@ -7,20 +7,12 @@ import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskPageRe
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO; import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* OTA 升级任务 Mapper
*
* @author Shelly
*/
@Mapper @Mapper
public interface IotOtaTaskMapper extends BaseMapperX<IotOtaTaskDO> { public interface IotOtaTaskMapper extends BaseMapperX<IotOtaTaskDO> {
default List<IotOtaTaskDO> selectByFirmwareIdAndName(Long firmwareId, String name) { default IotOtaTaskDO selectByFirmwareIdAndName(Long firmwareId, String name) {
return selectList(new LambdaQueryWrapperX<IotOtaTaskDO>() return selectOne(IotOtaTaskDO::getFirmwareId, firmwareId,
.eqIfPresent(IotOtaTaskDO::getFirmwareId, firmwareId) IotOtaTaskDO::getName, name);
.eqIfPresent(IotOtaTaskDO::getName, name));
} }
default PageResult<IotOtaTaskDO> selectUpgradeTaskPage(IotOtaTaskPageReqVO pageReqVO) { default PageResult<IotOtaTaskDO> selectUpgradeTaskPage(IotOtaTaskPageReqVO pageReqVO) {

View File

@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.iot.dal.mysql.ota;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
import java.util.Set;
@Mapper
public interface IotOtaTaskRecordMapper extends BaseMapperX<IotOtaTaskRecordDO> {
default List<IotOtaTaskRecordDO> selectListByFirmwareIdAndTaskId(Long firmwareId, Long taskId) {
return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
.eqIfPresent(IotOtaTaskRecordDO::getFirmwareId, firmwareId)
.eqIfPresent(IotOtaTaskRecordDO::getTaskId, taskId)
.select(IotOtaTaskRecordDO::getDeviceId, IotOtaTaskRecordDO::getStatus));
}
default PageResult<IotOtaTaskRecordDO> selectPage(IotOtaTaskRecordPageReqVO pageReqVO) {
return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
.eqIfPresent(IotOtaTaskRecordDO::getTaskId, pageReqVO.getTaskId()));
}
default void updateByTaskIdAndStatus(Long taskId, Integer fromStatus, IotOtaTaskRecordDO updateRecord) {
update(updateRecord, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
.eq(IotOtaTaskRecordDO::getTaskId, taskId)
.eq(IotOtaTaskRecordDO::getStatus, fromStatus));
}
default List<IotOtaTaskRecordDO> selectListByDeviceIdAndStatus(Set<Long> deviceIds, Set<Integer> statuses) {
return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
.inIfPresent(IotOtaTaskRecordDO::getDeviceId, deviceIds)
.inIfPresent(IotOtaTaskRecordDO::getStatus, statuses));
}
}

View File

@ -1,111 +0,0 @@
package cn.iocoder.yudao.module.iot.dal.mysql.ota;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Map;
@Mapper
public interface IotOtaUpgradeRecordMapper extends BaseMapperX<IotOtaTaskRecordDO> {
// TODO @liselectByFirmwareIdAndTaskIdAndDeviceId让方法自解释
/**
* 根据条件查询单个OTA升级记录
*
* @param firmwareId 固件ID可选参数用于筛选固件ID匹配的记录
* @param taskId 任务ID可选参数用于筛选任务ID匹配的记录
* @param deviceId 设备ID可选参数用于筛选设备ID匹配的记录
* @return 返回符合条件的单个OTA升级记录如果不存在则返回null
*/
default IotOtaTaskRecordDO selectByConditions(Long firmwareId, Long taskId, String deviceId) {
// 使用LambdaQueryWrapperX构建查询条件根据传入的参数动态添加查询条件
return selectOne(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
.eqIfPresent(IotOtaTaskRecordDO::getFirmwareId, firmwareId)
.eqIfPresent(IotOtaTaskRecordDO::getTaskId, taskId)
.eqIfPresent(IotOtaTaskRecordDO::getDeviceId, deviceId));
}
// TODO @li这个是不是 groupby status ok
/**
* 根据任务ID和设备名称查询OTA升级记录的状态统计信息
* 该函数通过SQL查询统计不同状态0到5的记录数量并返回一个包含统计结果的Map列表
*
* @param taskId 任务ID用于筛选特定任务的OTA升级记录
* @param deviceName 设备名称支持模糊查询用于筛选特定设备的OTA升级记录
* @return 返回一个Map列表每个Map包含不同状态0到5的记录数量
*/
@Select("select count(case when status = 0 then 1 else 0) as `0` " +
"count(case when status = 1 then 1 else 0) as `1` " +
"count(case when status = 2 then 1 else 0) as `2` " +
"count(case when status = 3 then 1 else 0) as `3` " +
"count(case when status = 4 then 1 else 0) as `4` " +
"count(case when status = 5 then 1 else 0) as `5` " +
"from iot_ota_upgrade_record " +
"where task_id = #{taskId} " +
"and device_name like concat('%', #{deviceName}, '%') " +
"and status = #{status}")
List<Map<String, Object>> selectOtaUpgradeRecordCount(@Param("taskId") Long taskId,
@Param("deviceName") String deviceName);
/**
* 根据固件ID查询OTA升级记录的状态统计信息
* 该函数通过SQL查询统计不同状态0到5的记录数量并返回一个包含统计结果的Map列表
*
* @param firmwareId 固件ID用于筛选特定固件的OTA升级记录
* @return 返回一个Map列表每个Map包含不同状态0到5的记录数量
*/
@Select("select count(case when status = 0 then 1 else 0) as `0` " +
"count(case when status = 1 then 1 else 0) as `1` " +
"count(case when status = 2 then 1 else 0) as `2` " +
"count(case when status = 3 then 1 else 0) as `3` " +
"count(case when status = 4 then 1 else 0) as `4` " +
"count(case when status = 5 then 1 else 0) as `5` " +
"from iot_ota_upgrade_record " +
"where firmware_id = #{firmwareId}")
List<Map<String, Object>> selectOtaUpgradeRecordStatistics(Long firmwareId);
// TODO @li这里的注释可以去掉哈
/**
* 根据分页查询条件获取 OTA升级记录的分页结果
*
* @param pageReqVO 分页查询请求参数包含设备名称任务ID等查询条件
* @return 返回分页查询结果包含符合条件的 OTA升级记录列表
*/
// TODO @liselectPage ok
default PageResult<IotOtaTaskRecordDO> selectUpgradeRecordPage(IotOtaTaskRecordPageReqVO pageReqVO) {
// TODO @li这里的注释可以去掉哈然后下面的如果也没必要注释
// 使用LambdaQueryWrapperX构建查询条件并根据请求参数动态添加查询条件
return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
// .likeIfPresent(IotOtaTaskRecordDO::getDeviceName, pageReqVO.getDeviceName()) // 如果设备名称存在则添加模糊查询条件
.eqIfPresent(IotOtaTaskRecordDO::getTaskId, pageReqVO.getTaskId())); // 如果任务ID存在则添加等值查询条件
}
// TODO @li这里的注释可以去掉哈
/**
* 根据任务ID和状态更新升级记录的状态
* <p>
* 该函数用于将符合指定任务ID和状态的升级记录的状态更新为新的状态
*
* @param setStatus 要设置的新状态值类型为Integer
* @param taskId 要更新的升级记录对应的任务ID类型为Long
* @param whereStatus 用于筛选升级记录的当前状态值类型为Integer
*/
// TODO @li改成 updateByTaskIdAndStatus(taskId, status, IotOtaUpgradeRecordDO) 更通用一些
default void updateUpgradeRecordStatusByTaskIdAndStatus(Integer setStatus, Long taskId, Integer whereStatus) {
// 使用LambdaUpdateWrapper构建更新条件将指定状态的记录更新为指定状态
update(new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
.set(IotOtaTaskRecordDO::getStatus, setStatus)
.eq(IotOtaTaskRecordDO::getTaskId, taskId)
.eq(IotOtaTaskRecordDO::getStatus, whereStatus)
);
}
}

View File

@ -16,4 +16,7 @@ public class DictTypeConstants {
public static final String ALERT_LEVEL = "iot_alert_level"; public static final String ALERT_LEVEL = "iot_alert_level";
public static final String OTA_TASK_DEVICE_SCOPE = "iot_ota_task_device_scope";
public static final String OTA_TASK_STATUS = "iot_ota_task_status";
} }

View File

@ -41,20 +41,25 @@ public interface ErrorCodeConstants {
ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在"); ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在");
ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除"); ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除");
// ========== 固件相关 1-050-008-000 ========== // ========== OTA 固件相关 1-050-008-000 ==========
ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在"); ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在");
ErrorCode OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE = new ErrorCode(1_050_008_001, "产品版本号重复"); ErrorCode OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE = new ErrorCode(1_050_008_001, "产品版本号重复");
ErrorCode OTA_UPGRADE_TASK_NOT_EXISTS = new ErrorCode(1_050_008_100, "升级任务不存在"); // ========== OTA 升级任务相关 1-050-008-100 ==========
ErrorCode OTA_UPGRADE_TASK_NAME_DUPLICATE = new ErrorCode(1_050_008_101, "升级任务名称重复");
ErrorCode OTA_UPGRADE_TASK_DEVICE_IDS_EMPTY = new ErrorCode(1_050_008_102, "设备编号列表不能为空");
ErrorCode OTA_UPGRADE_TASK_DEVICE_LIST_EMPTY = new ErrorCode(1_050_008_103, "设备列表不能为空");
ErrorCode OTA_UPGRADE_TASK_CANNOT_CANCEL = new ErrorCode(1_050_008_104, "升级任务不能取消");
ErrorCode OTA_UPGRADE_RECORD_NOT_EXISTS = new ErrorCode(1_050_008_200, "升级记录不存在"); ErrorCode OTA_TASK_NOT_EXISTS = new ErrorCode(1_050_008_100, "升级任务不存在");
ErrorCode OTA_UPGRADE_RECORD_DUPLICATE = new ErrorCode(1_050_008_201, "升级记录重复"); ErrorCode OTA_TASK_CREATE_FAIL_NAME_DUPLICATE = new ErrorCode(1_050_008_101, "创建 OTA 任务失败,原因:任务名称重复");
ErrorCode OTA_UPGRADE_RECORD_CANNOT_RETRY = new ErrorCode(1_050_008_202, "升级记录不能重试"); ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_FIRMWARE_EXISTS = new ErrorCode(1_050_008_102,
"创建 OTA 任务失败,原因:设备({})已经是该固件版本");
ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_OTA_IN_PROCESS = new ErrorCode(1_050_008_102,
"创建 OTA 任务失败,原因:设备({})已经在升级中...");
ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_EMPTY = new ErrorCode(1_050_008_103, "创建 OTA 任务失败,原因:没有可升级的设备");
ErrorCode OTA_TASK_CANCEL_FAIL_STATUS_END = new ErrorCode(1_050_008_104, "取消 OTA 任务失败,原因:任务状态不是进行中");
// ========== OTA 升级任务相关 1-050-008-100 ==========
ErrorCode OTA_TASK_RECORD_NOT_EXISTS = new ErrorCode(1_050_008_200, "升级记录不存在");
// ========== IoT 数据流转规则 1-050-010-000 ========== // ========== IoT 数据流转规则 1-050-010-000 ==========
ErrorCode DATA_RULE_NOT_EXISTS = new ErrorCode(1_050_010_000, "数据流转规则不存在"); ErrorCode DATA_RULE_NOT_EXISTS = new ErrorCode(1_050_010_000, "数据流转规则不存在");

View File

@ -2,10 +2,13 @@ package cn.iocoder.yudao.module.iot.enums.ota;
import cn.iocoder.yudao.framework.common.core.ArrayValuable; import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import java.util.Set;
/** /**
* IoT OTA 升级任务记录的状态枚举 * IoT OTA 升级任务记录的状态枚举
@ -26,6 +29,17 @@ public enum IotOtaTaskRecordStatusEnum implements ArrayValuable<Integer> {
public static final Integer[] ARRAYS = Arrays.stream(values()) public static final Integer[] ARRAYS = Arrays.stream(values())
.map(IotOtaTaskRecordStatusEnum::getStatus).toArray(Integer[]::new); .map(IotOtaTaskRecordStatusEnum::getStatus).toArray(Integer[]::new);
public static final Set<Integer> IN_PROCESS_STATUSES = SetUtils.asSet(
PENDING.getStatus(),
PUSHED.getStatus(),
UPGRADING.getStatus(),
SUCCESS.getStatus());
public static final List<Integer> PRIORITY_STATUSES = Arrays.asList(
SUCCESS.getStatus(),
PENDING.getStatus(), PUSHED.getStatus(), UPGRADING.getStatus(),
FAILURE.getStatus(), CANCELED.getStatus());
/** /**
* 状态 * 状态
*/ */

View File

@ -12,7 +12,6 @@ import java.time.LocalDateTime;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* IoT 设备 Service 接口 * IoT 设备 Service 接口
@ -155,21 +154,13 @@ public interface IotDeviceService {
List<IotDeviceDO> getDeviceListByState(Integer state); List<IotDeviceDO> getDeviceListByState(Integer state);
/** /**
* 根据产品ID获取设备列表 * 根据产品编号获取设备列表
* *
* @param productId 产品ID用于查询特定产品的设备列表 * @param productId 产品编号
* @return 返回与指定产品ID关联的设备列表列表中的每个元素为IotDeviceDO对象 * @return 设备列表
*/ */
List<IotDeviceDO> getDeviceListByProductId(Long productId); List<IotDeviceDO> getDeviceListByProductId(Long productId);
/**
* 根据设备ID列表获取设备信息列表
*
* @param deviceIdList 设备ID列表包含需要查询的设备ID
* @return 返回与设备ID列表对应的设备信息列表列表中的每个元素为IotDeviceDO对象
*/
List<IotDeviceDO> getDeviceListByIdList(List<Long> deviceIdList);
/** /**
* 基于产品编号获得设备数量 * 基于产品编号获得设备数量
* *
@ -248,6 +239,6 @@ public interface IotDeviceService {
* *
* @param ids 设备编号数组 * @param ids 设备编号数组
*/ */
void validateDevicesExist(Set<Long> ids); List<IotDeviceDO> validateDeviceListExists(Collection<Long> ids);
} }

View File

@ -278,11 +278,6 @@ public class IotDeviceServiceImpl implements IotDeviceService {
return deviceMapper.selectListByProductId(productId); return deviceMapper.selectListByProductId(productId);
} }
@Override
public List<IotDeviceDO> getDeviceListByIdList(List<Long> deviceIdList) {
return deviceMapper.selectByIds(deviceIdList);
}
@Override @Override
public void updateDeviceState(IotDeviceDO device, Integer state) { public void updateDeviceState(IotDeviceDO device, Integer state) {
// 1. 更新状态和时间 // 1. 更新状态和时间
@ -474,14 +469,15 @@ public class IotDeviceServiceImpl implements IotDeviceService {
} }
@Override @Override
public void validateDevicesExist(Set<Long> ids) { public List<IotDeviceDO> validateDeviceListExists(Collection<Long> ids) {
if (CollUtil.isEmpty(ids)) { if (CollUtil.isEmpty(ids)) {
return; return Collections.emptyList();
} }
List<IotDeviceDO> deviceIds = deviceMapper.selectByIds(ids); List<IotDeviceDO> devices = deviceMapper.selectByIds(ids);
if (deviceIds.size() != ids.size()) { if (devices.size() != ids.size()) {
throw exception(DEVICE_NOT_EXISTS); throw exception(DEVICE_NOT_EXISTS);
} }
return devices;
} }
private IotDeviceServiceImpl getSelf() { private IotDeviceServiceImpl getSelf() {

View File

@ -8,7 +8,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import jakarta.validation.Valid; import jakarta.validation.Valid;
/** /**
* OTA 固件管理 Service * OTA 固件管理 Service 接口
* *
* @author Shelly Chan * @author Shelly Chan
*/ */

View File

@ -23,9 +23,14 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_FIRMWARE_NOT_EXISTS; import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_FIRMWARE_NOT_EXISTS;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE; import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE;
@Slf4j /**
* OTA 固件管理 Service 实现类
*
* @author Shelly Chan
*/
@Service @Service
@Validated @Validated
@Slf4j
public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService { public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService {
@Resource @Resource
@ -37,7 +42,9 @@ public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService {
@Override @Override
public Long createOtaFirmware(IotOtaFirmwareCreateReqVO saveReqVO) { public Long createOtaFirmware(IotOtaFirmwareCreateReqVO saveReqVO) {
// 1.1 校验固件产品 + 版本号不能重复 // 1.1 校验固件产品 + 版本号不能重复
validateProductAndVersionDuplicate(saveReqVO.getProductId(), saveReqVO.getVersion()); if (otaFirmwareMapper.selectByProductIdAndVersion(saveReqVO.getProductId(), saveReqVO.getVersion()) != null) {
throw exception(OTA_FIRMWARE_PRODUCT_VERSION_DUPLICATE);
}
// 1.2 校验产品存在 // 1.2 校验产品存在
productService.validateProductExists(saveReqVO.getProductId()); productService.validateProductExists(saveReqVO.getProductId());
@ -83,25 +90,12 @@ public class IotOtaFirmwareServiceImpl implements IotOtaFirmwareService {
return firmware; return firmware;
} }
/**
* 验证产品和版本号是否重复
*/
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 固件对象 * @param firmware 固件对象
* @throws Exception 下载或计算签名失败时抛出异常
*/ */
private void calculateFileDigest(IotOtaFirmwareDO firmware) throws Exception { private void calculateFileDigest(IotOtaFirmwareDO firmware) {
String fileUrl = firmware.getFileUrl(); String fileUrl = firmware.getFileUrl();
// 下载文件并计算签名 // 下载文件并计算签名
byte[] fileBytes = HttpUtil.downloadBytes(fileUrl); byte[] fileBytes = HttpUtil.downloadBytes(fileUrl);

View File

@ -2,67 +2,67 @@ package cn.iocoder.yudao.module.iot.service.ota;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO; import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO; import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
// TODO @li注释写的有点冗余可以看看别的模块哈= = AI 生成的注释有的时候太啰嗦了需要处理下的哈
/** /**
* IotOtaUpgradeRecordService 接口定义了与物联网设备OTA升级记录相关的操作 * IoT OTA 升级记录 Service 接口
* 该接口提供了创建更新查询统计和重试升级记录的功能
*/ */
public interface IotOtaTaskRecordService { public interface IotOtaTaskRecordService {
/** /**
* 批量创建 OTA 升级记录 * 批量创建 OTA 升级记录
* 该函数用于为指定的设备列表固件ID和升级任务ID创建OTA升级记录
* *
* @param deviceIds 设备ID列表表示需要升级的设备集合 * @param devices 设备列表
* @param firmwareId 固件ID表示要升级到的固件版本 * @param firmwareId 固件编号
* @param upgradeTaskId 升级任务ID表示此次升级任务的唯一标识 * @param taskId 任务编号
*/ */
void createOtaTaskRecordBatch(List<Long> deviceIds, Long firmwareId, Long upgradeTaskId); void createOtaTaskRecordList(List<IotDeviceDO> devices, Long firmwareId, Long taskId);
/** /**
* 获取 OTA 升级记录的数量统计 * 获取 OTA 升级记录的状态统计
* *
* @return 返回一个 Map其中键为状态码值为对应状态的升级记录数量 * @param firmwareId 固件编号
* @param taskId 任务编号
* @return 状态统计 Mapkey 为状态码value 为对应状态的升级记录数量
*/ */
Map<Integer, Long> getOtaTaskRecordCount(@Valid IotOtaTaskRecordPageReqVO pageReqVO); Map<Integer, Long> getOtaTaskRecordStatusCountMap(Long firmwareId, Long taskId);
/** /**
* 获取 OTA 升级记录的统计信息 * 获取 OTA 升级记录
* *
* @return 返回一个 Map其中键为状态码值为对应状态的升级记录统计信息 * @param id 编号
*/ * @return OTA 升级记录
Map<Integer, Long> getOtaTaskRecordStatistics(Long firmwareId);
/**
* 获取指定 ID OTA 升级记录的详细信息
*
* @param id 需要查询的升级记录的 ID
* @return 返回包含升级记录详细信息的响应对象
*/ */
IotOtaTaskRecordDO getOtaTaskRecord(Long id); IotOtaTaskRecordDO getOtaTaskRecord(Long id);
/** /**
* 分页查询 OTA 升级记录 * 获取 OTA 升级记录分页
* *
* @param pageReqVO 包含分页查询条件的请求对象必须经过验证 * @param pageReqVO 分页查询
* @return 返回包含分页查询结果的响应对象 * @return OTA 升级记录分页
*/ */
PageResult<IotOtaTaskRecordDO> getOtaTaskRecordPage(@Valid IotOtaTaskRecordPageReqVO pageReqVO); PageResult<IotOtaTaskRecordDO> getOtaTaskRecordPage(@Valid IotOtaTaskRecordPageReqVO pageReqVO);
/** /**
* 根据任务 ID 取消升级记录 * 根据 OTA 任务编号取消未结束的升级记录
* <p>
* 该函数用于根据给定的任务ID取消与该任务相关的升级记录通常用于在任务执行失败或用户手动取消时
* 清理或标记相关的升级记录为取消状态
* *
* @param taskId 要取消升级记录的任务ID该ID唯一标识一个任务通常由任务管理系统生成 * @param taskId 升级任务编号
*/ */
void cancelUpgradeRecordByTaskId(Long taskId); void cancelTaskRecordListByTaskId(Long taskId);
/**
* 根据设备编号和记录状态获取 OTA 升级记录列表
*
* @param deviceIds 设备编号集合
* @param statuses 记录状态集合
* @return OTA 升级记录列表
*/
List<IotOtaTaskRecordDO> getOtaTaskRecordListByDeviceIdAndStatus(Set<Long> deviceIds, Set<Integer> statuses);
} }

View File

@ -2,177 +2,100 @@ package cn.iocoder.yudao.module.iot.service.ota;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO; import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO; import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO; import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaTaskRecordMapper;
import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaUpgradeRecordMapper;
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum; import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_TASK_RECORD_NOT_EXISTS;
// TODO @li@Service@Validated@Slf4j先用关键注解2类注释简单写 /**
@Slf4j * OTA 升级任务记录 Service 实现类
*/
@Service @Service
@Validated @Validated
@Slf4j
public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService { public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
@Resource @Resource
private IotOtaUpgradeRecordMapper iotOtaUpgradeRecordMapper; private IotOtaTaskRecordMapper otaTaskRecordMapper;
@Resource
private IotDeviceService deviceService;
@Resource
private IotOtaFirmwareService firmwareService;
@Resource
private IotOtaTaskService upgradeTaskService;
@Override @Override
public void createOtaTaskRecordBatch(List<Long> deviceIds, Long firmwareId, Long upgradeTaskId) { public void createOtaTaskRecordList(List<IotDeviceDO> devices, Long firmwareId, Long taskId) {
// 1. 校验升级记录信息是否存在并且已经取消的任务可以重新开始 List<IotOtaTaskRecordDO> records = convertList(devices, device ->
deviceIds.forEach(deviceId -> validateUpgradeRecordDuplicate(firmwareId, upgradeTaskId, String.valueOf(deviceId))); IotOtaTaskRecordDO.builder().firmwareId(firmwareId).taskId(taskId)
.deviceId(device.getId()).fromFirmwareId(Convert.toLong(device.getFirmwareId()))
// 2. 初始化OTA升级记录列表信息 .status(IotOtaTaskRecordStatusEnum.PENDING.getStatus()).progress(0).build());
IotOtaTaskDO upgradeTask = upgradeTaskService.getOtaTask(upgradeTaskId); otaTaskRecordMapper.insertBatch(records);
IotOtaFirmwareDO firmware = firmwareService.getOtaFirmware(firmwareId);
List<IotDeviceDO> deviceList = deviceService.getDeviceListByIdList(deviceIds);
List<IotOtaTaskRecordDO> upgradeRecordList = deviceList.stream().map(device -> {
IotOtaTaskRecordDO upgradeRecord = new IotOtaTaskRecordDO();
upgradeRecord.setFirmwareId(firmware.getId());
upgradeRecord.setTaskId(upgradeTask.getId());
upgradeRecord.setDeviceId(device.getId());
upgradeRecord.setFromFirmwareId(Convert.toLong(device.getFirmwareId()));
upgradeRecord.setStatus(IotOtaTaskRecordStatusEnum.PENDING.getStatus());
upgradeRecord.setProgress(0);
return upgradeRecord;
}).toList();
// 3. 保存数据
iotOtaUpgradeRecordMapper.insertBatch(upgradeRecordList);
// TODO @芋艿在这里需要处理推送升级任务的逻辑
}
// TODO @li1方法注释简单写2父类写了注释子类就不用写了
/**
* 获取OTA升级记录的数量统计
* 该方法根据传入的查询条件统计不同状态的OTA升级记录数量并返回一个包含各状态数量的映射
*
* @param pageReqVO 包含查询条件的请求对象主要包括任务ID和设备名称等信息
* @return 返回一个Map其中键为状态常量值为对应状态的记录数量
*/
@Override
@Transactional
public Map<Integer, Long> getOtaTaskRecordCount(IotOtaTaskRecordPageReqVO pageReqVO) {
// 分别查询不同状态的OTA升级记录数量
List<Map<String, Object>> upgradeRecordCountList = iotOtaUpgradeRecordMapper.selectOtaUpgradeRecordCount(
pageReqVO.getTaskId(), pageReqVO.getDeviceName());
Map<String, Object> upgradeRecordCountMap = ObjectUtils.defaultIfNull(upgradeRecordCountList.get(0));
Objects.requireNonNull(upgradeRecordCountMap);
return upgradeRecordCountMap.entrySet().stream().collect(Collectors.toMap(
entry -> Convert.toInt(entry.getKey()),
entry -> Convert.toLong(entry.getValue())));
} }
@Override @Override
@Transactional public Map<Integer, Long> getOtaTaskRecordStatusCountMap(Long firmwareId, Long taskId) {
public Map<Integer, Long> getOtaTaskRecordStatistics(Long firmwareId) { // 按照 status 枚举初始化 countMap 0
// 查询并统计不同状态的OTA升级记录数量 Map<Integer, Long> countMap = convertMap(Arrays.asList(IotOtaTaskRecordStatusEnum.values()),
List<Map<String, Object>> upgradeRecordStatisticsList = iotOtaUpgradeRecordMapper.selectOtaUpgradeRecordStatistics(firmwareId); IotOtaTaskRecordStatusEnum::getStatus, iotOtaTaskRecordStatusEnum -> 0L);
Map<String, Object> upgradeRecordStatisticsMap = ObjectUtils.defaultIfNull(upgradeRecordStatisticsList.get(0));
Objects.requireNonNull(upgradeRecordStatisticsMap); // 查询记录只返回 idstatus 字段
return upgradeRecordStatisticsMap.entrySet().stream().collect(Collectors.toMap( List<IotOtaTaskRecordDO> records = otaTaskRecordMapper.selectListByFirmwareIdAndTaskId(firmwareId, taskId);
entry -> Convert.toInt(entry.getKey()), Map<Long, List<Integer>> deviceStatusesMap = convertMultiMap(records,
entry -> Convert.toLong(entry.getValue()))); IotOtaTaskRecordDO::getDeviceId, IotOtaTaskRecordDO::getStatus);
// 找到第一个匹配的优先级状态避免重复计算
deviceStatusesMap.forEach((deviceId, statuses) -> {
for (Integer priorityStatus : IotOtaTaskRecordStatusEnum.PRIORITY_STATUSES) {
if (statuses.contains(priorityStatus)) {
countMap.put(priorityStatus, countMap.get(priorityStatus) + 1);
return;
}
}
});
return countMap;
} }
@Override @Override
public IotOtaTaskRecordDO getOtaTaskRecord(Long id) { public IotOtaTaskRecordDO getOtaTaskRecord(Long id) {
return iotOtaUpgradeRecordMapper.selectById(id); return otaTaskRecordMapper.selectById(id);
} }
@Override @Override
public PageResult<IotOtaTaskRecordDO> getOtaTaskRecordPage(IotOtaTaskRecordPageReqVO pageReqVO) { public PageResult<IotOtaTaskRecordDO> getOtaTaskRecordPage(IotOtaTaskRecordPageReqVO pageReqVO) {
return iotOtaUpgradeRecordMapper.selectUpgradeRecordPage(pageReqVO); return otaTaskRecordMapper.selectPage(pageReqVO);
} }
@Override @Override
public void cancelUpgradeRecordByTaskId(Long taskId) { public void cancelTaskRecordListByTaskId(Long taskId) {
// 暂定只有待推送的升级记录可以取消 TODO @芋艿可以看看阿里云哪些可以取消 // 设置取消记录的描述
iotOtaUpgradeRecordMapper.updateUpgradeRecordStatusByTaskIdAndStatus( IotOtaTaskRecordDO updateRecord = IotOtaTaskRecordDO.builder()
IotOtaTaskRecordStatusEnum.CANCELED.getStatus(), taskId, .status(IotOtaTaskRecordStatusEnum.CANCELED.getStatus())
IotOtaTaskRecordStatusEnum.PENDING.getStatus()); .description("管理员取消升级任务")
.build();
otaTaskRecordMapper.updateByTaskIdAndStatus(
taskId, IotOtaTaskRecordStatusEnum.PENDING.getStatus(), updateRecord);
}
@Override
public List<IotOtaTaskRecordDO> getOtaTaskRecordListByDeviceIdAndStatus(Set<Long> deviceIds, Set<Integer> statuses) {
return otaTaskRecordMapper.selectListByDeviceIdAndStatus(deviceIds, statuses);
} }
/**
* 验证指定的升级记录是否存在
* <p>
* 该函数通过给定的ID查询升级记录如果查询结果为空则抛出异常表示升级记录不存在
*
* @param id 升级记录的唯一标识符类型为Long
* @throws cn.iocoder.yudao.framework.common.exception.ServiceException则抛出异常异常类型为OTA_UPGRADE_RECORD_NOT_EXISTS
*/
private IotOtaTaskRecordDO validateUpgradeRecordExists(Long id) { private IotOtaTaskRecordDO validateUpgradeRecordExists(Long id) {
// 根据ID查询升级记录 IotOtaTaskRecordDO upgradeRecord = otaTaskRecordMapper.selectById(id);
IotOtaTaskRecordDO upgradeRecord = iotOtaUpgradeRecordMapper.selectById(id);
// 如果查询结果为空抛出异常
if (upgradeRecord == null) { if (upgradeRecord == null) {
throw exception(OTA_UPGRADE_RECORD_NOT_EXISTS); throw exception(OTA_TASK_RECORD_NOT_EXISTS);
} }
return upgradeRecord; return upgradeRecord;
} }
// TODO @li注释有点冗余
/**
* 校验固件升级记录是否重复
* <p>
* 该函数用于检查给定的固件ID任务ID和设备ID是否已经存在未取消的升级记录
* 如果存在未取消的记录则抛出异常提示升级记录重复
*
* @param firmwareId 固件ID用于标识特定的固件版本
* @param taskId 任务ID用于标识特定的升级任务
* @param deviceId 设备ID用于标识特定的设备
*/
private void validateUpgradeRecordDuplicate(Long firmwareId, Long taskId, String deviceId) {
IotOtaTaskRecordDO upgradeRecord = iotOtaUpgradeRecordMapper.selectByConditions(firmwareId, taskId, deviceId);
if (upgradeRecord == null) {
return;
}
if (!Objects.equals(upgradeRecord.getStatus(), IotOtaTaskRecordStatusEnum.CANCELED.getStatus())) {
throw exception(OTA_UPGRADE_RECORD_DUPLICATE);
}
}
// TODO @li注释有点冗余
/**
* 验证升级记录是否可以重试
* <p>
* 该方法用于检查给定的升级记录是否处于允许重试的状态如果升级记录的状态为
* PENDINGPUSHED UPGRADING则抛出异常表示不允许重试
*
* @param upgradeRecord 需要验证的升级记录对象类型为 IotOtaUpgradeRecordDO
* @throws cn.iocoder.yudao.framework.common.exception.ServiceException则抛出 OTA_UPGRADE_RECORD_CANNOT_RETRY 异常
*/
// TODO @li这种一次性的方法不复用的其实一步一定要抽成小方法
private void validateUpgradeRecordCanRetry(IotOtaTaskRecordDO upgradeRecord) {
if (ObjectUtils.equalsAny(upgradeRecord.getStatus(),
IotOtaTaskRecordStatusEnum.PENDING.getStatus(),
IotOtaTaskRecordStatusEnum.PUSHED.getStatus(),
IotOtaTaskRecordStatusEnum.UPGRADING.getStatus())) {
throw exception(OTA_UPGRADE_RECORD_CANNOT_RETRY);
}
}
} }

View File

@ -14,7 +14,7 @@ import jakarta.validation.Valid;
public interface IotOtaTaskService { public interface IotOtaTaskService {
/** /**
* 创建 OTA 级任务 * 创建 OTA 级任务
* *
* @param createReqVO 创建请求对象 * @param createReqVO 创建请求对象
* @return 升级任务编号 * @return 升级任务编号

View File

@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.iot.service.ota; package cn.iocoder.yudao.module.iot.service.ota;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert; import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskCreateReqVO; import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskCreateReqVO;
@ -9,8 +9,10 @@ import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskPageRe
import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO; import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO; import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaTaskMapper; import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaTaskMapper;
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskDeviceScopeEnum; import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskDeviceScopeEnum;
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskStatusEnum; import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskStatusEnum;
import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@ -24,6 +26,7 @@ import java.util.List;
import java.util.Objects; import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
/** /**
@ -45,26 +48,29 @@ public class IotOtaTaskServiceImpl implements IotOtaTaskService {
private IotOtaFirmwareService otaFirmwareService; private IotOtaFirmwareService otaFirmwareService;
@Resource @Resource
@Lazy // 延迟避免循环依赖报错 @Lazy // 延迟避免循环依赖报错
private IotOtaTaskRecordService otaUpgradeRecordService; private IotOtaTaskRecordService otaTaskRecordService;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public Long createOtaTask(IotOtaTaskCreateReqVO createReqVO) { public Long createOtaTask(IotOtaTaskCreateReqVO createReqVO) {
// 1.1 校验同一固件的升级任务名称不重复 // 1.1 校验固件信息是否存在
validateFirmwareTaskDuplicate(createReqVO.getFirmwareId(), createReqVO.getName());
// 1.2 校验固件信息是否存在
IotOtaFirmwareDO firmware = otaFirmwareService.validateFirmwareExists(createReqVO.getFirmwareId()); IotOtaFirmwareDO firmware = otaFirmwareService.validateFirmwareExists(createReqVO.getFirmwareId());
// 1.3 补全设备范围信息并且校验是否又设备可以升级如果没有设备可以升级则报错 // 1.2 校验同一固件的升级任务名称不重复
validateScopeAndDevice(createReqVO.getDeviceScope(), createReqVO.getDeviceIds(), firmware.getProductId()); if (otaTaskMapper.selectByFirmwareIdAndName(firmware.getId(), createReqVO.getName()) != null) {
throw exception(OTA_TASK_CREATE_FAIL_NAME_DUPLICATE);
}
// 1.3 校验设备范围信息
List<IotDeviceDO> devices = validateOtaTaskDeviceScope(createReqVO, firmware.getProductId());
// 2. 保存 OTA 升级任务信息到数据库 // 2. 保存升级任务直接转换
IotOtaTaskDO upgradeTask = initOtaUpgradeTask(createReqVO, firmware.getProductId()); IotOtaTaskDO task = BeanUtils.toBean(createReqVO, IotOtaTaskDO.class)
otaTaskMapper.insert(upgradeTask); .setStatus(IotOtaTaskStatusEnum.IN_PROGRESS.getStatus())
.setDeviceTotalCount(devices.size()).setDeviceSuccessCount(0);
otaTaskMapper.insert(task);
// 3. 生成设备升级记录信息并存储等待定时任务轮询 // 3. 生成设备升级记录
// TODO @芋艿在处理 otaTaskRecordService.createOtaTaskRecordList(devices, firmware.getId(), task.getId());
// otaUpgradeRecordService.createOtaUpgradeRecordBatch(upgradeTask.getDeviceIds(), firmware.getId(), upgradeTask.getId()); return task.getId();
return upgradeTask.getId();
} }
@Override @Override
@ -73,18 +79,17 @@ public class IotOtaTaskServiceImpl implements IotOtaTaskService {
// 1.1 校验升级任务是否存在 // 1.1 校验升级任务是否存在
IotOtaTaskDO upgradeTask = validateUpgradeTaskExists(id); IotOtaTaskDO upgradeTask = validateUpgradeTaskExists(id);
// 1.2 校验升级任务是否可以取消 // 1.2 校验升级任务是否可以取消
// TODO @liObjUtil notequals if (ObjUtil.notEqual(upgradeTask.getStatus(), IotOtaTaskStatusEnum.IN_PROGRESS.getStatus())) {
if (!Objects.equals(upgradeTask.getStatus(), IotOtaTaskStatusEnum.IN_PROGRESS.getStatus())) { throw exception(OTA_TASK_CANCEL_FAIL_STATUS_END);
throw exception(OTA_UPGRADE_TASK_CANNOT_CANCEL);
} }
// 2. 更新 OTA 升级任务状态为已取消 // 2. 更新升级任务状态为已取消
otaTaskMapper.updateById(IotOtaTaskDO.builder() otaTaskMapper.updateById(IotOtaTaskDO.builder()
.id(id).status(IotOtaTaskStatusEnum.CANCELED.getStatus()) .id(id).status(IotOtaTaskStatusEnum.CANCELED.getStatus())
.build()); .build());
// 3. 更新 OTA 升级记录状态为已取消 // 3. 更新升级记录状态为已取消
otaUpgradeRecordService.cancelUpgradeRecordByTaskId(id); otaTaskRecordService.cancelTaskRecordListByTaskId(id);
} }
@Override @Override
@ -97,50 +102,55 @@ public class IotOtaTaskServiceImpl implements IotOtaTaskService {
return otaTaskMapper.selectUpgradeTaskPage(pageReqVO); return otaTaskMapper.selectUpgradeTaskPage(pageReqVO);
} }
/** private List<IotDeviceDO> validateOtaTaskDeviceScope(IotOtaTaskCreateReqVO createReqVO, Long productId) {
* 校验固件升级任务是否重复 // 情况一选择设备
*/ if (Objects.equals(createReqVO.getDeviceScope(), IotOtaTaskDeviceScopeEnum.SELECT.getScope())) {
private void validateFirmwareTaskDuplicate(Long firmwareId, String taskName) { // 1.1 校验设备存在
List<IotOtaTaskDO> upgradeTaskList = otaTaskMapper.selectByFirmwareIdAndName(firmwareId, taskName); List<IotDeviceDO> devices = deviceService.validateDeviceListExists(createReqVO.getDeviceIds());
if (CollUtil.isNotEmpty(upgradeTaskList)) { for (IotDeviceDO device : devices) {
throw exception(OTA_UPGRADE_TASK_NAME_DUPLICATE); if (ObjUtil.notEqual(device.getProductId(), productId)) {
} throw exception(DEVICE_NOT_EXISTS);
} }
private void validateScopeAndDevice(Integer scope, List<Long> deviceIds, Long productId) {
if (Objects.equals(scope, IotOtaTaskDeviceScopeEnum.SELECT.getScope())) {
if (CollUtil.isEmpty(deviceIds)) {
throw exception(OTA_UPGRADE_TASK_DEVICE_IDS_EMPTY);
} }
return; // 1.2 校验设备是否已经是该固件版本
devices.forEach(device -> {
if (Objects.equals(device.getFirmwareId(), createReqVO.getFirmwareId())) {
throw exception(OTA_TASK_CREATE_FAIL_DEVICE_FIRMWARE_EXISTS, device.getDeviceName());
}
});
// 1.3 校验设备是否已经在升级中
List<IotOtaTaskRecordDO> records = otaTaskRecordService.getOtaTaskRecordListByDeviceIdAndStatus(
convertSet(devices, IotDeviceDO::getId), IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES);
devices.forEach(device -> {
if (CollUtil.contains(records, item -> item.getDeviceId().equals(device.getId()))) {
throw exception(OTA_TASK_CREATE_FAIL_DEVICE_OTA_IN_PROCESS, device.getDeviceName());
}
});
return devices;
} }
// 情况二全部设备
if (Objects.equals(scope, IotOtaTaskDeviceScopeEnum.ALL.getScope())) { if (Objects.equals(createReqVO.getDeviceScope(), IotOtaTaskDeviceScopeEnum.ALL.getScope())) {
List<IotDeviceDO> deviceList = deviceService.getDeviceListByProductId(productId); List<IotDeviceDO> devices = deviceService.getDeviceListByProductId(productId);
if (CollUtil.isEmpty(deviceList)) { // 2.1.1 移除已经是该固件版本的设备
throw exception(OTA_UPGRADE_TASK_DEVICE_LIST_EMPTY); devices.removeIf(device -> Objects.equals(device.getFirmwareId(), createReqVO.getFirmwareId()));
// 2.1.2 移除已经在升级中的设备
List<IotOtaTaskRecordDO> records = otaTaskRecordService.getOtaTaskRecordListByDeviceIdAndStatus(
convertSet(devices, IotDeviceDO::getId), IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES);
devices.removeIf(device -> CollUtil.contains(records,
item -> item.getDeviceId().equals(device.getId())));
// 2.2 校验是否有可升级的设备
if (CollUtil.isEmpty(devices)) {
throw exception(OTA_TASK_CREATE_FAIL_DEVICE_EMPTY);
} }
return devices;
} }
throw new IllegalArgumentException("不支持的设备范围:" + createReqVO.getDeviceScope());
} }
private IotOtaTaskDO validateUpgradeTaskExists(Long id) { private IotOtaTaskDO validateUpgradeTaskExists(Long id) {
IotOtaTaskDO upgradeTask = otaTaskMapper.selectById(id); IotOtaTaskDO upgradeTask = otaTaskMapper.selectById(id);
if (Objects.isNull(upgradeTask)) { if (Objects.isNull(upgradeTask)) {
throw exception(OTA_UPGRADE_TASK_NOT_EXISTS); throw exception(OTA_TASK_NOT_EXISTS);
}
return upgradeTask;
}
private IotOtaTaskDO initOtaUpgradeTask(IotOtaTaskCreateReqVO createReqVO, Long productId) {
IotOtaTaskDO upgradeTask = BeanUtils.toBean(createReqVO, IotOtaTaskDO.class);
upgradeTask.setDeviceTotalCount(Convert.toLong(CollUtil.size(createReqVO.getDeviceIds())))
.setStatus(IotOtaTaskStatusEnum.IN_PROGRESS.getStatus());
if (Objects.equals(createReqVO.getDeviceScope(), IotOtaTaskDeviceScopeEnum.ALL.getScope())) {
List<IotDeviceDO> deviceList = deviceService.getDeviceListByProductId(productId);
upgradeTask.setDeviceTotalCount((long) deviceList.size());
// upgradeTask.setDeviceIds(
// deviceList.stream().map(IotDeviceDO::getId).collect(Collectors.toList()));
} }
return upgradeTask; return upgradeTask;
} }

View File

@ -121,7 +121,7 @@ public class IotDataRuleServiceImpl implements IotDataRuleService {
convertSet(sourceConfigs, IotDataRuleDO.SourceConfig::getProductId)); convertSet(sourceConfigs, IotDataRuleDO.SourceConfig::getProductId));
// 2. 校验设备 // 2. 校验设备
deviceService.validateDevicesExist(convertSet(sourceConfigs, IotDataRuleDO.SourceConfig::getDeviceId, deviceService.validateDeviceListExists(convertSet(sourceConfigs, IotDataRuleDO.SourceConfig::getDeviceId,
config -> ObjUtil.notEqual(config.getDeviceId(), IotDeviceDO.DEVICE_ID_ALL))); config -> ObjUtil.notEqual(config.getDeviceId(), IotDeviceDO.DEVICE_ID_ALL)));
// 3. 校验物模型存在 // 3. 校验物模型存在