Commit 7670ac19e5b4cb096653919d18b2343602b844c3

Authored by YunaiV
1 parent e650e752

【代码新增】IoT:增加 plugin 插件的心跳机制,以及 Job 超时离线

Showing 16 changed files with 279 additions and 105 deletions
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/IotDeviceUpstreamApi.java
... ... @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
5 5 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
6 6 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
  7 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
7 8 import cn.iocoder.yudao.module.iot.enums.ApiConstants;
8 9 import jakarta.validation.Valid;
9 10 import org.springframework.web.bind.annotation.PostMapping;
... ... @@ -20,6 +21,8 @@ public interface IotDeviceUpstreamApi {
20 21  
21 22 String PREFIX = ApiConstants.PREFIX + "/device/upstream";
22 23  
  24 + // ========== 设备相关 ==========
  25 +
23 26 /**
24 27 * 更新设备状态
25 28 *
... ... @@ -44,4 +47,14 @@ public interface IotDeviceUpstreamApi {
44 47 @PostMapping(PREFIX + "/report-event")
45 48 CommonResult<Boolean> reportDeviceEvent(@Valid @RequestBody IotDeviceEventReportReqDTO reportReqDTO);
46 49  
  50 + // ========== 插件相关 ==========
  51 +
  52 + /**
  53 + * 心跳插件实例
  54 + *
  55 + * @param heartbeatReqDTO 心跳插件实例 DTO
  56 + */
  57 + @PostMapping(PREFIX + "/heartbeat-plugin-instance")
  58 + CommonResult<Boolean> heartbeatPluginInstance(@Valid @RequestBody IotPluginInstanceHeartbeatReqDTO heartbeatReqDTO);
  59 +
47 60 }
48 61 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotPluginInstanceHeartbeatReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
  2 +
  3 +import jakarta.validation.constraints.NotEmpty;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +/**
  8 + * IoT 插件实例心跳 Request DTO
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +public class IotPluginInstanceHeartbeatReqDTO {
  14 +
  15 + /**
  16 + * 请求编号
  17 + */
  18 + @NotEmpty(message = "请求编号不能为空")
  19 + private String processId;
  20 +
  21 + /**
  22 + * 插件包标识符
  23 + */
  24 + @NotEmpty(message = "插件包标识符不能为空")
  25 + private String pluginKey;
  26 +
  27 + /**
  28 + * 插件实例所在 IP
  29 + */
  30 + @NotEmpty(message = "插件实例所在 IP 不能为空")
  31 + private String hostIp;
  32 + /**
  33 + * 插件实例的进程编号
  34 + */
  35 + @NotNull(message = "插件实例的进程编号不能为空")
  36 + private Integer downstreamPort;
  37 +
  38 + /**
  39 + * 是否在线
  40 + */
  41 + @NotNull(message = "是否在线不能为空")
  42 + private Boolean online;
  43 +
  44 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java
... ... @@ -4,7 +4,9 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
5 5 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
6 6 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
  7 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
7 8 import cn.iocoder.yudao.module.iot.service.device.control.IotDeviceUpstreamService;
  9 +import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService;
8 10 import org.springframework.validation.annotation.Validated;
9 11 import org.springframework.web.bind.annotation.RestController;
10 12  
... ... @@ -21,6 +23,10 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
21 23  
22 24 @Resource
23 25 private IotDeviceUpstreamService deviceUpstreamService;
  26 + @Resource
  27 + private IotPluginInstanceService pluginInstanceService;
  28 +
  29 + // ========== 设备相关 ==========
24 30  
25 31 @Override
26 32 public CommonResult<Boolean> updateDeviceState(IotDeviceStateUpdateReqDTO updateReqDTO) {
... ... @@ -40,4 +46,12 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
40 46 return success(true);
41 47 }
42 48  
  49 + // ========== 插件相关 ==========
  50 +
  51 + @Override
  52 + public CommonResult<Boolean> heartbeatPluginInstance(IotPluginInstanceHeartbeatReqDTO heartbeatReqDTO) {
  53 + pluginInstanceService.heartbeatPluginInstance(heartbeatReqDTO);
  54 + return success(true);
  55 + }
  56 +
