Commit af1f993a4fa833cbf7f18b808f8d0f4d40ad034b

Authored by YunaiV
1 parent 14cbdad3

feat:【IoT 物联网】实现 OTA 升级推送任务 IotOtaUpgradeJob

yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/ota/IotOtaTaskRecordMapper.java
... ... @@ -40,6 +40,13 @@ public interface IotOtaTaskRecordMapper extends BaseMapperX<IotOtaTaskRecordDO>
40 40 .in(IotOtaTaskRecordDO::getStatus, statuses));
41 41 }
42 42  
  43 + default int updateByIdAndStatus(Long id, Integer status,
  44 + IotOtaTaskRecordDO updateObj) {
  45 + return update(updateObj, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
  46 + .eq(IotOtaTaskRecordDO::getId, id)
  47 + .eq(IotOtaTaskRecordDO::getStatus, status));
  48 + }
  49 +
43 50 default int updateByIdAndStatus(Long id, Collection<Integer> whereStatuses,
44 51 IotOtaTaskRecordDO updateObj) {
45 52 return update(updateObj, new LambdaUpdateWrapper<IotOtaTaskRecordDO>()
... ... @@ -60,4 +67,8 @@ public interface IotOtaTaskRecordMapper extends BaseMapperX&lt;IotOtaTaskRecordDO&gt;
60 67 .inIfPresent(IotOtaTaskRecordDO::getStatus, statuses));
61 68 }
62 69  
  70 + default List<IotOtaTaskRecordDO> selectListByStatus(Integer status) {
  71 + return selectList(IotOtaTaskRecordDO::getStatus, status);
  72 + }
  73 +
