Commit af1f993a4fa833cbf7f18b808f8d0f4d40ad034b
1 parent
14cbdad3
feat:【IoT 物联网】实现 OTA 升级推送任务 IotOtaUpgradeJob
Showing
8 changed files
with
168 additions
and
2 deletions
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<IotOtaTaskRecordDO> |
| 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
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<String> { |
| 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
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 | } | ... | ... |