43 57 }
44 58 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/plugin/IotPluginInstanceDO.java
... ... @@ -67,6 +67,6 @@ public class IotPluginInstanceDO extends BaseDO {
67 67 *
68 68 * 目的:心路时间超过一定时间后,会被进行下线处理
69 69 */
70   - private Long heartbeatTime;
  70 + private LocalDateTime heartbeatTime;
71 71  
72 72 }
73 73 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/IotPluginInfoMapper.java
... ... @@ -25,4 +25,8 @@ public interface IotPluginInfoMapper extends BaseMapperX&lt;IotPluginInfoDO&gt; {
25 25 .orderByAsc(IotPluginInfoDO::getId));
26 26 }
27 27  
  28 + default IotPluginInfoDO selectByPluginKey(String pluginKey) {
  29 + return selectOne(IotPluginInfoDO::getPluginKey, pluginKey);
  30 + }
  31 +
28 32 }
29 33 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/plugin/IotPluginInstanceMapper.java
1 1 package cn.iocoder.yudao.module.iot.dal.mysql.plugin;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4   -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 4 import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInstanceDO;
  5 +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
6 6 import org.apache.ibatis.annotations.Mapper;
7 7  
  8 +import java.time.LocalDateTime;
  9 +import java.util.List;
  10 +
8 11 @Mapper
9 12 public interface IotPluginInstanceMapper extends BaseMapperX<IotPluginInstanceDO> {
10 13  
11   - // TODO @芋艿:方法名,重构
12   - default IotPluginInstanceDO selectByMainIdAndPluginId(String mainId, Long pluginId) {
13   - return selectOne(new LambdaQueryWrapperX<IotPluginInstanceDO>()
14   - .eq(IotPluginInstanceDO::getProcessId, mainId)
15   - .eq(IotPluginInstanceDO::getPluginId, pluginId));
  14 + default IotPluginInstanceDO selectByProcessId(String processId) {
  15 + return selectOne(IotPluginInstanceDO::getProcessId, processId);
  16 + }
  17 +
  18 + default List<IotPluginInstanceDO> selectListByHeartbeatTimeLt(LocalDateTime heartbeatTime) {
  19 + return selectList(new LambdaQueryWrapper<IotPluginInstanceDO>()
  20 + .lt(IotPluginInstanceDO::getHeartbeatTime, heartbeatTime));
16 21 }
17 22  
18 23 }
19 24 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/IotPluginInstancesJob.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.job.plugin;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
  5 +import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService;
  6 +import org.springframework.stereotype.Component;
  7 +
  8 +import javax.annotation.Resource;
  9 +import java.time.Duration;
  10 +import java.time.LocalDateTime;
  11 +
  12 +/**
  13 + * IoT 插件实例离线检查 Job
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Component
  18 +public class IotPluginInstancesJob implements JobHandler {
  19 +
  20 + /**
  21 + * 插件离线超时时间
  22 + *
  23 + * TODO 芋艿:暂定 10 分钟,后续看要不要做配置
  24 + */
  25 + public static final Duration OFFLINE_TIMEOUT = Duration.ofMinutes(10);
  26 +
  27 + @Resource
  28 + private IotPluginInstanceService pluginInstanceService;
  29 +
  30 + @Override
  31 + public String execute(String param) {
  32 + int count = pluginInstanceService.offlineTimeoutPluginInstance(
  33 + LocalDateTime.now().minus(OFFLINE_TIMEOUT));
  34 + return StrUtil.format("离线超时插件实例数量为: {}", count);
  35 + }
  36 +
  37 +}
0 38 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/plugin/PluginInstancesJob.java deleted
1   -package cn.iocoder.yudao.module.iot.job.plugin;
2   -
3   -import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
4   -import cn.iocoder.yudao.module.iot.service.plugin.IotPluginInstanceService;
5   -import org.springframework.scheduling.annotation.Scheduled;
6   -import org.springframework.stereotype.Component;
7   -
8   -import javax.annotation.Resource;
9   -import java.util.concurrent.TimeUnit;
10   -
11   -// TODO 芋艿:后续再看看
12   -/**
13   - * 插件实例 Job
14   - *
15   - * @author 芋道源码
16   - */
17   -@Component
18   -public class PluginInstancesJob {
19   -
20   - @Resource
21   - private IotPluginInstanceService pluginInstanceService;
22   -
23   - @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)
24   - public void updatePluginInstances() {
25   - TenantUtils.executeIgnore(() -> {
26   - pluginInstanceService.reportPluginInstances();
27   - });
28   - }
29   -
30   -}
31 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInfoService.java
... ... @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.plugin.vo.info.PluginInfoSav
6 6 import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInfoDO;
7 7 import cn.iocoder.yudao.module.iot.enums.plugin.IotPluginStatusEnum;
8 8 import jakarta.validation.Valid;
  9 +import jakarta.validation.constraints.NotEmpty;