63 74 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeJob.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.job.ota;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
  6 +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
  7 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum;
  8 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  9 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
  10 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
  11 +import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
  12 +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
  13 +import cn.iocoder.yudao.module.iot.service.ota.IotOtaFirmwareService;
  14 +import cn.iocoder.yudao.module.iot.service.ota.IotOtaTaskRecordService;
  15 +import jakarta.annotation.Resource;
  16 +import lombok.extern.slf4j.Slf4j;
  17 +import org.springframework.stereotype.Component;
  18 +
  19 +import java.util.HashMap;
  20 +import java.util.List;
  21 +import java.util.Map;
  22 +
  23 +/**
  24 + * IoT OTA 升级推送 Job:查询待推送的 OTA 升级记录,并推送给设备
  25 + *
  26 + * @author 芋道源码
  27 + */
  28 +@Component
  29 +@Slf4j
  30 +public class IotOtaUpgradeJob implements JobHandler {
  31 +
  32 + @Resource
  33 + private IotOtaTaskRecordService otaTaskRecordService;
  34 + @Resource
  35 + private IotOtaFirmwareService otaFirmwareService;
  36 + @Resource
  37 + private IotDeviceService deviceService;
  38 +
  39 + @Override
  40 + @TenantJob
  41 + public String execute(String param) throws Exception {
  42 + // 1. 查询待推送的 OTA 升级记录
  43 + List<IotOtaTaskRecordDO> records = otaTaskRecordService.getOtaRecordListByStatus(
  44 + IotOtaTaskRecordStatusEnum.PENDING.getStatus());
  45 + if (CollUtil.isEmpty(records)) {
  46 + return null;
  47 + }
  48 +
  49 + // 2. 遍历推送记录
  50 + int successCount = 0;
  51 + int failureCount = 0;
  52 + Map<Long, IotOtaFirmwareDO> otaFirmwares = new HashMap<>();
  53 + for (IotOtaTaskRecordDO record : records) {
  54 + try {
  55 + // 2.1 设备如果不在线,直接跳过
  56 + IotDeviceDO device = deviceService.getDeviceFromCache(record.getDeviceId());
  57 + if (device == null || IotDeviceStateEnum.isNotOnline(device.getState())) {
  58 + continue;
  59 + }
  60 + // 2.2 获取 OTA 固件信息
  61 + IotOtaFirmwareDO fireware = otaFirmwares.get(record.getFirmwareId());
  62 + if (fireware == null) {
  63 + fireware = otaFirmwareService.getOtaFirmware(record.getFirmwareId());
  64 + otaFirmwares.put(record.getFirmwareId(), fireware);
  65 + }
  66 + // 2.3 推送 OTA 升级任务
  67 + boolean result = otaTaskRecordService.pushOtaTaskRecord(record, fireware, device);
  68 + if (result) {
  69 + successCount++;
  70 + } else {
  71 + failureCount++;
  72 + }
  73 + } catch (Exception e) {
  74 + failureCount++;
  75 + log.error("[execute][推送 OTA 升级任务({})发生异常]", record.getId(), e);
  76 + }
  77 + }
  78 + return StrUtil.format("升级任务推送成功:{} 条,送失败:{} 条", successCount, failureCount);
  79 + }
  80 +
  81 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordService.java
... ... @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.iot.service.ota;
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
5 5 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
6 7 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
7 8 import jakarta.validation.Valid;
8 9  
... ... @@ -66,10 +67,28 @@ public interface IotOtaTaskRecordService {
66 67 List<IotOtaTaskRecordDO> getOtaTaskRecordListByDeviceIdAndStatus(Set<Long> deviceIds, Set<Integer> statuses);
67 68  
68 69 /**
  70 + * 根据记录状态,获取 OTA 升级记录列表
  71 + *
  72 + * @param status 升级记录状态
  73 + * @return 升级记录列表
  74 + */
  75 + List<IotOtaTaskRecordDO> getOtaRecordListByStatus(Integer status);
  76 +
  77 + /**
69 78 * 取消 OTA 升级记录
70 79 *
71 80 * @param id 记录编号
72 81 */
73 82 void cancelOtaTaskRecord(Long id);
74 83  
  84 + /**
  85 + * 推送 OTA 升级任务记录
  86 + *
  87 + * @param record 任务记录
  88 + * @param fireware 固件信息
  89 + * @param device 设备信息
  90 + * @return 是否推送成功
  91 + */
  92 + boolean pushOtaTaskRecord(IotOtaTaskRecordDO record, IotOtaFirmwareDO fireware, IotDeviceDO device);
  93 +
75 94 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordServiceImpl.java
... ... @@ -2,12 +2,18 @@ package cn.iocoder.yudao.module.iot.service.ota;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.convert.Convert;
  5 +import cn.hutool.core.lang.Assert;
  6 +import cn.hutool.core.util.StrUtil;
5 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
6 8 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
  9 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
7 10 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  11 +import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
8 12 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
9 13 import cn.iocoder.yudao.module.iot.dal.mysql.ota.IotOtaTaskRecordMapper;
10 14 import cn.iocoder.yudao.module.iot.enums.ota.IotOtaTaskRecordStatusEnum;
  15 +import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
  16 +import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
11 17 import jakarta.annotation.Resource;
12 18 import lombok.extern.slf4j.Slf4j;
13 19 import org.springframework.stereotype.Service;
... ... @@ -33,6 +39,10 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
33 39  
34 40 @Resource
35 41 private IotOtaTaskService otaTaskService;
  42 + @Resource
  43 + private IotDeviceMessageService deviceMessageService;
  44 + @Resource
  45 + private IotDeviceService deviceService;
36 46  
37 47 @Override
38 48 public void createOtaTaskRecordList(List<IotDeviceDO> devices, Long firmwareId, Long taskId) {
... ... @@ -86,7 +96,7 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
86 96 Collection<Long> ids = convertSet(records, IotOtaTaskRecordDO::getId);
87 97 otaTaskRecordMapper.updateListByIdAndStatus(ids, IotOtaTaskRecordStatusEnum.IN_PROCESS_STATUSES,
88 98 IotOtaTaskRecordDO.builder().status(IotOtaTaskRecordStatusEnum.CANCELED.getStatus())
89   - .description(IotOtaTaskRecordDO.DESCRIPTION_CANCEL_BY_RECORD).build());
  99 + .description(IotOtaTaskRecordDO.DESCRIPTION_CANCEL_BY_TASK).build());
90 100 }
91 101  
92 102 @Override
... ... @@ -95,6 +105,11 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
95 105 }
96 106  
97 107 @Override
  108 + public List<IotOtaTaskRecordDO> getOtaRecordListByStatus(Integer status) {
  109 + return otaTaskRecordMapper.selectListByStatus(status);
  110 + }
  111 +
  112 + @Override
