Commit 14cbdad374608249a65c4dd8fccd7f8e80de2ed8

Authored by YunaiV
1 parent e5ce1952

feat:【IoT 物联网】增加 OTA 相关的任务、记录取消

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;
22 22 import org.springframework.security.access.prepost.PreAuthorize;
23 23 import org.springframework.validation.annotation.Validated;
24 24 import org.springframework.web.bind.annotation.GetMapping;
  25 +import org.springframework.web.bind.annotation.PutMapping;
25 26 import org.springframework.web.bind.annotation.RequestMapping;
26 27 import org.springframework.web.bind.annotation.RequestParam;
27 28 import org.springframework.web.bind.annotation.RestController;
... ... @@ -90,4 +91,13 @@ public class IotOtaTaskRecordController {
90 91 return success(BeanUtils.toBean(upgradeRecord, IotOtaTaskRecordRespVO.class));
91 92 }
92 93  
  94 + @PutMapping("/cancel")
  95 + @Operation(summary = "取消 OTA 升级记录")
  96 + @PreAuthorize("@ss.hasPermission('iot:ota-task-record:cancel')")
  97 + @Parameter(name = "id", description = "升级记录编号", required = true, example = "1024")
  98 + public CommonResult<Boolean> cancelOtaTaskRecord(@RequestParam("id") Long id) {
  99 + otaTaskRecordService.cancelOtaTaskRecord(id);
  100 + return success(true);
  101 + }
  102 +
93 103 }
... ...
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;
25 25 @AllArgsConstructor
26 26 public class IotOtaTaskRecordDO extends BaseDO {
27 27  
  28 + public static final String DESCRIPTION_CANCEL_BY_TASK = "管理员手动取消升级任务(批量)";
  29 +
  30 + public static final String DESCRIPTION_CANCEL_BY_RECORD = "管理员手动取消升级记录(单个)";
  31 +
  32 + /**
  33 + * 升级记录编号
  34 + */
28 35 @TableId
29 36 private Long id;
30 37  
... ...
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;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 6 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.IotOtaTaskPageReqVO;
7 7 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskDO;
  8 +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
8 9 import org.apache.ibatis.annotations.Mapper;
9 10  
10 11 @Mapper
... ... @@ -15,10 +16,17 @@ public interface IotOtaTaskMapper extends BaseMapperX&lt;IotOtaTaskDO&gt; {
15 16 IotOtaTaskDO::getName, name);
16 17 }
17 18  
18   - default PageResult<IotOtaTaskDO> selectUpgradeTaskPage(IotOtaTaskPageReqVO pageReqVO) {
  19 + default PageResult<IotOtaTaskDO> selectPage(IotOtaTaskPageReqVO pageReqVO) {
19 20 return selectPage(pageReqVO, new LambdaQueryWrapperX<IotOtaTaskDO>()
20 21 .eqIfPresent(IotOtaTaskDO::getFirmwareId, pageReqVO.getFirmwareId())
21   - .likeIfPresent(IotOtaTaskDO::getName, pageReqVO.getName()));
  22 + .likeIfPresent(IotOtaTaskDO::getName, pageReqVO.getName())
  23 + .orderByDesc(IotOtaTaskDO::getId));
  24 + }
  25 +
  26 + default int updateByIdAndStatus(Long id, Integer whereStatus, IotOtaTaskDO updateObj) {
  27 + return update(updateObj, new LambdaUpdateWrapper<IotOtaTaskDO>()
  28 + .eq(IotOtaTaskDO::getId, id)
  29 + .eq(IotOtaTaskDO::getStatus, whereStatus));
22 30 }
23 31  
24 32 }
... ...
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;
8 8 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
9 9 import org.apache.ibatis.annotations.Mapper;
10 10  
  11 +import java.util.Collection;
