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 101 <artifactId>spring-boot-starter-amqp</artifactId>
102 102 <optional>true</optional>
103 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 104 </dependencies>
117 105  
118 106 </project>
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/IoTDeviceApiImpl.java
1 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 6 import cn.iocoder.yudao.framework.common.enums.RpcConstants;
4 7 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
6 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 12 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
12 13 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
13 14 import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO;
14 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 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 21 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
17 22 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
18 23 import jakarta.annotation.Resource;
19 24 import jakarta.annotation.security.PermitAll;
  25 +import org.springframework.context.annotation.Lazy;
20 26 import org.springframework.context.annotation.Primary;
21 27 import org.springframework.validation.annotation.Validated;
22 28 import org.springframework.web.bind.annotation.PostMapping;
23 29 import org.springframework.web.bind.annotation.RequestBody;
24 30 import org.springframework.web.bind.annotation.RestController;
25 31  
  32 +import java.util.ArrayList;
26 33 import java.util.List;
  34 +import java.util.Map;
  35 +import java.util.Set;
27 36  
28 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 41 * IoT 设备 API 实现类
... ... @@ -41,6 +51,12 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
41 51 private IotDeviceService deviceService;
42 52 @Resource
43 53 private IotProductService productService;
  54 + @Resource
  55 + @Lazy // 延迟加载,解决循环依赖
  56 + private IotDeviceModbusConfigService modbusConfigService;
  57 + @Resource
  58 + @Lazy // 延迟加载,解决循环依赖
  59 + private IotDeviceModbusPointService modbusPointService;