98 113 public void cancelOtaTaskRecord(Long id) {
99 114 // 1. 校验记录是否存在
100 115 IotOtaTaskRecordDO record = validateUpgradeRecordExists(id);
... ... @@ -111,6 +126,30 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
111 126 checkAndUpdateOtaTaskStatus(record.getTaskId());
112 127 }
113 128  
  129 + @Override
  130 + public boolean pushOtaTaskRecord(IotOtaTaskRecordDO record, IotOtaFirmwareDO fireware, IotDeviceDO device) {
  131 + try {
  132 + // 1. 推送 OTA 任务记录
  133 + IotDeviceMessage message = IotDeviceMessage.buildOtaUpgrade(
  134 + fireware.getVersion(), fireware.getFileUrl(), fireware.getFileSize(),
  135 + fireware.getFileDigestAlgorithm(), fireware.getFileDigestValue());
  136 + deviceMessageService.sendDeviceMessage(message, device);
  137 +
  138 + // 2. 更新 OTA 升级记录状态为进行中
  139 + int updateCount = otaTaskRecordMapper.updateByIdAndStatus(
  140 + record.getId(), IotOtaTaskRecordStatusEnum.PENDING.getStatus(),
  141 + IotOtaTaskRecordDO.builder().status(IotOtaTaskRecordStatusEnum.PUSHED.getStatus())
  142 + .description(StrUtil.format("已推送,设备消息编号({})", message.getId())).build());
  143 + Assert.isTrue(updateCount == 1, "更新设备记录({})状态失败", record.getId());
  144 + return true;
  145 + } catch (Exception ex) {
  146 + log.error("[pushOtaTaskRecord][推送 OTA 任务记录({}) 失败]", record.getId(), ex);
  147 + otaTaskRecordMapper.updateById(IotOtaTaskRecordDO.builder().id(record.getId())
  148 + .description(StrUtil.format("推送失败,错误信息({})", ex.getMessage())).build());
  149 + return false;
  150 + }
  151 + }
  152 +
114 153 private IotOtaTaskRecordDO validateUpgradeRecordExists(Long id) {
115 154 IotOtaTaskRecordDO upgradeRecord = otaTaskRecordMapper.selectById(id);
116 155 if (upgradeRecord == null) {
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskService.java
... ... @@ -46,7 +46,7 @@ public interface IotOtaTaskService {
46 46  
47 47 /**
48 48 * 更新 OTA 任务状态为已结束
49   - *
  49 + *
50 50 * @param id 任务编号
51 51 */
52 52 void updateOtaTaskStatusEnd(Long id);
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java
... ... @@ -42,6 +42,10 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable&lt;String&gt; {
42 42  
43 43 CONFIG_PUSH("thing.config.push", "配置推送", true),
44 44  
  45 + // ========== OTA 固件 ==========
  46 +
  47 + OTA_UPGRADE("thing.ota.upgrade", "OTA 推送固定信息", false),
  48 + OTA_PROGRESS("thing.ota.progress", "OTA 上报升级进度", true),
45 49 ;
46 50  
47 51 public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageMethodEnum::getMethod)
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceStateEnum.java
... ... @@ -39,4 +39,8 @@ public enum IotDeviceStateEnum implements ArrayValuable&lt;Integer&gt; {
39 39 return ONLINE.getState().equals(state);
40 40 }
41 41  
  42 + public static boolean isNotOnline(Integer state) {
  43 + return !isOnline(state);
  44 + }
  45 +
42 46 }
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java
... ... @@ -140,4 +140,12 @@ public class IotDeviceMessage {
140 140 MapUtil.of("state", IotDeviceStateEnum.OFFLINE.getState()));
141 141 }
142 142  
  143 + public static IotDeviceMessage buildOtaUpgrade(String version, String fileUrl, Long fileSize,
  144 + String fileDigestAlgorithm, String fileDigestValue) {
  145 + return requestOf(IotDeviceMessageMethodEnum.OTA_UPGRADE.getMethod(), MapUtil.builder()
  146 + .put("version", version).put("fileUrl", fileUrl).put("fileSize", fileSize)
  147 + .put("fileDigestAlgorithm", fileDigestAlgorithm).put("fileDigestValue", fileDigestValue)
  148 + .build());
  149 + }
  150 +
143 151 }
... ...