9 10 import org.springframework.web.multipart.MultipartFile;
10 11  
11 12 import java.util.List;
... ... @@ -85,4 +86,7 @@ public interface IotPluginInfoService {
85 86 * @return 插件信息列表
86 87 */
87 88 List<IotPluginInfoDO> getPluginInfoListByStatus(Integer status);
  89 +
  90 + IotPluginInfoDO getPluginInfoByPluginKey(@NotEmpty(message = "插件包标识符不能为空") String pluginKey);
  91 +
88 92 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInfoServiceImpl.java
... ... @@ -41,6 +41,7 @@ public class IotPluginInfoServiceImpl implements IotPluginInfoService {
41 41  
42 42 @Override
43 43 public Long createPluginInfo(PluginInfoSaveReqVO createReqVO) {
  44 + // TODO @haohao:pluginKey 唯一值
44 45 IotPluginInfoDO pluginInfo = BeanUtils.toBean(createReqVO, IotPluginInfoDO.class);
45 46 pluginInfoMapper.insert(pluginInfo);
46 47 return pluginInfo.getId();
... ... @@ -155,4 +156,9 @@ public class IotPluginInfoServiceImpl implements IotPluginInfoService {
155 156 return pluginInfoMapper.selectListByStatus(status);
156 157 }
157 158  
  159 + @Override
  160 + public IotPluginInfoDO getPluginInfoByPluginKey(String pluginKey) {
  161 + return pluginInfoMapper.selectByPluginKey(pluginKey);
  162 + }
  163 +
158 164 }
159 165 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceService.java
1 1 package cn.iocoder.yudao.module.iot.service.plugin;
2 2  
  3 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
3 4 import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInfoDO;
4 5 import org.springframework.web.multipart.MultipartFile;
5 6  
  7 +import java.time.LocalDateTime;
  8 +
6 9 /**
7 10 * IoT 插件实例 Service 接口
8 11 *
... ... @@ -10,11 +13,19 @@ import org.springframework.web.multipart.MultipartFile;
10 13 */
11 14 public interface IotPluginInstanceService {
12 15  
13   - // TODO @芋艿:这个是否应该放到 plugin 主动心跳,而是 server 自己心跳
14 16 /**
15   - * 上报插件实例(心跳)
  17 + * 心跳插件实例
  18 + *
  19 + * @param heartbeatReqDTO 心跳插件实例 DTO
  20 + */
  21 + void heartbeatPluginInstance(IotPluginInstanceHeartbeatReqDTO heartbeatReqDTO);
  22 +
  23 + /**
  24 + * 离线超时插件实例
  25 + *
  26 + * @param maxHeartbeatTime 最大心跳时间
16 27 */
17   - void reportPluginInstances();
  28 + int offlineTimeoutPluginInstance(LocalDateTime maxHeartbeatTime);
18 29  
19 30 /**
20 31 * 停止并卸载插件
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugin/IotPluginInstanceServiceImpl.java
1 1 package cn.iocoder.yudao.module.iot.service.plugin;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.core.io.FileUtil;
4   -import cn.hutool.core.net.NetUtil;
5   -import cn.hutool.core.util.IdUtil;
  5 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
6 6 import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInfoDO;
7 7 import cn.iocoder.yudao.module.iot.dal.dataobject.plugin.IotPluginInstanceDO;
8   -import cn.iocoder.yudao.module.iot.dal.mysql.plugin.IotPluginInfoMapper;
9 8 import cn.iocoder.yudao.module.iot.dal.mysql.plugin.IotPluginInstanceMapper;
10 9 import cn.iocoder.yudao.module.iot.dal.redis.plugin.DevicePluginProcessIdRedisDAO;
11 10 import cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants;
... ... @@ -16,18 +15,20 @@ import org.pf4j.PluginState;
16 15 import org.pf4j.PluginWrapper;
17 16 import org.pf4j.spring.SpringPluginManager;
18 17 import org.springframework.beans.factory.annotation.Value;
  18 +import org.springframework.context.annotation.Lazy;
19 19 import org.springframework.stereotype.Service;
20 20 import org.springframework.validation.annotation.Validated;
21 21 import org.springframework.web.multipart.MultipartFile;
22 22  
23 23 import java.io.File;
24 24 import java.io.IOException;
25   -import java.nio.file.*;
  25 +import java.nio.file.Files;
  26 +import java.nio.file.Path;
  27 +import java.nio.file.Paths;
  28 +import java.nio.file.StandardCopyOption;
  29 +import java.time.LocalDateTime;
26 30 import java.util.List;
27   -import java.util.Map;
28 31 import java.util.concurrent.TimeUnit;
29   -import java.util.function.Function;
30   -import java.util.stream.Collectors;
31 32  
32 33 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
33 34  
... ... @@ -41,12 +42,9 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
41 42 @Slf4j
42 43 public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
43 44  
44   - // TODO @haohao:mac@uuid
45   - public static final String MAIN_ID = IdUtil.fastSimpleUUID();
46   -
47   - // TODO @haohao:不直接操作,通过 Service 哈
48 45 @Resource
49   - private IotPluginInfoMapper pluginInfoMapper;
  46 + @Lazy // 延迟加载,避免循环依赖
  47 + private IotPluginInfoService pluginInfoService;
50 48  
51 49 @Resource
52 50 private IotPluginInstanceMapper pluginInstanceMapper;
... ... @@ -59,8 +57,60 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
59 57  
60 58 @Value("${pf4j.pluginsDir}")
61 59 private String pluginsDir;
62   - @Value("${server.port:48080}")
63   - private int port;
  60 +
  61 + @Override
  62 + public void heartbeatPluginInstance(IotPluginInstanceHeartbeatReqDTO heartbeatReqDTO) {
  63 + // 情况一:已存在,则进行更新
  64 + IotPluginInstanceDO instance = pluginInstanceMapper.selectByProcessId(heartbeatReqDTO.getProcessId());
  65 + if (instance != null) {
  66 + IotPluginInstanceDO.IotPluginInstanceDOBuilder updateObj = IotPluginInstanceDO.builder().id(instance.getId())
  67 + .hostIp(heartbeatReqDTO.getHostIp()).downstreamPort(heartbeatReqDTO.getDownstreamPort())
  68 + .online(heartbeatReqDTO.getOnline()).heartbeatTime(LocalDateTime.now());
  69 + if (Boolean.TRUE.equals(heartbeatReqDTO.getOnline())) {
  70 + if (Boolean.FALSE.equals(instance.getOnline())) { // 当前处于离线时,才需要更新上线时间
  71 + updateObj.onlineTime(LocalDateTime.now());
  72 + }
  73 + } else {
  74 + updateObj.offlineTime(LocalDateTime.now());
  75 + }
  76 + pluginInstanceMapper.updateById(updateObj.build());
  77 + return;
  78 + }
  79 +
  80 + // 情况二:不存在,则创建
  81 + IotPluginInfoDO info = pluginInfoService.getPluginInfoByPluginKey(heartbeatReqDTO.getPluginKey());
  82 + if (info == null) {
  83 + log.error("[heartbeatPluginInstance][心跳({}) 对应的插件不存在]", heartbeatReqDTO);
  84 + return;
  85 + }
  86 + IotPluginInstanceDO.IotPluginInstanceDOBuilder insertObj = IotPluginInstanceDO.builder()
  87 + .pluginId(info.getId()).processId(heartbeatReqDTO.getProcessId())
  88 + .hostIp(heartbeatReqDTO.getHostIp()).downstreamPort(heartbeatReqDTO.getDownstreamPort())
  89 + .online(heartbeatReqDTO.getOnline()).heartbeatTime(LocalDateTime.now());
  90 + if (Boolean.TRUE.equals(heartbeatReqDTO.getOnline())) {
  91 + insertObj.onlineTime(LocalDateTime.now());
  92 + } else {
  93 + insertObj.offlineTime(LocalDateTime.now());
  94 + }
  95 + pluginInstanceMapper.insert(insertObj.build());
  96 + }
  97 +
  98 + @Override
  99 + public int offlineTimeoutPluginInstance(LocalDateTime maxHeartbeatTime) {
  100 + List<IotPluginInstanceDO> list = pluginInstanceMapper.selectListByHeartbeatTimeLt(maxHeartbeatTime);
  101 + if (CollUtil.isEmpty(list)) {
  102 + return 0;
  103 + }
  104 +
  105 + // 更新插件实例为离线
  106 + int count = 0;
  107 + for (IotPluginInstanceDO instance : list) {
  108 + pluginInstanceMapper.updateById(IotPluginInstanceDO.builder().id(instance.getId())
  109 + .online(false).offlineTime(LocalDateTime.now()).build());
  110 + count++;
  111 + }
  112 + return count;
  113 + }
64 114  
65 115 @Override
66 116 public void stopAndUnloadPlugin(String pluginKey) {
... ... @@ -146,57 +196,6 @@ public class IotPluginInstanceServiceImpl implements IotPluginInstanceService {
146 196 }
147 197 }
148 198  
149   - @Override
150   - public void reportPluginInstances() {
151   - // 1.1 获取 pf4j 插件列表
152   - List<PluginWrapper> plugins = pluginManager.getPlugins();
153   -
154   - // 1.2 获取插件信息列表并转换为 Map 以便快速查找
155   - List<IotPluginInfoDO> pluginInfos = pluginInfoMapper.selectList();
156   - Map<String, IotPluginInfoDO> pluginInfoMap = pluginInfos.stream()
157   - .collect(Collectors.toMap(IotPluginInfoDO::getPluginKey, Function.identity()));
158   -
159   - // 1.3 获取本机 IP 和 MAC 地址,mac@uuid
160   - String ip = NetUtil.getLocalhostStr();
161   - String mac = NetUtil.getLocalMacAddress();
162   - String mainId = mac + "@" + MAIN_ID;
163   -
164   - // 2. 遍历插件列表,并保存为插件实例
165   - for (PluginWrapper plugin : plugins) {
166   - String pluginKey = plugin.getPluginId();
167   -
168   - // 2.1 查找插件信息
169   - IotPluginInfoDO pluginInfo = pluginInfoMap.get(pluginKey);
170   - if (pluginInfo == null) {
171   - log.error("插件信息不存在,pluginKey = {}", pluginKey);
172   - continue;
173   - }
174   -
175   - // 2.2 情况一:如果插件实例不存在,则创建
176   - IotPluginInstanceDO pluginInstance = pluginInstanceMapper.selectByMainIdAndPluginId(mainId,
177   - pluginInfo.getId());
178   - // TODO @芋艿:稍后优化;
179   -// if (pluginInstance == null) {
180   -// // 4.4 如果插件实例不存在,则创建
181   -// pluginInstance = PluginInstanceDO.builder()
182   -// .pluginId(pluginInfo.getId())
183   -// .mainId(MAIN_ID + "-" + mac)
184   -// .hostIp(ip)
185   -// .port(port)
186   -// .heartbeatAt(System.currentTimeMillis())
187   -// .build();
188   -// pluginInstanceMapper.insert(pluginInstance);
189   -// } else {
190   -// // 2.2 情况二:如果存在,则更新 heartbeatAt
191   -// PluginInstanceDO updatePluginInstance = PluginInstanceDO.builder()
192   -// .id(pluginInstance.getId())
193   -// .heartbeatAt(System.currentTimeMillis())
194   -// .build();
195   -// pluginInstanceMapper.updateById(updatePluginInstance);
196   -// }
197   - }
198   - }
199   -
200 199 // ========== 设备与插件的映射操作 ==========
201 200  
202 201 @Override
... ...
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/config/IotPluginCommonAutoConfiguration.java
... ... @@ -3,11 +3,13 @@ package cn.iocoder.yudao.module.iot.plugin.common.config;
3 3 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
4 4 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamHandler;
5 5 import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer;
  6 +import cn.iocoder.yudao.module.iot.plugin.common.heartbeta.IotPluginInstanceHeartbeatJob;
6 7 import cn.iocoder.yudao.module.iot.plugin.common.upstream.IotDeviceUpstreamClient;
7 8 import org.springframework.beans.factory.annotation.Value;
8 9 import org.springframework.boot.autoconfigure.AutoConfiguration;
9 10 import org.springframework.boot.web.client.RestTemplateBuilder;
10 11 import org.springframework.context.annotation.Bean;
  12 +import org.springframework.scheduling.annotation.EnableScheduling;
11 13 import org.springframework.web.client.RestTemplate;
12 14  
13 15 import java.time.Duration;
... ... @@ -18,6 +20,7 @@ import java.time.Duration;
18 20 * @author haohao
19 21 */
20 22 @AutoConfiguration
  23 +@EnableScheduling // 开启定时任务,因为 IotPluginInstanceHeartbeatJob 是一个定时任务
21 24 public class IotPluginCommonAutoConfiguration {
22 25  
23 26 // TODO @haohao:这个要不搞个配置类哈
... ... @@ -55,4 +58,10 @@ public class IotPluginCommonAutoConfiguration {
55 58 return new IotDeviceDownstreamServer(deviceDownstreamHandler);
56 59 }
57 60  
  61 + @Bean(initMethod = "init", destroyMethod = "stop")
  62 + public IotPluginInstanceHeartbeatJob pluginInstanceHeartbeatJob(
  63 + IotDeviceUpstreamApi deviceDataApi, IotDeviceDownstreamServer deviceDownstreamServer) {
  64 + return new IotPluginInstanceHeartbeatJob(deviceDataApi, deviceDownstreamServer);
  65 + }
  66 +
58 67 }
59 68 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/heartbeta/IotPluginInstanceHeartbeatJob.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin.common.heartbeta;
  2 +
  3 +import cn.hutool.system.SystemUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
  6 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
  7 +import cn.iocoder.yudao.module.iot.plugin.common.downstream.IotDeviceDownstreamServer;
  8 +import cn.iocoder.yudao.module.iot.plugin.common.util.IotPluginCommonUtils;
  9 +import lombok.RequiredArgsConstructor;
  10 +import org.springframework.scheduling.annotation.Scheduled;
  11 +
  12 +import java.util.concurrent.TimeUnit;
  13 +
  14 +/**
  15 + * IoT 插件实例心跳 Job
  16 + *
  17 + * 用于定时发送心跳给服务端
  18 + */
  19 +@RequiredArgsConstructor
  20 +public class IotPluginInstanceHeartbeatJob {
  21 +
  22 + private final IotDeviceUpstreamApi deviceUpstreamApi;
  23 + private final IotDeviceDownstreamServer deviceDownstreamServer;
  24 +
  25 + public void init() {
  26 + CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(buildPluginInstanceHeartbeatReqDTO(true));
  27 + // TODO @芋艿:结果的处理
  28 + }
  29 +
  30 + public void stop() {
  31 + CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(buildPluginInstanceHeartbeatReqDTO(false));
  32 + // TODO @芋艿:结果的处理
  33 + }
  34 +
  35 + @Scheduled(initialDelay = 3, fixedRate = 3, timeUnit = TimeUnit.MINUTES) // 3 分钟执行一次
  36 + public void execute() {
  37 + CommonResult<Boolean> result = deviceUpstreamApi.heartbeatPluginInstance(buildPluginInstanceHeartbeatReqDTO(true));
  38 + // TODO @芋艿:结果的处理
  39 + }
  40 +
  41 + private IotPluginInstanceHeartbeatReqDTO buildPluginInstanceHeartbeatReqDTO(Boolean online) {
  42 + // TODO @芋艿:pluginKey 的获取???
  43 + return new IotPluginInstanceHeartbeatReqDTO()
  44 + .setPluginKey("yudao-module-iot-plugin-http").setProcessId(IotPluginCommonUtils.getProcessId())
  45 + .setHostIp(SystemUtil.getHostInfo().getAddress()).setDownstreamPort(deviceDownstreamServer.getPort())
  46 + .setOnline(online);
  47 + }
  48 +
  49 +}
... ...
yudao-module-iot/yudao-module-iot-plugins/yudao-module-iot-plugin-common/src/main/java/cn/iocoder/yudao/module/iot/plugin/common/upstream/IotDeviceUpstreamClient.java
... ... @@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
5 5 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
6 6 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
7 7 import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceStateUpdateReqDTO;
  8 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotPluginInstanceHeartbeatReqDTO;
8 9 import lombok.extern.slf4j.Slf4j;
9 10 import org.springframework.web.client.RestTemplate;
10 11  
... ... @@ -51,6 +52,12 @@ public class IotDeviceUpstreamClient implements IotDeviceUpstreamApi {
51 52 return doPost(url, reportReqDTO, "reportDevicePropertyData");
52 53 }
53 54  
  55 + @Override
  56 + public CommonResult<Boolean> heartbeatPluginInstance(IotPluginInstanceHeartbeatReqDTO heartbeatReqDTO) {
  57 + String url = deviceDataUrl + URL_PREFIX + "/heartbeat-plugin-instance";
  58 + return doPost(url, heartbeatReqDTO, "heartbeatPluginInstance");
  59 + }
  60 +
54 61 // TODO @haohao:未来可能有 get 类型哈
55 62 /**
56 63 * 将与远程服务交互的通用逻辑抽取成一个私有方法
... ...
yudao-server/src/main/resources/application.yaml
... ... @@ -284,6 +284,8 @@ yudao:
284 284 - infra_job
285 285 - infra_job_log
286 286 - infra_job_log
  287 + - iot_plugin_info
  288 + - iot_plugin_instance
287 289 - infra_data_source_config
288 290 - jimu_dict
289 291 - jimu_dict_item
... ...