Commit 78bcd54e34c1e55b8f92d2e515e383bc1d3060ac

Authored by 芋道源码
Committed by Gitee
2 parents 6c2cd0ec bf3b19d3

!1518 接入 modbus 协议,重构所有协议的配置

Merge pull request !1518 from 芋道源码/feature/iot
Showing 238 changed files with 14808 additions and 7751 deletions

Too many changes.

To preserve performance only 100 of 238 files are displayed.

yudao-module-iot/yudao-module-iot-biz/pom.xml
@@ -101,18 +101,6 @@ @@ -101,18 +101,6 @@
101 <artifactId>spring-boot-starter-amqp</artifactId> 101 <artifactId>spring-boot-starter-amqp</artifactId>
102 <optional>true</optional> 102 <optional>true</optional>
103 </dependency> 103 </dependency>
104 -  
105 - <!-- IoT 网络组件:接收来自设备的上行数据 -->  
106 - <!-- <dependency>-->  
107 - <!-- <groupId>cn.iocoder.boot</groupId>-->  
108 - <!-- <artifactId>yudao-module-iot-net-component-http</artifactId>-->  
109 - <!-- <version>${revision}</version>-->  
110 - <!-- </dependency>-->  
111 - <!-- <dependency>-->  
112 - <!-- <groupId>cn.iocoder.boot</groupId>-->  
113 - <!-- <artifactId>yudao-module-iot-net-component-emqx</artifactId>-->  
114 - <!-- <version>${revision}</version>-->  
115 - <!-- </dependency>-->  
116 </dependencies> 104 </dependencies>
117 105
118 </project> 106 </project>
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java
1 package cn.iocoder.yudao.module.iot.api.device; 1 package cn.iocoder.yudao.module.iot.api.device;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
  5 +import cn.hutool.core.util.StrUtil;
3 import cn.iocoder.yudao.framework.common.enums.RpcConstants; 6 import cn.iocoder.yudao.framework.common.enums.RpcConstants;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 7 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
6 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; 10 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
7 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;  
8 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;  
9 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;  
10 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; 11 +import cn.iocoder.yudao.module.iot.core.biz.dto.*;
11 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; 12 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
12 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; 13 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
13 import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; 14 import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO;
14 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 15 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  16 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
  17 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
15 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; 18 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
  19 +import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusConfigService;
  20 +import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService;
16 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 21 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
17 import cn.iocoder.yudao.module.iot.service.product.IotProductService; 22 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
18 import jakarta.annotation.Resource; 23 import jakarta.annotation.Resource;
19 import jakarta.annotation.security.PermitAll; 24 import jakarta.annotation.security.PermitAll;
  25 +import org.springframework.context.annotation.Lazy;
20 import org.springframework.context.annotation.Primary; 26 import org.springframework.context.annotation.Primary;
21 import org.springframework.validation.annotation.Validated; 27 import org.springframework.validation.annotation.Validated;
22 import org.springframework.web.bind.annotation.PostMapping; 28 import org.springframework.web.bind.annotation.PostMapping;
23 import org.springframework.web.bind.annotation.RequestBody; 29 import org.springframework.web.bind.annotation.RequestBody;
24 import org.springframework.web.bind.annotation.RestController; 30 import org.springframework.web.bind.annotation.RestController;
25 31
  32 +import java.util.ArrayList;
26 import java.util.List; 33 import java.util.List;
  34 +import java.util.Map;
  35 +import java.util.Set;