44 60  
45 61 @Override
46 62 @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/auth")
... ... @@ -58,12 +74,58 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
58 74 return success(BeanUtils.toBean(device, IotDeviceRespDTO.class, deviceDTO -> {
59 75 IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId());
60 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 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 129 @PostMapping(RpcConstants.RPC_API_PREFIX + "/iot/device/register")
68 130 @PermitAll
69 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 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 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 6 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
7 7 import io.swagger.v3.oas.annotations.media.Schema;
8 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 38 @Schema(description = "请求编号", example = "req_123")
39 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 42 private String method;
43 43  
44 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 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 152 @GetMapping("/simple-list")
145 153 @Operation(summary = "获取产品的精简信息列表", description = "主要用于前端的下拉选项")
146 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 67 @DictFormat(DictTypeConstants.NET_TYPE)
68 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 80 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
76 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 1 package cn.iocoder.yudao.module.iot.controller.admin.product.vo.product;
2 2  
3 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 6 import cn.iocoder.yudao.module.iot.enums.product.IotNetTypeEnum;
5 7 import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
6 8 import io.swagger.v3.oas.annotations.media.Schema;
... ... @@ -44,9 +46,15 @@ public class IotProductSaveReqVO {
44 46 @InEnum(value = IotNetTypeEnum.class, message = "联网方式必须是 {value}")
45 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 59 @Schema(description = "是否开启动态注册", example = "false")
52 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 5 import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageReqVO;
6 6 import cn.iocoder.yudao.module.iot.controller.admin.statistics.vo.IotStatisticsDeviceMessageSummaryByDateRespVO;
7 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 9 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
10 10 import cn.iocoder.yudao.module.iot.service.device.message.IotDeviceMessageService;
11 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 4 import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
5 5 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
6 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 8 import com.baomidou.mybatisplus.annotation.KeySequence;
9 9 import com.baomidou.mybatisplus.annotation.TableField;
10 10 import com.baomidou.mybatisplus.annotation.TableId;
... ... @@ -109,10 +109,6 @@ public class IotDeviceDO extends TenantBaseDO {
109 109 private LocalDateTime activeTime;
110 110  
111 111 /**
112   - * 设备的 IP 地址
113   - */
114   - private String ip;
115   - /**
116 112 * 固件编号
117 113 *
118 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 84 * 请求方法
85 85 *
86 86 * 枚举 {@link IotDeviceMessageMethodEnum}
87   - * 例如说:thing.property.report 属性上报
  87 + * 例如说:thing.property.post 属性上报
88 88 */
89 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 78 */
79 79 private Integer netType;
80 80 /**
81   - * 数据格式(编解码器类型)
  81 + * 协议类型
82 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 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 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 45 default Long selectCountByCreateTime(@Nullable LocalDateTime createTime) {
42 46 return selectCount(new LambdaQueryWrapperX<IotProductDO>()
43 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 public class DictTypeConstants {
9 9  
10 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 14 public static final String PRODUCT_STATUS = "iot_product_status";
15 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 54 ErrorCode DEVICE_GROUP_NOT_EXISTS = new ErrorCode(1_050_005_000, "设备分组不存在");
55 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 65 // ========== OTA 固件相关 1-050-008-000 ==========
58 66  
59 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 19 TCP(2, "TCP"),
20 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 25 REDIS(21, "Redis"),
26 26  
27 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 4 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
5 5 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
6 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 8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
9 9 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
10 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 4 import cn.hutool.core.util.StrUtil;
5 5 import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler;
6 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 8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
9 9 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
10 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 3 import cn.hutool.core.util.ObjectUtil;
4 4 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
5 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 7 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
8 8 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
9 9 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
... ... @@ -67,7 +67,6 @@ public class IotDeviceMessageSubscriber implements IotMessageSubscriber&lt;IotDevic
67 67 IotDeviceDO device = deviceService.validateDeviceExistsFromCache(message.getDeviceId());
68 68 devicePropertyService.updateDeviceReportTimeAsync(device.getId(), LocalDateTime.now());
69 69 // 1.2 更新设备的连接 server
70   - // TODO 芋艿:HTTP 网关的上行消息,不应该更新 serverId,会覆盖掉 MQTT 等长连接的 serverId,导致下行消息无法发送。
71 70 devicePropertyService.updateDeviceServerIdAsync(device.getId(), message.getServerId());
72 71  
73 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 17 */
18 18 @Component
19 19 @Slf4j
20   -public class IotDataRuleMessageHandler implements IotMessageSubscriber<IotDeviceMessage> {
  20 +public class IotDataRuleMessageSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
21 21  
22 22 @Resource
23 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 9 import lombok.extern.slf4j.Slf4j;
10 10 import org.springframework.stereotype.Component;
11 11  
12   -// TODO @puhui999:后面重构哈
13 12 /**
14 13 * 针对 {@link IotDeviceMessage} 的消费者,处理规则场景
15 14 *
... ... @@ -17,7 +16,7 @@ import org.springframework.stereotype.Component;
17 16 */
18 17 @Component
19 18 @Slf4j
20   -public class IotSceneRuleMessageHandler implements IotMessageSubscriber<IotDeviceMessage> {
  19 +public class IotSceneRuleMessageSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
21 20  
22 21 @Resource
23 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 4 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.*;
5 5 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
6 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 8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
9 9 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
10 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 17 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
18 18 import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO;
19 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 21 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
22 22 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
23 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 29 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO;
30 30 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetRespDTO;
31 31 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
  32 +import cn.iocoder.yudao.module.iot.core.util.IotProductAuthUtils;
32 33 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
33 34 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceGroupDO;
34 35 import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
... ... @@ -819,8 +820,9 @@ public class IotDeviceServiceImpl implements IotDeviceService {
819 820 if (BooleanUtil.isFalse(product.getRegisterEnabled())) {
820 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 826 throw exception(DEVICE_REGISTER_SECRET_INVALID);
825 827 }
826 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 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.convert.Convert;
5 5 import cn.hutool.core.lang.Assert;
6   -import cn.hutool.core.map.MapUtil;
7 6 import cn.hutool.core.util.StrUtil;
8 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 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 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 15 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
12 16 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaFirmwareDO;
13 17 import cn.iocoder.yudao.module.iot.dal.dataobject.ota.IotOtaTaskRecordDO;
... ... @@ -133,9 +137,9 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
133 137 public boolean pushOtaTaskRecord(IotOtaTaskRecordDO record, IotOtaFirmwareDO fireware, IotDeviceDO device) {
134 138 try {
135 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 143 deviceMessageService.sendDeviceMessage(message, device);
140 144  
141 145 // 2. 更新 OTA 升级记录状态为进行中
... ... @@ -163,17 +167,16 @@ public class IotOtaTaskRecordServiceImpl implements IotOtaTaskRecordService {
163 167  
164 168 @Override
165 169 @Transactional(rollbackFor = Exception.class)
166   - @SuppressWarnings("unchecked")
167 170 public void updateOtaRecordProgress(IotDeviceDO device, IotDeviceMessage message) {
168 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 174 Assert.notBlank(version, "version 不能为空");
172   - Integer status = MapUtil.getInt(params, "status");
  175 + Integer status = params.getStatus();
173 176 Assert.notNull(status, "status 不能为空");
174 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 180 Assert.notNull(progress, "progress 不能为空");
178 181 Assert.isTrue(progress >= 0 && progress <= 100, "progress 必须在 0-100 之间");
179 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 10 import java.time.LocalDateTime;
11 11 import java.util.Collection;
12 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 18 * IoT 产品 Service 接口
... ... @@ -122,10 +125,35 @@ public interface IotProductService {
122 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 148 * @param ids 产品编号集合
128 149 */
129 150 void validateProductsExist(Collection<Long> ids);
130 151  
  152 + /**
  153 + * 同步产品的 TDengine 表结构
  154 + *
  155 + * 目的:当 MySQL 和 TDengine 不同步时,强制将已发布产品的表结构同步到 TDengine 中
  156 + */
  157 + void syncProductPropertyTable();
  158 +
131 159 }
132 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 15 import cn.iocoder.yudao.module.iot.service.device.property.IotDevicePropertyService;
16 16 import com.baomidou.dynamic.datasource.annotation.DSTransactional;
17 17 import jakarta.annotation.Resource;
  18 +import lombok.extern.slf4j.Slf4j;
18 19 import org.springframework.cache.annotation.CacheEvict;
19 20 import org.springframework.cache.annotation.Cacheable;
  21 +import org.springframework.context.annotation.Lazy;
20 22 import org.springframework.stereotype.Service;
21 23 import org.springframework.validation.annotation.Validated;
22 24  
... ... @@ -33,6 +35,7 @@ import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.*;
33 35 *
34 36 * @author ahh
35 37 */
  38 +@Slf4j
36 39 @Service
37 40 @Validated
38 41 public class IotProductServiceImpl implements IotProductService {
... ... @@ -41,9 +44,10 @@ public class IotProductServiceImpl implements IotProductService {
41 44 private IotProductMapper productMapper;
42 45  
43 46 @Resource
44   - private IotDevicePropertyService devicePropertyDataService;
45   - @Resource
46 47 private IotDeviceService deviceService;
  48 + @Resource
  49 + @Lazy // 延迟加载,避免循环依赖
  50 + private IotDevicePropertyService devicePropertyDataService;
47 51  
48 52 @Override
49 53 public Long createProduct(IotProductSaveReqVO createReqVO) {
... ... @@ -172,6 +176,32 @@ public class IotProductServiceImpl implements IotProductService {
172 176 }
173 177  
174 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 205 public void validateProductsExist(Collection<Long> ids) {
176 206 if (CollUtil.isEmpty(ids)) {
177 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 14  
15 15 // TODO @芋艿:数据库
16 16 // TODO @芋艿:mqtt
17   -// TODO @芋艿:tcp
18   -// TODO @芋艿:websocket
19 17  
20 18 /**
21 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 15 import org.springframework.stereotype.Component;
16 16  
17 17 import java.util.List;
18   -import java.util.Map;
19 18  
20 19 /**
21 20 * IoT 设备属性设置的 {@link IotSceneRuleAction} 实现类
... ... @@ -24,7 +23,7 @@ import java.util.Map;
24 23 */
25 24 @Component
26 25 @Slf4j
27   -public class IotDeviceControlSceneRuleAction implements IotSceneRuleAction {
  26 +public class IotDevicePropertySetSceneRuleAction implements IotSceneRuleAction {
28 27  
29 28 @Resource
30 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 15 import cn.iocoder.yudao.module.iot.dal.mysql.thingmodel.IotThingModelMapper;
16 16 import cn.iocoder.yudao.module.iot.dal.redis.RedisKeyConstants;
17 17 import cn.iocoder.yudao.module.iot.enums.product.IotProductStatusEnum;
  18 +import cn.iocoder.yudao.module.iot.service.device.IotDeviceModbusPointService;
18 19 import cn.iocoder.yudao.module.iot.service.product.IotProductService;
19 20 import jakarta.annotation.Resource;
20 21 import lombok.extern.slf4j.Slf4j;
... ... @@ -50,6 +51,9 @@ public class IotThingModelServiceImpl implements IotThingModelService {
50 51 @Resource
51 52 @Lazy // 延迟加载,解决循环依赖
52 53 private IotProductService productService;
  54 + @Resource
  55 + @Lazy // 延迟加载,解决循环依赖
  56 + private IotDeviceModbusPointService deviceModbusPointService;
53 57  
54 58 @Override
55 59 @Transactional(rollbackFor = Exception.class)
... ... @@ -84,7 +88,11 @@ public class IotThingModelServiceImpl implements IotThingModelService {
84 88 IotThingModelDO thingModel = IotThingModelConvert.INSTANCE.convert(updateReqVO);
85 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 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 3 import cn.hutool.core.collection.ListUtil;
4 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 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 7 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
8 8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDevicePropertyDO;
9 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 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 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 5 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
6 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 1 package cn.iocoder.yudao.module.iot.service.rule.scene.matcher.trigger;
2 2  
3 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 5 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 6 import cn.iocoder.yudao.module.iot.dal.dataobject.rule.IotSceneRuleDO;
7 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 1 package cn.iocoder.yudao.module.iot.core.biz;
2 2  
3 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 5 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
9 6 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
10 7 import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO;
... ... @@ -50,4 +47,12 @@ public interface IotDeviceCommonApi {
50 47 */
51 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 34 */
35 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 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 64 // ========== OTA 固件 ==========
65 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 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 3 import cn.iocoder.yudao.framework.common.core.ArrayValuable;
4 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 24 */
25 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 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 26 */
27 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 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 1 package cn.iocoder.yudao.module.iot.core.mq.message;
2 2  
3   -import cn.hutool.core.map.MapUtil;
4 3 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
5 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 7 import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
8 8 import lombok.AllArgsConstructor;
9 9 import lombok.Builder;
... ... @@ -60,7 +60,7 @@ public class IotDeviceMessage {
60 60 */
61 61 private String serverId;
62 62  
63   - // ========== codec(编解码)字段 ==========
  63 + // ========== serialize(序列化)相关字段 ==========
64 64  
65 65 /**
66 66 * 请求编号
... ... @@ -72,7 +72,7 @@ public class IotDeviceMessage {
72 72 * 请求方法
73 73 *
74 74 * 枚举 {@link IotDeviceMessageMethodEnum}
75   - * 例如说:thing.property.report 属性上报
  75 + * 例如说:thing.property.post 属性上报
76 76 */
77 77 private String method;
78 78 /**
... ... @@ -94,7 +94,7 @@ public class IotDeviceMessage {
94 94 */
95 95 private String msg;
96 96  
97   - // ========== 基础方法:只传递"codec(编解码)字段" ==========
  97 + // ========== 基础方法:只传递"serialize(序列化)相关字段" ==========
98 98  
99 99 public static IotDeviceMessage requestOf(String method) {
100 100 return requestOf(null, method, null);
... ... @@ -149,20 +149,12 @@ public class IotDeviceMessage {
149 149  
150 150 public static IotDeviceMessage buildStateUpdateOnline() {
151 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 155 public static IotDeviceMessage buildStateOffline() {
156 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 1 package cn.iocoder.yudao.module.iot.core.topic.auth;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 4 import jakarta.validation.constraints.NotEmpty;
4 5 import lombok.Data;
5 6  
6 7 /**
7 8 * IoT 设备动态注册 Request DTO
8 9 * <p>
9   - * 用于直连设备/网关的一型一密动态注册:使用 productSecret 验证,返回 deviceSecret
  10 + * 用于 {@link IotDeviceMessageMethodEnum#DEVICE_REGISTER} 消息的 params 参数
  11 + * <p>
  12 + * 直连设备/网关的一型一密动态注册:使用 productSecret 验证,返回 deviceSecret
10 13 *
11 14 * @author 芋道源码
12 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 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 1 package cn.iocoder.yudao.module.iot.core.topic.auth;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 4 import lombok.AllArgsConstructor;
4 5 import lombok.Data;
5 6 import lombok.NoArgsConstructor;
... ... @@ -7,7 +8,7 @@ import lombok.NoArgsConstructor;
7 8 /**
8 9 * IoT 设备动态注册 Response DTO
9 10 * <p>
10   - * 用于直连设备/网关的一型一密动态注册响应
  11 + * 用于 {@link IotDeviceMessageMethodEnum#DEVICE_REGISTER} 响应的设备信息
11 12 *
12 13 * @author 芋道源码
13 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 1 package cn.iocoder.yudao.module.iot.core.topic.auth;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 4 import jakarta.validation.constraints.NotEmpty;
4 5 import lombok.Data;
5 6  
6 7 /**
7 8 * IoT 子设备动态注册 Request DTO
8 9 * <p>
9   - * 用于 thing.auth.register.sub 消息的 params 数组元素
10   - *
  10 + * 用于 {@link IotDeviceMessageMethodEnum#SUB_DEVICE_REGISTER} 消息的 params 数组元素
  11 + * <p>
11 12 * 特殊:网关子设备的动态注册,必须已经创建好该网关子设备(不然哪来的 {@link #deviceName} 字段)。更多的好处,是设备不用提前烧录 deviceSecret 密钥。
12 13 *
13 14 * @author 芋道源码
... ...
yudao-module-iot/yudao-module-iot-core/src/main/java/cn/iocoder/yudao/module/iot/core/topic/auth/IotSubDeviceRegisterRespDTO.java
1 1 package cn.iocoder.yudao.module.iot.core.topic.auth;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 4 import lombok.AllArgsConstructor;
4 5 import lombok.Data;
5 6 import lombok.NoArgsConstructor;
... ... @@ -7,7 +8,7 @@ import lombok.NoArgsConstructor;
7 8 /**
8 9 * IoT 子设备动态注册 Response DTO
9 10 * <p>
10   - * 用于 thing.auth.register.sub 响应的设备信息
  11 + * 用于 {@link IotDeviceMessageMethodEnum#SUB_DEVICE_REGISTER} 响应的设备信息
11 12 *
12 13 * @author 芋道源码
13 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 1 package cn.iocoder.yudao.module.iot.core.topic.event;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 4 import lombok.Data;
4 5  
5 6 /**
6 7 * IoT 设备事件上报 Request DTO
7 8 * <p>
8   - * 用于 thing.event.post 消息的 params 参数
  9 + * 用于 {@link IotDeviceMessageMethodEnum#EVENT_POST} 消息的 params 参数
9 10 *
10 11 * @author 芋道源码
11 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 1 package cn.iocoder.yudao.module.iot.core.topic.property;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 4 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
4 5 import lombok.Data;
5 6  
... ... @@ -9,7 +10,7 @@ import java.util.Map;
9 10 /**
10 11 * IoT 设备属性批量上报 Request DTO
11 12 * <p>
12   - * 用于 thing.event.property.pack.post 消息的 params 参数
  13 + * 用于 {@link IotDeviceMessageMethodEnum#PROPERTY_PACK_POST} 消息的 params 参数
13 14 *
14 15 * @author 芋道源码
15 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 1 package cn.iocoder.yudao.module.iot.core.topic.property;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  4 +
3 5 import java.util.HashMap;
4 6 import java.util.Map;
5 7  
6 8 /**
7 9 * IoT 设备属性上报 Request DTO
8 10 * <p>
9   - * 用于 thing.property.post 消息的 params 参数
  11 + * 用于 {@link IotDeviceMessageMethodEnum#PROPERTY_POST} 消息的 params 参数
10 12 * <p>
11 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 1 package cn.iocoder.yudao.module.iot.core.topic.topo;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
  4 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
4 5 import jakarta.validation.constraints.NotEmpty;
5 6 import lombok.Data;
6 7  
... ... @@ -9,7 +10,7 @@ import java.util.List;
9 10 /**
10 11 * IoT 设备拓扑添加 Request DTO
11 12 * <p>
12   - * 用于 thing.topo.add 消息的 params 参数
  13 + * 用于 {@link IotDeviceMessageMethodEnum#TOPO_ADD} 消息的 params 参数
13 14 *
14 15 * @author 芋道源码
15 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 1 package cn.iocoder.yudao.module.iot.core.topic.topo;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 4 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
4 5 import lombok.AllArgsConstructor;
5 6 import lombok.Data;
... ... @@ -10,7 +11,7 @@ import java.util.List;
10 11 /**
11 12 * IoT 设备拓扑关系变更通知 Request DTO
12 13 * <p>
13   - * 用于 thing.topo.change 下行消息的 params 参数
  14 + * 用于 {@link IotDeviceMessageMethodEnum#TOPO_CHANGE} 下行消息的 params 参数
14 15 *
15 16 * @author 芋道源码
16 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 1 package cn.iocoder.yudao.module.iot.core.topic.topo;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 4 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
4 5 import jakarta.validation.Valid;
5 6 import jakarta.validation.constraints.NotEmpty;
... ... @@ -10,7 +11,7 @@ import java.util.List;
10 11 /**
11 12 * IoT 设备拓扑删除 Request DTO
12 13 * <p>
13   - * 用于 thing.topo.delete 消息的 params 参数
  14 + * 用于 {@link IotDeviceMessageMethodEnum#TOPO_DELETE} 消息的 params 参数
14 15 *
15 16 * @author 芋道源码
16 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 1 package cn.iocoder.yudao.module.iot.core.topic.topo;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 4 import lombok.Data;
4 5  
5 6 /**
6 7 * IoT 设备拓扑关系获取 Request DTO
7 8 * <p>
8   - * 用于 thing.topo.get 请求的 params 参数(目前为空,预留扩展)
  9 + * 用于 {@link IotDeviceMessageMethodEnum#TOPO_GET} 请求的 params 参数(目前为空,预留扩展)
9 10 *
10 11 * @author 芋道源码
11 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 1 package cn.iocoder.yudao.module.iot.core.topic.topo;
2 2  
  3 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
3 4 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
4 5 import lombok.Data;
5 6  
... ... @@ -8,7 +9,7 @@ import java.util.List;
8 9 /**
9 10 * IoT 设备拓扑关系获取 Response DTO
10 11 * <p>
11   - * 用于 thing.topo.get 响应
  12 + * 用于 {@link IotDeviceMessageMethodEnum#TOPO_GET} 响应
12 13 *
13 14 * @author 芋道源码
14 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 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 36 public static String buildUsername(String productKey, String deviceName) {
29 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 33 <groupId>org.apache.rocketmq</groupId>
34 34 <artifactId>rocketmq-spring-boot-starter</artifactId>
35 35 <!-- TODO @芋艿:消息队列,后续可能去掉,默认不使用 rocketmq -->
36   -<!-- <optional>true</optional> -->
  36 + <optional>true</optional>
37 37 </dependency>
38 38  
39 39 <!-- 工具类相关 -->
... ... @@ -48,6 +48,13 @@
48 48 <artifactId>vertx-mqtt</artifactId>
49 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 58 <!-- CoAP 相关 - Eclipse Californium -->
52 59 <dependency>
53 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 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 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 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 5 import org.springframework.boot.context.properties.EnableConfigurationProperties;
31 6 import org.springframework.context.annotation.Bean;
32 7 import org.springframework.context.annotation.Configuration;
33 8  
  9 +/**
  10 + * IoT 网关配置类
  11 + *
  12 + * @author 芋道源码
  13 + */
34 14 @Configuration
35 15 @EnableConfigurationProperties(IotGatewayProperties.class)
36   -@Slf4j
37 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 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 14 import jakarta.validation.constraints.NotEmpty;
4 15 import jakarta.validation.constraints.NotNull;
5 16 import lombok.Data;
... ... @@ -24,9 +35,9 @@ public class IotGatewayProperties {
24 35 private TokenProperties token;
25 36  
26 37 /**
27   - * 协议配置
  38 + * 协议实例列表
28 39 */
29   - private ProtocolProperties protocol;
  40 + private List<ProtocolProperties> protocols;
30 41  
31 42 @Data
32 43 public static class RpcProperties {
... ... @@ -65,581 +76,157 @@ public class IotGatewayProperties {
65 76  
66 77 }
67 78  
  79 + /**
  80 + * 协议实例配置
  81 + */
68 82 @Data
69 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 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 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 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 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 4 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 5 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
5 6 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6 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 9 import lombok.extern.slf4j.Slf4j;
10 10  
11 11 /**
12   - * IoT 网关 EMQX 订阅者:接收下行给设备的消息
  12 + * IoT 协议下行消息订阅者抽象类
  13 + *
  14 + * 负责接收来自消息总线的下行消息,并委托给子类进行业务处理
13 15 *
14 16 * @author 芋道源码
15 17 */
  18 +@AllArgsConstructor
16 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 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 26 @Override
37 27 public String getTopic() {
38 28 return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(protocol.getServerId());
39 29 }
40 30  
  31 + /**
  32 + * 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
  33 + */
41 34 @Override
42 35 public String getGroup() {
43   - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
44 36 return getTopic();
45 37 }
46 38  
47 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 52 public void onMessage(IotDeviceMessage message) {
49 53 log.debug("[onMessage][接收到下行消息, messageId: {}, method: {}, deviceId: {}]",
50 54 message.getId(), message.getMethod(), message.getDeviceId());
51 55 try {
52 56 // 1. 校验
53 57 String method = message.getMethod();
54   - if (method == null) {
  58 + if (StrUtil.isBlank(method)) {
55 59 log.warn("[onMessage][消息方法为空, messageId: {}, deviceId: {}]",
56 60 message.getId(), message.getDeviceId());
57 61 return;
58 62 }
59 63  
60 64 // 2. 处理下行消息
61   - downstreamHandler.handle(message);
  65 + handleMessage(message);
62 66 } catch (Exception e) {
63 67 log.error("[onMessage][处理下行消息失败, messageId: {}, method: {}, deviceId: {}]",
64 68 message.getId(), message.getMethod(), message.getDeviceId(), e);
65 69 }
66 70 }
67 71  
68   -}
69 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 +}
... ...