Commit bc9b3715b1e6d03e6cb0aa117ec0e0afdd02137d

Authored by YunaiV
1 parent 4254c06c

【功能新增】IoT:设备拓扑图的添加

Showing 17 changed files with 161 additions and 18 deletions
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/IotDeviceUpstreamApi.java
... ... @@ -62,6 +62,15 @@ public interface IotDeviceUpstreamApi {
62 62 @PostMapping(PREFIX + "/register-sub")
63 63 CommonResult<Boolean> registerSubDevice(@Valid @RequestBody IotDeviceRegisterSubReqDTO registerReqDTO);
64 64  
  65 + // TODO @芋艿:这个需要 plugins 接入下
  66 + /**
  67 + * 注册设备拓扑
  68 + *
  69 + * @param addReqDTO 注册设备拓扑 DTO
  70 + */
  71 + @PostMapping(PREFIX + "/add-topology")
  72 + CommonResult<Boolean> addDeviceTopology(@Valid @RequestBody IotDeviceTopologyAddReqDTO addReqDTO);
  73 +
65 74 // ========== 插件相关 ==========
66 75  
67 76 /**
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceEventReportReqDTO.java
... ... @@ -7,6 +7,8 @@ import java.util.Map;
7 7  
8 8 /**
9 9 * IoT 设备【事件】上报 Request DTO
  10 + *
  11 + * @author 芋道源码
10 12 */
11 13 @Data
12 14 public class IotDeviceEventReportReqDTO extends IotDeviceUpstreamAbstractReqDTO {
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDevicePropertyReportReqDTO.java
... ... @@ -7,6 +7,8 @@ import java.util.Map;
7 7  
8 8 /**
9 9 * IoT 设备【属性】上报 Request DTO
  10 + *
  11 + * @author 芋道源码
10 12 */
11 13 @Data
12 14 public class IotDevicePropertyReportReqDTO extends IotDeviceUpstreamAbstractReqDTO {
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceRegisterSubReqDTO.java
... ... @@ -13,6 +13,7 @@ import java.util.List;
13 13 @Data
14 14 public class IotDeviceRegisterSubReqDTO extends IotDeviceUpstreamAbstractReqDTO {
15 15  
  16 + // TODO @芋艿:看看要不要优化命名
16 17 /**
17 18 * 子设备数组
18 19 */
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceStateUpdateReqDTO.java
... ... @@ -7,6 +7,8 @@ import lombok.Data;
7 7  
8 8 /**
9 9 * IoT 设备【状态】更新 Request DTO
  10 + *
  11 + * @author 芋道源码
10 12 */
11 13 @Data
12 14 public class IotDeviceStateUpdateReqDTO extends IotDeviceUpstreamAbstractReqDTO {
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/dto/control/upstream/IotDeviceTopologyAddReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.api.device.dto.control.upstream;
  2 +
  3 +import jakarta.validation.constraints.NotEmpty;
  4 +import lombok.Data;
  5 +
  6 +import java.util.List;
  7 +
  8 +/**
  9 + * IoT 设备【拓扑】添加 Request DTO
  10 + */
  11 +@Data
  12 +public class IotDeviceTopologyAddReqDTO extends IotDeviceUpstreamAbstractReqDTO {
  13 +
  14 + // TODO @芋艿:看看要不要优化命名
  15 + /**
  16 + * 子设备数组
  17 + */
  18 + @NotEmpty(message = "子设备不能为空")
  19 + private List<IotDeviceRegisterSubReqDTO.Device> params;
  20 +
  21 + /**
  22 + * 设备信息
  23 + */
  24 + @Data
  25 + public static class Device {
  26 +
  27 + /**
  28 + * 产品标识
  29 + */
  30 + @NotEmpty(message = "产品标识不能为空")
  31 + private String productKey;
  32 +
  33 + /**
  34 + * 设备名称
  35 + */
  36 + @NotEmpty(message = "设备名称不能为空")
  37 + private String deviceName;
  38 +
  39 + // TODO @芋艿:阿里云还有 sign 签名
  40 +
  41 + }
  42 +
  43 +}
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageIdentifierEnum.java
... ... @@ -30,7 +30,10 @@ public enum IotDeviceMessageIdentifierEnum {
30 30  
31 31 REGISTER_REGISTER("register"), // 上行
32 32 REGISTER_REGISTER_SUB("register_sub"), // 上行
33   - REGISTER_UNREGISTER_SUB("unregister_sub"),; // 下行
  33 + REGISTER_UNREGISTER_SUB("unregister_sub"), // 下行
  34 +
  35 + TOPOLOGY_ADD("topology_add"), // 下行;
  36 + ;
34 37  
35 38 /**
36 39 * 标志符
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/device/IotDeviceMessageTypeEnum.java
... ... @@ -19,7 +19,8 @@ public enum IotDeviceMessageTypeEnum implements ArrayValuable&lt;String&gt; {
19 19 SERVICE("service"), // 设备服务:可参考 https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services 设备属性、事件、服务
20 20 CONFIG("config"), // 设备配置:可参考 https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1 远程配置
21 21 OTA("ota"), // 设备 OTA:可参考 https://help.aliyun.com/zh/iot/user-guide/ota-update OTA 升级
22   - REGISTER("register"),; // 设备注册:可参考 https://help.aliyun.com/zh/iot/user-guide/register-devices 设备身份注册
  22 + REGISTER("register"), // 设备注册:可参考 https://help.aliyun.com/zh/iot/user-guide/register-devices 设备身份注册
  23 + TOPOLOGY("topology"),; // 设备拓扑:可参考 https://help.aliyun.com/zh/iot/user-guide/manage-topological-relationships 设备拓扑
23 24  
24 25 public static final String[] ARRAYS = Arrays.stream(values()).map(IotDeviceMessageTypeEnum::getType).toArray(String[]::new);
25 26  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceUpstreamApiImpl.java
... ... @@ -55,6 +55,12 @@ public class IoTDeviceUpstreamApiImpl implements IotDeviceUpstreamApi {
55 55 return success(true);
56 56 }
57 57  
  58 + @Override
  59 + public CommonResult<Boolean> addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO) {
  60 + deviceUpstreamService.addDeviceTopology(addReqDTO);
  61 + return success(true);
  62 + }
  63 +
58 64 // ========== 插件相关 ==========
59 65  
60 66 @Override
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceController.java
... ... @@ -61,6 +61,8 @@ public class IotDeviceController {
61 61 return success(true);
62 62 }
63 63  
  64 + // TODO @芋艿:参考阿里云:1)绑定网关;2)解绑网关
  65 +
64 66 @PutMapping("/update-group")
65 67 @Operation(summary = "更新设备分组")
66 68 @PreAuthorize("@ss.hasPermission('iot:device:update')")
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/message/IotDeviceMessage.java
... ... @@ -9,8 +9,9 @@ import lombok.NoArgsConstructor;
9 9  
10 10 import java.time.LocalDateTime;
11 11  
  12 +// TODO @芋艿:参考阿里云的物模型,优化 IoT 上下行消息的设计,尽量保持一致(渐进式,不要一口气)!
12 13 /**
13   - * 设备消息
  14 + * IoT 设备消息
14 15 */
15 16 @Data
16 17 @NoArgsConstructor
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
... ... @@ -30,12 +30,12 @@ public interface IotDeviceService {
30 30 *
31 31 * @param productKey 产品标识
32 32 * @param deviceName 设备名称
33   - * @param gatewayId 网关设备 ID
  33 + * @param gatewayId 网关设备 ID
34 34 * @return 设备
35 35 */
36 36 IotDeviceDO createDevice(@NotEmpty(message = "产品标识不能为空") String productKey,
37   - @NotEmpty(message = "设备名称不能为空") String deviceName,
38   - Long gatewayId);
  37 + @NotEmpty(message = "设备名称不能为空") String deviceName,
  38 + Long gatewayId);
39 39  
40 40 /**
41 41 * 更新设备
... ... @@ -58,7 +58,7 @@ public interface IotDeviceService {
58 58 /**
59 59 * 更新设备状态
60 60 *
61   - * @param id 编号
  61 + * @param id 编号
62 62 * @param state 状态
63 63 */
64 64 void updateDeviceState(Long id, Integer state);
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
... ... @@ -81,8 +81,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
81 81 public IotDeviceDO createDevice(String productKey, String deviceName, Long gatewayId) {
82 82 String deviceKey = generateDeviceKey();
83 83 // 1.1 校验产品是否存在
84   - IotProductDO product = TenantUtils.executeIgnore(() ->
85   - productService.getProductByProductKey(productKey));
  84 + IotProductDO product = TenantUtils.executeIgnore(() -> productService.getProductByProductKey(productKey));
86 85 if (product == null) {
87 86 throw exception(PRODUCT_NOT_EXISTS);
88 87 }
... ... @@ -100,7 +99,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
100 99 }
101 100  
102 101 private void validateCreateDeviceParam(String productKey, String deviceName, String deviceKey,
103   - Long gatewayId, IotProductDO product) {
  102 + Long gatewayId, IotProductDO product) {
104 103 TenantUtils.executeIgnore(() -> {
105 104 // 校验设备名称在同一产品下是否唯一
106 105 if (deviceMapper.selectByProductKeyAndDeviceName(productKey, deviceName) != null) {
... ... @@ -120,7 +119,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
120 119 }
121 120  
122 121 private void initDevice(IotDeviceDO device, IotProductDO product) {
123   - device.setProductId(product.getId()).setProductKey(product.getProductKey()).setDeviceType(product.getDeviceType());
  122 + device.setProductId(product.getId()).setProductKey(product.getProductKey())
  123 + .setDeviceType(product.getDeviceType());
124 124 // 生成并设置必要的字段
125 125 // TODO @芋艿:各种 mqtt 是不是可以简化!
126 126 device.setDeviceSecret(generateDeviceSecret())
... ... @@ -269,7 +269,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
269 269 // 2. 更新状态和时间
270 270 IotDeviceDO updateObj = new IotDeviceDO().setId(id).setState(state);
271 271 if (device.getOnlineTime() == null
272   - && Objects.equals(state, IotDeviceStateEnum.ONLINE.getState())) {
  272 + && Objects.equals(state, IotDeviceStateEnum.ONLINE.getState())) {
273 273 updateObj.setActiveTime(LocalDateTime.now());
274 274 }
275 275 if (Objects.equals(state, IotDeviceStateEnum.ONLINE.getState())) {
... ... @@ -363,7 +363,7 @@ public class IotDeviceServiceImpl implements IotDeviceService {
363 363 // 2.1.1 校验字段是否符合要求
364 364 try {
365 365 ValidationUtils.validate(importDevice);
366   - } catch (ConstraintViolationException ex){
  366 + } catch (ConstraintViolationException ex) {
367 367 respVO.getFailureDeviceNames().put(importDevice.getDeviceName(), ex.getMessage());
368 368 return;
369 369 }
... ... @@ -427,7 +427,8 @@ public class IotDeviceServiceImpl implements IotDeviceService {
427 427 }
428 428  
429 429 @CacheEvict(value = RedisKeyConstants.DEVICE, key = "#device.productKey + '_' + #device.deviceName")
430   - public void deleteDeviceCache0(IotDeviceDO device) {}
  430 + public void deleteDeviceCache0(IotDeviceDO device) {
  431 + }
431 432  
432 433 private IotDeviceServiceImpl getSelf() {
433 434 return SpringUtil.getBean(getClass());
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceDownstreamServiceImpl.java
... ... @@ -88,6 +88,7 @@ public class IotDeviceDownstreamServiceImpl implements IotDeviceDownstreamServic
88 88 if (Objects.equals(downstreamReqVO.getType(), IotDeviceMessageTypeEnum.OTA.getType())) {
89 89 return otaUpgrade(downstreamReqVO, device, parentDevice);
90 90 }
  91 + // TODO @芋艿:取消设备的网关的时,要不要下发 REGISTER_UNREGISTER_SUB ?
91 92 throw new IllegalArgumentException("不支持的下行消息类型:" + downstreamReqVO);
92 93 }
93 94  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamService.java
... ... @@ -55,4 +55,11 @@ public interface IotDeviceUpstreamService {
55 55 */
56 56 void registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO);
57 57  
  58 + /**
  59 + * 添加设备拓扑
  60 + *
  61 + * @param addReqDTO 添加设备拓扑 DTO
  62 + */
  63 + void addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO);
  64 +
58 65 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/control/IotDeviceUpstreamServiceImpl.java
... ... @@ -220,6 +220,7 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
220 220 if (CollUtil.isNotEmpty(registerReqDTO.getParams())) {
221 221 registerReqDTO.getParams().forEach(subDevice -> registerDevice0(
222 222 subDevice.getProductKey(), subDevice.getDeviceName(), device.getId(), registerReqDTO));
  223 + // TODO @芋艿:后续要处理,每个设备是否成功
223 224 }
224 225  
225 226 // 3. 发送设备消息
... ... @@ -230,6 +231,52 @@ public class IotDeviceUpstreamServiceImpl implements IotDeviceUpstreamService {
230 231 sendDeviceMessage(message, device);
231 232 }
232 233  
  234 + @Override
  235 + public void addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO) {
  236 + // 1.1 获得设备
  237 + log.info("[addDeviceTopology][添加设备拓扑: {}]", addReqDTO);
  238 + IotDeviceDO device = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
  239 + addReqDTO.getProductKey(), addReqDTO.getDeviceName());
  240 + if (device == null) {
  241 + log.error("[addDeviceTopology][设备({}/{}) 不存在]",
  242 + addReqDTO.getProductKey(), addReqDTO.getDeviceName());
  243 + return;
  244 + }
  245 + if (!IotProductDeviceTypeEnum.isGateway(device.getDeviceType())) {
  246 + log.error("[addDeviceTopology][设备({}/{}) 不是网关设备({}),无法进行拓扑添加]",
  247 + addReqDTO.getProductKey(), addReqDTO.getDeviceName(), device);
  248 + return;
  249 + }
  250 + // 1.2 记录设备的最后时间
  251 + updateDeviceLastTime(device, addReqDTO);
  252 +
  253 + // 2. 处理拓扑
  254 + if (CollUtil.isNotEmpty(addReqDTO.getParams())) {
  255 + TenantUtils.execute(device.getTenantId(), () -> {
  256 + addReqDTO.getParams().forEach(subDevice -> {
  257 + IotDeviceDO subDeviceDO = deviceService.getDeviceByProductKeyAndDeviceNameFromCache(
  258 + subDevice.getProductKey(), subDevice.getDeviceName());
  259 + // TODO @芋艿:后续要处理,每个设备是否成功
  260 + if (subDeviceDO == null) {
  261 + log.error("[addDeviceTopology][子设备({}/{}) 不存在]",
  262 + subDevice.getProductKey(), subDevice.getDeviceName());
  263 + return;
  264 + }
  265 + deviceService.updateDeviceGateway(subDeviceDO.getId(), device.getId());
  266 + log.info("[addDeviceTopology][子设备({}/{}) 添加到网关设备({}) 成功]",
  267 + subDevice.getProductKey(), subDevice.getDeviceName(), device);
  268 + });
  269 + });
  270 + }
  271 +
  272 + // 3. 发送设备消息
  273 + IotDeviceMessage message = BeanUtils.toBean(addReqDTO, IotDeviceMessage.class)
  274 + .setType(IotDeviceMessageTypeEnum.TOPOLOGY.getType())
  275 + .setIdentifier(IotDeviceMessageIdentifierEnum.TOPOLOGY_ADD.getIdentifier())
  276 + .setData(addReqDTO.getParams());
  277 + sendDeviceMessage(message, device);
  278 + }
  279 +
233 280 private void updateDeviceLastTime(IotDeviceDO device, IotDeviceUpstreamAbstractReqDTO reqDTO) {
234 281 // 1. 【异步】记录设备与插件实例的映射
235 282 pluginInstanceService.updateDevicePluginInstanceProcessIdAsync(device.getDeviceKey(), reqDTO.getProcessId());
... ...
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
... ... @@ -2,10 +2,7 @@ package cn.iocoder.yudao.module.iot.plugin.common.upstream;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 4 import cn.iocoder.yudao.module.iot.api.device.IotDeviceUpstreamApi;
5   -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDeviceEventReportReqDTO;
6   -import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.IotDevicePropertyReportReqDTO;
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;
  5 +import cn.iocoder.yudao.module.iot.api.device.dto.control.upstream.*;
9 6 import cn.iocoder.yudao.module.iot.plugin.common.config.IotPluginCommonProperties;
10 7 import lombok.RequiredArgsConstructor;
11 8 import lombok.extern.slf4j.Slf4j;
... ... @@ -42,6 +39,24 @@ public class IotDeviceUpstreamClient implements IotDeviceUpstreamApi {
42 39 return doPost(url, reportReqDTO);
43 40 }
44 41  
  42 + // TODO @芋艿:待实现
  43 + @Override
  44 + public CommonResult<Boolean> registerDevice(IotDeviceRegisterReqDTO registerReqDTO) {
  45 + return null;
  46 + }
  47 +
  48 + // TODO @芋艿:待实现
  49 + @Override
  50 + public CommonResult<Boolean> registerSubDevice(IotDeviceRegisterSubReqDTO registerReqDTO) {
  51 + return null;
  52 + }
  53 +
  54 + // TODO @芋艿:待实现
  55 + @Override
  56 + public CommonResult<Boolean> addDeviceTopology(IotDeviceTopologyAddReqDTO addReqDTO) {
  57 + return null;
  58 + }
  59 +
45 60 @Override
46 61 public CommonResult<Boolean> reportDeviceProperty(IotDevicePropertyReportReqDTO reportReqDTO) {
47 62 String url = properties.getUpstreamUrl() + URL_PREFIX + "/report-property";
... ...