11 12 import java.util.List;
12 13 import java.util.Set;
13 14  
... ... @@ -27,10 +28,30 @@ public interface IotOtaTaskRecordMapper extends BaseMapperX&lt;IotOtaTaskRecordDO&gt;
27 28 .eqIfPresent(IotOtaTaskRecordDO::getStatus, pageReqVO.getStatus()));
28 29 }
29 30  
30   - default void updateByTaskIdAndStatus(Long taskId, Integer fromStatus, IotOtaTaskRecordDO updateRecord) {
31   - update(updateRecord, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
  31 + default List<IotOtaTaskRecordDO> selectListByTaskIdAndStatus(Long taskId, Collection<Integer> statuses) {
  32 + return selectList(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
  33 + .eq(IotOtaTaskRecordDO::getTaskId, taskId)
  34 + .in(IotOtaTaskRecordDO::getStatus, statuses));
  35 + }
  36 +
  37 + default Long selectCountByTaskIdAndStatus(Long taskId, Collection<Integer> statuses) {
  38 + return selectCount(new LambdaQueryWrapperX<IotOtaTaskRecordDO>()
32 39 .eq(IotOtaTaskRecordDO::getTaskId, taskId)
33   - .eq(IotOtaTaskRecordDO::getStatus, fromStatus));
  40 + .in(IotOtaTaskRecordDO::getStatus, statuses));
  41 + }
  42 +
  43 + default int updateByIdAndStatus(Long id, Collection<Integer> whereStatuses,
  44 + IotOtaTaskRecordDO updateObj) {
  45 + return update(updateObj, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
  46 + .eq(IotOtaTaskRecordDO::getId, id)
  47 + .in(IotOtaTaskRecordDO::getStatus, whereStatuses));
  48 + }
  49 +
  50 + default void updateListByIdAndStatus(Collection<Long> ids, Collection<Integer> whereStatuses,
  51 + IotOtaTaskRecordDO updateObj) {
  52 + update(updateObj, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
  53 + .in(IotOtaTaskRecordDO::getId, ids)
  54 + .in(IotOtaTaskRecordDO::getStatus, whereStatuses));
34 55 }
35 56  
36 57 default List<IotOtaTaskRecordDO> selectListByDeviceIdAndStatus(Set<Long> deviceIds, Set<Integer> statuses) {
... ...
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 {
57 57 ErrorCode OTA_TASK_CREATE_FAIL_DEVICE_EMPTY = new ErrorCode(1_050_008_103, "创建 OTA 任务失败,原因:没有可升级的设备");
58 58 ErrorCode OTA_TASK_CANCEL_FAIL_STATUS_END = new ErrorCode(1_050_008_104, "取消 OTA 任务失败,原因:任务状态不是进行中");
59 59  
60   - // ========== OTA 升级任务相关 1-050-008-100 ==========
  60 + // ========== OTA 升级任务记录相关 1-050-008-200 ==========
61 61  
62 62 ErrorCode OTA_TASK_RECORD_NOT_EXISTS = new ErrorCode(1_050_008_200, "升级记录不存在");
  63 + ErrorCode OTA_TASK_RECORD_CANCEL_FAIL_STATUS_ERROR = new ErrorCode(1_050_008_201, "取消 OTA 升级记录失败,原因:记录状态不是进行中");
63 64  
64 65 // ========== IoT 数据流转规则 1-050-010-000 ==========
65 66 ErrorCode DATA_RULE_NOT_EXISTS = new ErrorCode(1_050_010_000, "数据流转规则不存在");
... ...
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&lt;Integer&gt; {
32 32 public static final Set<Integer> IN_PROCESS_STATUSES = SetUtils.asSet(
33 33 PENDING.getStatus(),
34 34 PUSHED.getStatus(),
35   - UPGRADING.getStatus(),
36   - SUCCESS.getStatus());
  35 + UPGRADING.getStatus());
37 36  
38 37 public static final List<Integer> PRIORITY_STATUSES = Arrays.asList(
39 38 SUCCESS.getStatus(),
... ...
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;
16 16 public enum IotOtaTaskStatusEnum implements ArrayValuable<Integer> {
17 17  
18 18 IN_PROGRESS(10), // 进行中(升级中)
19   - COMPLETED(20), // 已完成(包括全部成功、部分成功)
  19 + END(20), // 已结束(包括全部成功、部分成功)
20 20 CANCELED(30),; // 已取消(一般是主动取消任务)
21 21  
22 22 public static final Integer[] ARRAYS = Arrays.stream(values())
... ...
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 {
65 65 */
66 66 List<IotOtaTaskRecordDO> getOtaTaskRecordListByDeviceIdAndStatus(Set<Long> deviceIds, Set<Integer> statuses);
67 67  
  68 + /**
  69 + * 取消 OTA 升级记录
  70 + *
  71 + * @param id 记录编号
  72 + */
  73 + void cancelOtaTaskRecord(Long id);
  74 +
68 75 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordServiceImpl.java
1 1 package cn.iocoder.yudao.module.iot.service.ota;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.core.convert.Convert;
4 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 6 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
... ... @@ -12,14 +13,12 @@ import lombok.extern.slf4j.Slf4j;
12 13 import org.springframework.stereotype.Service;
13 14 import org.springframework.validation.annotation.Validated;
14 15  
15   -import java.util.Arrays;
16   -import java.util.List;
17   -import java.util.Map;
18   -import java.util.Set;
  16 +import java.util.*;
19 17  
20 18 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
21 19 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
22 20 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_TASK_RECORD_NOT_EXISTS;
  21 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.OTA_TASK_RECORD_CANCEL_FAIL_STATUS_ERROR;
23 22  
24 23 /**
25 24 * OTA 升级任务记录 Service 实现类
... ... @@ -32,6 +31,9 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
32 31 @Resource
33 32 private IotOtaTaskRecordMapper otaTaskRecordMapper;
34 33  
  34 + @Resource
  35 + private IotOtaTaskService otaTaskService;
  36 +
35 37 @Override
36 38 public void createOtaTaskRecordList(List<IotDeviceDO> devices, Long firmwareId, Long taskId) {
37 39 List<IotOtaTaskRecordDO> records = convertList(devices, device ->
... ... @@ -75,14 +77,16 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
75 77  
76 78 @Override
77 79 public void cancelTaskRecordListByTaskId(Long taskId) {
78   - // 设置取消记录的描述
79   - IotOtaTaskRecordDO updateRecord = IotOtaTaskRecordDO.builder()
80   - .status(IotOtaTaskRecordStatusEnum.CANCELED.getStatus())
81   - .description("管理员取消升级任务")
82   - .build();
83   -
84   - otaTaskRecordMapper.updateByTaskIdAndStatus(
85   - taskId, IotOtaTaskRecordStatusEnum.PENDING.getStatus(), updateRecord);
  80 + List<IotOtaTaskRecordDO> records = otaTaskRecordMapper.selectListByTaskIdAndStatus(
  81 + taskId, IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES);
  82 + if (CollUtil.isEmpty(records)) {
  83 + return;
  84 + }
  85 + // 批量更新
  86 + Collection<Long> ids = convertSet(records, IotOtaTaskRecordDO::getId);
  87 + otaTaskRecordMapper.updateListByIdAndStatus(ids, IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES,
  88 + IotOtaTaskRecordDO.builder().status(IotOtaTaskRecordStatusEnum.CANCELED.getStatus())
  89 + .description(IotOtaTaskRecordDO.DESCRIPTION_CANCEL_BY_RECORD).build());
86 90 }
87 91  
88 92 @Override
... ... @@ -90,6 +94,23 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
90 94 return otaTaskRecordMapper.selectListByDeviceIdAndStatus(deviceIds, statuses);
91 95 }
92 96  
  97 + @Override
  98 + public void cancelOtaTaskRecord(Long id) {
  99 + // 1. 校验记录是否存在
  100 + IotOtaTaskRecordDO record = validateUpgradeRecordExists(id);
  101 +
  102 + // 2. 更新记录状态为取消
  103 + int updateCount = otaTaskRecordMapper.updateByIdAndStatus(record.getId(), IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES,
  104 + IotOtaTaskRecordDO.builder().id(id).status(IotOtaTaskRecordStatusEnum.CANCELED.getStatus())
  105 + .description(IotOtaTaskRecordDO.DESCRIPTION_CANCEL_BY_RECORD).build());
  106 + if (updateCount == 0) {
  107 + throw exception(OTA_TASK_RECORD_CANCEL_FAIL_STATUS_ERROR);
  108 + }
  109 +
  110 + // 3. 检查并更新任务状态
  111 + checkAndUpdateOtaTaskStatus(record.getTaskId());
  112 + }
  113 +
93 114 private IotOtaTaskRecordDO validateUpgradeRecordExists(Long id) {
94 115 IotOtaTaskRecordDO upgradeRecord = otaTaskRecordMapper.selectById(id);
95 116 if (upgradeRecord == null) {
... ... @@ -98,4 +119,20 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
98 119 return upgradeRecord;
99 120 }
100 121  
  122 + /**
  123 + * 检查并更新任务状态
  124 + * 如果任务下没有进行中的记录,则将任务状态更新为已结束
  125 + */
  126 + private void checkAndUpdateOtaTaskStatus(Long taskId) {
  127 + // 如果还有进行中的记录,直接返回
  128 + Long inProcessCount = otaTaskRecordMapper.selectCountByTaskIdAndStatus(
  129 + taskId, IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES);
  130 + if (inProcessCount > 0) {
  131 + return;
  132 + }
  133 +
  134 + // 没有进行中的记录,将任务状态更新为已结束
  135 + otaTaskService.updateOtaTaskStatusEnd(taskId);
  136 + }
  137 +
101 138 }
... ...
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 {
44 44 */
45 45 PageResult<IotOtaTaskDO> getOtaTaskPage(@Valid IotOtaTaskPageReqVO pageReqVO);
46 46  
  47 + /**
  48 + * 更新 OTA 任务状态为已结束
  49 + *
  50 + * @param id 任务编号
  51 + */
  52 + void updateOtaTaskStatusEnd(Long id);
  53 +
47 54 }
... ...
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 {
99 99  
100 100 @Override
101 101 public PageResult<IotOtaTaskDO> getOtaTaskPage(IotOtaTaskPageReqVO pageReqVO) {
102   - return otaTaskMapper.selectUpgradeTaskPage(pageReqVO);
  102 + return otaTaskMapper.selectPage(pageReqVO);
  103 + }
  104 +
  105 + @Override
  106 + public void updateOtaTaskStatusEnd(Long taskId) {
  107 + int updateCount = otaTaskMapper.updateByIdAndStatus(taskId, IotOtaTaskStatusEnum.IN_PROGRESS.getStatus(),
  108 + new IotOtaTaskDO().setStatus(IotOtaTaskStatusEnum.END.getStatus()));
  109 + if (updateCount == 0) {
  110 + log.warn("[updateOtaTaskStatusEnd][任务({})不存在或状态不是进行中,无法更新]", taskId);
  111 + }
103 112 }
104 113  
105 114 private List<IotDeviceDO> validateOtaTaskDeviceScope(IotOtaTaskCreateReqVO createReqVO, Long productId) {
... ...