27 36
28 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 37 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  38 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
29 39
30 /** 40 /**
31 * IoT 设备 API 实现类 41 * IoT 设备 API 实现类
@@ -41,6 +51,12 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { @@ -41,6 +51,12 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
41 private IotDeviceService deviceService; 51 private IotDeviceService deviceService;
42 @Resource 52 @Resource
43 private IotProductService productService; 53 private IotProductService productService;
  54 + @Resource
  55 + @Lazy // 延迟加载,解决循环依赖
  56 + private IotDeviceModbusConfigService modbusConfigService;
  57 + @Resource
  58 + @Lazy // 延迟加载,解决循环依赖
  59 + private IotDeviceModbusPointService modbusPointService;
44 60
45 @Override 61 @Override
46 @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth") 62 @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth")
@@ -58,12 +74,58 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi { @@ -58,12 +74,58 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
58 return success(BeanUtils.toBean(device, IotDeviceRespDTO.class, deviceDTO -> { 74 return success(BeanUtils.toBean(device, IotDeviceRespDTO.class, deviceDTO -> {
59 IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId()); 75 IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId());
60 if (product != null) { 76 if (product != null) {
61 - deviceDTO.setCodecType(product.getCodecType()); 77 + deviceDTO.setProtocolType(product.getProtocolType()).setSerializeType(product.getSerializeType());
62 } 78 }
63 })); 79 }));
64 } 80 }
65 81
66 @Override 82 @Override
  83 + @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/modbus/config-list")
  84 + @PermitAll
  85 + @TenantIgnore
  86 + public CommonResult<List<IotModbusDeviceConfigRespDTO>> getModbusDeviceConfigList(
  87 + @RequestBody IotModbusDeviceConfigListReqDTO listReqDTO) {
  88 + // 1. 获取 Modbus 连接配置
  89 + List<IotDeviceModbusConfigDO> configList = modbusConfigService.getDeviceModbusConfigList(listReqDTO);
  90 + if (CollUtil.isEmpty(configList)) {
  91 + return success(new ArrayList<>());
  92 + }
  93 +
  94 + // 2. 组装返回结果
  95 + Set<Long> deviceIds = convertSet(configList, IotDeviceModbusConfigDO::getDeviceId);
  96 + Map<Long, IotDeviceDO> deviceMap = deviceService.getDeviceMap(deviceIds);
  97 + Map<Long, List<IotDeviceModbusPointDO>> pointMap = modbusPointService.getEnabledDeviceModbusPointMapByDeviceIds(deviceIds);
  98 + Map<Long, IotProductDO> productMap = productService.getProductMap(convertSet(deviceMap.values(), IotDeviceDO::getProductId));
  99 + List<IotModbusDeviceConfigRespDTO> result = new ArrayList<>(configList.size());
  100 + for (IotDeviceModbusConfigDO config : configList) {
  101 + // 3.1 获取设备信息
  102 + IotDeviceDO device = deviceMap.get(config.getDeviceId());
  103 + if (device == null) {
  104 + continue;
  105 + }
  106 + // 3.2 按 protocolType 筛选(如果非空)
  107 + if (StrUtil.isNotEmpty(listReqDTO.getProtocolType())) {
  108 + IotProductDO product = productMap.get(device.getProductId());
  109 + if (product == null || ObjUtil.notEqual(listReqDTO.getProtocolType(), product.getProtocolType())) {
  110 + continue;
  111 + }
  112 + }
  113 + // 3.3 获取启用的点位列表
  114 + List<IotDeviceModbusPointDO> pointList = pointMap.get(config.getDeviceId());
  115 + if (CollUtil.isEmpty(pointList)) {
  116 + continue;
  117 + }
  118 +
  119 + // 3.4 构建 IotModbusDeviceConfigRespDTO 对象
  120 + IotModbusDeviceConfigRespDTO configDTO = BeanUtils.toBean(config, IotModbusDeviceConfigRespDTO.class, o ->
  121 + o.setProductKey(device.getProductKey()).setDeviceName(device.getDeviceName())
  122 + .setPoints(BeanUtils.toBean(pointList, IotModbusPointRespDTO.class)));
  123 + result.add(configDTO);
  124 + }
  125 + return success(result);
  126 + }
  127 +
  128 + @Override
67 @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/register") 129 @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/register")
68 @PermitAll 130 @PermitAll
69 public CommonResult<IotDeviceRegisterRespDTO> registerDevice(@RequestBody IotDeviceRegisterReqDTO reqDTO) { 131 public CommonResult<IotDeviceRegisterRespDTO> registerDevice(@RequestBody IotDeviceRegisterReqDTO reqDTO) {
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceModbusConfigController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.device;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigRespVO;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
  8 +import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusConfigService;
  9 +import io.swagger.v3.oas.annotations.Operation;
  10 +import io.swagger.v3.oas.annotations.Parameter;
  11 +import io.swagger.v3.oas.annotations.tags.Tag;
  12 +import jakarta.annotation.Resource;
  13 +import jakarta.validation.Valid;
  14 +import org.springframework.security.access.prepost.PreAuthorize;
  15 +import org.springframework.validation.annotation.Validated;
  16 +import org.springframework.web.bind.annotation.*;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  19 +
  20 +@Tag(name = "管理后台 - IoT 设备 Modbus 连接配置")
  21 +@RestController
  22 +@RequestMapping("/iot/device-modbus-config")
  23 +@Validated
  24 +public class IotDeviceModbusConfigController {
  25 +
  26 + @Resource
  27 + private IotDeviceModbusConfigService modbusConfigService;
  28 +
  29 + @PostMapping("/save")
  30 + @Operation(summary = "保存设备 Modbus 连接配置")
  31 + @PreAuthorize("@ss.hasPermission('iot:device:update')")
  32 + public CommonResult<Boolean> saveDeviceModbusConfig(@Valid @RequestBody IotDeviceModbusConfigSaveReqVO saveReqVO) {
  33 + modbusConfigService.saveDeviceModbusConfig(saveReqVO);
  34 + return success(true);
  35 + }
  36 +
  37 + @GetMapping("/get")
  38 + @Operation(summary = "获得设备 Modbus 连接配置")
  39 + @Parameter(name = "id", description = "编号", example = "1024")
  40 + @Parameter(name = "deviceId", description = "设备编号", example = "2048")
  41 + @PreAuthorize("@ss.hasPermission('iot:device:query')")
  42 + public CommonResult<IotDeviceModbusConfigRespVO> getDeviceModbusConfig(
  43 + @RequestParam(value = "id", required = false) Long id,
  44 + @RequestParam(value = "deviceId", required = false) Long deviceId) {
  45 + IotDeviceModbusConfigDO modbusConfig = null;
  46 + if (id != null) {
  47 + modbusConfig = modbusConfigService.getDeviceModbusConfig(id);
  48 + } else if (deviceId != null) {
  49 + modbusConfig = modbusConfigService.getDeviceModbusConfigByDeviceId(deviceId);
  50 + }
  51 + return success(BeanUtils.toBean(modbusConfig, IotDeviceModbusConfigRespVO.class));
  52 + }
  53 +
  54 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/IotDeviceModbusPointController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.device;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO;
  7 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointRespVO;
  8 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointSaveReqVO;
  9 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
  10 +import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService;
  11 +import io.swagger.v3.oas.annotations.Operation;
  12 +import io.swagger.v3.oas.annotations.Parameter;
  13 +import io.swagger.v3.oas.annotations.tags.Tag;
  14 +import jakarta.annotation.Resource;
  15 +import jakarta.validation.Valid;
  16 +import org.springframework.security.access.prepost.PreAuthorize;
  17 +import org.springframework.validation.annotation.Validated;
  18 +import org.springframework.web.bind.annotation.*;
  19 +
  20 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  21 +
  22 +@Tag(name = "管理后台 - IoT 设备 Modbus 点位配置")
  23 +@RestController
  24 +@RequestMapping("/iot/device-modbus-point")
  25 +@Validated
  26 +public class IotDeviceModbusPointController {
  27 +
  28 + @Resource
  29 + private IotDeviceModbusPointService modbusPointService;
  30 +
  31 + @PostMapping("/create")
  32 + @Operation(summary = "创建设备 Modbus 点位配置")
  33 + @PreAuthorize("@ss.hasPermission('iot:device:update')")
  34 + public CommonResult<Long> createDeviceModbusPoint(@Valid @RequestBody IotDeviceModbusPointSaveReqVO createReqVO) {
  35 + return success(modbusPointService.createDeviceModbusPoint(createReqVO));
  36 + }
  37 +
  38 + @PutMapping("/update")
  39 + @Operation(summary = "更新设备 Modbus 点位配置")
  40 + @PreAuthorize("@ss.hasPermission('iot:device:update')")
  41 + public CommonResult<Boolean> updateDeviceModbusPoint(@Valid @RequestBody IotDeviceModbusPointSaveReqVO updateReqVO) {
  42 + modbusPointService.updateDeviceModbusPoint(updateReqVO);
  43 + return success(true);
  44 + }
  45 +
  46 + @DeleteMapping("/delete")
  47 + @Operation(summary = "删除设备 Modbus 点位配置")
  48 + @Parameter(name = "id", description = "编号", required = true)
  49 + @PreAuthorize("@ss.hasPermission('iot:device:update')")
  50 + public CommonResult<Boolean> deleteDeviceModbusPoint(@RequestParam("id") Long id) {
  51 + modbusPointService.deleteDeviceModbusPoint(id);
  52 + return success(true);
  53 + }
  54 +
  55 + @GetMapping("/get")
  56 + @Operation(summary = "获得设备 Modbus 点位配置")
  57 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  58 + @PreAuthorize("@ss.hasPermission('iot:device:query')")
  59 + public CommonResult<IotDeviceModbusPointRespVO> getDeviceModbusPoint(@RequestParam("id") Long id) {
  60 + IotDeviceModbusPointDO modbusPoint = modbusPointService.getDeviceModbusPoint(id);
  61 + return success(BeanUtils.toBean(modbusPoint, IotDeviceModbusPointRespVO.class));
  62 + }
  63 +
  64 + @GetMapping("/page")
  65 + @Operation(summary = "获得设备 Modbus 点位配置分页")
  66 + @PreAuthorize("@ss.hasPermission('iot:device:query')")
  67 + public CommonResult<PageResult<IotDeviceModbusPointRespVO>> getDeviceModbusPointPage(@Valid IotDeviceModbusPointPageReqVO pageReqVO) {
  68 + PageResult<IotDeviceModbusPointDO> pageResult = modbusPointService.getDeviceModbusPointPage(pageReqVO);
  69 + return success(BeanUtils.toBean(pageResult, IotDeviceModbusPointRespVO.class));
  70 + }
  71 +
  72 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/device/IotDevicePageReqVO.java
@@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device; @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.controller.admin.device.vo.device;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageParam; 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 import cn.iocoder.yudao.framework.common.validation.InEnum; 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
5 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; 5 +import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
6 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum; 6 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
7 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
8 import lombok.Data; 8 import lombok.Data;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/message/IotDeviceMessageRespVO.java
@@ -38,7 +38,7 @@ public class IotDeviceMessageRespVO { @@ -38,7 +38,7 @@ public class IotDeviceMessageRespVO {
38 @Schema(description = "请求编号", example = "req_123") 38 @Schema(description = "请求编号", example = "req_123")
39 private String requestId; 39 private String requestId;
40 40
41 - @Schema(description = "请求方法", requiredMode = Schema.RequiredMode.REQUIRED, example = "thing.property.report") 41 + @Schema(description = "请求方法", requiredMode = Schema.RequiredMode.REQUIRED, example = "thing.property.post")
42 private String method; 42 private String method;
43 43
44 @Schema(description = "请求参数") 44 @Schema(description = "请求参数")
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusConfigRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - IoT 设备 Modbus 连接配置 Response VO")
  9 +@Data
  10 +public class IotDeviceModbusConfigRespVO {
  11 +
  12 + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  13 + private Long id;
  14 +
  15 + @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  16 + private Long deviceId;
  17 +
  18 + @Schema(description = "设备名称", example = "温湿度传感器")
  19 + private String deviceName;
  20 +
  21 + @Schema(description = "Modbus 服务器 IP 地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "192.168.1.100")
  22 + private String ip;
  23 +
  24 + @Schema(description = "Modbus 端口", requiredMode = Schema.RequiredMode.REQUIRED, example = "502")
  25 + private Integer port;
  26 +
  27 + @Schema(description = "从站地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  28 + private Integer slaveId;
  29 +
  30 + @Schema(description = "连接超时时间(毫秒)", example = "3000")
  31 + private Integer timeout;
  32 +
  33 + @Schema(description = "重试间隔(毫秒)", example = "1000")
  34 + private Integer retryInterval;
  35 +
  36 + @Schema(description = "工作模式", example = "1")
  37 + private Integer mode;
  38 +
  39 + @Schema(description = "数据帧格式", example = "1")
  40 + private Integer frameFormat;
  41 +
  42 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  43 + private Integer status;
  44 +
  45 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  46 + private LocalDateTime createTime;
  47 +
  48 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusConfigSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusFrameFormatEnum;
  5 +import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusModeEnum;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import jakarta.validation.constraints.NotNull;
  8 +import lombok.Data;
  9 +
  10 +@Schema(description = "管理后台 - IoT 设备 Modbus 连接配置新增/修改 Request VO")
  11 +@Data
  12 +public class IotDeviceModbusConfigSaveReqVO {
  13 +
  14 + @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  15 + @NotNull(message = "设备编号不能为空")
  16 + private Long deviceId;
  17 +
  18 + @Schema(description = "Modbus 服务器 IP 地址", example = "192.168.1.100")
  19 + private String ip;
  20 +
  21 + @Schema(description = "Modbus 端口", example = "502")
  22 + private Integer port;
  23 +
  24 + @Schema(description = "从站地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  25 + @NotNull(message = "从站地址不能为空")
  26 + private Integer slaveId;
  27 +
  28 + @Schema(description = "连接超时时间(毫秒)", example = "3000")
  29 + private Integer timeout;
  30 +
  31 + @Schema(description = "重试间隔(毫秒)", example = "1000")
  32 + private Integer retryInterval;
  33 +
  34 + @Schema(description = "工作模式", example = "1")
  35 + @InEnum(IotModbusModeEnum.class)
  36 + private Integer mode;
  37 +
  38 + @Schema(description = "数据帧格式", example = "1")
  39 + @InEnum(IotModbusFrameFormatEnum.class)
  40 + private Integer frameFormat;
  41 +
  42 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  43 + @NotNull(message = "状态不能为空")
  44 + private Integer status;
  45 +
  46 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import lombok.ToString;
  8 +
  9 +@Schema(description = "管理后台 - IoT 设备 Modbus 点位配置分页 Request VO")
  10 +@Data
  11 +@EqualsAndHashCode(callSuper = true)
  12 +@ToString(callSuper = true)
  13 +public class IotDeviceModbusPointPageReqVO extends PageParam {
  14 +
  15 + @Schema(description = "设备编号", example = "1024")
  16 + private Long deviceId;
  17 +
  18 + @Schema(description = "属性标识符", example = "temperature")
  19 + private String identifier;
  20 +
  21 + @Schema(description = "属性名称", example = "温度")
  22 + private String name;
  23 +
  24 + @Schema(description = "Modbus 功能码", example = "3")
  25 + private Integer functionCode;
  26 +
  27 + @Schema(description = "状态", example = "0")
  28 + private Integer status;
  29 +
  30 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.math.BigDecimal;
  7 +import java.time.LocalDateTime;
  8 +
  9 +@Schema(description = "管理后台 - IoT 设备 Modbus 点位配置 Response VO")
  10 +@Data
  11 +public class IotDeviceModbusPointRespVO {
  12 +
  13 + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  14 + private Long id;
  15 +
  16 + @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  17 + private Long deviceId;
  18 +
  19 + @Schema(description = "物模型属性编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  20 + private Long thingModelId;
  21 +
  22 + @Schema(description = "属性标识符", requiredMode = Schema.RequiredMode.REQUIRED, example = "temperature")
  23 + private String identifier;
  24 +
  25 + @Schema(description = "属性名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "温度")
  26 + private String name;
  27 +
  28 + @Schema(description = "Modbus 功能码", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
  29 + private Integer functionCode;
  30 +
  31 + @Schema(description = "寄存器起始地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  32 + private Integer registerAddress;
  33 +
  34 + @Schema(description = "寄存器数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  35 + private Integer registerCount;
  36 +
  37 + @Schema(description = "字节序", requiredMode = Schema.RequiredMode.REQUIRED, example = "AB")
  38 + private String byteOrder;
  39 +
  40 + @Schema(description = "原始数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "INT16")
  41 + private String rawDataType;
  42 +
  43 + @Schema(description = "缩放因子", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
  44 + private BigDecimal scale;
  45 +
  46 + @Schema(description = "轮询间隔(毫秒)", requiredMode = Schema.RequiredMode.REQUIRED, example = "5000")
  47 + private Integer pollInterval;
  48 +
  49 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  50 + private Integer status;
  51 +
  52 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  53 + private LocalDateTime createTime;
  54 +
  55 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/device/vo/modbus/IotDeviceModbusPointSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import java.math.BigDecimal;
  9 +
  10 +@Schema(description = "管理后台 - IoT 设备 Modbus 点位配置新增/修改 Request VO")
  11 +@Data
  12 +public class IotDeviceModbusPointSaveReqVO {
  13 +
  14 + @Schema(description = "主键", example = "1")
  15 + private Long id;
  16 +
  17 + @Schema(description = "设备编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  18 + @NotNull(message = "设备编号不能为空")
  19 + private Long deviceId;
  20 +
  21 + @Schema(description = "物模型属性编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  22 + @NotNull(message = "物模型属性编号不能为空")
  23 + private Long thingModelId;
  24 +
  25 + @Schema(description = "Modbus 功能码", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
  26 + @NotNull(message = "Modbus 功能码不能为空")
  27 + private Integer functionCode;
  28 +
  29 + @Schema(description = "寄存器起始地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  30 + @NotNull(message = "寄存器起始地址不能为空")
  31 + private Integer registerAddress;
  32 +
  33 + @Schema(description = "寄存器数量", example = "1")
  34 + private Integer registerCount;
  35 +
  36 + @Schema(description = "字节序", requiredMode = Schema.RequiredMode.REQUIRED, example = "AB")
  37 + @NotEmpty(message = "字节序不能为空")
  38 + private String byteOrder;
  39 +
  40 + @Schema(description = "原始数据类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "INT16")
  41 + @NotEmpty(message = "原始数据类型不能为空")
  42 + private String rawDataType;
  43 +
  44 + @Schema(description = "缩放因子", example = "1.0")
  45 + private BigDecimal scale;
  46 +
  47 + @Schema(description = "轮询间隔(毫秒)", example = "5000")
  48 + private Integer pollInterval;
  49 +
  50 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  51 + @NotNull(message = "状态不能为空")
  52 + private Integer status;
  53 +
  54 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.http 0 → 100644
  1 +### 请求 /iot/product/sync-property-table 接口 => 成功
  2 +POST {{baseUrl}}/iot/product/sync-property-table
  3 +Content-Type: application/json
  4 +tenant-id: {{adminTenantId}}
  5 +Authorization: Bearer {{token}}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/IotProductController.java
@@ -141,6 +141,14 @@ public class IotProductController { @@ -141,6 +141,14 @@ public class IotProductController {
141 result.getData().getList()); 141 result.getData().getList());
142 } 142 }
143 143
  144 + @PostMapping("/sync-property-table")
  145 + @Operation(summary = "同步产品属性表结构到 TDengine")
  146 + @PreAuthorize("@ss.hasPermission('iot:product:update')")
  147 + public CommonResult<Boolean> syncProductPropertyTable() {
  148 + productService.syncProductPropertyTable();
  149 + return success(true);
  150 + }
  151 +
144 @GetMapping("/simple-list") 152 @GetMapping("/simple-list")
145 @Operation(summary = "获取产品的精简信息列表", description = "主要用于前端的下拉选项") 153 @Operation(summary = "获取产品的精简信息列表", description = "主要用于前端的下拉选项")
146 @Parameter(name = "deviceType", description = "设备类型", example = "1") 154 @Parameter(name = "deviceType", description = "设备类型", example = "1")
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductRespVO.java
@@ -67,10 +67,15 @@ public class IotProductRespVO { @@ -67,10 +67,15 @@ public class IotProductRespVO {
67 @DictFormat(DictTypeConstants.NET_TYPE) 67 @DictFormat(DictTypeConstants.NET_TYPE)
68 private Integer netType; 68 private Integer netType;
69 69
70 - @Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")  
71 - @ExcelProperty(value = "数据格式", converter = DictConvert.class)  
72 - @DictFormat(DictTypeConstants.CODEC_TYPE)  
73 - private String codecType; 70 + @Schema(description = "协议类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "mqtt")
  71 + @ExcelProperty(value = "协议类型", converter = DictConvert.class)
  72 + @DictFormat(DictTypeConstants.PROTOCOL_TYPE)
  73 + private String protocolType;
  74 +
  75 + @Schema(description = "序列化类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "json")
  76 + @ExcelProperty(value = "序列化类型", converter = DictConvert.class)
  77 + @DictFormat(DictTypeConstants.SERIALIZE_TYPE)
  78 + private String serializeType;
74 79
75 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 80 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
76 @ExcelProperty("创建时间") 81 @ExcelProperty("创建时间")
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/product/vo/product/IotProductSaveReqVO.java
1 package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product; 1 package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product;
2 2
3 import cn.iocoder.yudao.framework.common.validation.InEnum; 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum;
4 import cn.iocoder.yudao.module.iot.enums.product.IotNetTypeEnum; 6 import cn.iocoder.yudao.module.iot.enums.product.IotNetTypeEnum;
5 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum; 7 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
6 import io.swagger.v3.oas.annotations.media.Schema; 8 import io.swagger.v3.oas.annotations.media.Schema;
@@ -44,9 +46,15 @@ public class IotProductSaveReqVO { @@ -44,9 +46,15 @@ public class IotProductSaveReqVO {
44 @InEnum(value = IotNetTypeEnum.class, message = "联网方式必须是 {value}") 46 @InEnum(value = IotNetTypeEnum.class, message = "联网方式必须是 {value}")
45 private Integer netType; 47 private Integer netType;
46 48
47 - @Schema(description = "数据格式", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")  
48 - @NotEmpty(message = "数据格式不能为空")  
49 - private String codecType; 49 + @Schema(description = "协议类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "mqtt")
  50 + @InEnum(value = IotProtocolTypeEnum.class, message = "协议类型必须是 {value}")
  51 + @NotEmpty(message = "协议类型不能为空")
  52 + private String protocolType;
  53 +
  54 + @Schema(description = "序列化类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "json")
  55 + @InEnum(value = IotSerializeTypeEnum.class, message = "序列化类型必须是 {value}")
  56 + @NotEmpty(message = "序列化类型不能为空")
  57 + private String serializeType;
50 58
51 @Schema(description = "是否开启动态注册", example = "false") 59 @Schema(description = "是否开启动态注册", example = "false")
52 @NotNull(message = "是否开启动态注册不能为空") 60 @NotNull(message = "是否开启动态注册不能为空")
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/statistics/IotStatisticsController.java
@@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; @@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
5 import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageReqVO; 5 import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageReqVO;
6 import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageSummaryByDateRespVO; 6 import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageSummaryByDateRespVO;
7 import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsSummaryRespVO; 7 import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsSummaryRespVO;
8 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; 8 +import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
9 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; 9 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
10 import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService; 10 import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
11 import cn.iocoder.yudao.module.iot.service.product.IotProductCategoryService; 11 import cn.iocoder.yudao.module.iot.service.product.IotProductCategoryService;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceDO.java
@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.type.LongSetTypeHandler; @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.type.LongSetTypeHandler;
4 import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; 4 import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
5 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO; 5 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
6 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; 6 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
7 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; 7 +import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
8 import com.baomidou.mybatisplus.annotation.KeySequence; 8 import com.baomidou.mybatisplus.annotation.KeySequence;
9 import com.baomidou.mybatisplus.annotation.TableField; 9 import com.baomidou.mybatisplus.annotation.TableField;
10 import com.baomidou.mybatisplus.annotation.TableId; 10 import com.baomidou.mybatisplus.annotation.TableId;
@@ -109,10 +109,6 @@ public class IotDeviceDO extends TenantBaseDO { @@ -109,10 +109,6 @@ public class IotDeviceDO extends TenantBaseDO {
109 private LocalDateTime activeTime; 109 private LocalDateTime activeTime;
110 110
111 /** 111 /**
112 - * 设备的 IP 地址  
113 - */  
114 - private String ip;  
115 - /**  
116 * 固件编号 112 * 固件编号
117 * 113 *
118 * 关联 {@link IotOtaFirmwareDO#getId()} 114 * 关联 {@link IotOtaFirmwareDO#getId()}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceMessageDO.java
@@ -84,7 +84,7 @@ public class IotDeviceMessageDO { @@ -84,7 +84,7 @@ public class IotDeviceMessageDO {
84 * 请求方法 84 * 请求方法
85 * 85 *
86 * 枚举 {@link IotDeviceMessageMethodEnum} 86 * 枚举 {@link IotDeviceMessageMethodEnum}
87 - * 例如说:thing.property.report 属性上报 87 + * 例如说:thing.property.post 属性上报
88 */ 88 */
89 private String method; 89 private String method;
90 /** 90 /**
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusConfigDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.dataobject.device;
  2 +
  3 +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
  4 +import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusFrameFormatEnum;
  5 +import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusModeEnum;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
  7 +import com.baomidou.mybatisplus.annotation.KeySequence;
  8 +import com.baomidou.mybatisplus.annotation.TableId;
  9 +import com.baomidou.mybatisplus.annotation.TableName;
  10 +import lombok.*;
  11 +
  12 +/**
  13 + * IoT 设备 Modbus 连接配置 DO
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@TableName("iot_device_modbus_config")
  18 +@KeySequence("iot_device_modbus_config_seq")
  19 +@Data
  20 +@Builder
  21 +@NoArgsConstructor
  22 +@AllArgsConstructor
  23 +public class IotDeviceModbusConfigDO extends TenantBaseDO {
  24 +
  25 + /**
  26 + * 主键
  27 + */
  28 + @TableId
  29 + private Long id;
  30 + /**
  31 + * 产品编号
  32 + *
  33 + * 关联 {@link IotProductDO#getId()}
  34 + */
  35 + private Long productId;
  36 + /**
  37 + * 设备编号
  38 + *
  39 + * 关联 {@link IotDeviceDO#getId()}
  40 + */
  41 + private Long deviceId;
  42 +
  43 + /**
  44 + * Modbus 服务器 IP 地址
  45 + */
  46 + private String ip;
  47 + /**
  48 + * Modbus 服务器端口
  49 + */
  50 + private Integer port;
  51 + /**
  52 + * 从站地址
  53 + */
  54 + private Integer slaveId;
  55 + /**
  56 + * 连接超时时间,单位:毫秒
  57 + */
  58 + private Integer timeout;
  59 + /**
  60 + * 重试间隔,单位:毫秒
  61 + */
  62 + private Integer retryInterval;
  63 + /**
  64 + * 模式
  65 + *
  66 + * @see IotModbusModeEnum
  67 + */
  68 + private Integer mode;
  69 + /**
  70 + * 数据帧格式
  71 + *
  72 + * @see IotModbusFrameFormatEnum
  73 + */
  74 + private Integer frameFormat;
  75 + /**
  76 + * 状态
  77 + *
  78 + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
  79 + */
  80 + private Integer status;
  81 +
  82 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/device/IotDeviceModbusPointDO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.dataobject.device;
  2 +
  3 +import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
  4 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
  5 +import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusByteOrderEnum;
  6 +import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusRawDataTypeEnum;
  7 +import com.baomidou.mybatisplus.annotation.KeySequence;
  8 +import com.baomidou.mybatisplus.annotation.TableId;
  9 +import com.baomidou.mybatisplus.annotation.TableName;
  10 +import lombok.*;
  11 +
  12 +import java.math.BigDecimal;
  13 +
  14 +/**
  15 + * IoT 设备 Modbus 点位配置 DO
  16 + *
  17 + * @author 芋道源码
  18 + */
  19 +@TableName("iot_device_modbus_point")
  20 +@KeySequence("iot_device_modbus_point_seq")
  21 +@Data
  22 +@Builder
  23 +@NoArgsConstructor
  24 +@AllArgsConstructor
  25 +public class IotDeviceModbusPointDO extends TenantBaseDO {
  26 +
  27 + /**
  28 + * 主键
  29 + */
  30 + @TableId
  31 + private Long id;
  32 + /**
  33 + * 设备编号
  34 + *
  35 + * 关联 {@link IotDeviceDO#getId()}
  36 + */
  37 + private Long deviceId;
  38 + /**
  39 + * 物模型属性编号
  40 + *
  41 + * 关联 {@link IotThingModelDO#getId()}
  42 + */
  43 + private Long thingModelId;
  44 + /**
  45 + * 属性标识符
  46 + *
  47 + * 冗余 {@link IotThingModelDO#getIdentifier()}
  48 + */
  49 + private String identifier;
  50 + /**
  51 + * 属性名称
  52 + *
  53 + * 冗余 {@link IotThingModelDO#getName()}
  54 + */
  55 + private String name;
  56 +
  57 + // ========== Modbus 协议配置 ==========
  58 +
  59 + /**
  60 + * Modbus 功能码
  61 + *
  62 + * 取值范围:FC01-04(读线圈、读离散输入、读保持寄存器、读输入寄存器)
  63 + */
  64 + private Integer functionCode;
  65 + /**
  66 + * 寄存器起始地址
  67 + */
  68 + private Integer registerAddress;
  69 + /**
  70 + * 寄存器数量
  71 + */
  72 + private Integer registerCount;
  73 + /**
  74 + * 字节序
  75 + *
  76 + * 枚举 {@link IotModbusByteOrderEnum}
  77 + */
  78 + private String byteOrder;
  79 + /**
  80 + * 原始数据类型
  81 + *
  82 + * 枚举 {@link IotModbusRawDataTypeEnum}
  83 + */
  84 + private String rawDataType;
  85 + /**
  86 + * 缩放因子
  87 + */
  88 + private BigDecimal scale;
  89 + /**
  90 + * 轮询间隔(毫秒)
  91 + */
  92 + private Integer pollInterval;
  93 + /**
  94 + * 状态
  95 + *
  96 + * 枚举 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
  97 + */
  98 + private Integer status;
  99 +
  100 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/dataobject/product/IotProductDO.java
@@ -78,12 +78,16 @@ public class IotProductDO extends TenantBaseDO { @@ -78,12 +78,16 @@ public class IotProductDO extends TenantBaseDO {
78 */ 78 */
79 private Integer netType; 79 private Integer netType;
80 /** 80 /**
81 - * 数据格式(编解码器类型) 81 + * 协议类型
82 * <p> 82 * <p>
83 - * 字典 {@link cn.iocoder.yudao.module.iot.enums.DictTypeConstants#CODEC_TYPE}  
84 - *  
85 - * 目的:用于 gateway-server 解析消息格式 83 + * 枚举 {@link cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum}
86 */ 84 */
87 - private String codecType; 85 + private String protocolType;
  86 + /**
  87 + * 序列化类型
  88 + * <p>
  89 + * 枚举 {@link cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum}
  90 + */
  91 + private String serializeType;
88 92
89 } 93 }
90 \ No newline at end of file 94 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusConfigMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.mysql.device;
  2 +
  3 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  5 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
  7 +import org.apache.ibatis.annotations.Mapper;
  8 +
  9 +import java.util.List;
  10 +
  11 +/**
  12 + * IoT 设备 Modbus 连接配置 Mapper
  13 + *
  14 + * @author 芋道源码
  15 + */
  16 +@Mapper
  17 +public interface IotDeviceModbusConfigMapper extends BaseMapperX<IotDeviceModbusConfigDO> {
  18 +
  19 + default IotDeviceModbusConfigDO selectByDeviceId(Long deviceId) {
  20 + return selectOne(IotDeviceModbusConfigDO::getDeviceId, deviceId);
  21 + }
  22 +
  23 + default List<IotDeviceModbusConfigDO> selectList(IotModbusDeviceConfigListReqDTO reqDTO) {
  24 + return selectList(new LambdaQueryWrapperX<IotDeviceModbusConfigDO>()
  25 + .eqIfPresent(IotDeviceModbusConfigDO::getStatus, reqDTO.getStatus())
  26 + .eqIfPresent(IotDeviceModbusConfigDO::getMode, reqDTO.getMode())
  27 + .inIfPresent(IotDeviceModbusConfigDO::getDeviceId, reqDTO.getDeviceIds()));
  28 + }
  29 +
  30 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/device/IotDeviceModbusPointMapper.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.dal.mysql.device;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO;
  7 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
  8 +import org.apache.ibatis.annotations.Mapper;
  9 +
  10 +import java.util.Collection;
  11 +import java.util.List;
  12 +
  13 +/**
  14 + * IoT 设备 Modbus 点位配置 Mapper
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +@Mapper
  19 +public interface IotDeviceModbusPointMapper extends BaseMapperX<IotDeviceModbusPointDO> {
  20 +
  21 + default PageResult<IotDeviceModbusPointDO> selectPage(IotDeviceModbusPointPageReqVO reqVO) {
  22 + return selectPage(reqVO, new LambdaQueryWrapperX<IotDeviceModbusPointDO>()
  23 + .eqIfPresent(IotDeviceModbusPointDO::getDeviceId, reqVO.getDeviceId())
  24 + .likeIfPresent(IotDeviceModbusPointDO::getIdentifier, reqVO.getIdentifier())
  25 + .likeIfPresent(IotDeviceModbusPointDO::getName, reqVO.getName())
  26 + .eqIfPresent(IotDeviceModbusPointDO::getFunctionCode, reqVO.getFunctionCode())
  27 + .eqIfPresent(IotDeviceModbusPointDO::getStatus, reqVO.getStatus())
  28 + .orderByDesc(IotDeviceModbusPointDO::getId));
  29 + }
  30 +
  31 + default List<IotDeviceModbusPointDO> selectListByDeviceIdsAndStatus(Collection<Long> deviceIds, Integer status) {
  32 + return selectList(new LambdaQueryWrapperX<IotDeviceModbusPointDO>()
  33 + .in(IotDeviceModbusPointDO::getDeviceId, deviceIds)
  34 + .eq(IotDeviceModbusPointDO::getStatus, status));
  35 + }
  36 +
  37 + default IotDeviceModbusPointDO selectByDeviceIdAndIdentifier(Long deviceId, String identifier) {
  38 + return selectOne(IotDeviceModbusPointDO::getDeviceId, deviceId,
  39 + IotDeviceModbusPointDO::getIdentifier, identifier);
  40 + }
  41 +
  42 + default void updateByThingModelId(Long thingModelId, IotDeviceModbusPointDO updateObj) {
  43 + update(updateObj, new LambdaQueryWrapperX<IotDeviceModbusPointDO>()
  44 + .eq(IotDeviceModbusPointDO::getThingModelId, thingModelId));
  45 + }
  46 +
  47 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/mysql/product/IotProductMapper.java
@@ -38,6 +38,10 @@ public interface IotProductMapper extends BaseMapperX&lt;IotProductDO&gt; { @@ -38,6 +38,10 @@ public interface IotProductMapper extends BaseMapperX&lt;IotProductDO&gt; {
38 .apply("LOWER(product_key) = {0}", productKey.toLowerCase())); 38 .apply("LOWER(product_key) = {0}", productKey.toLowerCase()));
39 } 39 }
40 40
  41 + default List<IotProductDO> selectListByStatus(Integer status) {
  42 + return selectList(IotProductDO::getStatus, status);
  43 + }
  44 +
41 default Long selectCountByCreateTime(@Nullable LocalDateTime createTime) { 45 default Long selectCountByCreateTime(@Nullable LocalDateTime createTime) {
42 return selectCount(new LambdaQueryWrapperX<IotProductDO>() 46 return selectCount(new LambdaQueryWrapperX<IotProductDO>()
43 .geIfPresent(IotProductDO::getCreateTime, createTime)); 47 .geIfPresent(IotProductDO::getCreateTime, createTime));
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/DictTypeConstants.java
@@ -8,8 +8,8 @@ package cn.iocoder.yudao.module.iot.enums; @@ -8,8 +8,8 @@ package cn.iocoder.yudao.module.iot.enums;
8 public class DictTypeConstants { 8 public class DictTypeConstants {
9 9
10 public static final String NET_TYPE = "iot_net_type"; 10 public static final String NET_TYPE = "iot_net_type";
11 - public static final String LOCATION_TYPE = "iot_location_type";  
12 - public static final String CODEC_TYPE = "iot_codec_type"; 11 + public static final String PROTOCOL_TYPE = "iot_protocol_type";
  12 + public static final String SERIALIZE_TYPE = "iot_serialize_type";
13 13
14 public static final String PRODUCT_STATUS = "iot_product_status"; 14 public static final String PRODUCT_STATUS = "iot_product_status";
15 public static final String PRODUCT_DEVICE_TYPE = "iot_product_device_type"; 15 public static final String PRODUCT_DEVICE_TYPE = "iot_product_device_type";
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/ErrorCodeConstants.java
@@ -54,6 +54,14 @@ public interface ErrorCodeConstants { @@ -54,6 +54,14 @@ public interface ErrorCodeConstants {
54 ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在"); 54 ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在");
55 ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除"); 55 ErrorCode DEVICE_GROUP_DELETE_FAIL_DEVICE_EXISTS = new ErrorCode(1_050_005_001, "设备分组下存在设备,不允许删除");
56 56
  57 + // ========== 设备 Modbus 配置 1-050-006-000 ==========
  58 + ErrorCode DEVICE_MODBUS_CONFIG_NOT_EXISTS = new ErrorCode(1_050_006_000, "设备 Modbus 连接配置不存在");
  59 + ErrorCode DEVICE_MODBUS_CONFIG_EXISTS = new ErrorCode(1_050_006_001, "设备 Modbus 连接配置已存在");
  60 +
  61 + // ========== 设备 Modbus 点位 1-050-007-000 ==========
  62 + ErrorCode DEVICE_MODBUS_POINT_NOT_EXISTS = new ErrorCode(1_050_007_000, "设备 Modbus 点位配置不存在");
  63 + ErrorCode DEVICE_MODBUS_POINT_EXISTS = new ErrorCode(1_050_007_001, "设备 Modbus 点位配置已存在");
  64 +
57 // ========== OTA 固件相关 1-050-008-000 ========== 65 // ========== OTA 固件相关 1-050-008-000 ==========
58 66
59 ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在"); 67 ErrorCode OTA_FIRMWARE_NOT_EXISTS = new ErrorCode(1_050_008_000, "固件信息不存在");
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/enums/rule/IotDataSinkTypeEnum.java
@@ -19,9 +19,9 @@ public enum IotDataSinkTypeEnum implements ArrayValuable&lt;Integer&gt; { @@ -19,9 +19,9 @@ public enum IotDataSinkTypeEnum implements ArrayValuable&lt;Integer&gt; {
19 TCP(2, "TCP"), 19 TCP(2, "TCP"),
20 WEBSOCKET(3, "WebSocket"), 20 WEBSOCKET(3, "WebSocket"),
21 21
22 - MQTT(10, "MQTT"), // TODO 待实现; 22 + MQTT(10, "MQTT"), // TODO @puhui999:待实现;
23 23
24 - DATABASE(20, "Database"), // TODO @puhui999:待实现;可以简单点,对应的表名是什么,字段先固定了。 24 + DATABASE(20, "Database"), // TODO @puhui999:待实现;
25 REDIS(21, "Redis"), 25 REDIS(21, "Redis"),
26 26
27 ROCKETMQ(30, "RocketMQ"), 27 ROCKETMQ(30, "RocketMQ"),
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/device/IotDeviceOfflineCheckJob.java
@@ -4,7 +4,7 @@ import cn.hutool.core.collection.CollUtil; @@ -4,7 +4,7 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; 5 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
6 import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; 6 import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
7 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; 7 +import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
9 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 9 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
10 import cn.iocoder.yudao.module.iot.framework.iot.config.YudaoIotProperties; 10 import cn.iocoder.yudao.module.iot.framework.iot.config.YudaoIotProperties;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/job/ota/IotOtaUpgradeJob.java
@@ -4,7 +4,7 @@ import cn.hutool.core.collection.CollUtil; @@ -4,7 +4,7 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.StrUtil; 4 import cn.hutool.core.util.StrUtil;
5 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; 5 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
6 import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; 6 import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
7 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; 7 +import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
9 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO; 9 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
10 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO; 10 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/device/IotDeviceMessageSubscriber.java
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.mq.consumer.device; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.mq.consumer.device;
3 import cn.hutool.core.util.ObjectUtil; 3 import cn.hutool.core.util.ObjectUtil;
4 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; 4 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
5 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 5 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
6 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; 6 +import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
7 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 7 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
8 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; 8 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
9 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 9 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
@@ -67,7 +67,6 @@ public class IotDeviceMessageSubscriber implements IotMessageSubscriber&lt;IotDevic @@ -67,7 +67,6 @@ public class IotDeviceMessageSubscriber implements IotMessageSubscriber&lt;IotDevic
67 IotDeviceDO device = deviceService.validateDeviceExistsFromCache(message.getDeviceId()); 67 IotDeviceDO device = deviceService.validateDeviceExistsFromCache(message.getDeviceId());
68 devicePropertyService.updateDeviceReportTimeAsync(device.getId(), LocalDateTime.now()); 68 devicePropertyService.updateDeviceReportTimeAsync(device.getId(), LocalDateTime.now());
69 // 1.2 更新设备的连接 server 69 // 1.2 更新设备的连接 server
70 - // TODO 芋艿:HTTP 网关的上行消息,不应该更新 serverId,会覆盖掉 MQTT 等长连接的 serverId,导致下行消息无法发送。  
71 devicePropertyService.updateDeviceServerIdAsync(device.getId(), message.getServerId()); 70 devicePropertyService.updateDeviceServerIdAsync(device.getId(), message.getServerId());
72 71
73 // 2. 未上线的设备,强制上线 72 // 2. 未上线的设备,强制上线
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotDataRuleMessageHandler.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotDataRuleMessageSubscriber.java
@@ -17,7 +17,7 @@ import org.springframework.stereotype.Component; @@ -17,7 +17,7 @@ import org.springframework.stereotype.Component;
17 */ 17 */
18 @Component 18 @Component
19 @Slf4j 19 @Slf4j
20 -public class IotDataRuleMessageHandler implements IotMessageSubscriber<IotDeviceMessage> { 20 +public class IotDataRuleMessageSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
21 21
22 @Resource 22 @Resource
23 private IotDataRuleService dataRuleService; 23 private IotDataRuleService dataRuleService;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageHandler.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/rule/IotSceneRuleMessageSubscriber.java
@@ -9,7 +9,6 @@ import jakarta.annotation.Resource; @@ -9,7 +9,6 @@ import jakarta.annotation.Resource;
9 import lombok.extern.slf4j.Slf4j; 9 import lombok.extern.slf4j.Slf4j;
10 import org.springframework.stereotype.Component; 10 import org.springframework.stereotype.Component;
11 11
12 -// TODO @puhui999:后面重构哈  
13 /** 12 /**
14 * 针对 {@link IotDeviceMessage} 的消费者,处理规则场景 13 * 针对 {@link IotDeviceMessage} 的消费者,处理规则场景
15 * 14 *
@@ -17,7 +16,7 @@ import org.springframework.stereotype.Component; @@ -17,7 +16,7 @@ import org.springframework.stereotype.Component;
17 */ 16 */
18 @Component 17 @Component
19 @Slf4j 18 @Slf4j
20 -public class IotSceneRuleMessageHandler implements IotMessageSubscriber<IotDeviceMessage> { 19 +public class IotSceneRuleMessageSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
21 20
22 @Resource 21 @Resource
23 private IotSceneRuleService sceneRuleService; 22 private IotSceneRuleService sceneRuleService;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusConfigService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.device;
  2 +
  3 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO;
  4 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO;
  5 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
  6 +import jakarta.validation.Valid;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * IoT 设备 Modbus 连接配置 Service 接口
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +public interface IotDeviceModbusConfigService {
  16 +
  17 + /**
  18 + * 保存设备 Modbus 连接配置(新增或更新)
  19 + *
  20 + * @param saveReqVO 保存信息
  21 + */
  22 + void saveDeviceModbusConfig(@Valid IotDeviceModbusConfigSaveReqVO saveReqVO);
  23 +
  24 + /**
  25 + * 获得设备 Modbus 连接配置
  26 + *
  27 + * @param id 编号
  28 + * @return 设备 Modbus 连接配置
  29 + */
  30 + IotDeviceModbusConfigDO getDeviceModbusConfig(Long id);
  31 +
  32 + /**
  33 + * 根据设备编号获得 Modbus 连接配置
  34 + *
  35 + * @param deviceId 设备编号
  36 + * @return 设备 Modbus 连接配置
  37 + */
  38 + IotDeviceModbusConfigDO getDeviceModbusConfigByDeviceId(Long deviceId);
  39 +
  40 + /**
  41 + * 获得 Modbus 连接配置列表
  42 + *
  43 + * @param listReqDTO 查询参数
  44 + * @return Modbus 连接配置列表
  45 + */
  46 + List<IotDeviceModbusConfigDO> getDeviceModbusConfigList(IotModbusDeviceConfigListReqDTO listReqDTO);
  47 +
  48 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusConfigServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.device;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusConfigSaveReqVO;
  7 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotModbusDeviceConfigListReqDTO;
  8 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
  9 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusConfigDO;
  10 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
  11 +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
  12 +import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceModbusConfigMapper;
  13 +import cn.iocoder.yudao.module.iot.service.product.IotProductService;
  14 +import jakarta.annotation.Resource;
  15 +import org.springframework.stereotype.Service;
  16 +import org.springframework.validation.annotation.Validated;
  17 +
  18 +import java.util.List;
  19 +
  20 +/**
  21 + * IoT 设备 Modbus 连接配置 Service 实现类
  22 + *
  23 + * @author 芋道源码
  24 + */
  25 +@Service
  26 +@Validated
  27 +public class IotDeviceModbusConfigServiceImpl implements IotDeviceModbusConfigService {
  28 +
  29 + @Resource
  30 + private IotDeviceModbusConfigMapper modbusConfigMapper;
  31 +
  32 + @Resource
  33 + private IotDeviceService deviceService;
  34 + @Resource
  35 + private IotProductService productService;
  36 +
  37 + @Override
  38 + public void saveDeviceModbusConfig(IotDeviceModbusConfigSaveReqVO saveReqVO) {
  39 + // 1.1 校验设备存在
  40 + IotDeviceDO device = deviceService.validateDeviceExists(saveReqVO.getDeviceId());
  41 + // 1.2 根据产品 protocolType 条件校验
  42 + IotProductDO product = productService.getProduct(device.getProductId());
  43 + Assert.notNull(product, "产品不存在");
  44 + validateModbusConfigByProtocolType(saveReqVO, product.getProtocolType());
  45 +
  46 + // 2. 根据数据库中是否已有配置,决定是新增还是更新
  47 + IotDeviceModbusConfigDO existConfig = modbusConfigMapper.selectByDeviceId(saveReqVO.getDeviceId());
  48 + if (existConfig == null) {
  49 + IotDeviceModbusConfigDO modbusConfig = BeanUtils.toBean(saveReqVO, IotDeviceModbusConfigDO.class);
  50 + modbusConfigMapper.insert(modbusConfig);
  51 + } else {
  52 + IotDeviceModbusConfigDO updateObj = BeanUtils.toBean(saveReqVO, IotDeviceModbusConfigDO.class,
  53 + o -> o.setId(existConfig.getId()));
  54 + modbusConfigMapper.updateById(updateObj);
  55 + }
  56 + }
  57 +
  58 + @Override
  59 + public IotDeviceModbusConfigDO getDeviceModbusConfig(Long id) {
  60 + return modbusConfigMapper.selectById(id);
  61 + }
  62 +
  63 + @Override
  64 + public IotDeviceModbusConfigDO getDeviceModbusConfigByDeviceId(Long deviceId) {
  65 + return modbusConfigMapper.selectByDeviceId(deviceId);
  66 + }
  67 +
  68 + @Override
  69 + public List<IotDeviceModbusConfigDO> getDeviceModbusConfigList(IotModbusDeviceConfigListReqDTO listReqDTO) {
  70 + return modbusConfigMapper.selectList(listReqDTO);
  71 + }
  72 +
  73 + private void validateModbusConfigByProtocolType(IotDeviceModbusConfigSaveReqVO saveReqVO, String protocolType) {
  74 + IotProtocolTypeEnum protocolTypeEnum = IotProtocolTypeEnum.of(protocolType);
  75 + if (protocolTypeEnum == null) {
  76 + return;
  77 + }
  78 + if (protocolTypeEnum == IotProtocolTypeEnum.MODBUS_TCP_CLIENT) {
  79 + Assert.isTrue(StrUtil.isNotEmpty(saveReqVO.getIp()), "Client 模式下,IP 地址不能为空");
  80 + Assert.notNull(saveReqVO.getPort(), "Client 模式下,端口不能为空");
  81 + Assert.notNull(saveReqVO.getTimeout(), "Client 模式下,连接超时时间不能为空");
  82 + Assert.notNull(saveReqVO.getRetryInterval(), "Client 模式下,重试间隔不能为空");
  83 + } else if (protocolTypeEnum == IotProtocolTypeEnum.MODBUS_TCP_SERVER) {
  84 + Assert.notNull(saveReqVO.getMode(), "Server 模式下,工作模式不能为空");
  85 + Assert.notNull(saveReqVO.getFrameFormat(), "Server 模式下,数据帧格式不能为空");
  86 + }
  87 + }
  88 +
  89 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusPointService.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.device;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO;
  5 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointSaveReqVO;
  6 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
  7 +import jakarta.validation.Valid;
  8 +
  9 +import java.util.Collection;
  10 +import java.util.List;
  11 +import java.util.Map;
  12 +
  13 +/**
  14 + * IoT 设备 Modbus 点位配置 Service 接口
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +public interface IotDeviceModbusPointService {
  19 +
  20 + /**
  21 + * 创建设备 Modbus 点位配置
  22 + *
  23 + * @param createReqVO 创建信息
  24 + * @return 编号
  25 + */
  26 + Long createDeviceModbusPoint(@Valid IotDeviceModbusPointSaveReqVO createReqVO);
  27 +
  28 + /**
  29 + * 更新设备 Modbus 点位配置
  30 + *
  31 + * @param updateReqVO 更新信息
  32 + */
  33 + void updateDeviceModbusPoint(@Valid IotDeviceModbusPointSaveReqVO updateReqVO);
  34 +
  35 + /**
  36 + * 删除设备 Modbus 点位配置
  37 + *
  38 + * @param id 编号
  39 + */
  40 + void deleteDeviceModbusPoint(Long id);
  41 +
  42 + /**
  43 + * 获得设备 Modbus 点位配置
  44 + *
  45 + * @param id 编号
  46 + * @return 设备 Modbus 点位配置
  47 + */
  48 + IotDeviceModbusPointDO getDeviceModbusPoint(Long id);
  49 +
  50 + /**
  51 + * 获得设备 Modbus 点位配置分页
  52 + *
  53 + * @param pageReqVO 分页查询
  54 + * @return 设备 Modbus 点位配置分页
  55 + */
  56 + PageResult<IotDeviceModbusPointDO> getDeviceModbusPointPage(IotDeviceModbusPointPageReqVO pageReqVO);
  57 +
  58 + /**
  59 + * 物模型变更时,更新关联点位的冗余字段(identifier、name)
  60 + *
  61 + * @param thingModelId 物模型编号
  62 + * @param identifier 物模型标识符
  63 + * @param name 物模型名称
  64 + */
  65 + void updateDeviceModbusPointByThingModel(Long thingModelId, String identifier, String name);
  66 +
  67 + /**
  68 + * 根据设备编号批量获得启用的点位配置 Map
  69 + *
  70 + * @param deviceIds 设备编号集合
  71 + * @return 设备点位 Map,key 为设备编号,value 为点位配置列表
  72 + */
  73 + Map<Long, List<IotDeviceModbusPointDO>> getEnabledDeviceModbusPointMapByDeviceIds(Collection<Long> deviceIds);
  74 +
  75 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceModbusPointServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.service.device;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
  5 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointPageReqVO;
  9 +import cn.iocoder.yudao.module.iot.controller.admin.device.vo.modbus.IotDeviceModbusPointSaveReqVO;
  10 +import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceModbusPointDO;
  11 +import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
  12 +import cn.iocoder.yudao.module.iot.dal.mysql.device.IotDeviceModbusPointMapper;
  13 +import cn.iocoder.yudao.module.iot.service.thingmodel.IotThingModelService;
  14 +import jakarta.annotation.Resource;
  15 +import org.springframework.stereotype.Service;
  16 +import org.springframework.validation.annotation.Validated;
  17 +
  18 +import java.util.Collection;
  19 +import java.util.Collections;
  20 +import java.util.List;
  21 +import java.util.Map;
  22 +
  23 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  24 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
  25 +import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
  26 +
  27 +/**
  28 + * IoT 设备 Modbus 点位配置 Service 实现类
  29 + *
  30 + * @author 芋道源码
  31 + */
  32 +@Service
  33 +@Validated
  34 +public class IotDeviceModbusPointServiceImpl implements IotDeviceModbusPointService {
  35 +
  36 + @Resource
  37 + private IotDeviceModbusPointMapper modbusPointMapper;
  38 +
  39 + @Resource
  40 + private IotDeviceService deviceService;
  41 +
  42 + @Resource
  43 + private IotThingModelService thingModelService;
  44 +
  45 + @Override
  46 + public Long createDeviceModbusPoint(IotDeviceModbusPointSaveReqVO createReqVO) {
  47 + // 1.1 校验设备存在
  48 + deviceService.validateDeviceExists(createReqVO.getDeviceId());
  49 + // 1.2 校验物模型属性存在
  50 + IotThingModelDO thingModel = validateThingModelExists(createReqVO.getThingModelId());
  51 + // 1.3 校验同一设备下点位唯一性(基于 identifier)
  52 + validateDeviceModbusPointUnique(createReqVO.getDeviceId(), thingModel.getIdentifier(), null);
  53 +
  54 + // 2. 插入
  55 + IotDeviceModbusPointDO modbusPoint = BeanUtils.toBean(createReqVO, IotDeviceModbusPointDO.class,
  56 + o -> o.setIdentifier(thingModel.getIdentifier()).setName(thingModel.getName()));
  57 + modbusPointMapper.insert(modbusPoint);
  58 + return modbusPoint.getId();
  59 + }
  60 +
  61 + @Override
  62 + public void updateDeviceModbusPoint(IotDeviceModbusPointSaveReqVO updateReqVO) {
  63 + // 1.1 校验存在
  64 + validateDeviceModbusPointExists(updateReqVO.getId());
  65 + // 1.2 校验设备存在
  66 + deviceService.validateDeviceExists(updateReqVO.getDeviceId());
  67 + // 1.3 校验物模型属性存在
  68 + IotThingModelDO thingModel = validateThingModelExists(updateReqVO.getThingModelId());
  69 + // 1.4 校验同一设备下点位唯一性
  70 + validateDeviceModbusPointUnique(updateReqVO.getDeviceId(), thingModel.getIdentifier(), updateReqVO.getId());
  71 +
  72 + // 2. 更新
  73 + IotDeviceModbusPointDO updateObj = BeanUtils.toBean(updateReqVO, IotDeviceModbusPointDO.class,
  74 + o -> o.setIdentifier(thingModel.getIdentifier()).setName(thingModel.getName()));
  75 + modbusPointMapper.updateById(updateObj);
  76 + }
  77 +
  78 + @Override
  79 + public void updateDeviceModbusPointByThingModel(Long thingModelId, String identifier, String name) {
  80 + IotDeviceModbusPointDO updateObj = new IotDeviceModbusPointDO()
  81 + .setIdentifier(identifier).setName(name);
  82 + modbusPointMapper.updateByThingModelId(thingModelId, updateObj);
  83 + }
  84 +
  85 + private IotThingModelDO validateThingModelExists(Long id) {
  86 + IotThingModelDO thingModel = thingModelService.getThingModel(id);
  87 + if (thingModel == null) {
  88 + throw exception(THING_MODEL_NOT_EXISTS);
  89 + }
  90 + return thingModel;
  91 + }
  92 +
  93 + @Override
  94 + public void deleteDeviceModbusPoint(Long id) {
  95 + // 校验存在
  96 + validateDeviceModbusPointExists(id);
  97 + // 删除
  98 + modbusPointMapper.deleteById(id);
  99 + }
  100 +
  101 + private void validateDeviceModbusPointExists(Long id) {
  102 + IotDeviceModbusPointDO point = modbusPointMapper.selectById(id);
  103 + if (point == null) {
  104 + throw exception(DEVICE_MODBUS_POINT_NOT_EXISTS);
  105 + }
  106 + }
  107 +
  108 + private void validateDeviceModbusPointUnique(Long deviceId, String identifier, Long excludeId) {
  109 + IotDeviceModbusPointDO point = modbusPointMapper.selectByDeviceIdAndIdentifier(deviceId, identifier);
  110 + if (point != null && ObjUtil.notEqual(point.getId(), excludeId)) {
  111 + throw exception(DEVICE_MODBUS_POINT_EXISTS);
  112 + }
  113 + }
  114 +
  115 + @Override
  116 + public IotDeviceModbusPointDO getDeviceModbusPoint(Long id) {
  117 + return modbusPointMapper.selectById(id);
  118 + }
  119 +
  120 + @Override
  121 + public PageResult<IotDeviceModbusPointDO> getDeviceModbusPointPage(IotDeviceModbusPointPageReqVO pageReqVO) {
  122 + return modbusPointMapper.selectPage(pageReqVO);
  123 + }
  124 +
  125 + @Override
  126 + public Map<Long, List<IotDeviceModbusPointDO>> getEnabledDeviceModbusPointMapByDeviceIds(Collection<Long> deviceIds) {
  127 + if (CollUtil.isEmpty(deviceIds)) {
  128 + return Collections.emptyMap();
  129 + }
  130 + List<IotDeviceModbusPointDO> pointList = modbusPointMapper.selectListByDeviceIdsAndStatus(
  131 + deviceIds, CommonStatusEnum.ENABLE.getStatus());
  132 + return convertMultiMap(pointList, IotDeviceModbusPointDO::getDeviceId);
  133 + }
  134 +
  135 +}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceService.java
@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*; 4 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; 6 import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO;
7 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; 7 +import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
9 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; 9 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
10 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; 10 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceServiceImpl.java
@@ -17,7 +17,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*; @@ -17,7 +17,7 @@ import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
17 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 17 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
18 import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; 18 import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO;
19 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 19 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
20 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; 20 +import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
21 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 21 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
22 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; 22 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
23 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; 23 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
@@ -29,6 +29,7 @@ import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoChangeReqDTO; @@ -29,6 +29,7 @@ import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoChangeReqDTO;
29 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO; 29 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO;
30 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetRespDTO; 30 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetRespDTO;
31 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils; 31 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
  32 +import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils;
32 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 33 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
33 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO; 34 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
34 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO; 35 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
@@ -819,8 +820,9 @@ public class IotDeviceServiceImpl implements IotDeviceService { @@ -819,8 +820,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
819 if (BooleanUtil.isFalse(product.getRegisterEnabled())) { 820 if (BooleanUtil.isFalse(product.getRegisterEnabled())) {
820 throw exception(DEVICE_REGISTER_DISABLED); 821 throw exception(DEVICE_REGISTER_DISABLED);
821 } 822 }
822 - // 1.3 验证 productSecret  
823 - if (ObjUtil.notEqual(product.getProductSecret(), reqDTO.getProductSecret())) { 823 + // 1.3 【重要!!!】验证签名
  824 + if (!IotProductAuthUtils.verifySign(reqDTO.getProductKey(), reqDTO.getDeviceName(),
  825 + product.getProductSecret(), reqDTO.getSign())) {
824 throw exception(DEVICE_REGISTER_SECRET_INVALID); 826 throw exception(DEVICE_REGISTER_SECRET_INVALID);
825 } 827 }
826 return TenantUtils.execute(product.getTenantId(), () -> { 828 return TenantUtils.execute(product.getTenantId(), () -> {
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/ota/IotOtaTaskRecordServiceImpl.java
@@ -3,11 +3,15 @@ package cn.iocoder.yudao.module.iot.service.ota; @@ -3,11 +3,15 @@ package cn.iocoder.yudao.module.iot.service.ota;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.convert.Convert; 4 import cn.hutool.core.convert.Convert;
5 import cn.hutool.core.lang.Assert; 5 import cn.hutool.core.lang.Assert;
6 -import cn.hutool.core.map.MapUtil;  
7 import cn.hutool.core.util.StrUtil; 6 import cn.hutool.core.util.StrUtil;
8 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  9 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO; 10 import cn.iocoder.yudao.module.iot.controller.admin.ota.vo.task.record.IotOtaTaskRecordPageReqVO;
  11 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
10 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 12 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  13 +import cn.iocoder.yudao.module.iot.core.topic.ota.IotDeviceOtaProgressReqDTO;
  14 +import cn.iocoder.yudao.module.iot.core.topic.ota.IotDeviceOtaUpgradeReqDTO;
11 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 15 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
12 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO; 16 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
13 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO; 17 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
@@ -133,9 +137,9 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService { @@ -133,9 +137,9 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
133 public boolean pushOtaTaskRecord(IotOtaTaskRecordDO record, IotOtaFirmwareDO fireware, IotDeviceDO device) { 137 public boolean pushOtaTaskRecord(IotOtaTaskRecordDO record, IotOtaFirmwareDO fireware, IotDeviceDO device) {
134 try { 138 try {
135 // 1. 推送 OTA 任务记录 139 // 1. 推送 OTA 任务记录
136 - IotDeviceMessage message = IotDeviceMessage.buildOtaUpgrade(  
137 - fireware.getVersion(), fireware.getFileUrl(), fireware.getFileSize(),  
138 - fireware.getFileDigestAlgorithm(), fireware.getFileDigestValue()); 140 + IotDeviceOtaUpgradeReqDTO params = BeanUtils.toBean(fireware, IotDeviceOtaUpgradeReqDTO.class);
  141 + IotDeviceMessage message = IotDeviceMessage.requestOf(
  142 + IotDeviceMessageMethodEnum.OTA_UPGRADE.getMethod(), params);
139 deviceMessageService.sendDeviceMessage(message, device); 143 deviceMessageService.sendDeviceMessage(message, device);
140 144
141 // 2. 更新 OTA 升级记录状态为进行中 145 // 2. 更新 OTA 升级记录状态为进行中
@@ -163,17 +167,16 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService { @@ -163,17 +167,16 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
163 167
164 @Override 168 @Override
165 @Transactional(rollbackFor = Exception.class) 169 @Transactional(rollbackFor = Exception.class)
166 - @SuppressWarnings("unchecked")  
167 public void updateOtaRecordProgress(IotDeviceDO device, IotDeviceMessage message) { 170 public void updateOtaRecordProgress(IotDeviceDO device, IotDeviceMessage message) {
168 // 1.1 参数解析 171 // 1.1 参数解析
169 - Map<String, Object> params = (Map<String, Object>) message.getParams();  
170 - String version = MapUtil.getStr(params, "version"); 172 + IotDeviceOtaProgressReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceOtaProgressReqDTO.class);
  173 + String version = params.getVersion();
171 Assert.notBlank(version, "version 不能为空"); 174 Assert.notBlank(version, "version 不能为空");
172 - Integer status = MapUtil.getInt(params, "status"); 175 + Integer status = params.getStatus();
173 Assert.notNull(status, "status 不能为空"); 176 Assert.notNull(status, "status 不能为空");
174 Assert.notNull(IotOtaTaskRecordStatusEnum.of(status), "status 状态不正确"); 177 Assert.notNull(IotOtaTaskRecordStatusEnum.of(status), "status 状态不正确");
175 - String description = MapUtil.getStr(params, "description");  
176 - Integer progress = MapUtil.getInt(params, "progress"); 178 + String description = params.getDescription();
  179 + Integer progress = params.getProgress();
177 Assert.notNull(progress, "progress 不能为空"); 180 Assert.notNull(progress, "progress 不能为空");
178 Assert.isTrue(progress >= 0 && progress <= 100, "progress 必须在 0-100 之间"); 181 Assert.isTrue(progress >= 0 && progress <= 100, "progress 必须在 0-100 之间");
179 // 1.2 查询 OTA 升级记录 182 // 1.2 查询 OTA 升级记录
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductService.java
@@ -10,6 +10,9 @@ import javax.annotation.Nullable; @@ -10,6 +10,9 @@ import javax.annotation.Nullable;
10 import java.time.LocalDateTime; 10 import java.time.LocalDateTime;
11 import java.util.Collection; 11 import java.util.Collection;
12 import java.util.List; 12 import java.util.List;
  13 +import java.util.Map;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
13 16
14 /** 17 /**
15 * IoT 产品 Service 接口 18 * IoT 产品 Service 接口
@@ -122,10 +125,35 @@ public interface IotProductService { @@ -122,10 +125,35 @@ public interface IotProductService {
122 Long getProductCount(@Nullable LocalDateTime createTime); 125 Long getProductCount(@Nullable LocalDateTime createTime);
123 126
124 /** 127 /**
  128 + * 批量获得产品列表
  129 + *
  130 + * @param ids 产品编号集合
  131 + * @return 产品列表
  132 + */
  133 + List<IotProductDO> getProductList(Collection<Long> ids);
  134 +
  135 + /**
  136 + * 批量获得产品 Map
  137 + *
  138 + * @param ids 产品编号集合
  139 + * @return 产品 Map(key: 产品编号, value: 产品)
  140 + */
  141 + default Map<Long, IotProductDO> getProductMap(Collection<Long> ids) {
  142 + return convertMap(getProductList(ids), IotProductDO::getId);
  143 + }
  144 +
  145 + /**
125 * 批量校验产品存在 146 * 批量校验产品存在
126 * 147 *
127 * @param ids 产品编号集合 148 * @param ids 产品编号集合
128 */ 149 */
129 void validateProductsExist(Collection<Long> ids); 150 void validateProductsExist(Collection<Long> ids);
130 151
  152 + /**
  153 + * 同步产品的 TDengine 表结构
  154 + *
  155 + * 目的:当 MySQL 和 TDengine 不同步时,强制将已发布产品的表结构同步到 TDengine 中
  156 + */
  157 + void syncProductPropertyTable();
  158 +
131 } 159 }
132 \ No newline at end of file 160 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/product/IotProductServiceImpl.java
@@ -15,8 +15,10 @@ import cn.iocoder.yudao.module.iot.service.device.IotDeviceService; @@ -15,8 +15,10 @@ import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
15 import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService; 15 import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
16 import com.baomidou.dynamic.datasource.annotation.DSTransactional; 16 import com.baomidou.dynamic.datasource.annotation.DSTransactional;
17 import jakarta.annotation.Resource; 17 import jakarta.annotation.Resource;
  18 +import lombok.extern.slf4j.Slf4j;
18 import org.springframework.cache.annotation.CacheEvict; 19 import org.springframework.cache.annotation.CacheEvict;
19 import org.springframework.cache.annotation.Cacheable; 20 import org.springframework.cache.annotation.Cacheable;
  21 +import org.springframework.context.annotation.Lazy;
20 import org.springframework.stereotype.Service; 22 import org.springframework.stereotype.Service;
21 import org.springframework.validation.annotation.Validated; 23 import org.springframework.validation.annotation.Validated;
22 24
@@ -33,6 +35,7 @@ import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*; @@ -33,6 +35,7 @@ import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
33 * 35 *
34 * @author ahh 36 * @author ahh
35 */ 37 */
  38 +@Slf4j
36 @Service 39 @Service
37 @Validated 40 @Validated
38 public class IotProductServiceImpl implements IotProductService { 41 public class IotProductServiceImpl implements IotProductService {
@@ -41,9 +44,10 @@ public class IotProductServiceImpl implements IotProductService { @@ -41,9 +44,10 @@ public class IotProductServiceImpl implements IotProductService {
41 private IotProductMapper productMapper; 44 private IotProductMapper productMapper;
42 45
43 @Resource 46 @Resource
44 - private IotDevicePropertyService devicePropertyDataService;  
45 - @Resource  
46 private IotDeviceService deviceService; 47 private IotDeviceService deviceService;
  48 + @Resource
  49 + @Lazy // 延迟加载,避免循环依赖
  50 + private IotDevicePropertyService devicePropertyDataService;
47 51
48 @Override 52 @Override
49 public Long createProduct(IotProductSaveReqVO createReqVO) { 53 public Long createProduct(IotProductSaveReqVO createReqVO) {
@@ -172,6 +176,32 @@ public class IotProductServiceImpl implements IotProductService { @@ -172,6 +176,32 @@ public class IotProductServiceImpl implements IotProductService {
172 } 176 }
173 177
174 @Override 178 @Override
  179 + public List<IotProductDO> getProductList(Collection<Long> ids) {
  180 + return productMapper.selectByIds(ids);
  181 + }
  182 +
  183 + @Override
  184 + public void syncProductPropertyTable() {
  185 + // 1. 获取所有已发布的产品
  186 + List<IotProductDO> products = productMapper.selectListByStatus(
  187 + IotProductStatusEnum.PUBLISHED.getStatus());
  188 + log.info("[syncProductPropertyTable][开始同步,已发布产品数量({})]", products.size());
  189 +
  190 + // 2. 遍历同步 TDengine 表结构(创建产品超级表数据模型)
  191 + int successCount = 0;
  192 + for (IotProductDO product : products) {
  193 + try {
  194 + devicePropertyDataService.defineDevicePropertyData(product.getId());
  195 + successCount++;
  196 + log.info("[syncProductPropertyTable][产品({}/{}) 同步成功]", product.getId(), product.getName());
  197 + } catch (Exception e) {
  198 + log.error("[syncProductPropertyTable][产品({}/{}) 同步失败]", product.getId(), product.getName(), e);
  199 + }
  200 + }
  201 + log.info("[syncProductPropertyTable][同步完成,成功({}/{})个]", successCount, products.size());
  202 + }
  203 +
  204 + @Override
175 public void validateProductsExist(Collection<Long> ids) { 205 public void validateProductsExist(Collection<Long> ids) {
176 if (CollUtil.isEmpty(ids)) { 206 if (CollUtil.isEmpty(ids)) {
177 return; 207 return;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/data/action/IotDataRuleCacheableAction.java
@@ -14,8 +14,6 @@ import java.time.Duration; @@ -14,8 +14,6 @@ import java.time.Duration;
14 14
15 // TODO @芋艿:数据库 15 // TODO @芋艿:数据库
16 // TODO @芋艿:mqtt 16 // TODO @芋艿:mqtt
17 -// TODO @芋艿:tcp  
18 -// TODO @芋艿:websocket  
19 17
20 /** 18 /**
21 * 可缓存的 {@link IotDataRuleAction} 抽象实现 19 * 可缓存的 {@link IotDataRuleAction} 抽象实现
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDeviceControlSceneRuleAction.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/rule/scene/action/IotDevicePropertySetSceneRuleAction.java
@@ -15,7 +15,6 @@ import lombok.extern.slf4j.Slf4j; @@ -15,7 +15,6 @@ import lombok.extern.slf4j.Slf4j;
15 import org.springframework.stereotype.Component; 15 import org.springframework.stereotype.Component;
16 16
17 import java.util.List; 17 import java.util.List;
18 -import java.util.Map;  
19 18
20 /** 19 /**
21 * IoT 设备属性设置的 {@link IotSceneRuleAction} 实现类 20 * IoT 设备属性设置的 {@link IotSceneRuleAction} 实现类
@@ -24,7 +23,7 @@ import java.util.Map; @@ -24,7 +23,7 @@ import java.util.Map;
24 */ 23 */
25 @Component 24 @Component
26 @Slf4j 25 @Slf4j
27 -public class IotDeviceControlSceneRuleAction implements IotSceneRuleAction { 26 +public class IotDevicePropertySetSceneRuleAction implements IotSceneRuleAction {
28 27
29 @Resource 28 @Resource
30 private IotDeviceService deviceService; 29 private IotDeviceService deviceService;
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/thingmodel/IotThingModelServiceImpl.java
@@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO; @@ -15,6 +15,7 @@ import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotThingModelDO;
15 import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper; 15 import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper;
16 import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants; 16 import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
17 import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum; 17 import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
  18 +import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService;
18 import cn.iocoder.yudao.module.iot.service.product.IotProductService; 19 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
19 import jakarta.annotation.Resource; 20 import jakarta.annotation.Resource;
20 import lombok.extern.slf4j.Slf4j; 21 import lombok.extern.slf4j.Slf4j;
@@ -50,6 +51,9 @@ public class IotThingModelServiceImpl implements IotThingModelService { @@ -50,6 +51,9 @@ public class IotThingModelServiceImpl implements IotThingModelService {
50 @Resource 51 @Resource
51 @Lazy // 延迟加载,解决循环依赖 52 @Lazy // 延迟加载,解决循环依赖
52 private IotProductService productService; 53 private IotProductService productService;
  54 + @Resource
  55 + @Lazy // 延迟加载,解决循环依赖
  56 + private IotDeviceModbusPointService deviceModbusPointService;
53 57
54 @Override 58 @Override
55 @Transactional(rollbackFor = Exception.class) 59 @Transactional(rollbackFor = Exception.class)
@@ -84,7 +88,11 @@ public class IotThingModelServiceImpl implements IotThingModelService { @@ -84,7 +88,11 @@ public class IotThingModelServiceImpl implements IotThingModelService {
84 IotThingModelDO thingModel = IotThingModelConvert.INSTANCE.convert(updateReqVO); 88 IotThingModelDO thingModel = IotThingModelConvert.INSTANCE.convert(updateReqVO);
85 thingModelMapper.updateById(thingModel); 89 thingModelMapper.updateById(thingModel);
86 90
87 - // 3. 删除缓存 91 + // 3. 同步更新 Modbus 点位的冗余字段(identifier、name)
  92 + deviceModbusPointService.updateDeviceModbusPointByThingModel(
  93 + updateReqVO.getId(), updateReqVO.getIdentifier(), updateReqVO.getName());
  94 +
  95 + // 4. 删除缓存
88 deleteThingModelListCache(updateReqVO.getProductId()); 96 deleteThingModelListCache(updateReqVO.getProductId());
89 } 97 }
90 98
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/IotSceneRuleTimerConditionIntegrationTest.java
@@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene; @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.iot.service.rule.scene;
3 import cn.hutool.core.collection.ListUtil; 3 import cn.hutool.core.collection.ListUtil;
4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest; 5 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
6 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; 6 +import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
7 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO; 7 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO; 8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
9 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; 9 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/condition/IotDeviceStateConditionMatcherTest.java
1 package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition; 1 package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.condition;
2 2
3 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; 3 +import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; 5 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
6 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; 6 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
yudao-module-iot/yudao-module-iot-biz/src/test/java/cn/iocoder/yudao/module/iot/service/rule/scene/matcher/trigger/IotDeviceStateUpdateTriggerMatcherTest.java
1 package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger; 1 package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
2 2
3 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 3 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
4 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; 4 +import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO; 6 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
7 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum; 7 import cn.iocoder.yudao.module.iot.enums.rule.IotSceneRuleConditionOperatorEnum;
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/IotDeviceCommonApi.java
1 package cn.iocoder.yudao.module.iot.core.biz; 1 package cn.iocoder.yudao.module.iot.core.biz;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;  
5 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceGetReqDTO;  
6 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;  
7 -import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; 4 +import cn.iocoder.yudao.module.iot.core.biz.dto.*;
8 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; 5 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
9 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; 6 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
10 import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; 7 import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO;
@@ -50,4 +47,12 @@ public interface IotDeviceCommonApi { @@ -50,4 +47,12 @@ public interface IotDeviceCommonApi {
50 */ 47 */
51 CommonResult<List<IotSubDeviceRegisterRespDTO>> registerSubDevices(IotSubDeviceRegisterFullReqDTO reqDTO); 48 CommonResult<List<IotSubDeviceRegisterRespDTO>> registerSubDevices(IotSubDeviceRegisterFullReqDTO reqDTO);
52 49
  50 + /**
  51 + * 获取 Modbus 设备配置列表
  52 + *
  53 + * @param listReqDTO 查询参数
  54 + * @return Modbus 设备配置列表
  55 + */
  56 + CommonResult<List<IotModbusDeviceConfigRespDTO>> getModbusDeviceConfigList(IotModbusDeviceConfigListReqDTO listReqDTO);
  57 +
53 } 58 }
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotDeviceRespDTO.java
@@ -34,8 +34,12 @@ public class IotDeviceRespDTO { @@ -34,8 +34,12 @@ public class IotDeviceRespDTO {
34 */ 34 */
35 private Long productId; 35 private Long productId;
36 /** 36 /**
37 - * 编解码器类型 37 + * 协议类型
38 */ 38 */
39 - private String codecType; 39 + private String protocolType;
  40 + /**
  41 + * 序列化类型
  42 + */
  43 + private String serializeType;
40 44
41 } 45 }
42 \ No newline at end of file 46 \ No newline at end of file
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusDeviceConfigListReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.biz.dto;
  2 +
  3 +import lombok.Data;
  4 +import lombok.experimental.Accessors;
  5 +
  6 +import java.util.Set;
  7 +
  8 +/**
  9 + * IoT Modbus 设备配置列表查询 Request DTO
  10 + *
  11 + * @author 芋道源码
  12 + */
  13 +@Data
  14 +@Accessors(chain = true)
  15 +public class IotModbusDeviceConfigListReqDTO {
  16 +
  17 + /**
  18 + * 状态
  19 + */
  20 + private Integer status;
  21 +
  22 + /**
  23 + * 模式
  24 + */
  25 + private Integer mode;
  26 +
  27 + /**
  28 + * 协议类型
  29 + */
  30 + private String protocolType;
  31 +
  32 + /**
  33 + * 设备 ID 集合
  34 + */
  35 + private Set<Long> deviceIds;
  36 +
  37 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusDeviceConfigRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.biz.dto;
  2 +
  3 +import lombok.Data;
  4 +
  5 +import java.util.List;
  6 +
  7 +/**
  8 + * IoT Modbus 设备配置 Response DTO
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +public class IotModbusDeviceConfigRespDTO {
  14 +
  15 + /**
  16 + * 设备编号
  17 + */
  18 + private Long deviceId;
  19 + /**
  20 + * 产品标识
  21 + */
  22 + private String productKey;
  23 + /**
  24 + * 设备名称
  25 + */
  26 + private String deviceName;
  27 +
  28 + // ========== Modbus 连接配置 ==========
  29 +
  30 + /**
  31 + * Modbus 服务器 IP 地址
  32 + */
  33 + private String ip;
  34 + /**
  35 + * Modbus 服务器端口
  36 + */
  37 + private Integer port;
  38 + /**
  39 + * 从站地址
  40 + */
  41 + private Integer slaveId;
  42 + /**
  43 + * 连接超时时间,单位:毫秒
  44 + */
  45 + private Integer timeout;
  46 + /**
  47 + * 重试间隔,单位:毫秒
  48 + */
  49 + private Integer retryInterval;
  50 + /**
  51 + * 模式
  52 + */
  53 + private Integer mode;
  54 + /**
  55 + * 数据帧格式
  56 + */
  57 + private Integer frameFormat;
  58 +
  59 + // ========== Modbus 点位配置 ==========
  60 +
  61 + /**
  62 + * 点位列表
  63 + */
  64 + private List<IotModbusPointRespDTO> points;
  65 +
  66 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/biz/dto/IotModbusPointRespDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.biz.dto;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusByteOrderEnum;
  4 +import cn.iocoder.yudao.module.iot.core.enums.modbus.IotModbusRawDataTypeEnum;
  5 +import lombok.Data;
  6 +
  7 +import java.math.BigDecimal;
  8 +
  9 +/**
  10 + * IoT Modbus 点位配置 Response DTO
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Data
  15 +public class IotModbusPointRespDTO {
  16 +
  17 + /**
  18 + * 点位编号
  19 + */
  20 + private Long id;
  21 + /**
  22 + * 属性标识符(物模型的 identifier)
  23 + */
  24 + private String identifier;
  25 + /**
  26 + * 属性名称(物模型的 name)
  27 + */
  28 + private String name;
  29 +
  30 + // ========== Modbus 协议配置 ==========
  31 +
  32 + /**
  33 + * Modbus 功能码
  34 + *
  35 + * 取值范围:FC01-04(读线圈、读离散输入、读保持寄存器、读输入寄存器)
  36 + */
  37 + private Integer functionCode;
  38 + /**
  39 + * 寄存器起始地址
  40 + */
  41 + private Integer registerAddress;
  42 + /**
  43 + * 寄存器数量
  44 + */
  45 + private Integer registerCount;
  46 + /**
  47 + * 字节序
  48 + *
  49 + * 枚举 {@link IotModbusByteOrderEnum}
  50 + */
  51 + private String byteOrder;
  52 + /**
  53 + * 原始数据类型
  54 + *
  55 + * 枚举 {@link IotModbusRawDataTypeEnum}
  56 + */
  57 + private String rawDataType;
  58 + /**
  59 + * 缩放因子
  60 + */
  61 + private BigDecimal scale;
  62 + /**
  63 + * 轮询间隔(毫秒)
  64 + */
  65 + private Integer pollInterval;
  66 +
  67 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceMessageMethodEnum.java
@@ -64,7 +64,7 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable&lt;String&gt; { @@ -64,7 +64,7 @@ public enum IotDeviceMessageMethodEnum implements ArrayValuable&lt;String&gt; {
64 // ========== OTA 固件 ========== 64 // ========== OTA 固件 ==========
65 // 可参考:https://help.aliyun.com/zh/iot/user-guide/perform-ota-updates 65 // 可参考:https://help.aliyun.com/zh/iot/user-guide/perform-ota-updates
66 66
67 - OTA_UPGRADE("thing.ota.upgrade", "OTA 固定信息推送", false), 67 + OTA_UPGRADE("thing.ota.upgrade", "OTA 固件信息推送", false),
68 OTA_PROGRESS("thing.ota.progress", "OTA 升级进度上报", true), 68 OTA_PROGRESS("thing.ota.progress", "OTA 升级进度上报", true),
69 69
70 ; 70 ;
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotProtocolTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.enums;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  5 +import lombok.Getter;
  6 +import lombok.RequiredArgsConstructor;
  7 +
  8 +import java.util.Arrays;
  9 +
  10 +/**
  11 + * IoT 协议类型枚举
  12 + *
  13 + * 用于定义传输层协议类型
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@RequiredArgsConstructor
  18 +@Getter
  19 +public enum IotProtocolTypeEnum implements ArrayValuable<String> {
  20 +
  21 + TCP("tcp"),
  22 + UDP("udp"),
  23 + WEBSOCKET("websocket"),
  24 + HTTP("http"),
  25 + MQTT("mqtt"),
  26 + EMQX("emqx"),
  27 + COAP("coap"),
  28 + MODBUS_TCP_CLIENT("modbus_tcp_client"),
  29 + MODBUS_TCP_SERVER("modbus_tcp_server");
  30 +
  31 + public static final String[] ARRAYS = Arrays.stream(values()).map(IotProtocolTypeEnum::getType).toArray(String[]::new);
  32 +
  33 + /**
  34 + * 类型
  35 + */
  36 + private final String type;
  37 +
  38 + @Override
  39 + public String[] array() {
  40 + return ARRAYS;
  41 + }
  42 +
  43 + public static IotProtocolTypeEnum of(String type) {
  44 + return ArrayUtil.firstMatch(e -> e.getType().equals(type), values());
  45 + }
  46 +
  47 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotSerializeTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.enums;
  2 +
  3 +import cn.hutool.core.util.ArrayUtil;
  4 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  5 +import lombok.Getter;
  6 +import lombok.RequiredArgsConstructor;
  7 +
  8 +import java.util.Arrays;
  9 +
  10 +/**
  11 + * IoT 序列化类型枚举
  12 + *
  13 + * 用于定义设备消息的序列化格式
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@RequiredArgsConstructor
  18 +@Getter
  19 +public enum IotSerializeTypeEnum implements ArrayValuable<String> {
  20 +
  21 + JSON("json"),
  22 + BINARY("binary");
  23 +
  24 + public static final String[] ARRAYS = Arrays.stream(values()).map(IotSerializeTypeEnum::getType).toArray(String[]::new);
  25 +
  26 + /**
  27 + * 类型
  28 + */
  29 + private final String type;
  30 +
  31 + @Override
  32 + public String[] array() {
  33 + return ARRAYS;
  34 + }
  35 +
  36 + public static IotSerializeTypeEnum of(String type) {
  37 + return ArrayUtil.firstMatch(e -> e.getType().equals(type), values());
  38 + }
  39 +
  40 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/IotDeviceStateEnum.java renamed to yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/device/IotDeviceStateEnum.java
1 -package cn.iocoder.yudao.module.iot.core.enums; 1 +package cn.iocoder.yudao.module.iot.core.enums.device;
2 2
3 import cn.iocoder.yudao.framework.common.core.ArrayValuable; 3 import cn.iocoder.yudao.framework.common.core.ArrayValuable;
4 import lombok.Getter; 4 import lombok.Getter;
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusByteOrderEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.enums.modbus;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  4 +import lombok.Getter;
  5 +import lombok.RequiredArgsConstructor;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * IoT Modbus 字节序枚举
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Getter
  15 +@RequiredArgsConstructor
  16 +public enum IotModbusByteOrderEnum implements ArrayValuable<String> {
  17 +
  18 + AB("AB", "大端序(16位)", 2),
  19 + BA("BA", "小端序(16位)", 2),
  20 + ABCD("ABCD", "大端序(32位)", 4),
  21 + CDAB("CDAB", "大端字交换(32位)", 4),
  22 + DCBA("DCBA", "小端序(32位)", 4),
  23 + BADC("BADC", "小端字交换(32位)", 4);
  24 +
  25 + public static final String[] ARRAYS = Arrays.stream(values())
  26 + .map(IotModbusByteOrderEnum::getOrder)
  27 + .toArray(String[]::new);
  28 +
  29 + /**
  30 + * 字节序
  31 + */
  32 + private final String order;
  33 + /**
  34 + * 名称
  35 + */
  36 + private final String name;
  37 + /**
  38 + * 字节数
  39 + */
  40 + private final Integer byteCount;
  41 +
  42 + @Override
  43 + public String[] array() {
  44 + return ARRAYS;
  45 + }
  46 +
  47 + public static IotModbusByteOrderEnum getByOrder(String order) {
  48 + return Arrays.stream(values())
  49 + .filter(e -> e.getOrder().equals(order))
  50 + .findFirst()
  51 + .orElse(null);
  52 + }
  53 +
  54 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusFrameFormatEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.enums.modbus;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  4 +import lombok.Getter;
  5 +import lombok.RequiredArgsConstructor;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * IoT Modbus 数据帧格式枚举
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Getter
  15 +@RequiredArgsConstructor
  16 +public enum IotModbusFrameFormatEnum implements ArrayValuable<Integer> {
  17 +
  18 + MODBUS_TCP(1),
  19 + MODBUS_RTU(2);
  20 +
  21 + public static final Integer[] ARRAYS = Arrays.stream(values())
  22 + .map(IotModbusFrameFormatEnum::getFormat)
  23 + .toArray(Integer[]::new);
  24 +
  25 + /**
  26 + * 格式
  27 + */
  28 + private final Integer format;
  29 +
  30 + @Override
  31 + public Integer[] array() {
  32 + return ARRAYS;
  33 + }
  34 +
  35 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusModeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.enums.modbus;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  4 +import lombok.Getter;
  5 +import lombok.RequiredArgsConstructor;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * IoT Modbus 工作模式枚举
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Getter
  15 +@RequiredArgsConstructor
  16 +public enum IotModbusModeEnum implements ArrayValuable<Integer> {
  17 +
  18 + POLLING(1, "云端轮询"),
  19 + ACTIVE_REPORT(2, "边缘采集");
  20 +
  21 + public static final Integer[] ARRAYS = Arrays.stream(values())
  22 + .map(IotModbusModeEnum::getMode)
  23 + .toArray(Integer[]::new);
  24 +
  25 + /**
  26 + * 工作模式
  27 + */
  28 + private final Integer mode;
  29 + /**
  30 + * 模式名称
  31 + */
  32 + private final String name;
  33 +
  34 + @Override
  35 + public Integer[] array() {
  36 + return ARRAYS;
  37 + }
  38 +
  39 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/enums/modbus/IotModbusRawDataTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.enums.modbus;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.ArrayValuable;
  4 +import lombok.Getter;
  5 +import lombok.RequiredArgsConstructor;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * IoT Modbus 原始数据类型枚举
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Getter
  15 +@RequiredArgsConstructor
  16 +public enum IotModbusRawDataTypeEnum implements ArrayValuable<String> {
  17 +
  18 + INT16("INT16", "有符号 16 位整数", 1),
  19 + UINT16("UINT16", "无符号 16 位整数", 1),
  20 + INT32("INT32", "有符号 32 位整数", 2),
  21 + UINT32("UINT32", "无符号 32 位整数", 2),
  22 + FLOAT("FLOAT", "32 位浮点数", 2),
  23 + DOUBLE("DOUBLE", "64 位浮点数", 4),
  24 + BOOLEAN("BOOLEAN", "布尔值(用于线圈)", 1),
  25 + STRING("STRING", "字符串", null); // null 表示可变长度
  26 +
  27 + public static final String[] ARRAYS = Arrays.stream(values())
  28 + .map(IotModbusRawDataTypeEnum::getType)
  29 + .toArray(String[]::new);
  30 +
  31 + /**
  32 + * 数据类型
  33 + */
  34 + private final String type;
  35 + /**
  36 + * 名称
  37 + */
  38 + private final String name;
  39 + /**
  40 + * 寄存器数量(null 表示可变)
  41 + */
  42 + private final Integer registerCount;
  43 +
  44 + @Override
  45 + public String[] array() {
  46 + return ARRAYS;
  47 + }
  48 +
  49 + public static IotModbusRawDataTypeEnum getByType(String type) {
  50 + return Arrays.stream(values())
  51 + .filter(e -> e.getType().equals(type))
  52 + .findFirst()
  53 + .orElse(null);
  54 + }
  55 +
  56 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageBus.java
@@ -24,4 +24,14 @@ public interface IotMessageBus { @@ -24,4 +24,14 @@ public interface IotMessageBus {
24 */ 24 */
25 void register(IotMessageSubscriber<?> subscriber); 25 void register(IotMessageSubscriber<?> subscriber);
26 26
  27 + /**
  28 + * 取消注册消息订阅者
  29 + *
  30 + * @param subscriber 订阅者
  31 + */
  32 + default void unregister(IotMessageSubscriber<?> subscriber) {
  33 + // TODO 芋艿:暂时不实现,需求量不大,但是
  34 + // throw new UnsupportedOperationException("取消注册消息订阅者功能,尚未实现");
  35 + }
  36 +
27 } 37 }
28 \ No newline at end of file 38 \ No newline at end of file
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/messagebus/core/IotMessageSubscriber.java
@@ -26,4 +26,16 @@ public interface IotMessageSubscriber&lt;T&gt; { @@ -26,4 +26,16 @@ public interface IotMessageSubscriber&lt;T&gt; {
26 */ 26 */
27 void onMessage(T message); 27 void onMessage(T message);
28 28
  29 + /**
  30 + * 启动订阅
  31 + */
  32 + default void start() {
  33 + }
  34 +
  35 + /**
  36 + * 停止订阅
  37 + */
  38 + default void stop() {
  39 + }
  40 +
29 } 41 }
30 \ No newline at end of file 42 \ No newline at end of file
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/mq/message/IotDeviceMessage.java
1 package cn.iocoder.yudao.module.iot.core.mq.message; 1 package cn.iocoder.yudao.module.iot.core.mq.message;
2 2
3 -import cn.hutool.core.map.MapUtil;  
4 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants; 3 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
5 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum; 4 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
6 -import cn.iocoder.yudao.module.iot.core.enums.IotDeviceStateEnum; 5 +import cn.iocoder.yudao.module.iot.core.enums.device.IotDeviceStateEnum;
  6 +import cn.iocoder.yudao.module.iot.core.topic.state.IotDeviceStateUpdateReqDTO;
7 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 7 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
8 import lombok.AllArgsConstructor; 8 import lombok.AllArgsConstructor;
9 import lombok.Builder; 9 import lombok.Builder;
@@ -60,7 +60,7 @@ public class IotDeviceMessage { @@ -60,7 +60,7 @@ public class IotDeviceMessage {
60 */ 60 */
61 private String serverId; 61 private String serverId;
62 62
63 - // ========== codec(编解码)字段 ========== 63 + // ========== serialize(序列化)相关字段 ==========
64 64
65 /** 65 /**
66 * 请求编号 66 * 请求编号
@@ -72,7 +72,7 @@ public class IotDeviceMessage { @@ -72,7 +72,7 @@ public class IotDeviceMessage {
72 * 请求方法 72 * 请求方法
73 * 73 *
74 * 枚举 {@link IotDeviceMessageMethodEnum} 74 * 枚举 {@link IotDeviceMessageMethodEnum}
75 - * 例如说:thing.property.report 属性上报 75 + * 例如说:thing.property.post 属性上报
76 */ 76 */
77 private String method; 77 private String method;
78 /** 78 /**
@@ -94,7 +94,7 @@ public class IotDeviceMessage { @@ -94,7 +94,7 @@ public class IotDeviceMessage {
94 */ 94 */
95 private String msg; 95 private String msg;
96 96
97 - // ========== 基础方法:只传递"codec(编解码)字段" ========== 97 + // ========== 基础方法:只传递"serialize(序列化)相关字段" ==========
98 98
99 public static IotDeviceMessage requestOf(String method) { 99 public static IotDeviceMessage requestOf(String method) {
100 return requestOf(null, method, null); 100 return requestOf(null, method, null);
@@ -149,20 +149,12 @@ public class IotDeviceMessage { @@ -149,20 +149,12 @@ public class IotDeviceMessage {
149 149
150 public static IotDeviceMessage buildStateUpdateOnline() { 150 public static IotDeviceMessage buildStateUpdateOnline() {
151 return requestOf(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(), 151 return requestOf(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(),
152 - MapUtil.of("state", IotDeviceStateEnum.ONLINE.getState())); 152 + new IotDeviceStateUpdateReqDTO(IotDeviceStateEnum.ONLINE.getState()));
153 } 153 }
154 154
155 public static IotDeviceMessage buildStateOffline() { 155 public static IotDeviceMessage buildStateOffline() {
156 return requestOf(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(), 156 return requestOf(IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(),
157 - MapUtil.of("state", IotDeviceStateEnum.OFFLINE.getState()));  
158 - }  
159 -  
160 - public static IotDeviceMessage buildOtaUpgrade(String version, String fileUrl, Long fileSize,  
161 - String fileDigestAlgorithm, String fileDigestValue) {  
162 - return requestOf(IotDeviceMessageMethodEnum.OTA_UPGRADE.getMethod(), MapUtil.builder()  
163 - .put("version", version).put("fileUrl", fileUrl).put("fileSize", fileSize)  
164 - .put("fileDigestAlgorithm", fileDigestAlgorithm).put("fileDigestValue", fileDigestValue)  
165 - .build()); 157 + new IotDeviceStateUpdateReqDTO(IotDeviceStateEnum.OFFLINE.getState()));
166 } 158 }
167 159
168 } 160 }
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterReqDTO.java
1 package cn.iocoder.yudao.module.iot.core.topic.auth; 1 package cn.iocoder.yudao.module.iot.core.topic.auth;
2 2
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
4 import lombok.Data; 5 import lombok.Data;
5 6
6 /** 7 /**
7 * IoT 设备动态注册 Request DTO 8 * IoT 设备动态注册 Request DTO
8 * <p> 9 * <p>
9 - * 用于直连设备/网关的一型一密动态注册:使用 productSecret 验证,返回 deviceSecret 10 + * 用于 {@link IotDeviceMessageMethodEnum#DEVICE_REGISTER} 消息的 params 参数
  11 + * <p>
  12 + * 直连设备/网关的一型一密动态注册:使用 productSecret 验证,返回 deviceSecret
10 * 13 *
11 * @author 芋道源码 14 * @author 芋道源码
12 * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a> 15 * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
@@ -27,9 +30,11 @@ public class IotDeviceRegisterReqDTO { @@ -27,9 +30,11 @@ public class IotDeviceRegisterReqDTO {
27 private String deviceName; 30 private String deviceName;
28 31
29 /** 32 /**
30 - * 产品密钥 33 + * 注册签名
  34 + *
  35 + * @see cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils#buildSign(String, String, String)
31 */ 36 */
32 - @NotEmpty(message = "产品密钥不能为空")  
33 - private String productSecret; 37 + @NotEmpty(message = "签名不能为空")
  38 + private String sign;
34 39
35 } 40 }
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotDeviceRegisterRespDTO.java
1 package cn.iocoder.yudao.module.iot.core.topic.auth; 1 package cn.iocoder.yudao.module.iot.core.topic.auth;
2 2
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
4 import lombok.Data; 5 import lombok.Data;
5 import lombok.NoArgsConstructor; 6 import lombok.NoArgsConstructor;
@@ -7,7 +8,7 @@ import lombok.NoArgsConstructor; @@ -7,7 +8,7 @@ import lombok.NoArgsConstructor;
7 /** 8 /**
8 * IoT 设备动态注册 Response DTO 9 * IoT 设备动态注册 Response DTO
9 * <p> 10 * <p>
10 - * 用于直连设备/网关的一型一密动态注册响应 11 + * 用于 {@link IotDeviceMessageMethodEnum#DEVICE_REGISTER} 响应的设备信息
11 * 12 *
12 * @author 芋道源码 13 * @author 芋道源码
13 * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a> 14 * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterReqDTO.java
1 package cn.iocoder.yudao.module.iot.core.topic.auth; 1 package cn.iocoder.yudao.module.iot.core.topic.auth;
2 2
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
4 import lombok.Data; 5 import lombok.Data;
5 6
6 /** 7 /**
7 * IoT 子设备动态注册 Request DTO 8 * IoT 子设备动态注册 Request DTO
8 * <p> 9 * <p>
9 - * 用于 thing.auth.register.sub 消息的 params 数组元素  
10 - * 10 + * 用于 {@link IotDeviceMessageMethodEnum#SUB_DEVICE_REGISTER} 消息的 params 数组元素
  11 + * <p>
11 * 特殊:网关子设备的动态注册,必须已经创建好该网关子设备(不然哪来的 {@link #deviceName} 字段)。更多的好处,是设备不用提前烧录 deviceSecret 密钥。 12 * 特殊:网关子设备的动态注册,必须已经创建好该网关子设备(不然哪来的 {@link #deviceName} 字段)。更多的好处,是设备不用提前烧录 deviceSecret 密钥。
12 * 13 *
13 * @author 芋道源码 14 * @author 芋道源码
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterRespDTO.java
1 package cn.iocoder.yudao.module.iot.core.topic.auth; 1 package cn.iocoder.yudao.module.iot.core.topic.auth;
2 2
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
4 import lombok.Data; 5 import lombok.Data;
5 import lombok.NoArgsConstructor; 6 import lombok.NoArgsConstructor;
@@ -7,7 +8,7 @@ import lombok.NoArgsConstructor; @@ -7,7 +8,7 @@ import lombok.NoArgsConstructor;
7 /** 8 /**
8 * IoT 子设备动态注册 Response DTO 9 * IoT 子设备动态注册 Response DTO
9 * <p> 10 * <p>
10 - * 用于 thing.auth.register.sub 响应的设备信息 11 + * 用于 {@link IotDeviceMessageMethodEnum#SUB_DEVICE_REGISTER} 响应的设备信息
11 * 12 *
12 * @author 芋道源码 13 * @author 芋道源码
13 * @see <a href="https://help.aliyun.com/zh/iot/user-guide/register-devices">阿里云 - 动态注册子设备</a> 14 * @see <a href="https://help.aliyun.com/zh/iot/user-guide/register-devices">阿里云 - 动态注册子设备</a>
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/config/IotDeviceConfigPushReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.topic.config;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +/**
  9 + * IoT 设备配置推送 Request DTO
  10 + * <p>
  11 + * 用于 {@link IotDeviceMessageMethodEnum#CONFIG_PUSH} 下行消息的 params 参数
  12 + *
  13 + * @author 芋道源码
  14 + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/remote-configuration-1">阿里云 - 远程配置</a>
  15 + */
  16 +@Data
  17 +@NoArgsConstructor
  18 +@AllArgsConstructor
  19 +public class IotDeviceConfigPushReqDTO {
  20 +
  21 + /**
  22 + * 配置编号
  23 + */
  24 + private String configId;
  25 +
  26 + /**
  27 + * 配置文件大小(字节)
  28 + */
  29 + private Long configSize;
  30 +
  31 + /**
  32 + * 签名方法
  33 + */
  34 + private String signMethod;
  35 +
  36 + /**
  37 + * 签名
  38 + */
  39 + private String sign;
  40 +
  41 + /**
  42 + * 配置文件下载地址
  43 + */
  44 + private String url;
  45 +
  46 + /**
  47 + * 获取类型
  48 + * <p>
  49 + * file: 文件
  50 + * content: 内容
  51 + */
  52 + private String getType;
  53 +
  54 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/event/IotDeviceEventPostReqDTO.java
1 package cn.iocoder.yudao.module.iot.core.topic.event; 1 package cn.iocoder.yudao.module.iot.core.topic.event;
2 2
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 import lombok.Data; 4 import lombok.Data;
4 5
5 /** 6 /**
6 * IoT 设备事件上报 Request DTO 7 * IoT 设备事件上报 Request DTO
7 * <p> 8 * <p>
8 - * 用于 thing.event.post 消息的 params 参数 9 + * 用于 {@link IotDeviceMessageMethodEnum#EVENT_POST} 消息的 params 参数
9 * 10 *
10 * @author 芋道源码 11 * @author 芋道源码
11 * @see <a href="http://help.aliyun.com/zh/marketplace/device-reporting-events">阿里云 - 设备上报事件</a> 12 * @see <a href="http://help.aliyun.com/zh/marketplace/device-reporting-events">阿里云 - 设备上报事件</a>
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/ota/IotDeviceOtaProgressReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.topic.ota;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +/**
  9 + * IoT 设备 OTA 升级进度上报 Request DTO
  10 + * <p>
  11 + * 用于 {@link IotDeviceMessageMethodEnum#OTA_PROGRESS} 上行消息的 params 参数
  12 + *
  13 + * @author 芋道源码
  14 + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/perform-ota-updates">阿里云 - OTA 升级</a>
  15 + */
  16 +@Data
  17 +@NoArgsConstructor
  18 +@AllArgsConstructor
  19 +public class IotDeviceOtaProgressReqDTO {
  20 +
  21 + /**
  22 + * 固件版本号
  23 + */
  24 + private String version;
  25 +
  26 + /**
  27 + * 升级状态
  28 + */
  29 + private Integer status;
  30 +
  31 + /**
  32 + * 描述信息
  33 + */
  34 + private String description;
  35 +
  36 + /**
  37 + * 升级进度(0-100)
  38 + */
  39 + private Integer progress;
  40 +
  41 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/ota/IotDeviceOtaUpgradeReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.topic.ota;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +/**
  9 + * IoT 设备 OTA 固件升级推送 Request DTO
  10 + * <p>
  11 + * 用于 {@link IotDeviceMessageMethodEnum#OTA_UPGRADE} 下行消息的 params 参数
  12 + *
  13 + * @author 芋道源码
  14 + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/perform-ota-updates">阿里云 - OTA 升级</a>
  15 + */
  16 +@Data
  17 +@NoArgsConstructor
  18 +@AllArgsConstructor
  19 +public class IotDeviceOtaUpgradeReqDTO {
  20 +
  21 + /**
  22 + * 固件版本号
  23 + */
  24 + private String version;
  25 +
  26 + /**
  27 + * 固件文件下载地址
  28 + */
  29 + private String fileUrl;
  30 +
  31 + /**
  32 + * 固件文件大小(字节)
  33 + */
  34 + private Long fileSize;
  35 +
  36 + /**
  37 + * 固件文件摘要算法
  38 + */
  39 + private String fileDigestAlgorithm;
  40 +
  41 + /**
  42 + * 固件文件摘要值
  43 + */
  44 + private String fileDigestValue;
  45 +
  46 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPackPostReqDTO.java
1 package cn.iocoder.yudao.module.iot.core.topic.property; 1 package cn.iocoder.yudao.module.iot.core.topic.property;
2 2
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; 4 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
4 import lombok.Data; 5 import lombok.Data;
5 6
@@ -9,7 +10,7 @@ import java.util.Map; @@ -9,7 +10,7 @@ import java.util.Map;
9 /** 10 /**
10 * IoT 设备属性批量上报 Request DTO 11 * IoT 设备属性批量上报 Request DTO
11 * <p> 12 * <p>
12 - * 用于 thing.event.property.pack.post 消息的 params 参数 13 + * 用于 {@link IotDeviceMessageMethodEnum#PROPERTY_PACK_POST} 消息的 params 参数
13 * 14 *
14 * @author 芋道源码 15 * @author 芋道源码
15 * @see <a href="http://help.aliyun.com/zh/marketplace/gateway-reports-data-in-batches">阿里云 - 网关批量上报数据</a> 16 * @see <a href="http://help.aliyun.com/zh/marketplace/gateway-reports-data-in-batches">阿里云 - 网关批量上报数据</a>
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertyPostReqDTO.java
1 package cn.iocoder.yudao.module.iot.core.topic.property; 1 package cn.iocoder.yudao.module.iot.core.topic.property;
2 2
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  4 +
3 import java.util.HashMap; 5 import java.util.HashMap;
4 import java.util.Map; 6 import java.util.Map;
5 7
6 /** 8 /**
7 * IoT 设备属性上报 Request DTO 9 * IoT 设备属性上报 Request DTO
8 * <p> 10 * <p>
9 - * 用于 thing.property.post 消息的 params 参数 11 + * 用于 {@link IotDeviceMessageMethodEnum#PROPERTY_POST} 消息的 params 参数
10 * <p> 12 * <p>
11 * 本质是一个 Map,key 为属性标识符,value 为属性值 13 * 本质是一个 Map,key 为属性标识符,value 为属性值
12 * 14 *
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/property/IotDevicePropertySetReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.topic.property;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  4 +
  5 +import java.util.HashMap;
  6 +import java.util.Map;
  7 +
  8 +/**
  9 + * IoT 设备属性设置 Request DTO
  10 + * <p>
  11 + * 用于 {@link IotDeviceMessageMethodEnum#PROPERTY_SET} 下行消息的 params 参数
  12 + * <p>
  13 + * 本质是一个 Map,key 为属性标识符,value 为属性值
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +public class IotDevicePropertySetReqDTO extends HashMap<String, Object> {
  18 +
  19 + public IotDevicePropertySetReqDTO() {
  20 + super();
  21 + }
  22 +
  23 + public IotDevicePropertySetReqDTO(Map<String, Object> properties) {
  24 + super(properties);
  25 + }
  26 +
  27 + /**
  28 + * 创建属性设置 DTO
  29 + *
  30 + * @param properties 属性数据
  31 + * @return DTO 对象
  32 + */
  33 + public static IotDevicePropertySetReqDTO of(Map<String, Object> properties) {
  34 + return new IotDevicePropertySetReqDTO(properties);
  35 + }
  36 +
  37 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/service/IotDeviceServiceInvokeReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.topic.service;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +import java.util.Map;
  9 +
  10 +/**
  11 + * IoT 设备服务调用 Request DTO
  12 + * <p>
  13 + * 用于 {@link IotDeviceMessageMethodEnum#SERVICE_INVOKE} 下行消息的 params 参数
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Data
  18 +@NoArgsConstructor
  19 +@AllArgsConstructor
  20 +public class IotDeviceServiceInvokeReqDTO {
  21 +
  22 + /**
  23 + * 服务标识符
  24 + */
  25 + private String identifier;
  26 +
  27 + /**
  28 + * 服务输入参数
  29 + */
  30 + private Map<String, Object> inputParams;
  31 +
  32 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/state/IotDeviceStateUpdateReqDTO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.topic.state;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Data;
  6 +import lombok.NoArgsConstructor;
  7 +
  8 +/**
  9 + * IoT 设备状态更新 Request DTO
  10 + * <p>
  11 + * 用于 {@link IotDeviceMessageMethodEnum#STATE_UPDATE} 消息的 params 参数
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Data
  16 +@NoArgsConstructor
  17 +@AllArgsConstructor
  18 +public class IotDeviceStateUpdateReqDTO {
  19 +
  20 + /**
  21 + * 设备状态
  22 + */
  23 + private Integer state;
  24 +
  25 +}
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoAddReqDTO.java
1 package cn.iocoder.yudao.module.iot.core.topic.topo; 1 package cn.iocoder.yudao.module.iot.core.topic.topo;
2 2
3 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; 3 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
  4 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
4 import jakarta.validation.constraints.NotEmpty; 5 import jakarta.validation.constraints.NotEmpty;
5 import lombok.Data; 6 import lombok.Data;
6 7
@@ -9,7 +10,7 @@ import java.util.List; @@ -9,7 +10,7 @@ import java.util.List;
9 /** 10 /**
10 * IoT 设备拓扑添加 Request DTO 11 * IoT 设备拓扑添加 Request DTO
11 * <p> 12 * <p>
12 - * 用于 thing.topo.add 消息的 params 参数 13 + * 用于 {@link IotDeviceMessageMethodEnum#TOPO_ADD} 消息的 params 参数
13 * 14 *
14 * @author 芋道源码 15 * @author 芋道源码
15 * @see <a href="http://help.aliyun.com/zh/marketplace/add-topological-relationship">阿里云 - 添加拓扑关系</a> 16 * @see <a href="http://help.aliyun.com/zh/marketplace/add-topological-relationship">阿里云 - 添加拓扑关系</a>
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoChangeReqDTO.java
1 package cn.iocoder.yudao.module.iot.core.topic.topo; 1 package cn.iocoder.yudao.module.iot.core.topic.topo;
2 2
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; 4 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
4 import lombok.AllArgsConstructor; 5 import lombok.AllArgsConstructor;
5 import lombok.Data; 6 import lombok.Data;
@@ -10,7 +11,7 @@ import java.util.List; @@ -10,7 +11,7 @@ import java.util.List;
10 /** 11 /**
11 * IoT 设备拓扑关系变更通知 Request DTO 12 * IoT 设备拓扑关系变更通知 Request DTO
12 * <p> 13 * <p>
13 - * 用于 thing.topo.change 下行消息的 params 参数 14 + * 用于 {@link IotDeviceMessageMethodEnum#TOPO_CHANGE} 下行消息的 params 参数
14 * 15 *
15 * @author 芋道源码 16 * @author 芋道源码
16 * @see <a href="https://help.aliyun.com/zh/marketplace/notify-gateway-topology-changes">阿里云 - 通知网关拓扑关系变化</a> 17 * @see <a href="https://help.aliyun.com/zh/marketplace/notify-gateway-topology-changes">阿里云 - 通知网关拓扑关系变化</a>
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoDeleteReqDTO.java
1 package cn.iocoder.yudao.module.iot.core.topic.topo; 1 package cn.iocoder.yudao.module.iot.core.topic.topo;
2 2
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; 4 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
4 import jakarta.validation.Valid; 5 import jakarta.validation.Valid;
5 import jakarta.validation.constraints.NotEmpty; 6 import jakarta.validation.constraints.NotEmpty;
@@ -10,7 +11,7 @@ import java.util.List; @@ -10,7 +11,7 @@ import java.util.List;
10 /** 11 /**
11 * IoT 设备拓扑删除 Request DTO 12 * IoT 设备拓扑删除 Request DTO
12 * <p> 13 * <p>
13 - * 用于 thing.topo.delete 消息的 params 参数 14 + * 用于 {@link IotDeviceMessageMethodEnum#TOPO_DELETE} 消息的 params 参数
14 * 15 *
15 * @author 芋道源码 16 * @author 芋道源码
16 * @see <a href="https://help.aliyun.com/zh/marketplace/delete-a-topological-relationship">阿里云 - 删除拓扑关系</a> 17 * @see <a href="https://help.aliyun.com/zh/marketplace/delete-a-topological-relationship">阿里云 - 删除拓扑关系</a>
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoGetReqDTO.java
1 package cn.iocoder.yudao.module.iot.core.topic.topo; 1 package cn.iocoder.yudao.module.iot.core.topic.topo;
2 2
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 import lombok.Data; 4 import lombok.Data;
4 5
5 /** 6 /**
6 * IoT 设备拓扑关系获取 Request DTO 7 * IoT 设备拓扑关系获取 Request DTO
7 * <p> 8 * <p>
8 - * 用于 thing.topo.get 请求的 params 参数(目前为空,预留扩展) 9 + * 用于 {@link IotDeviceMessageMethodEnum#TOPO_GET} 请求的 params 参数(目前为空,预留扩展)
9 * 10 *
10 * @author 芋道源码 11 * @author 芋道源码
11 * @see <a href="https://help.aliyun.com/zh/marketplace/obtain-topological-relationship">阿里云 - 获取拓扑关系</a> 12 * @see <a href="https://help.aliyun.com/zh/marketplace/obtain-topological-relationship">阿里云 - 获取拓扑关系</a>
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/topo/IotDeviceTopoGetRespDTO.java
1 package cn.iocoder.yudao.module.iot.core.topic.topo; 1 package cn.iocoder.yudao.module.iot.core.topic.topo;
2 2
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; 4 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
4 import lombok.Data; 5 import lombok.Data;
5 6
@@ -8,7 +9,7 @@ import java.util.List; @@ -8,7 +9,7 @@ import java.util.List;
8 /** 9 /**
9 * IoT 设备拓扑关系获取 Response DTO 10 * IoT 设备拓扑关系获取 Response DTO
10 * <p> 11 * <p>
11 - * 用于 thing.topo.get 响应 12 + * 用于 {@link IotDeviceMessageMethodEnum#TOPO_GET} 响应
12 * 13 *
13 * @author 芋道源码 14 * @author 芋道源码
14 * @see <a href="https://help.aliyun.com/zh/marketplace/obtain-topological-relationship">阿里云 - 获取拓扑关系</a> 15 * @see <a href="https://help.aliyun.com/zh/marketplace/obtain-topological-relationship">阿里云 - 获取拓扑关系</a>
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotDeviceAuthUtils.java
@@ -25,6 +25,14 @@ public class IotDeviceAuthUtils { @@ -25,6 +25,14 @@ public class IotDeviceAuthUtils {
25 return String.format("%s.%s", productKey, deviceName); 25 return String.format("%s.%s", productKey, deviceName);
26 } 26 }
27 27
  28 + public static String buildClientIdFromUsername(String username) {
  29 + IotDeviceIdentity identity = parseUsername(username);
  30 + if (identity == null) {
  31 + return null;
  32 + }
  33 + return buildClientId(identity.getProductKey(), identity.getDeviceName());
  34 + }
  35 +
28 public static String buildUsername(String productKey, String deviceName) { 36 public static String buildUsername(String productKey, String deviceName) {
29 return String.format("%s&%s", deviceName, productKey); 37 return String.format("%s&%s", deviceName, productKey);
30 } 38 }
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/util/IotProductAuthUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.core.util;
  2 +
  3 +import cn.hutool.core.util.StrUtil;
  4 +import cn.hutool.crypto.digest.DigestUtil;
  5 +import cn.hutool.crypto.digest.HmacAlgorithm;
  6 +
  7 +/**
  8 + * IoT 产品【动态注册】认证工具类
  9 + * <p>
  10 + * 用于一型一密场景,使用 productSecret 生成签名
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +public class IotProductAuthUtils {
  15 +
  16 + /**
  17 + * 生成设备动态注册签名
  18 + *
  19 + * @param productKey 产品标识
  20 + * @param deviceName 设备名称
  21 + * @param productSecret 产品密钥
  22 + * @return 签名
  23 + */
  24 + public static String buildSign(String productKey, String deviceName, String productSecret) {
  25 + String content = buildContent(productKey, deviceName);
  26 + return DigestUtil.hmac(HmacAlgorithm.HmacSHA256, StrUtil.utf8Bytes(productSecret))
  27 + .digestHex(content);
  28 + }
  29 +
  30 + /**
  31 + * 验证设备动态注册签名
  32 + *
  33 + * @param productKey 产品标识
  34 + * @param deviceName 设备名称
  35 + * @param productSecret 产品密钥
  36 + * @param sign 待验证的签名
  37 + * @return 是否验证通过
  38 + */
  39 + public static boolean verifySign(String productKey, String deviceName, String productSecret, String sign) {
  40 + String expectedSign = buildSign(productKey, deviceName, productSecret);
  41 + return expectedSign.equals(sign);
  42 + }
  43 +
  44 + /**
  45 + * 构建签名内容
  46 + *
  47 + * @param productKey 产品标识
  48 + * @param deviceName 设备名称
  49 + * @return 签名内容
  50 + */
  51 + private static String buildContent(String productKey, String deviceName) {
  52 + return "deviceName" + deviceName + "productKey" + productKey;
  53 + }
  54 +
  55 +}
yudao-module-iot/yudao-module-iot-gateway/pom.xml
@@ -33,7 +33,7 @@ @@ -33,7 +33,7 @@
33 <groupId>org.apache.rocketmq</groupId> 33 <groupId>org.apache.rocketmq</groupId>
34 <artifactId>rocketmq-spring-boot-starter</artifactId> 34 <artifactId>rocketmq-spring-boot-starter</artifactId>
35 <!-- TODO @芋艿:消息队列,后续可能去掉,默认不使用 rocketmq --> 35 <!-- TODO @芋艿:消息队列,后续可能去掉,默认不使用 rocketmq -->
36 -<!-- <optional>true</optional> --> 36 + <optional>true</optional>
37 </dependency> 37 </dependency>
38 38
39 <!-- 工具类相关 --> 39 <!-- 工具类相关 -->
@@ -48,6 +48,13 @@ @@ -48,6 +48,13 @@
48 <artifactId>vertx-mqtt</artifactId> 48 <artifactId>vertx-mqtt</artifactId>
49 </dependency> 49 </dependency>
50 50
  51 + <!-- Modbus 相关 -->
  52 + <dependency>
  53 + <groupId>com.ghgande</groupId>
  54 + <artifactId>j2mod</artifactId>
  55 + <version>3.2.1</version>
  56 + </dependency>
  57 +
51 <!-- CoAP 相关 - Eclipse Californium --> 58 <!-- CoAP 相关 - Eclipse Californium -->
52 <dependency> 59 <dependency>
53 <groupId>org.eclipse.californium</groupId> 60 <groupId>org.eclipse.californium</groupId>
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/alink/IotAlinkDeviceMessageCodec.java deleted
1 -package cn.iocoder.yudao.module.iot.gateway.codec.alink;  
2 -  
3 -import cn.hutool.core.lang.Assert;  
4 -import cn.iocoder.yudao.framework.common.pojo.CommonResult;  
5 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
6 -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;  
7 -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;  
8 -import lombok.AllArgsConstructor;  
9 -import lombok.Data;  
10 -import lombok.NoArgsConstructor;  
11 -import org.springframework.stereotype.Component;  
12 -  
13 -/**  
14 - * 阿里云 Alink {@link IotDeviceMessage} 的编解码器  
15 - *  
16 - * @author 芋道源码  
17 - */  
18 -@Component  
19 -public class IotAlinkDeviceMessageCodec implements IotDeviceMessageCodec {  
20 -  
21 - public static final String TYPE = "Alink";  
22 -  
23 - @Data  
24 - @NoArgsConstructor  
25 - @AllArgsConstructor  
26 - private static class AlinkMessage {  
27 -  
28 - public static final String VERSION_1 = "1.0";  
29 -  
30 - /**  
31 - * 消息 ID,且每个消息 ID 在当前设备具有唯一性  
32 - */  
33 - private String id;  
34 -  
35 - /**  
36 - * 版本号  
37 - */  
38 - private String version;  
39 -  
40 - /**  
41 - * 请求方法  
42 - */  
43 - private String method;  
44 -  
45 - /**  
46 - * 请求参数  
47 - */  
48 - private Object params;  
49 -  
50 - /**  
51 - * 响应结果  
52 - */  
53 - private Object data;  
54 - /**  
55 - * 响应错误码  
56 - */  
57 - private Integer code;  
58 - /**  
59 - * 响应提示  
60 - *  
61 - * 特殊:这里阿里云是 message,为了保持和项目的 {@link CommonResult#getMsg()} 一致。  
62 - */  
63 - private String msg;  
64 -  
65 - }  
66 -  
67 - @Override  
68 - public String type() {  
69 - return TYPE;  
70 - }  
71 -  
72 - @Override  
73 - public byte[] encode(IotDeviceMessage message) {  
74 - AlinkMessage alinkMessage = new AlinkMessage(message.getRequestId(), AlinkMessage.VERSION_1,  
75 - message.getMethod(), message.getParams(), message.getData(), message.getCode(), message.getMsg());  
76 - return JsonUtils.toJsonByte(alinkMessage);  
77 - }  
78 -  
79 - @Override  
80 - @SuppressWarnings("DataFlowIssue")  
81 - public IotDeviceMessage decode(byte[] bytes) {  
82 - AlinkMessage alinkMessage = JsonUtils.parseObject(bytes, AlinkMessage.class);  
83 - Assert.notNull(alinkMessage, "消息不能为空");  
84 - Assert.equals(alinkMessage.getVersion(), AlinkMessage.VERSION_1, "消息版本号必须是 1.0");  
85 - return IotDeviceMessage.of(alinkMessage.getId(), alinkMessage.getMethod(), alinkMessage.getParams(),  
86 - alinkMessage.getData(), alinkMessage.getCode(), alinkMessage.getMsg());  
87 - }  
88 -  
89 -}  
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/package-info.java deleted
1 -/**  
2 - * 提供设备接入的各种数据(请求、响应)的编解码  
3 - */  
4 -package cn.iocoder.yudao.module.iot.gateway.codec;  
5 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/simple/package-info.java deleted
1 -/**  
2 - * TODO @芋艿:实现一个 alink 的 xml 版本  
3 - */  
4 -package cn.iocoder.yudao.module.iot.gateway.codec.simple;  
5 \ No newline at end of file 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/codec/tcp/IotTcpJsonDeviceMessageCodec.java deleted
1 -package cn.iocoder.yudao.module.iot.gateway.codec.tcp;  
2 -  
3 -import cn.hutool.core.lang.Assert;  
4 -import cn.hutool.core.util.StrUtil;  
5 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
6 -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;  
7 -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;  
8 -import lombok.AllArgsConstructor;  
9 -import lombok.Data;  
10 -import lombok.NoArgsConstructor;  
11 -import org.springframework.stereotype.Component;  
12 -  
13 -/**  
14 - * TCP/UDP JSON 格式 {@link IotDeviceMessage} 编解码器  
15 - *  
16 - * 采用纯 JSON 格式传输,格式如下:  
17 - * {  
18 - * "id": "消息 ID",  
19 - * "method": "消息方法",  
20 - * "params": {...}, // 请求参数  
21 - * "data": {...}, // 响应结果  
22 - * "code": 200, // 响应错误码  
23 - * "msg": "success", // 响应提示  
24 - * "timestamp": 时间戳  
25 - * }  
26 - *  
27 - * @author 芋道源码  
28 - */  
29 -@Component  
30 -public class IotTcpJsonDeviceMessageCodec implements IotDeviceMessageCodec {  
31 -  
32 - public static final String TYPE = "TCP_JSON";  
33 -  
34 - @Data  
35 - @NoArgsConstructor  
36 - @AllArgsConstructor  
37 - private static class TcpJsonMessage {  
38 -  
39 - /**  
40 - * 消息 ID,且每个消息 ID 在当前设备具有唯一性  
41 - */  
42 - private String id;  
43 -  
44 - /**  
45 - * 请求方法  
46 - */  
47 - private String method;  
48 -  
49 - /**  
50 - * 请求参数  
51 - */  
52 - private Object params;  
53 -  
54 - /**  
55 - * 响应结果  
56 - */  
57 - private Object data;  
58 -  
59 - /**  
60 - * 响应错误码  
61 - */  
62 - private Integer code;  
63 -  
64 - /**  
65 - * 响应提示  
66 - */  
67 - private String msg;  
68 -  
69 - /**  
70 - * 时间戳  
71 - */  
72 - private Long timestamp;  
73 -  
74 - }  
75 -  
76 - @Override  
77 - public String type() {  
78 - return TYPE;  
79 - }  
80 -  
81 - @Override  
82 - public byte[] encode(IotDeviceMessage message) {  
83 - TcpJsonMessage tcpJsonMessage = new TcpJsonMessage(  
84 - message.getRequestId(),  
85 - message.getMethod(),  
86 - message.getParams(),  
87 - message.getData(),  
88 - message.getCode(),  
89 - message.getMsg(),  
90 - System.currentTimeMillis());  
91 - return JsonUtils.toJsonByte(tcpJsonMessage);  
92 - }  
93 -  
94 - @Override  
95 - @SuppressWarnings("DataFlowIssue")  
96 - public IotDeviceMessage decode(byte[] bytes) {  
97 - String jsonStr = StrUtil.utf8Str(bytes).trim();  
98 - TcpJsonMessage tcpJsonMessage = JsonUtils.parseObject(jsonStr, TcpJsonMessage.class);  
99 - Assert.notNull(tcpJsonMessage, "消息不能为空");  
100 - Assert.notBlank(tcpJsonMessage.getMethod(), "消息方法不能为空");  
101 - return IotDeviceMessage.of(  
102 - tcpJsonMessage.getId(),  
103 - tcpJsonMessage.getMethod(),  
104 - tcpJsonMessage.getParams(),  
105 - tcpJsonMessage.getData(),  
106 - tcpJsonMessage.getCode(),  
107 - tcpJsonMessage.getMsg());  
108 - }  
109 -  
110 -}  
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
1 package cn.iocoder.yudao.module.iot.gateway.config; 1 package cn.iocoder.yudao.module.iot.gateway.config;
2 2
3 -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;  
4 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapDownstreamSubscriber;  
5 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapUpstreamProtocol;  
6 -import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol;  
7 -import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscriber;  
8 -import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol;  
9 -import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpDownstreamSubscriber;  
10 -import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpUpstreamProtocol;  
11 -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttDownstreamSubscriber;  
12 -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;  
13 -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;  
14 -import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttDownstreamHandler;  
15 -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpDownstreamSubscriber;  
16 -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpUpstreamProtocol;  
17 -import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager;  
18 -import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpDownstreamSubscriber;  
19 -import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpUpstreamProtocol;  
20 -import cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager.IotUdpSessionManager;  
21 -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketDownstreamSubscriber;  
22 -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketUpstreamProtocol;  
23 -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager;  
24 -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;  
25 -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;  
26 -import io.vertx.core.Vertx;  
27 -import lombok.extern.slf4j.Slf4j;  
28 -import org.springframework.beans.factory.annotation.Qualifier;  
29 -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 3 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolManager;
  4 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager;
30 import org.springframework.boot.context.properties.EnableConfigurationProperties; 5 import org.springframework.boot.context.properties.EnableConfigurationProperties;
31 import org.springframework.context.annotation.Bean; 6 import org.springframework.context.annotation.Bean;
32 import org.springframework.context.annotation.Configuration; 7 import org.springframework.context.annotation.Configuration;
33 8
  9 +/**
  10 + * IoT 网关配置类
  11 + *
  12 + * @author 芋道源码
  13 + */
34 @Configuration 14 @Configuration
35 @EnableConfigurationProperties(IotGatewayProperties.class) 15 @EnableConfigurationProperties(IotGatewayProperties.class)
36 -@Slf4j  
37 public class IotGatewayConfiguration { 16 public class IotGatewayConfiguration {
38 17
39 - /**  
40 - * IoT 网关 HTTP 协议配置类  
41 - */  
42 - @Configuration  
43 - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.http", name = "enabled", havingValue = "true")  
44 - @Slf4j  
45 - public static class HttpProtocolConfiguration {  
46 -  
47 - @Bean(name = "httpVertx", destroyMethod = "close")  
48 - public Vertx httpVertx() {  
49 - return Vertx.vertx();  
50 - }  
51 -  
52 - @Bean  
53 - public IotHttpUpstreamProtocol iotHttpUpstreamProtocol(IotGatewayProperties gatewayProperties,  
54 - @Qualifier("httpVertx") Vertx httpVertx) {  
55 - return new IotHttpUpstreamProtocol(gatewayProperties.getProtocol().getHttp(), httpVertx);  
56 - }  
57 -  
58 - @Bean  
59 - public IotHttpDownstreamSubscriber iotHttpDownstreamSubscriber(IotHttpUpstreamProtocol httpUpstreamProtocol,  
60 - IotMessageBus messageBus) {  
61 - return new IotHttpDownstreamSubscriber(httpUpstreamProtocol, messageBus);  
62 - }  
63 - }  
64 -  
65 - /**  
66 - * IoT 网关 EMQX 协议配置类  
67 - */  
68 - @Configuration  
69 - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.emqx", name = "enabled", havingValue = "true")  
70 - @Slf4j  
71 - public static class EmqxProtocolConfiguration {  
72 -  
73 - @Bean(name = "emqxVertx", destroyMethod = "close")  
74 - public Vertx emqxVertx() {  
75 - return Vertx.vertx();  
76 - }  
77 -  
78 - @Bean  
79 - public IotEmqxAuthEventProtocol iotEmqxAuthEventProtocol(IotGatewayProperties gatewayProperties,  
80 - @Qualifier("emqxVertx") Vertx emqxVertx) {  
81 - return new IotEmqxAuthEventProtocol(gatewayProperties.getProtocol().getEmqx(), emqxVertx);  
82 - }  
83 -  
84 - @Bean  
85 - public IotEmqxUpstreamProtocol iotEmqxUpstreamProtocol(IotGatewayProperties gatewayProperties,  
86 - @Qualifier("emqxVertx") Vertx emqxVertx) {  
87 - return new IotEmqxUpstreamProtocol(gatewayProperties.getProtocol().getEmqx(), emqxVertx);  
88 - }  
89 -  
90 - @Bean  
91 - public IotEmqxDownstreamSubscriber iotEmqxDownstreamSubscriber(IotEmqxUpstreamProtocol mqttUpstreamProtocol,  
92 - IotMessageBus messageBus) {  
93 - return new IotEmqxDownstreamSubscriber(mqttUpstreamProtocol, messageBus);  
94 - } 18 + @Bean
  19 + public IotMessageSerializerManager iotMessageSerializerManager() {
  20 + return new IotMessageSerializerManager();
95 } 21 }
96 22
97 - /**  
98 - * IoT 网关 TCP 协议配置类  
99 - */  
100 - @Configuration  
101 - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.tcp", name = "enabled", havingValue = "true")  
102 - @Slf4j  
103 - public static class TcpProtocolConfiguration {  
104 -  
105 - @Bean(name = "tcpVertx", destroyMethod = "close")  
106 - public Vertx tcpVertx() {  
107 - return Vertx.vertx();  
108 - }  
109 -  
110 - @Bean  
111 - public IotTcpUpstreamProtocol iotTcpUpstreamProtocol(IotGatewayProperties gatewayProperties,  
112 - IotDeviceService deviceService,  
113 - IotDeviceMessageService messageService,  
114 - IotTcpConnectionManager connectionManager,  
115 - @Qualifier("tcpVertx") Vertx tcpVertx) {  
116 - return new IotTcpUpstreamProtocol(gatewayProperties.getProtocol().getTcp(),  
117 - deviceService, messageService, connectionManager, tcpVertx);  
118 - }  
119 -  
120 - @Bean  
121 - public IotTcpDownstreamSubscriber iotTcpDownstreamSubscriber(IotTcpUpstreamProtocol protocolHandler,  
122 - IotDeviceMessageService messageService,  
123 - IotTcpConnectionManager connectionManager,  
124 - IotMessageBus messageBus) {  
125 - return new IotTcpDownstreamSubscriber(protocolHandler, messageService, connectionManager, messageBus);  
126 - }  
127 -  
128 - }  
129 -  
130 - /**  
131 - * IoT 网关 MQTT 协议配置类  
132 - */  
133 - @Configuration  
134 - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.mqtt", name = "enabled", havingValue = "true")  
135 - @Slf4j  
136 - public static class MqttProtocolConfiguration {  
137 -  
138 - @Bean(name = "mqttVertx", destroyMethod = "close")  
139 - public Vertx mqttVertx() {  
140 - return Vertx.vertx();  
141 - }  
142 -  
143 - @Bean  
144 - public IotMqttUpstreamProtocol iotMqttUpstreamProtocol(IotGatewayProperties gatewayProperties,  
145 - IotDeviceMessageService messageService,  
146 - IotMqttConnectionManager connectionManager,  
147 - @Qualifier("mqttVertx") Vertx mqttVertx) {  
148 - return new IotMqttUpstreamProtocol(gatewayProperties.getProtocol().getMqtt(), messageService,  
149 - connectionManager, mqttVertx);  
150 - }  
151 -  
152 - @Bean  
153 - public IotMqttDownstreamHandler iotMqttDownstreamHandler(IotDeviceMessageService messageService,  
154 - IotMqttConnectionManager connectionManager) {  
155 - return new IotMqttDownstreamHandler(messageService, connectionManager);  
156 - }  
157 -  
158 - @Bean  
159 - public IotMqttDownstreamSubscriber iotMqttDownstreamSubscriber(IotMqttUpstreamProtocol mqttUpstreamProtocol,  
160 - IotMqttDownstreamHandler downstreamHandler,  
161 - IotMessageBus messageBus) {  
162 - return new IotMqttDownstreamSubscriber(mqttUpstreamProtocol, downstreamHandler, messageBus);  
163 - }  
164 -  
165 - }  
166 -  
167 - /**  
168 - * IoT 网关 UDP 协议配置类  
169 - */  
170 - @Configuration  
171 - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.udp", name = "enabled", havingValue = "true")  
172 - @Slf4j  
173 - public static class UdpProtocolConfiguration {  
174 -  
175 - @Bean(name = "udpVertx", destroyMethod = "close")  
176 - public Vertx udpVertx() {  
177 - return Vertx.vertx();  
178 - }  
179 -  
180 - @Bean  
181 - public IotUdpUpstreamProtocol iotUdpUpstreamProtocol(IotGatewayProperties gatewayProperties,  
182 - IotDeviceService deviceService,  
183 - IotDeviceMessageService messageService,  
184 - IotUdpSessionManager sessionManager,  
185 - @Qualifier("udpVertx") Vertx udpVertx) {  
186 - return new IotUdpUpstreamProtocol(gatewayProperties.getProtocol().getUdp(),  
187 - deviceService, messageService, sessionManager, udpVertx);  
188 - }  
189 -  
190 - @Bean  
191 - public IotUdpDownstreamSubscriber iotUdpDownstreamSubscriber(IotUdpUpstreamProtocol protocolHandler,  
192 - IotDeviceMessageService messageService,  
193 - IotUdpSessionManager sessionManager,  
194 - IotMessageBus messageBus) {  
195 - return new IotUdpDownstreamSubscriber(protocolHandler, messageService, sessionManager, messageBus);  
196 - }  
197 -  
198 - }  
199 -  
200 - /**  
201 - * IoT 网关 CoAP 协议配置类  
202 - */  
203 - @Configuration  
204 - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.coap", name = "enabled", havingValue = "true")  
205 - @Slf4j  
206 - public static class CoapProtocolConfiguration {  
207 -  
208 - @Bean  
209 - public IotCoapUpstreamProtocol iotCoapUpstreamProtocol(IotGatewayProperties gatewayProperties) {  
210 - return new IotCoapUpstreamProtocol(gatewayProperties.getProtocol().getCoap());  
211 - }  
212 -  
213 - @Bean  
214 - public IotCoapDownstreamSubscriber iotCoapDownstreamSubscriber(IotCoapUpstreamProtocol coapUpstreamProtocol,  
215 - IotMessageBus messageBus) {  
216 - return new IotCoapDownstreamSubscriber(coapUpstreamProtocol, messageBus);  
217 - }  
218 -  
219 - }  
220 -  
221 - /**  
222 - * IoT 网关 WebSocket 协议配置类  
223 - */  
224 - @Configuration  
225 - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.websocket", name = "enabled", havingValue = "true")  
226 - @Slf4j  
227 - public static class WebSocketProtocolConfiguration {  
228 -  
229 - @Bean(name = "websocketVertx", destroyMethod = "close")  
230 - public Vertx websocketVertx() {  
231 - return Vertx.vertx();  
232 - }  
233 -  
234 - @Bean  
235 - public IotWebSocketUpstreamProtocol iotWebSocketUpstreamProtocol(IotGatewayProperties gatewayProperties,  
236 - IotDeviceService deviceService,  
237 - IotDeviceMessageService messageService,  
238 - IotWebSocketConnectionManager connectionManager,  
239 - @Qualifier("websocketVertx") Vertx websocketVertx) {  
240 - return new IotWebSocketUpstreamProtocol(gatewayProperties.getProtocol().getWebsocket(),  
241 - deviceService, messageService, connectionManager, websocketVertx);  
242 - }  
243 -  
244 - @Bean  
245 - public IotWebSocketDownstreamSubscriber iotWebSocketDownstreamSubscriber(IotWebSocketUpstreamProtocol protocolHandler,  
246 - IotDeviceMessageService messageService,  
247 - IotWebSocketConnectionManager connectionManager,  
248 - IotMessageBus messageBus) {  
249 - return new IotWebSocketDownstreamSubscriber(protocolHandler, messageService, connectionManager, messageBus);  
250 - }  
251 - 23 + @Bean
  24 + public IotProtocolManager iotProtocolManager(IotGatewayProperties gatewayProperties) {
  25 + return new IotProtocolManager(gatewayProperties);
252 } 26 }
253 27
254 } 28 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
1 package cn.iocoder.yudao.module.iot.gateway.config; 1 package cn.iocoder.yudao.module.iot.gateway.config;
2 2
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
  4 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapConfig;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxConfig;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpConfig;
  7 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient.IotModbusTcpClientConfig;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpserver.IotModbusTcpServerConfig;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttConfig;
  10 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConfig;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpConfig;
  12 +import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketConfig;
  13 +import jakarta.validation.Valid;
3 import jakarta.validation.constraints.NotEmpty; 14 import jakarta.validation.constraints.NotEmpty;
4 import jakarta.validation.constraints.NotNull; 15 import jakarta.validation.constraints.NotNull;
5 import lombok.Data; 16 import lombok.Data;
@@ -24,9 +35,9 @@ public class IotGatewayProperties { @@ -24,9 +35,9 @@ public class IotGatewayProperties {
24 private TokenProperties token; 35 private TokenProperties token;
25 36
26 /** 37 /**
27 - * 协议配置 38 + * 协议实例列表
28 */ 39 */
29 - private ProtocolProperties protocol; 40 + private List<ProtocolProperties> protocols;
30 41
31 @Data 42 @Data
32 public static class RpcProperties { 43 public static class RpcProperties {
@@ -65,581 +76,157 @@ public class IotGatewayProperties { @@ -65,581 +76,157 @@ public class IotGatewayProperties {
65 76
66 } 77 }
67 78
  79 + /**
  80 + * 协议实例配置
  81 + */
68 @Data 82 @Data
69 public static class ProtocolProperties { 83 public static class ProtocolProperties {
70 84
71 /** 85 /**
72 - * HTTP 组件配置  
73 - */  
74 - private HttpProperties http;  
75 -  
76 - /**  
77 - * EMQX 组件配置  
78 - */  
79 - private EmqxProperties emqx;  
80 -  
81 - /**  
82 - * TCP 组件配置  
83 - */  
84 - private TcpProperties tcp;  
85 -  
86 - /**  
87 - * MQTT 组件配置  
88 - */  
89 - private MqttProperties mqtt;  
90 -  
91 - /**  
92 - * MQTT WebSocket 组件配置  
93 - */  
94 - private MqttWsProperties mqttWs;  
95 -  
96 - /**  
97 - * UDP 组件配置 86 + * 协议实例 ID,如 "http-alink"、"tcp-binary"
98 */ 87 */
99 - private UdpProperties udp;  
100 - 88 + @NotEmpty(message = "协议实例 ID 不能为空")
  89 + private String id;
101 /** 90 /**
102 - * CoAP 组件配置 91 + * 是否启用
103 */ 92 */
104 - private CoapProperties coap;  
105 - 93 + @NotNull(message = "是否启用不能为空")
  94 + private Boolean enabled = true;
106 /** 95 /**
107 - * WebSocket 组件配置 96 + * 协议类型
  97 + *
  98 + * @see cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum
108 */ 99 */
109 - private WebSocketProperties websocket;  
110 -  
111 - }  
112 -  
113 - @Data  
114 - public static class HttpProperties {  
115 -  
116 - /**  
117 - * 是否开启  
118 - */  
119 - @NotNull(message = "是否开启不能为空")  
120 - private Boolean enabled; 100 + @NotEmpty(message = "协议类型不能为空")
  101 + private String protocol;
121 /** 102 /**
122 * 服务端口 103 * 服务端口
123 - */  
124 - private Integer serverPort;  
125 -  
126 - /**  
127 - * 是否开启 SSL  
128 - */  
129 - @NotNull(message = "是否开启 SSL 不能为空")  
130 - private Boolean sslEnabled = false;  
131 -  
132 - /**  
133 - * SSL 证书路径  
134 - */  
135 - private String sslKeyPath;  
136 - /**  
137 - * SSL 证书路径  
138 - */  
139 - private String sslCertPath;  
140 -  
141 - }  
142 -  
143 - @Data  
144 - public static class EmqxProperties {  
145 -  
146 - /**  
147 - * 是否开启  
148 - */  
149 - @NotNull(message = "是否开启不能为空")  
150 - private Boolean enabled;  
151 -  
152 - /**  
153 - * HTTP 服务端口(默认:8090)  
154 - */  
155 - private Integer httpPort = 8090;  
156 -  
157 - /**  
158 - * MQTT 服务器地址  
159 - */  
160 - @NotEmpty(message = "MQTT 服务器地址不能为空")  
161 - private String mqttHost;  
162 -  
163 - /**  
164 - * MQTT 服务器端口(默认:1883)  
165 - */  
166 - @NotNull(message = "MQTT 服务器端口不能为空")  
167 - private Integer mqttPort = 1883;  
168 -  
169 - /**  
170 - * MQTT 用户名  
171 - */  
172 - @NotEmpty(message = "MQTT 用户名不能为空")  
173 - private String mqttUsername;  
174 -  
175 - /**  
176 - * MQTT 密码  
177 - */  
178 - @NotEmpty(message = "MQTT 密码不能为空")  
179 - private String mqttPassword;  
180 -  
181 - /**  
182 - * MQTT 客户端的 SSL 开关  
183 - */  
184 - @NotNull(message = "MQTT 是否开启 SSL 不能为空")  
185 - private Boolean mqttSsl = false;  
186 -  
187 - /**  
188 - * MQTT 客户端 ID(如果为空,系统将自动生成)  
189 - */  
190 - @NotEmpty(message = "MQTT 客户端 ID 不能为空")  
191 - private String mqttClientId;  
192 -  
193 - /**  
194 - * MQTT 订阅的主题  
195 - */  
196 - @NotEmpty(message = "MQTT 主题不能为空")  
197 - private List<@NotEmpty(message = "MQTT 主题不能为空") String> mqttTopics;  
198 -  
199 - /**  
200 - * 默认 QoS 级别  
201 * <p> 104 * <p>
202 - * 0 - 最多一次  
203 - * 1 - 至少一次  
204 - * 2 - 刚好一次 105 + * 不同协议含义不同:
  106 + * 1. TCP/UDP/HTTP/WebSocket/MQTT/CoAP:对应网关自身监听的服务端口
  107 + * 2. EMQX:对应网关提供给 EMQX 回调的 HTTP Hook 端口(/mqtt/auth、/mqtt/acl、/mqtt/event)
205 */ 108 */
206 - private Integer mqttQos = 1;  
207 - 109 + @NotNull(message = "服务端口不能为空")
  110 + private Integer port;
208 /** 111 /**
209 - * 连接超时时间(秒) 112 + * 序列化类型(可选)
  113 + *
  114 + * @see cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum
  115 + *
  116 + * 为什么是可选的呢?
  117 + * 1. {@link IotProtocolTypeEnum#HTTP}、{@link IotProtocolTypeEnum#COAP} 协议,目前强制是 JSON 格式
  118 + * 2. {@link IotProtocolTypeEnum#EMQX} 协议,目前支持根据产品(设备)配置的序列化类型来解析
210 */ 119 */
211 - private Integer connectTimeoutSeconds = 10; 120 + private String serialize;
212 121
213 - /**  
214 - * 重连延迟时间(毫秒)  
215 - */  
216 - private Long reconnectDelayMs = 5000L; 122 + // ========== SSL 配置 ==========
217 123
218 /** 124 /**
219 - * 是否启用 Clean Session (清理会话)  
220 - * true: 每次连接都是新会话,Broker 不保留离线消息和订阅关系。  
221 - * 对于网关这类“永远在线”且会主动重新订阅的应用,建议为 true。 125 + * SSL 配置(可选,配置文件中不配置则为 null)
222 */ 126 */
223 - private Boolean cleanSession = true; 127 + @Valid
  128 + private SslConfig ssl;
224 129
225 - /**  
226 - * 心跳间隔(秒)  
227 - * 用于保持连接活性,及时发现网络中断。  
228 - */  
229 - private Integer keepAliveIntervalSeconds = 60; 130 + // ========== 各协议配置 ==========
230 131
231 /** 132 /**
232 - * 最大未确认消息队列大小  
233 - * 限制已发送但未收到 Broker 确认的 QoS 1/2 消息数量,用于流量控制。 133 + * HTTP 协议配置
234 */ 134 */
235 - private Integer maxInflightQueue = 10000;  
236 - 135 + @Valid
  136 + private IotHttpConfig http;
237 /** 137 /**
238 - * 是否信任所有 SSL 证书  
239 - * 警告:此配置会绕过证书验证,仅建议在开发和测试环境中使用!  
240 - * 在生产环境中,应设置为 false,并配置正确的信任库。 138 + * WebSocket 协议配置
241 */ 139 */
242 - private Boolean trustAll = false; 140 + @Valid
  141 + private IotWebSocketConfig websocket;
243 142
244 /** 143 /**
245 - * 遗嘱消息配置 (用于网关异常下线时通知其他系统) 144 + * TCP 协议配置
246 */ 145 */
247 - private final Will will = new Will();  
248 - 146 + @Valid
  147 + private IotTcpConfig tcp;
249 /** 148 /**
250 - * 高级 SSL/TLS 配置 (用于生产环境) 149 + * UDP 协议配置
251 */ 150 */
252 - private final Ssl sslOptions = new Ssl(); 151 + @Valid
  152 + private IotUdpConfig udp;
253 153
254 /** 154 /**
255 - * 遗嘱消息 (Last Will and Testament) 155 + * CoAP 协议配置
256 */ 156 */
257 - @Data  
258 - public static class Will {  
259 -  
260 - /**  
261 - * 是否启用遗嘱消息  
262 - */  
263 - private boolean enabled = false;  
264 - /**  
265 - * 遗嘱消息主题  
266 - */  
267 - private String topic;  
268 - /**  
269 - * 遗嘱消息内容  
270 - */  
271 - private String payload;  
272 - /**  
273 - * 遗嘱消息 QoS 等级  
274 - */  
275 - private Integer qos = 1;  
276 - /**  
277 - * 遗嘱消息是否作为保留消息发布  
278 - */  
279 - private boolean retain = true;  
280 -  
281 - } 157 + @Valid
  158 + private IotCoapConfig coap;
282 159
283 /** 160 /**
284 - * 高级 SSL/TLS 配置 161 + * MQTT 协议配置
285 */ 162 */
286 - @Data  
287 - public static class Ssl {  
288 -  
289 - /**  
290 - * 密钥库(KeyStore)路径,例如:classpath:certs/client.jks  
291 - * 包含客户端自己的证书和私钥,用于向服务端证明身份(双向认证)。  
292 - */  
293 - private String keyStorePath;  
294 - /**  
295 - * 密钥库密码  
296 - */  
297 - private String keyStorePassword;  
298 - /**  
299 - * 信任库(TrustStore)路径,例如:classpath:certs/trust.jks  
300 - * 包含服务端信任的 CA 证书,用于验证服务端的身份,防止中间人攻击。  
301 - */  
302 - private String trustStorePath;  
303 - /**  
304 - * 信任库密码  
305 - */  
306 - private String trustStorePassword;  
307 -  
308 - }  
309 -  
310 - }  
311 -  
312 - @Data  
313 - public static class TcpProperties {  
314 - 163 + @Valid
  164 + private IotMqttConfig mqtt;
315 /** 165 /**
316 - * 是否开启 166 + * EMQX 协议配置
317 */ 167 */
318 - @NotNull(message = "是否开启不能为空")  
319 - private Boolean enabled; 168 + @Valid
  169 + private IotEmqxConfig emqx;
320 170
321 /** 171 /**
322 - * 服务器端口 172 + * Modbus TCP Client 协议配置
323 */ 173 */
324 - private Integer port = 8091; 174 + @Valid
  175 + private IotModbusTcpClientConfig modbusTcpClient;
325 176
326 /** 177 /**
327 - * 心跳超时时间(毫秒) 178 + * Modbus TCP Server 协议配置
328 */ 179 */
329 - private Long keepAliveTimeoutMs = 30000L;  
330 -  
331 - /**  
332 - * 最大连接数  
333 - */  
334 - private Integer maxConnections = 1000;  
335 -  
336 - /**  
337 - * 是否启用SSL  
338 - */  
339 - private Boolean sslEnabled = false;  
340 -  
341 - /**  
342 - * SSL证书路径  
343 - */  
344 - private String sslCertPath;  
345 -  
346 - /**  
347 - * SSL私钥路径  
348 - */  
349 - private String sslKeyPath; 180 + @Valid
  181 + private IotModbusTcpServerConfig modbusTcpServer;
350 182
351 } 183 }
352 184
  185 + /**
  186 + * SSL 配置
  187 + */
353 @Data 188 @Data
354 - public static class MqttProperties {  
355 -  
356 - /**  
357 - * 是否开启  
358 - */  
359 - @NotNull(message = "是否开启不能为空")  
360 - private Boolean enabled;  
361 -  
362 - /**  
363 - * 服务器端口  
364 - */  
365 - private Integer port = 1883;  
366 -  
367 - /**  
368 - * 最大消息大小(字节)  
369 - */  
370 - private Integer maxMessageSize = 8192;  
371 -  
372 - /**  
373 - * 连接超时时间(秒)  
374 - */  
375 - private Integer connectTimeoutSeconds = 60;  
376 - /**  
377 - * 保持连接超时时间(秒)  
378 - */  
379 - private Integer keepAliveTimeoutSeconds = 300; 189 + public static class SslConfig {
380 190
381 /** 191 /**
382 * 是否启用 SSL 192 * 是否启用 SSL
383 */ 193 */
384 - private Boolean sslEnabled = false;  
385 - /**  
386 - * SSL 配置  
387 - */  
388 - private SslOptions sslOptions = new SslOptions();  
389 -  
390 - /**  
391 - * SSL 配置选项  
392 - */  
393 - @Data  
394 - public static class SslOptions {  
395 -  
396 - /**  
397 - * 密钥证书选项  
398 - */  
399 - private io.vertx.core.net.KeyCertOptions keyCertOptions;  
400 - /**  
401 - * 信任选项  
402 - */  
403 - private io.vertx.core.net.TrustOptions trustOptions;  
404 - /**  
405 - * SSL 证书路径  
406 - */  
407 - private String certPath;  
408 - /**  
409 - * SSL 私钥路径  
410 - */  
411 - private String keyPath;  
412 - /**  
413 - * 信任存储路径  
414 - */  
415 - private String trustStorePath;  
416 - /**  
417 - * 信任存储密码  
418 - */  
419 - private String trustStorePassword;  
420 -  
421 - }  
422 -  
423 - }  
424 -  
425 - @Data  
426 - public static class MqttWsProperties {  
427 -  
428 - /**  
429 - * 是否开启  
430 - */  
431 - @NotNull(message = "是否开启不能为空")  
432 - private Boolean enabled;  
433 -  
434 - /**  
435 - * WebSocket 服务器端口(默认:8083)  
436 - */  
437 - private Integer port = 8083;  
438 -  
439 - /**  
440 - * WebSocket 路径(默认:/mqtt)  
441 - */  
442 - @NotEmpty(message = "WebSocket 路径不能为空")  
443 - private String path = "/mqtt";  
444 -  
445 - /**  
446 - * 最大消息大小(字节)  
447 - */  
448 - private Integer maxMessageSize = 8192;  
449 -  
450 - /**  
451 - * 连接超时时间(秒)  
452 - */  
453 - private Integer connectTimeoutSeconds = 60;  
454 -  
455 - /**  
456 - * 保持连接超时时间(秒)  
457 - */  
458 - private Integer keepAliveTimeoutSeconds = 300;  
459 -  
460 - /**  
461 - * 是否启用 SSL(wss://)  
462 - */  
463 - private Boolean sslEnabled = false;  
464 -  
465 - /**  
466 - * SSL 配置  
467 - */  
468 - private SslOptions sslOptions = new SslOptions();  
469 -  
470 - /**  
471 - * WebSocket 子协议(通常为 "mqtt" 或 "mqttv3.1")  
472 - */  
473 - @NotEmpty(message = "WebSocket 子协议不能为空")  
474 - private String subProtocol = "mqtt";  
475 -  
476 - /**  
477 - * 最大帧大小(字节)  
478 - */  
479 - private Integer maxFrameSize = 65536;  
480 -  
481 - /**  
482 - * SSL 配置选项  
483 - */  
484 - @Data  
485 - public static class SslOptions {  
486 -  
487 - /**  
488 - * 密钥证书选项  
489 - */  
490 - private io.vertx.core.net.KeyCertOptions keyCertOptions;  
491 -  
492 - /**  
493 - * 信任选项  
494 - */  
495 - private io.vertx.core.net.TrustOptions trustOptions;  
496 -  
497 - /**  
498 - * SSL 证书路径  
499 - */  
500 - private String certPath;  
501 -  
502 - /**  
503 - * SSL 私钥路径  
504 - */  
505 - private String keyPath;  
506 -  
507 - /**  
508 - * 信任存储路径  
509 - */  
510 - private String trustStorePath;  
511 -  
512 - /**  
513 - * 信任存储密码  
514 - */  
515 - private String trustStorePassword;  
516 -  
517 - }  
518 -  
519 - }  
520 -  
521 - @Data  
522 - public static class UdpProperties {  
523 -  
524 - /**  
525 - * 是否开启  
526 - */  
527 - @NotNull(message = "是否开启不能为空")  
528 - private Boolean enabled; 194 + @NotNull(message = "是否启用 SSL 不能为空")
  195 + private Boolean ssl = false;
529 196
530 /** 197 /**
531 - * 服务端口(默认 8093)  
532 - */  
533 - private Integer port = 8093;  
534 -  
535 - /**  
536 - * 接收缓冲区大小(默认 64KB) 198 + * SSL 证书路径
537 */ 199 */
538 - private Integer receiveBufferSize = 65536; 200 + @NotEmpty(message = "SSL 证书路径不能为空")
  201 + private String sslCertPath;
539 202
540 /** 203 /**
541 - * 发送缓冲区大小(默认 64KB) 204 + * SSL 私钥路径
542 */ 205 */
543 - private Integer sendBufferSize = 65536; 206 + @NotEmpty(message = "SSL 私钥路径不能为空")
  207 + private String sslKeyPath;
544 208
545 /** 209 /**
546 - * 会话超时时间(毫秒,默认 60 秒) 210 + * 密钥库(KeyStore)路径
547 * <p> 211 * <p>
548 - * 用于清理不活跃的设备地址映射  
549 - */  
550 - private Long sessionTimeoutMs = 60000L;  
551 -  
552 - /**  
553 - * 会话清理间隔(毫秒,默认 30 秒)  
554 - */  
555 - private Long sessionCleanIntervalMs = 30000L;  
556 -  
557 - }  
558 -  
559 - @Data  
560 - public static class CoapProperties {  
561 -  
562 - /**  
563 - * 是否开启  
564 - */  
565 - @NotNull(message = "是否开启不能为空")  
566 - private Boolean enabled;  
567 -  
568 - /**  
569 - * 服务端口(CoAP 默认端口 5683) 212 + * 包含客户端自己的证书和私钥,用于向服务端证明身份(双向认证)
570 */ 213 */
571 - @NotNull(message = "服务端口不能为空")  
572 - private Integer port = 5683;  
573 - 214 + private String keyStorePath;
574 /** 215 /**
575 - * 最大消息大小(字节) 216 + * 密钥库密码
576 */ 217 */
577 - @NotNull(message = "最大消息大小不能为空")  
578 - private Integer maxMessageSize = 1024; 218 + private String keyStorePassword;
579 219
580 /** 220 /**
581 - * ACK 超时时间(毫秒)  
582 - */  
583 - @NotNull(message = "ACK 超时时间不能为空")  
584 - private Integer ackTimeout = 2000;  
585 -  
586 - /**  
587 - * 最大重传次数  
588 - */  
589 - @NotNull(message = "最大重传次数不能为空")  
590 - private Integer maxRetransmit = 4;  
591 -  
592 - }  
593 -  
594 - @Data  
595 - public static class WebSocketProperties {  
596 -  
597 - /**  
598 - * 是否开启  
599 - */  
600 - @NotNull(message = "是否开启不能为空")  
601 - private Boolean enabled;  
602 -  
603 - /**  
604 - * 服务器端口(默认:8094)  
605 - */  
606 - private Integer port = 8094;  
607 -  
608 - /**  
609 - * WebSocket 路径(默认:/ws)  
610 - */  
611 - @NotEmpty(message = "WebSocket 路径不能为空")  
612 - private String path = "/ws";  
613 -  
614 - /**  
615 - * 最大消息大小(字节,默认 64KB)  
616 - */  
617 - private Integer maxMessageSize = 65536;  
618 -  
619 - /**  
620 - * 最大帧大小(字节,默认 64KB)  
621 - */  
622 - private Integer maxFrameSize = 65536;  
623 -  
624 - /**  
625 - * 空闲超时时间(秒,默认 60)  
626 - */  
627 - private Integer idleTimeoutSeconds = 60;  
628 -  
629 - /**  
630 - * 是否启用 SSL(wss://)  
631 - */  
632 - private Boolean sslEnabled = false;  
633 -  
634 - /**  
635 - * SSL 证书路径 221 + * 信任库(TrustStore)路径
  222 + * <p>
  223 + * 包含服务端信任的 CA 证书,用于验证服务端的身份
636 */ 224 */
637 - private String sslCertPath;  
638 - 225 + private String trustStorePath;
639 /** 226 /**
640 - * SSL 私钥路径 227 + * 信任库密码
641 */ 228 */
642 - private String sslKeyPath; 229 + private String trustStorePassword;
643 230
644 } 231 }
645 232
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/IotEmqxDownstreamSubscriber.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/AbstractIotProtocolDownstreamSubscriber.java
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.emqx; 1 +package cn.iocoder.yudao.module.iot.gateway.protocol;
2 2
  3 +import cn.hutool.core.util.StrUtil;
3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 4 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber; 5 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils; 7 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
7 -import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.router.IotEmqxDownstreamHandler;  
8 -import jakarta.annotation.PostConstruct; 8 +import lombok.AllArgsConstructor;
9 import lombok.extern.slf4j.Slf4j; 9 import lombok.extern.slf4j.Slf4j;
10 10
11 /** 11 /**
12 - * IoT 网关 EMQX 订阅者:接收下行给设备的消息 12 + * IoT 协议下行消息订阅者抽象类
  13 + *
  14 + * 负责接收来自消息总线的下行消息,并委托给子类进行业务处理
13 * 15 *
14 * @author 芋道源码 16 * @author 芋道源码
15 */ 17 */
  18 +@AllArgsConstructor
16 @Slf4j 19 @Slf4j
17 -public class IotEmqxDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> { 20 +public abstract class AbstractIotProtocolDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
18 21
19 - private final IotEmqxDownstreamHandler downstreamHandler; 22 + private final IotProtocol protocol;
20 23
21 private final IotMessageBus messageBus; 24 private final IotMessageBus messageBus;
22 25
23 - private final IotEmqxUpstreamProtocol protocol;  
24 -  
25 - public IotEmqxDownstreamSubscriber(IotEmqxUpstreamProtocol protocol, IotMessageBus messageBus) {  
26 - this.protocol = protocol;  
27 - this.messageBus = messageBus;  
28 - this.downstreamHandler = new IotEmqxDownstreamHandler(protocol);  
29 - }  
30 -  
31 - @PostConstruct  
32 - public void init() {  
33 - messageBus.register(this);  
34 - }  
35 -  
36 @Override 26 @Override
37 public String getTopic() { 27 public String getTopic() {
38 return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId()); 28 return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId());
39 } 29 }
40 30
  31 + /**
  32 + * 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
  33 + */
41 @Override 34 @Override
42 public String getGroup() { 35 public String getGroup() {
43 - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group  
44 return getTopic(); 36 return getTopic();
45 } 37 }
46 38
47 @Override 39 @Override
  40 + public void start() {
  41 + messageBus.register(this);
  42 + log.info("[start][{} 下行消息订阅成功,Topic:{}]", protocol.getType().name(), getTopic());
  43 + }
  44 +
  45 + @Override
  46 + public void stop() {
  47 + messageBus.unregister(this);
  48 + log.info("[stop][{} 下行消息订阅已停止,Topic:{}]", protocol.getType().name(), getTopic());
  49 + }
  50 +
  51 + @Override
48 public void onMessage(IotDeviceMessage message) { 52 public void onMessage(IotDeviceMessage message) {
49 log.debug("[onMessage][接收到下行消息, messageId: {}, method: {}, deviceId: {}]", 53 log.debug("[onMessage][接收到下行消息, messageId: {}, method: {}, deviceId: {}]",
50 message.getId(), message.getMethod(), message.getDeviceId()); 54 message.getId(), message.getMethod(), message.getDeviceId());
51 try { 55 try {
52 // 1. 校验 56 // 1. 校验
53 String method = message.getMethod(); 57 String method = message.getMethod();
54 - if (method == null) { 58 + if (StrUtil.isBlank(method)) {
55 log.warn("[onMessage][消息方法为空, messageId: {}, deviceId: {}]", 59 log.warn("[onMessage][消息方法为空, messageId: {}, deviceId: {}]",
56 message.getId(), message.getDeviceId()); 60 message.getId(), message.getDeviceId());
57 return; 61 return;
58 } 62 }
59 63
60 // 2. 处理下行消息 64 // 2. 处理下行消息
61 - downstreamHandler.handle(message); 65 + handleMessage(message);
62 } catch (Exception e) { 66 } catch (Exception e) {
63 log.error("[onMessage][处理下行消息失败, messageId: {}, method: {}, deviceId: {}]", 67 log.error("[onMessage][处理下行消息失败, messageId: {}, method: {}, deviceId: {}]",
64 message.getId(), message.getMethod(), message.getDeviceId(), e); 68 message.getId(), message.getMethod(), message.getDeviceId(), e);
65 } 69 }
66 } 70 }
67 71
68 -}  
69 \ No newline at end of file 72 \ No newline at end of file
  73 + /**
  74 + * 处理下行消息
  75 + *
  76 + * @param message 下行消息
  77 + */
  78 + protected abstract void handleMessage(IotDeviceMessage message);
  79 +
  80 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocol.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
  4 +
  5 +/**
  6 + * IoT 协议接口
  7 + *
  8 + * 定义传输层协议的生命周期管理
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +public interface IotProtocol {
  13 +
  14 + /**
  15 + * 获取协议实例 ID
  16 + *
  17 + * @return 协议实例 ID,如 "http-alink"、"tcp-binary"
  18 + */
  19 + String getId();
  20 +
  21 + /**
  22 + * 获取服务器 ID(用于消息追踪,全局唯一)
  23 + *
  24 + * @return 服务器 ID
  25 + */
  26 + String getServerId();
  27 +
  28 + /**
  29 + * 获取协议类型
  30 + *
  31 + * @return 协议类型枚举
  32 + */
  33 + IotProtocolTypeEnum getType();
  34 +
  35 + /**
  36 + * 启动协议服务
  37 + */
  38 + void start();
  39 +
  40 + /**
  41 + * 停止协议服务
  42 + */
  43 + void stop();
  44 +
  45 + /**
  46 + * 检查协议服务是否正在运行
  47 + *
  48 + * @return 是否正在运行
  49 + */
  50 + boolean isRunning();
  51 +
  52 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocolManager.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.BooleanUtil;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
  6 +import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
  7 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapProtocol;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxProtocol;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol;
  10 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpclient.IotModbusTcpClientProtocol;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.modbus.tcpserver.IotModbusTcpServerProtocol;
  12 +import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttProtocol;
  13 +import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpProtocol;
  14 +import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpProtocol;
  15 +import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketProtocol;
  16 +import lombok.Getter;
  17 +import lombok.extern.slf4j.Slf4j;
  18 +import org.springframework.context.SmartLifecycle;
  19 +
  20 +import java.util.ArrayList;
  21 +import java.util.List;
  22 +
  23 +/**
  24 + * IoT 协议管理器:负责根据配置创建和管理协议实例
  25 + *
  26 + * @author 芋道源码
  27 + */
  28 +@Slf4j
  29 +public class IotProtocolManager implements SmartLifecycle {
  30 +
  31 + private final IotGatewayProperties gatewayProperties;
  32 +
  33 + /**
  34 + * 协议实例列表
  35 + */
  36 + private final List<IotProtocol> protocols = new ArrayList<>();
  37 +
  38 + @Getter
  39 + private volatile boolean running = false;
  40 +
  41 + public IotProtocolManager(IotGatewayProperties gatewayProperties) {
  42 + this.gatewayProperties = gatewayProperties;
  43 + }
  44 +
  45 + @Override
  46 + public void start() {
  47 + if (running) {
  48 + return;
  49 + }
  50 + List<IotGatewayProperties.ProtocolProperties> protocolConfigs = gatewayProperties.getProtocols();
  51 + if (CollUtil.isEmpty(protocolConfigs)) {
  52 + log.info("[start][没有配置协议实例,跳过启动]");
  53 + return;
  54 + }
  55 +
  56 + for (IotGatewayProperties.ProtocolProperties config : protocolConfigs) {
  57 + if (BooleanUtil.isFalse(config.getEnabled())) {
  58 + log.info("[start][协议实例 {} 未启用,跳过]", config.getId());
  59 + continue;
  60 + }
  61 + IotProtocol protocol = createProtocol(config);
  62 + if (protocol == null) {
  63 + continue;
  64 + }
  65 + protocol.start();
  66 + protocols.add(protocol);
  67 + }
  68 + running = true;
  69 + log.info("[start][协议管理器启动完成,共启动 {} 个协议实例]", protocols.size());
  70 + }
  71 +
  72 + @Override
  73 + public void stop() {
  74 + if (!running) {
  75 + return;
  76 + }
  77 + for (IotProtocol protocol : protocols) {
  78 + try {
  79 + protocol.stop();
  80 + } catch (Exception e) {
  81 + log.error("[stop][协议实例 {} 停止失败]", protocol.getId(), e);
  82 + }
  83 + }
  84 + protocols.clear();
  85 + running = false;
  86 + log.info("[stop][协议管理器已停止]");
  87 + }
  88 +
  89 + /**
  90 + * 创建协议实例
  91 + *
  92 + * @param config 协议实例配置
  93 + * @return 协议实例
  94 + */
  95 + @SuppressWarnings({"EnhancedSwitchMigration"})
  96 + private IotProtocol createProtocol(IotGatewayProperties.ProtocolProperties config) {
  97 + IotProtocolTypeEnum protocolType = IotProtocolTypeEnum.of(config.getProtocol());
  98 + if (protocolType == null) {
  99 + log.error("[createProtocol][协议实例 {} 的协议类型 {} 不存在]", config.getId(), config.getProtocol());
  100 + return null;
  101 + }
  102 + switch (protocolType) {
  103 + case HTTP:
  104 + return createHttpProtocol(config);
  105 + case TCP:
  106 + return createTcpProtocol(config);
  107 + case UDP:
  108 + return createUdpProtocol(config);
  109 + case COAP:
  110 + return createCoapProtocol(config);
  111 + case WEBSOCKET:
  112 + return createWebSocketProtocol(config);
  113 + case MQTT:
  114 + return createMqttProtocol(config);
  115 + case EMQX:
  116 + return createEmqxProtocol(config);
  117 + case MODBUS_TCP_CLIENT:
  118 + return createModbusTcpClientProtocol(config);
  119 + case MODBUS_TCP_SERVER:
  120 + return createModbusTcpServerProtocol(config);
  121 + default:
  122 + throw new IllegalArgumentException(String.format(
  123 + "[createProtocol][协议实例 %s 的协议类型 %s 暂不支持]", config.getId(), protocolType));
  124 + }
  125 + }
  126 +
  127 + /**
  128 + * 创建 HTTP 协议实例
  129 + *
  130 + * @param config 协议实例配置
  131 + * @return HTTP 协议实例
  132 + */
  133 + private IotHttpProtocol createHttpProtocol(IotGatewayProperties.ProtocolProperties config) {
  134 + return new IotHttpProtocol(config);
  135 + }
  136 +
  137 + /**
  138 + * 创建 TCP 协议实例
  139 + *
  140 + * @param config 协议实例配置
  141 + * @return TCP 协议实例
  142 + */
  143 + private IotTcpProtocol createTcpProtocol(IotGatewayProperties.ProtocolProperties config) {
  144 + return new IotTcpProtocol(config);
  145 + }
  146 +
  147 + /**
  148 + * 创建 UDP 协议实例
  149 + *
  150 + * @param config 协议实例配置
  151 + * @return UDP 协议实例
  152 + */
  153 + private IotUdpProtocol createUdpProtocol(IotGatewayProperties.ProtocolProperties config) {
  154 + return new IotUdpProtocol(config);
  155 + }
  156 +
  157 + /**
  158 + * 创建 CoAP 协议实例
  159 + *
  160 + * @param config 协议实例配置
  161 + * @return CoAP 协议实例
  162 + */
  163 + private IotCoapProtocol createCoapProtocol(IotGatewayProperties.ProtocolProperties config) {
  164 + return new IotCoapProtocol(config);
  165 + }
  166 +
  167 + /**
  168 + * 创建 WebSocket 协议实例
  169 + *
  170 + * @param config 协议实例配置
  171 + * @return WebSocket 协议实例
  172 + */
  173 + private IotWebSocketProtocol createWebSocketProtocol(IotGatewayProperties.ProtocolProperties config) {
  174 + return new IotWebSocketProtocol(config);
  175 + }
  176 +
  177 + /**
  178 + * 创建 MQTT 协议实例
  179 + *
  180 + * @param config 协议实例配置
  181 + * @return MQTT 协议实例
  182 + */
  183 + private IotMqttProtocol createMqttProtocol(IotGatewayProperties.ProtocolProperties config) {
  184 + return new IotMqttProtocol(config);
  185 + }
  186 +
  187 + /**
  188 + * 创建 EMQX 协议实例
  189 + *
  190 + * @param config 协议实例配置
  191 + * @return EMQX 协议实例
  192 + */
  193 + private IotEmqxProtocol createEmqxProtocol(IotGatewayProperties.ProtocolProperties config) {
  194 + return new IotEmqxProtocol(config);
  195 + }
  196 +
  197 + /**
  198 + * 创建 Modbus TCP Client 协议实例
  199 + *
  200 + * @param config 协议实例配置
  201 + * @return Modbus TCP Client 协议实例
  202 + */
  203 + private IotModbusTcpClientProtocol createModbusTcpClientProtocol(IotGatewayProperties.ProtocolProperties config) {
  204 + return new IotModbusTcpClientProtocol(config);
  205 + }
  206 +
  207 + /**
  208 + * 创建 Modbus TCP Server 协议实例
  209 + *
  210 + * @param config 协议实例配置
  211 + * @return Modbus TCP Server 协议实例
  212 + */
  213 + private IotModbusTcpServerProtocol createModbusTcpServerProtocol(IotGatewayProperties.ProtocolProperties config) {
  214 + return new IotModbusTcpServerProtocol(config);
  215 + }
  216 +
  217 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap;
  2 +
  3 +import jakarta.validation.constraints.Min;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +/**
  8 + * IoT CoAP 协议配置
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +public class IotCoapConfig {
  14 +
  15 + /**
  16 + * 最大消息大小(字节)
  17 + */
  18 + @NotNull(message = "最大消息大小不能为空")
  19 + @Min(value = 64, message = "最大消息大小必须大于 64 字节")
  20 + private Integer maxMessageSize = 1024;
  21 +
  22 + /**
  23 + * ACK 超时时间(毫秒)
  24 + */
  25 + @NotNull(message = "ACK 超时时间不能为空")
  26 + @Min(value = 100, message = "ACK 超时时间必须大于 100 毫秒")
  27 + private Integer ackTimeoutMs = 2000;
  28 +
  29 + /**
  30 + * 最大重传次数
  31 + */
  32 + @NotNull(message = "最大重传次数不能为空")
  33 + @Min(value = 0, message = "最大重传次数必须大于等于 0")
  34 + private Integer maxRetransmit = 4;
  35 +
  36 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapDownstreamSubscriber.java deleted
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.coap;  
2 -  
3 -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;  
4 -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;  
5 -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;  
6 -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;  
7 -import jakarta.annotation.PostConstruct;  
8 -import lombok.RequiredArgsConstructor;  
9 -import lombok.extern.slf4j.Slf4j;  
10 -  
11 -/**  
12 - * IoT 网关 CoAP 订阅者:接收下行给设备的消息  
13 - *  
14 - * @author 芋道源码  
15 - */  
16 -@RequiredArgsConstructor  
17 -@Slf4j  
18 -public class IotCoapDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {  
19 -  
20 - private final IotCoapUpstreamProtocol protocol;  
21 -  
22 - private final IotMessageBus messageBus;  
23 -  
24 - @PostConstruct  
25 - public void init() {  
26 - messageBus.register(this);  
27 - }  
28 -  
29 - @Override  
30 - public String getTopic() {  
31 - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId());  
32 - }  
33 -  
34 - @Override  
35 - public String getGroup() {  
36 - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group  
37 - return getTopic();  
38 - }  
39 -  
40 - @Override  
41 - public void onMessage(IotDeviceMessage message) {  
42 - // 如需支持,可通过 CoAP Observe 模式实现(设备订阅资源,服务器推送变更)  
43 - log.warn("[onMessage][IoT 网关 CoAP 协议暂不支持下行消息,忽略消息:{}]", message);  
44 - }  
45 -  
46 -}  
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapProtocol.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap;
  2 +
  3 +import cn.hutool.extra.spring.SpringUtil;
  4 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
  5 +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
  6 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  7 +import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolProperties;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
  9 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.downstream.IotCoapDownstreamSubscriber;
  10 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapAuthHandler;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapAuthResource;
  12 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapRegisterHandler;
  13 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapRegisterResource;
  14 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapRegisterSubHandler;
  15 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapRegisterSubResource;
  16 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapUpstreamHandler;
  17 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream.IotCoapUpstreamTopicResource;
  18 +import lombok.Getter;
  19 +import lombok.extern.slf4j.Slf4j;
  20 +import org.eclipse.californium.core.CoapResource;
  21 +import org.eclipse.californium.core.CoapServer;
  22 +import org.eclipse.californium.core.config.CoapConfig;
  23 +import org.eclipse.californium.elements.config.Configuration;
  24 +import cn.hutool.core.lang.Assert;
  25 +
  26 +import java.util.concurrent.TimeUnit;
  27 +
  28 +/**
  29 + * IoT CoAP 协议实现
  30 + * <p>
  31 + * 基于 Eclipse Californium 实现,支持:
  32 + * 1. 认证:POST /auth
  33 + * 2. 设备动态注册:POST /auth/register/device
  34 + * 3. 子设备动态注册:POST /auth/register/sub-device/{productKey}/{deviceName}
  35 + * 4. 属性上报:POST /topic/sys/{productKey}/{deviceName}/thing/property/post
  36 + * 5. 事件上报:POST /topic/sys/{productKey}/{deviceName}/thing/event/post
  37 + *
  38 + * @author 芋道源码
  39 + */
  40 +@Slf4j
  41 +public class IotCoapProtocol implements IotProtocol {
  42 +
  43 + /**
  44 + * 协议配置
  45 + */
  46 + private final ProtocolProperties properties;
  47 + /**
  48 + * 服务器 ID(用于消息追踪,全局唯一)
  49 + */
  50 + @Getter
  51 + private final String serverId;
  52 +
  53 + /**
  54 + * 运行状态
  55 + */
  56 + @Getter
  57 + private volatile boolean running = false;
  58 +
  59 + /**
  60 + * CoAP 服务器
  61 + */
  62 + private CoapServer coapServer;
  63 +
  64 + /**
  65 + * 下行消息订阅者
  66 + */
  67 + private IotCoapDownstreamSubscriber downstreamSubscriber;
  68 +
  69 + public IotCoapProtocol(ProtocolProperties properties) {
  70 + IotCoapConfig coapConfig = properties.getCoap();
  71 + Assert.notNull(coapConfig, "CoAP 协议配置(coap)不能为空");
  72 + this.properties = properties;
  73 + this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort());
  74 + }
  75 +
  76 + @Override
  77 + public String getId() {
  78 + return properties.getId();
  79 + }
  80 +
  81 + @Override
  82 + public IotProtocolTypeEnum getType() {
  83 + return IotProtocolTypeEnum.COAP;
  84 + }
  85 +
  86 + @Override
  87 + public void start() {
  88 + if (running) {
  89 + log.warn("[start][IoT CoAP 协议 {} 已经在运行中]", getId());
  90 + return;
  91 + }
  92 +
  93 + try {
  94 + // 1.1 创建 CoAP 配置
  95 + IotCoapConfig coapConfig = properties.getCoap();
  96 + Configuration config = Configuration.createStandardWithoutFile();
  97 + config.set(CoapConfig.COAP_PORT, properties.getPort());
  98 + config.set(CoapConfig.MAX_MESSAGE_SIZE, coapConfig.getMaxMessageSize());
  99 + config.set(CoapConfig.ACK_TIMEOUT, coapConfig.getAckTimeoutMs(), TimeUnit.MILLISECONDS);
  100 + config.set(CoapConfig.MAX_RETRANSMIT, coapConfig.getMaxRetransmit());
  101 + // 1.2 创建 CoAP 服务器
  102 + coapServer = new CoapServer(config);
  103 +
  104 + // 2.1 添加 /auth 认证资源
  105 + IotCoapAuthHandler authHandler = new IotCoapAuthHandler(serverId);
  106 + IotCoapAuthResource authResource = new IotCoapAuthResource(authHandler);
  107 + coapServer.add(authResource);
  108 + // 2.2 添加 /auth/register/device 设备动态注册资源(一型一密)
  109 + IotCoapRegisterHandler registerHandler = new IotCoapRegisterHandler();
  110 + IotCoapRegisterResource registerResource = new IotCoapRegisterResource(registerHandler);
  111 + // 2.3 添加 /auth/register/sub-device/{productKey}/{deviceName} 子设备动态注册资源
  112 + IotCoapRegisterSubHandler registerSubHandler = new IotCoapRegisterSubHandler();
  113 + IotCoapRegisterSubResource registerSubResource = new IotCoapRegisterSubResource(registerSubHandler);
  114 + authResource.add(new CoapResource("register") {{
  115 + add(registerResource);
  116 + add(registerSubResource);
  117 + }});
  118 + // 2.4 添加 /topic 根资源(用于上行消息)
  119 + IotCoapUpstreamHandler upstreamHandler = new IotCoapUpstreamHandler(serverId);
  120 + IotCoapUpstreamTopicResource topicResource = new IotCoapUpstreamTopicResource(serverId, upstreamHandler);
  121 + coapServer.add(topicResource);
  122 +
  123 + // 3. 启动服务器
  124 + coapServer.start();
  125 + running = true;
  126 + log.info("[start][IoT CoAP 协议 {} 启动成功,端口:{},serverId:{}]",
  127 + getId(), properties.getPort(), serverId);
  128 +
  129 + // 4. 启动下行消息订阅者
  130 + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class);
  131 + this.downstreamSubscriber = new IotCoapDownstreamSubscriber(this, messageBus);
  132 + this.downstreamSubscriber.start();
  133 + } catch (Exception e) {
  134 + log.error("[start][IoT CoAP 协议 {} 启动失败]", getId(), e);
  135 + stop0();
  136 + throw e;
  137 + }
  138 + }
  139 +
  140 + @Override
  141 + public void stop() {
  142 + if (!running) {
  143 + return;
  144 + }
  145 + stop0();
  146 + }
  147 +
  148 + private void stop0() {
  149 + // 1. 停止下行消息订阅者
  150 + if (downstreamSubscriber != null) {
  151 + try {
  152 + downstreamSubscriber.stop();
  153 + log.info("[stop][IoT CoAP 协议 {} 下行消息订阅者已停止]", getId());
  154 + } catch (Exception e) {
  155 + log.error("[stop][IoT CoAP 协议 {} 下行消息订阅者停止失败]", getId(), e);
  156 + }
  157 + downstreamSubscriber = null;
  158 + }
  159 +
  160 + // 2. 关闭 CoAP 服务器
  161 + if (coapServer != null) {
  162 + try {
  163 + coapServer.stop();
  164 + coapServer.destroy();
  165 + coapServer = null;
  166 + log.info("[stop][IoT CoAP 协议 {} 服务器已停止]", getId());
  167 + } catch (Exception e) {
  168 + log.error("[stop][IoT CoAP 协议 {} 服务器停止失败]", getId(), e);
  169 + }
  170 + }
  171 + running = false;
  172 + log.info("[stop][IoT CoAP 协议 {} 已停止]", getId());
  173 + }
  174 +
  175 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapUpstreamProtocol.java deleted
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.coap;  
2 -  
3 -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;  
4 -import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;  
5 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapAuthHandler;  
6 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapAuthResource;  
7 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapRegisterHandler;  
8 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapRegisterResource;  
9 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapUpstreamTopicResource;  
10 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapUpstreamHandler;  
11 -import jakarta.annotation.PostConstruct;  
12 -import jakarta.annotation.PreDestroy;  
13 -import lombok.Getter;  
14 -import lombok.extern.slf4j.Slf4j;  
15 -import org.eclipse.californium.core.CoapResource;  
16 -import org.eclipse.californium.core.CoapServer;  
17 -import org.eclipse.californium.core.config.CoapConfig;  
18 -import org.eclipse.californium.elements.config.Configuration;  
19 -  
20 -import java.util.concurrent.TimeUnit;  
21 -  
22 -/**  
23 - * IoT 网关 CoAP 协议:接收设备上行消息  
24 - *  
25 - * 基于 Eclipse Californium 实现,支持:  
26 - * 1. 认证:POST /auth  
27 - * 2. 属性上报:POST /topic/sys/{productKey}/{deviceName}/thing/property/post  
28 - * 3. 事件上报:POST /topic/sys/{productKey}/{deviceName}/thing/event/post  
29 - *  
30 - * @author 芋道源码  
31 - */  
32 -@Slf4j  
33 -public class IotCoapUpstreamProtocol {  
34 -  
35 - private final IotGatewayProperties.CoapProperties coapProperties;  
36 -  
37 - private CoapServer coapServer;  
38 -  
39 - @Getter  
40 - private final String serverId;  
41 -  
42 - public IotCoapUpstreamProtocol(IotGatewayProperties.CoapProperties coapProperties) {  
43 - this.coapProperties = coapProperties;  
44 - this.serverId = IotDeviceMessageUtils.generateServerId(coapProperties.getPort());  
45 - }  
46 -  
47 - @PostConstruct  
48 - public void start() {  
49 - try {  
50 - // 1.1 创建网络配置(Californium 3.x API)  
51 - Configuration config = Configuration.createStandardWithoutFile();  
52 - config.set(CoapConfig.COAP_PORT, coapProperties.getPort());  
53 - config.set(CoapConfig.MAX_MESSAGE_SIZE, coapProperties.getMaxMessageSize());  
54 - config.set(CoapConfig.ACK_TIMEOUT, coapProperties.getAckTimeout(), TimeUnit.MILLISECONDS);  
55 - config.set(CoapConfig.MAX_RETRANSMIT, coapProperties.getMaxRetransmit());  
56 - // 1.2 创建 CoAP 服务器  
57 - coapServer = new CoapServer(config);  
58 -  
59 - // 2.1 添加 /auth 认证资源  
60 - IotCoapAuthHandler authHandler = new IotCoapAuthHandler();  
61 - IotCoapAuthResource authResource = new IotCoapAuthResource(this, authHandler);  
62 - coapServer.add(authResource);  
63 - // 2.2 添加 /auth/register/device 设备动态注册资源(一型一密)  
64 - IotCoapRegisterHandler registerHandler = new IotCoapRegisterHandler();  
65 - IotCoapRegisterResource registerResource = new IotCoapRegisterResource(registerHandler);  
66 - authResource.add(new CoapResource("register") {{  
67 - add(registerResource);  
68 - }});  
69 - // 2.3 添加 /topic 根资源(用于上行消息)  
70 - IotCoapUpstreamHandler upstreamHandler = new IotCoapUpstreamHandler();  
71 - IotCoapUpstreamTopicResource topicResource = new IotCoapUpstreamTopicResource(this, upstreamHandler);  
72 - coapServer.add(topicResource);  
73 -  
74 - // 3. 启动服务器  
75 - coapServer.start();  
76 - log.info("[start][IoT 网关 CoAP 协议启动成功,端口:{},资源:/auth, /auth/register/device, /topic]", coapProperties.getPort());  
77 - } catch (Exception e) {  
78 - log.error("[start][IoT 网关 CoAP 协议启动失败]", e);  
79 - throw e;  
80 - }  
81 - }  
82 -  
83 - @PreDestroy  
84 - public void stop() {  
85 - if (coapServer != null) {  
86 - try {  
87 - coapServer.stop();  
88 - log.info("[stop][IoT 网关 CoAP 协议已停止]");  
89 - } catch (Exception e) {  
90 - log.error("[stop][IoT 网关 CoAP 协议停止失败]", e);  
91 - }  
92 - }  
93 - }  
94 -  
95 -}  
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/downstream/IotCoapDownstreamSubscriber.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.downstream;
  2 +
  3 +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
  4 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  5 +import cn.iocoder.yudao.module.iot.gateway.protocol.AbstractIotProtocolDownstreamSubscriber;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapProtocol;
  7 +import lombok.extern.slf4j.Slf4j;
  8 +
  9 +/**
  10 + * IoT 网关 CoAP 订阅者:接收下行给设备的消息
  11 + *
  12 + * @author 芋道源码
  13 + */
  14 +@Slf4j
  15 +public class IotCoapDownstreamSubscriber extends AbstractIotProtocolDownstreamSubscriber {
  16 +
  17 + public IotCoapDownstreamSubscriber(IotCoapProtocol protocol, IotMessageBus messageBus) {
  18 + super(protocol, messageBus);
  19 + }
  20 +
  21 + @Override
  22 + protected void handleMessage(IotDeviceMessage message) {
  23 + // 如需支持,可通过 CoAP Observe 模式实现(设备订阅资源,服务器推送变更)
  24 + log.warn("[handleMessage][IoT 网关 CoAP 协议暂不支持下行消息,忽略消息:{}]", message);
  25 + }
  26 +
  27 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstream/IotCoapAbstractHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ArrayUtil;
  5 +import cn.hutool.core.util.ObjUtil;
  6 +import cn.hutool.core.util.StrUtil;
  7 +import cn.hutool.extra.spring.SpringUtil;
  8 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
  9 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  10 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  11 +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
  12 +import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
  13 +import lombok.extern.slf4j.Slf4j;
  14 +import org.eclipse.californium.core.coap.CoAP;
  15 +import org.eclipse.californium.core.coap.MediaTypeRegistry;
  16 +import org.eclipse.californium.core.coap.Option;
  17 +import org.eclipse.californium.core.server.resources.CoapExchange;
  18 +
  19 +import java.util.List;
  20 +
  21 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
  22 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  23 +
  24 +/**
  25 + * IoT 网关 CoAP 协议的处理器抽象基类:提供通用的前置处理(认证)、请求解析、响应处理、全局的异常捕获等
  26 + *
  27 + * @author 芋道源码
  28 + */
  29 +@Slf4j
  30 +public abstract class IotCoapAbstractHandler {
  31 +
  32 + /**
  33 + * 自定义 CoAP Option 编号,用于携带 Token
  34 + * <p>
  35 + * CoAP Option 范围 2048-65535 属于实验/自定义范围
  36 + */
  37 + public static final int OPTION_TOKEN = 2088;
  38 +
  39 + private final IotDeviceTokenService deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
  40 +
  41 + /**
  42 + * 处理 CoAP 请求(模板方法)
  43 + *
  44 + * @param exchange CoAP 交换对象
  45 + */
  46 + public final void handle(CoapExchange exchange) {
  47 + try {
  48 + // 1. 前置处理
  49 + beforeHandle(exchange);
  50 +
  51 + // 2. 执行业务逻辑
  52 + CommonResult<Object> result = handle0(exchange);
  53 + writeResponse(exchange, result);
  54 + } catch (ServiceException e) {
  55 + // 业务异常,返回对应的错误码和消息
  56 + writeResponse(exchange, CommonResult.error(e.getCode(), e.getMessage()));
  57 + } catch (IllegalArgumentException e) {
  58 + // 参数校验异常(hutool Assert 抛出),返回 BAD_REQUEST
  59 + writeResponse(exchange, CommonResult.error(BAD_REQUEST.getCode(), e.getMessage()));
  60 + } catch (Exception e) {
  61 + // 其他未知异常,返回 INTERNAL_SERVER_ERROR
  62 + log.error("[handle][CoAP 请求处理异常]", e);
  63 + writeResponse(exchange, CommonResult.error(INTERNAL_SERVER_ERROR));
  64 + }
  65 + }
  66 +
  67 + /**
  68 + * 处理 CoAP 请求(子类实现)
  69 + *
  70 + * @param exchange CoAP 交换对象
  71 + * @return 处理结果
  72 + */
  73 + protected abstract CommonResult<Object> handle0(CoapExchange exchange);
  74 +
  75 + /**
  76 + * 前置处理:认证等
  77 + *
  78 + * @param exchange CoAP 交换对象
  79 + */
  80 + private void beforeHandle(CoapExchange exchange) {
  81 + // 1.1 如果不需要认证,则不走前置处理
  82 + if (!requiresAuthentication()) {
  83 + return;
  84 + }
  85 + // 1.2 从自定义 Option 获取 token
  86 + String token = getTokenFromOption(exchange);
  87 + if (StrUtil.isEmpty(token)) {
  88 + throw exception(UNAUTHORIZED);
  89 + }
  90 + // 1.3 校验 token
  91 + IotDeviceIdentity deviceInfo = deviceTokenService.verifyToken(token);
  92 + if (deviceInfo == null) {
  93 + throw exception(UNAUTHORIZED);
  94 + }
  95 +
  96 + // 2.1 解析 productKey 和 deviceName
  97 + List<String> uriPath = exchange.getRequestOptions().getUriPath();
  98 + String productKey = getProductKey(uriPath);
  99 + String deviceName = getDeviceName(uriPath);
  100 + if (StrUtil.isEmpty(productKey) || StrUtil.isEmpty(deviceName)) {
  101 + throw exception(BAD_REQUEST);
  102 + }
  103 + // 2.2 校验设备信息是否匹配
  104 + if (ObjUtil.notEqual(productKey, deviceInfo.getProductKey())
  105 + || ObjUtil.notEqual(deviceName, deviceInfo.getDeviceName())) {
  106 + throw exception(FORBIDDEN);
  107 + }
  108 + }
  109 +
  110 + // ========== Token 相关方法 ==========
  111 +
  112 + /**
  113 + * 是否需要认证(子类可覆盖)
  114 + * <p>
  115 + * 默认不需要认证
  116 + *
  117 + * @return 是否需要认证
  118 + */
  119 + protected boolean requiresAuthentication() {
  120 + return false;
  121 + }
  122 +
  123 + /**
  124 + * 从 URI 路径中获取 productKey(子类实现)
  125 + * <p>
  126 + * 默认抛出异常,需要认证的子类必须实现此方法
  127 + *
  128 + * @param uriPath URI 路径
  129 + * @return productKey
  130 + */
  131 + protected String getProductKey(List<String> uriPath) {
  132 + throw new UnsupportedOperationException("子类需要实现 getProductKey 方法");
  133 + }
  134 +
  135 + /**
  136 + * 从 URI 路径中获取 deviceName(子类实现)
  137 + * <p>
  138 + * 默认抛出异常,需要认证的子类必须实现此方法
  139 + *
  140 + * @param uriPath URI 路径
  141 + * @return deviceName
  142 + */
  143 + protected String getDeviceName(List<String> uriPath) {
  144 + throw new UnsupportedOperationException("子类需要实现 getDeviceName 方法");
  145 + }
  146 +
  147 + /**
  148 + * 从自定义 CoAP Option 中获取 Token
  149 + *
  150 + * @param exchange CoAP 交换对象
  151 + * @return Token 值,如果不存在则返回 null
  152 + */
  153 + protected String getTokenFromOption(CoapExchange exchange) {
  154 + Option option = CollUtil.findOne(exchange.getRequestOptions().getOthers(),
  155 + o -> o.getNumber() == OPTION_TOKEN);
  156 + return option != null ? new String(option.getValue()) : null;
  157 + }
  158 +
  159 + // ========== 序列化相关方法 ==========
  160 +
  161 + /**
  162 + * 解析请求体为指定类型
  163 + *
  164 + * @param exchange CoAP 交换对象
  165 + * @param clazz 目标类型
  166 + * @param <T> 目标类型泛型
  167 + * @return 解析后的对象,解析失败返回 null
  168 + */
  169 + protected <T> T deserializeRequest(CoapExchange exchange, Class<T> clazz) {
  170 + byte[] payload = exchange.getRequestPayload();
  171 + if (ArrayUtil.isEmpty(payload)) {
  172 + return null;
  173 + }
  174 + return JsonUtils.parseObject(payload, clazz);
  175 + }
  176 +
  177 + private static String serializeResponse(Object data) {
  178 + return JsonUtils.toJsonString(data);
  179 + }
  180 +
  181 + protected void writeResponse(CoapExchange exchange, CommonResult<?> data) {
  182 + String json = serializeResponse(data);
  183 + exchange.respond(CoAP.ResponseCode.CONTENT, json, MediaTypeRegistry.APPLICATION_JSON);
  184 + }
  185 +
  186 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstream/IotCoapAuthHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.map.MapUtil;
  5 +import cn.hutool.core.util.BooleanUtil;
  6 +import cn.hutool.extra.spring.SpringUtil;
  7 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  8 +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
  9 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
  10 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  11 +import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
  12 +import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
  13 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  14 +import lombok.extern.slf4j.Slf4j;
  15 +import org.eclipse.californium.core.server.resources.CoapExchange;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  18 +import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_AUTH_FAIL;
  19 +
  20 +/**
  21 + * IoT 网关 CoAP 协议的【认证】处理器
  22 + *
  23 + * @author 芋道源码
  24 + */
  25 +@Slf4j
  26 +public class IotCoapAuthHandler extends IotCoapAbstractHandler {
  27 +
  28 + private final String serverId;
  29 +
  30 + private final IotDeviceTokenService deviceTokenService;
  31 + private final IotDeviceCommonApi deviceApi;
  32 + private final IotDeviceMessageService deviceMessageService;
  33 +
  34 + public IotCoapAuthHandler(String serverId) {
  35 + this.serverId = serverId;
  36 + this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
  37 + this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
  38 + this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
  39 + }
  40 +
  41 + @Override
  42 + @SuppressWarnings("DuplicatedCode")
  43 + protected CommonResult<Object> handle0(CoapExchange exchange) {
  44 + // 1. 解析参数
  45 + IotDeviceAuthReqDTO request = deserializeRequest(exchange, IotDeviceAuthReqDTO.class);
  46 + Assert.notNull(request, "请求体不能为空");
  47 + Assert.notBlank(request.getClientId(), "clientId 不能为空");
  48 + Assert.notBlank(request.getUsername(), "username 不能为空");
  49 + Assert.notBlank(request.getPassword(), "password 不能为空");
  50 +
  51 + // 2.1 执行认证
  52 + CommonResult<Boolean> result = deviceApi.authDevice(request);
  53 + result.checkError();
  54 + if (BooleanUtil.isFalse(result.getData())) {
  55 + throw exception(DEVICE_AUTH_FAIL);
  56 + }
  57 + // 2.2 生成 Token
  58 + IotDeviceIdentity deviceInfo = deviceTokenService.parseUsername(request.getUsername());
  59 + Assert.notNull(deviceInfo, "设备信息不能为空");
  60 + String token = deviceTokenService.createToken(deviceInfo.getProductKey(), deviceInfo.getDeviceName());
  61 + Assert.notBlank(token, "生成 token 不能为空");
  62 +
  63 + // 3. 执行上线
  64 + IotDeviceMessage message = IotDeviceMessage.buildStateUpdateOnline();
  65 + deviceMessageService.sendDeviceMessage(message,
  66 + deviceInfo.getProductKey(), deviceInfo.getDeviceName(), serverId);
  67 +
  68 + // 4. 构建响应数据
  69 + return CommonResult.success(MapUtil.of("token", token));
  70 + }
  71 +
  72 +}