diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskRecordController.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskRecordController.java index cc17108e5e..81ccea9b98 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskRecordController.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/ota/IotOtaTaskRecordController.java @@ -22,6 +22,7 @@ import org.dromara.hutool.core.collection.CollUtil; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -90,4 +91,13 @@ public class IotOtaTaskRecordController { return success(BeanUtils.toBean(upgradeRecord, IotOtaTaskRecordRespVO.class)); } + @PutMapping("/cancel") + @Operation(summary = "取消 OTA 升级记录") + @PreAuthorize("@ss.hasPermission('iot:ota-task-record:cancel')") + @Parameter(name = "id", description = "升级记录编号", required = true, example = "1024") + public CommonResult cancelOtaTaskRecord(@RequestParam("id") Long id) { + otaTaskRecordService.cancelOtaTaskRecord(id); + return success(true); + } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskRecordDO.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskRecordDO.java index 28b4ca6734..d99a1bb60a 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskRecordDO.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/ota/IotOtaTaskRecordDO.java @@ -25,6 +25,13 @@ import lombok.NoArgsConstructor; @AllArgsConstructor public class IotOtaTaskRecordDO extends BaseDO { + public static final String DESCRIPTION_CANCEL_BY_TASK = "管理员手动取消升级任务(批量)"; + + public static final String DESCRIPTION_CANCEL_BY_RECORD = "管理员手动取消升级记录(单个)"; + + /** + * 升级记录编号 + */ @TableId private Long id; diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskMapper.java index a792dd3cc3..cf73231234 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskMapper.java @@ -5,6 +5,7 @@ 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.IotOtaTaskPageReqVO; import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; @Mapper @@ -15,10 +16,17 @@ public interface IotOtaTaskMapper extends BaseMapperX { IotOtaTaskDO::getName, name); } - default PageResult selectUpgradeTaskPage(IotOtaTaskPageReqVO pageReqVO) { + default PageResult selectPage(IotOtaTaskPageReqVO pageReqVO) { return selectPage(pageReqVO, new LambdaQueryWrapperX() .eqIfPresent(IotOtaTaskDO::getFirmwareId, pageReqVO.getFirmwareId()) - .likeIfPresent(IotOtaTaskDO::getName, pageReqVO.getName())); + .likeIfPresent(IotOtaTaskDO::getName, pageReqVO.getName()) + .orderByDesc(IotOtaTaskDO::getId)); + } + + default int updateByIdAndStatus(Long id, Integer whereStatus, IotOtaTaskDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(IotOtaTaskDO::getId, id) + .eq(IotOtaTaskDO::getStatus, whereStatus)); } } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskRecordMapper.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskRecordMapper.java index 76c83beef1..2245b31a17 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskRecordMapper.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskRecordMapper.java @@ -8,6 +8,7 @@ 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.Collection; import java.util.List; import java.util.Set; @@ -27,10 +28,30 @@ public interface IotOtaTaskRecordMapper extends BaseMapperX .eqIfPresent(IotOtaTaskRecordDO::getStatus, pageReqVO.getStatus())); } - default void updateByTaskIdAndStatus(Long taskId, Integer fromStatus, IotOtaTaskRecordDO updateRecord) { - update(updateRecord, new LambdaUpdateWrapper() + default List selectListByTaskIdAndStatus(Long taskId, Collection statuses) { + return selectList(new LambdaQueryWrapperX() + .eq(IotOtaTaskRecordDO::getTaskId, taskId) + .in(IotOtaTaskRecordDO::getStatus, statuses)); + } + + default Long selectCountByTaskIdAndStatus(Long taskId, Collection statuses) { + return selectCount(new LambdaQueryWrapperX() .eq(IotOtaTaskRecordDO::getTaskId, taskId) - .eq(IotOtaTaskRecordDO::getStatus, fromStatus)); + .in(IotOtaTaskRecordDO::getStatus, statuses)); + } + + default int updateByIdAndStatus(Long id, Collection whereStatuses, + IotOtaTaskRecordDO updateObj) { + return update(updateObj, new LambdaUpdateWrapper() + .eq(IotOtaTaskRecordDO::getId, id) + .in(IotOtaTaskRecordDO::getStatus, whereStatuses)); + } + + default void updateListByIdAndStatus(Collection ids, Collection whereStatuses, + IotOtaTaskRecordDO updateObj) { + update(updateObj, new LambdaUpdateWrapper() + .in(IotOtaTaskRecordDO::getId, ids) + .in(IotOtaTaskRecordDO::getStatus, whereStatuses)); } default List selectListByDeviceIdAndStatus(Set deviceIds, Set statuses) { diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java index 63d4a253e4..21f53dba05 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java @@ -57,9 +57,10 @@ public interface ErrorCodeConstants { 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 ========== + // ========== OTA 升级任务记录相关 1-050-008-200 ========== ErrorCode OTA_TASK_RECORD_NOT_EXISTS = new ErrorCode(1_050_008_200, "升级记录不存在"); + ErrorCode OTA_TASK_RECORD_CANCEL_FAIL_STATUS_ERROR = new ErrorCode(1_050_008_201, "取消 OTA 升级记录失败,原因:记录状态不是进行中"); // ========== IoT 数据流转规则 1-050-010-000 ========== ErrorCode DATA_RULE_NOT_EXISTS = new ErrorCode(1_050_010_000, "数据流转规则不存在"); diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaTaskRecordStatusEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaTaskRecordStatusEnum.java index 8c423949b7..1bdc2c1c40 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaTaskRecordStatusEnum.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaTaskRecordStatusEnum.java @@ -32,8 +32,7 @@ public enum IotOtaTaskRecordStatusEnum implements ArrayValuable { public static final Set IN_PROCESS_STATUSES = SetUtils.asSet( PENDING.getStatus(), PUSHED.getStatus(), - UPGRADING.getStatus(), - SUCCESS.getStatus()); + UPGRADING.getStatus()); public static final List PRIORITY_STATUSES = Arrays.asList( SUCCESS.getStatus(), diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaTaskStatusEnum.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaTaskStatusEnum.java index 65147027e6..fc16e55a8f 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaTaskStatusEnum.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ota/IotOtaTaskStatusEnum.java @@ -16,7 +16,7 @@ import java.util.Arrays; public enum IotOtaTaskStatusEnum implements ArrayValuable { IN_PROGRESS(10), // 进行中(升级中) - COMPLETED(20), // 已完成(包括全部成功、部分成功) + END(20), // 已结束(包括全部成功、部分成功) CANCELED(30),; // 已取消(一般是主动取消任务) public static final Integer[] ARRAYS = Arrays.stream(values()) diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordService.java index ba5dc826c8..df5a873f7d 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordService.java @@ -65,4 +65,11 @@ public interface IotOtaTaskRecordService { */ List getOtaTaskRecordListByDeviceIdAndStatus(Set deviceIds, Set statuses); + /** + * 取消 OTA 升级记录 + * + * @param id 记录编号 + */ + void cancelOtaTaskRecord(Long id); + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordServiceImpl.java index 88b009caa3..27964a0395 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.iot.service.ota; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO; @@ -12,14 +13,12 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_TASK_RECORD_NOT_EXISTS; +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_TASK_RECORD_CANCEL_FAIL_STATUS_ERROR; /** * OTA 升级任务记录 Service 实现类 @@ -32,6 +31,9 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService { @Resource private IotOtaTaskRecordMapper otaTaskRecordMapper; + @Resource + private IotOtaTaskService otaTaskService; + @Override public void createOtaTaskRecordList(List devices, Long firmwareId, Long taskId) { List records = convertList(devices, device -> @@ -75,14 +77,16 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService { @Override public void cancelTaskRecordListByTaskId(Long taskId) { - // 设置取消记录的描述 - IotOtaTaskRecordDO updateRecord = IotOtaTaskRecordDO.builder() - .status(IotOtaTaskRecordStatusEnum.CANCELED.getStatus()) - .description("管理员取消升级任务") - .build(); - - otaTaskRecordMapper.updateByTaskIdAndStatus( - taskId, IotOtaTaskRecordStatusEnum.PENDING.getStatus(), updateRecord); + List records = otaTaskRecordMapper.selectListByTaskIdAndStatus( + taskId, IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES); + if (CollUtil.isEmpty(records)) { + return; + } + // 批量更新 + Collection ids = convertSet(records, IotOtaTaskRecordDO::getId); + otaTaskRecordMapper.updateListByIdAndStatus(ids, IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES, + IotOtaTaskRecordDO.builder().status(IotOtaTaskRecordStatusEnum.CANCELED.getStatus()) + .description(IotOtaTaskRecordDO.DESCRIPTION_CANCEL_BY_RECORD).build()); } @Override @@ -90,6 +94,23 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService { return otaTaskRecordMapper.selectListByDeviceIdAndStatus(deviceIds, statuses); } + @Override + public void cancelOtaTaskRecord(Long id) { + // 1. 校验记录是否存在 + IotOtaTaskRecordDO record = validateUpgradeRecordExists(id); + + // 2. 更新记录状态为取消 + int updateCount = otaTaskRecordMapper.updateByIdAndStatus(record.getId(), IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES, + IotOtaTaskRecordDO.builder().id(id).status(IotOtaTaskRecordStatusEnum.CANCELED.getStatus()) + .description(IotOtaTaskRecordDO.DESCRIPTION_CANCEL_BY_RECORD).build()); + if (updateCount == 0) { + throw exception(OTA_TASK_RECORD_CANCEL_FAIL_STATUS_ERROR); + } + + // 3. 检查并更新任务状态 + checkAndUpdateOtaTaskStatus(record.getTaskId()); + } + private IotOtaTaskRecordDO validateUpgradeRecordExists(Long id) { IotOtaTaskRecordDO upgradeRecord = otaTaskRecordMapper.selectById(id); if (upgradeRecord == null) { @@ -98,4 +119,20 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService { return upgradeRecord; } + /** + * 检查并更新任务状态 + * 如果任务下没有进行中的记录,则将任务状态更新为已结束 + */ + private void checkAndUpdateOtaTaskStatus(Long taskId) { + // 如果还有进行中的记录,直接返回 + Long inProcessCount = otaTaskRecordMapper.selectCountByTaskIdAndStatus( + taskId, IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES); + if (inProcessCount > 0) { + return; + } + + // 没有进行中的记录,将任务状态更新为已结束 + otaTaskService.updateOtaTaskStatusEnd(taskId); + } + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskService.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskService.java index 2e9153b7f8..7c3ce38e95 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskService.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskService.java @@ -44,4 +44,11 @@ public interface IotOtaTaskService { */ PageResult getOtaTaskPage(@Valid IotOtaTaskPageReqVO pageReqVO); + /** + * 更新 OTA 任务状态为已结束 + * + * @param id 任务编号 + */ + void updateOtaTaskStatusEnd(Long id); + } diff --git a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskServiceImpl.java b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskServiceImpl.java index 309cefa942..d6a9b9fda2 100644 --- a/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskServiceImpl.java +++ b/yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskServiceImpl.java @@ -99,7 +99,16 @@ public class IotOtaTaskServiceImpl implements IotOtaTaskService { @Override public PageResult getOtaTaskPage(IotOtaTaskPageReqVO pageReqVO) { - return otaTaskMapper.selectUpgradeTaskPage(pageReqVO); + return otaTaskMapper.selectPage(pageReqVO); + } + + @Override + public void updateOtaTaskStatusEnd(Long taskId) { + int updateCount = otaTaskMapper.updateByIdAndStatus(taskId, IotOtaTaskStatusEnum.IN_PROGRESS.getStatus(), + new IotOtaTaskDO().setStatus(IotOtaTaskStatusEnum.END.getStatus())); + if (updateCount == 0) { + log.warn("[updateOtaTaskStatusEnd][任务({})不存在或状态不是进行中,无法更新]", taskId); + } } private List validateOtaTaskDeviceScope(IotOtaTaskCreateReqVO createReqVO, Long productId) {