Commit 91dcd9223a5d2feba465fc2c9a93857205e824d4

Authored by YunaiV
2 parents 7ac6c02e 6991a2de

Merge branch 'feature/iot-coap' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into feature/iot

# Conflicts:
#	yudao-dependencies/pom.xml
#	yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
#	yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
Showing 17 changed files with 857 additions and 38 deletions
yudao-dependencies/pom.xml
@@ -68,6 +68,8 @@ @@ -68,6 +68,8 @@
68 <netty.version>4.2.9.Final</netty.version> 68 <netty.version>4.2.9.Final</netty.version>
69 <mqtt.version>1.2.5</mqtt.version> 69 <mqtt.version>1.2.5</mqtt.version>
70 <vertx.version>4.5.22</vertx.version> 70 <vertx.version>4.5.22</vertx.version>
  71 + <okhttp.version>4.12.0</okhttp.version>
  72 + <californium.version>3.12.0</californium.version>
71 <!-- 三方云服务相关 --> 73 <!-- 三方云服务相关 -->
72 <awssdk.version>2.40.15</awssdk.version> 74 <awssdk.version>2.40.15</awssdk.version>
73 <justauth.version>1.16.7</justauth.version> 75 <justauth.version>1.16.7</justauth.version>
@@ -76,7 +78,6 @@ @@ -76,7 +78,6 @@
76 <jimubi.version>2.3.0</jimubi.version> 78 <jimubi.version>2.3.0</jimubi.version>
77 <weixin-java.version>4.7.9-20251224.161447</weixin-java.version> 79 <weixin-java.version>4.7.9-20251224.161447</weixin-java.version>
78 <alipay-sdk-java.version>4.40.607.ALL</alipay-sdk-java.version> 80 <alipay-sdk-java.version>4.40.607.ALL</alipay-sdk-java.version>
79 - <okhttp.version>4.12.0</okhttp.version>  
80 </properties> 81 </properties>
81 82
82 <dependencyManagement> 83 <dependencyManagement>
@@ -556,6 +557,50 @@ @@ -556,6 +557,50 @@
556 <version>${jsoup.version}</version> 557 <version>${jsoup.version}</version>
557 </dependency> 558 </dependency>
558 559
  560 + <!-- Vert.x -->
  561 + <dependency>
  562 + <groupId>io.vertx</groupId>
  563 + <artifactId>vertx-core</artifactId>
  564 + <version>${vertx.version}</version>
  565 + </dependency>
  566 + <dependency>
  567 + <groupId>io.vertx</groupId>
  568 + <artifactId>vertx-web</artifactId>
  569 + <version>${vertx.version}</version>
  570 + </dependency>
  571 + <dependency>
  572 + <groupId>io.vertx</groupId>
  573 + <artifactId>vertx-mqtt</artifactId>
  574 + <version>${vertx.version}</version>
  575 + </dependency>
  576 +
  577 + <!-- MQTT -->
  578 + <dependency>
  579 + <groupId>org.eclipse.paho</groupId>
  580 + <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
  581 + <version>${mqtt.version}</version>
  582 + </dependency>
  583 +
  584 + <!-- OkHttp -->
  585 + <dependency>
  586 + <groupId>com.squareup.okhttp3</groupId>
  587 + <artifactId>okhttp</artifactId>
  588 + <version>${okhttp.version}</version>
  589 + </dependency>
  590 + <dependency>
  591 + <groupId>com.squareup.okhttp3</groupId>
  592 + <artifactId>mockwebserver</artifactId>
  593 + <version>${okhttp.version}</version>
  594 + <scope>test</scope>
  595 + </dependency>
  596 +
  597 + <!-- CoAP - Eclipse Californium -->
  598 + <dependency>
  599 + <groupId>org.eclipse.californium</groupId>
  600 + <artifactId>californium-core</artifactId>
  601 + <version>${californium.version}</version>
  602 + </dependency>
  603 +
559 <!-- 三方云服务相关 --> 604 <!-- 三方云服务相关 -->
560 <dependency> 605 <dependency>
561 <groupId>software.amazon.awssdk</groupId> 606 <groupId>software.amazon.awssdk</groupId>
@@ -630,43 +675,6 @@ @@ -630,43 +675,6 @@
630 </exclusion> 675 </exclusion>
631 </exclusions> 676 </exclusions>
632 </dependency> 677 </dependency>
633 -  
634 - <!-- Vert.x -->  
635 - <dependency>  
636 - <groupId>io.vertx</groupId>  
637 - <artifactId>vertx-core</artifactId>  
638 - <version>${vertx.version}</version>  
639 - </dependency>  
640 - <dependency>  
641 - <groupId>io.vertx</groupId>  
642 - <artifactId>vertx-web</artifactId>  
643 - <version>${vertx.version}</version>  
644 - </dependency>  
645 - <dependency>  
646 - <groupId>io.vertx</groupId>  
647 - <artifactId>vertx-mqtt</artifactId>  
648 - <version>${vertx.version}</version>  
649 - </dependency>  
650 -  
651 - <!-- MQTT -->  
652 - <dependency>  
653 - <groupId>org.eclipse.paho</groupId>  
654 - <artifactId>org.eclipse.paho.client.mqttv3</artifactId>  
655 - <version>${mqtt.version}</version>  
656 - </dependency>  
657 -  
658 - <!-- OkHttp -->  
659 - <dependency>  
660 - <groupId>com.squareup.okhttp3</groupId>  
661 - <artifactId>okhttp</artifactId>  
662 - <version>${okhttp.version}</version>  
663 - </dependency>  
664 - <dependency>  
665 - <groupId>com.squareup.okhttp3</groupId>  
666 - <artifactId>mockwebserver</artifactId>  
667 - <version>${okhttp.version}</version>  
668 - <scope>test</scope>  
669 - </dependency>  
670 </dependencies> 678 </dependencies>
671 </dependencyManagement> 679 </dependencyManagement>
672 680
yudao-module-iot/yudao-module-iot-gateway/pom.xml
@@ -48,6 +48,12 @@ @@ -48,6 +48,12 @@
48 <artifactId>vertx-mqtt</artifactId> 48 <artifactId>vertx-mqtt</artifactId>
49 </dependency> 49 </dependency>
50 50
  51 + <!-- CoAP 相关 - Eclipse Californium -->
  52 + <dependency>
  53 + <groupId>org.eclipse.californium</groupId>
  54 + <artifactId>californium-core</artifactId>
  55 + </dependency>
  56 +
51 <!-- 测试相关 --> 57 <!-- 测试相关 -->
52 <dependency> 58 <dependency>
53 <groupId>cn.iocoder.boot</groupId> 59 <groupId>cn.iocoder.boot</groupId>
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
1 package cn.iocoder.yudao.module.iot.gateway.config; 1 package cn.iocoder.yudao.module.iot.gateway.config;
2 2
3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus; 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;
4 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol;
5 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscriber; 7 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscriber;
6 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol; 8 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol;
@@ -232,4 +234,25 @@ public class IotGatewayConfiguration { @@ -232,4 +234,25 @@ public class IotGatewayConfiguration {
232 234
233 } 235 }
234 236
  237 + /**
  238 + * IoT 网关 CoAP 协议配置类
  239 + */
  240 + @Configuration
  241 + @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.coap", name = "enabled", havingValue = "true")
  242 + @Slf4j
  243 + public static class CoapProtocolConfiguration {
  244 +
  245 + @Bean
  246 + public IotCoapUpstreamProtocol iotCoapUpstreamProtocol(IotGatewayProperties gatewayProperties) {
  247 + return new IotCoapUpstreamProtocol(gatewayProperties.getProtocol().getCoap());
  248 + }
  249 +
  250 + @Bean
  251 + public IotCoapDownstreamSubscriber iotCoapDownstreamSubscriber(IotCoapUpstreamProtocol coapUpstreamProtocol,
  252 + IotMessageBus messageBus) {
  253 + return new IotCoapDownstreamSubscriber(coapUpstreamProtocol, messageBus);
  254 + }
  255 +
  256 + }
  257 +
235 } 258 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
@@ -98,6 +98,11 @@ public class IotGatewayProperties { @@ -98,6 +98,11 @@ public class IotGatewayProperties {
98 */ 98 */
99 private UdpProperties udp; 99 private UdpProperties udp;
100 100
  101 + /**
  102 + * CoAP 组件配置
  103 + */
  104 + private CoapProperties coap;
  105 +
101 } 106 }
102 107
103 @Data 108 @Data
@@ -546,4 +551,39 @@ public class IotGatewayProperties { @@ -546,4 +551,39 @@ public class IotGatewayProperties {
546 551
547 } 552 }
548 553
  554 + @Data
  555 + public static class CoapProperties {
  556 +
  557 + /**
  558 + * 是否开启
  559 + */
  560 + @NotNull(message = "是否开启不能为空")
  561 + private Boolean enabled;
  562 +
  563 + /**
  564 + * 服务端口(CoAP 默认端口 5683)
  565 + */
  566 + // TODO @AI:默认不为空
  567 + private Integer port = 5683;
  568 +
  569 + /**
  570 + * 最大消息大小(字节)
  571 + */
  572 + // TODO @AI:默认不为空
  573 + private Integer maxMessageSize = 1024;
  574 +
  575 + /**
  576 + * ACK 超时时间(毫秒)
  577 + */
  578 + // TODO @AI:默认不为空
  579 + private Integer ackTimeout = 2000;
  580 +
  581 + /**
  582 + * 最大重传次数
  583 + */
  584 + // TODO @AI:默认不为空
  585 + private Integer maxRetransmit = 4;
  586 +
  587 + }
  588 +
549 } 589 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapDownstreamSubscriber.java 0 → 100644
  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/IotCoapUpstreamProtocol.java 0 → 100644
  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.IotCoapUpstreamTopicResource;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapUpstreamHandler;
  9 +import jakarta.annotation.PostConstruct;
  10 +import jakarta.annotation.PreDestroy;
  11 +import lombok.Getter;
  12 +import lombok.extern.slf4j.Slf4j;
  13 +import org.eclipse.californium.core.CoapServer;
  14 +import org.eclipse.californium.core.config.CoapConfig;
  15 +import org.eclipse.californium.elements.config.Configuration;
  16 +
  17 +import java.util.concurrent.TimeUnit;
  18 +
  19 +/**
  20 + * IoT 网关 CoAP 协议:接收设备上行消息
  21 + *
  22 + * 基于 Eclipse Californium 实现,支持:
  23 + * 1. 认证:POST /auth
  24 + * 2. 属性上报:POST /topic/sys/{productKey}/{deviceName}/thing/property/post
  25 + * 3. 事件上报:POST /topic/sys/{productKey}/{deviceName}/thing/event/post
  26 + *
  27 + * @author 芋道源码
  28 + */
  29 +@Slf4j
  30 +public class IotCoapUpstreamProtocol {
  31 +
  32 + private final IotGatewayProperties.CoapProperties coapProperties;
  33 +
  34 + private CoapServer coapServer;
  35 +
  36 + @Getter
  37 + private final String serverId;
  38 +
  39 + public IotCoapUpstreamProtocol(IotGatewayProperties.CoapProperties coapProperties) {
  40 + this.coapProperties = coapProperties;
  41 + this.serverId = IotDeviceMessageUtils.generateServerId(coapProperties.getPort());
  42 + }
  43 +
  44 + @PostConstruct
  45 + public void start() {
  46 + try {
  47 + // 1.1 创建网络配置(Californium 3.x API)
  48 + Configuration config = Configuration.createStandardWithoutFile();
  49 + config.set(CoapConfig.COAP_PORT, coapProperties.getPort());
  50 + config.set(CoapConfig.MAX_MESSAGE_SIZE, coapProperties.getMaxMessageSize());
  51 + config.set(CoapConfig.ACK_TIMEOUT, coapProperties.getAckTimeout(), TimeUnit.MILLISECONDS);
  52 + config.set(CoapConfig.MAX_RETRANSMIT, coapProperties.getMaxRetransmit());
  53 + // 1.2 创建 CoAP 服务器
  54 + coapServer = new CoapServer(config);
  55 +
  56 + // 2.1 添加 /auth 认证资源
  57 + IotCoapAuthHandler authHandler = new IotCoapAuthHandler();
  58 + IotCoapAuthResource authResource = new IotCoapAuthResource(this, authHandler);
  59 + coapServer.add(authResource);
  60 + // 2.2 添加 /topic 根资源(用于上行消息)
  61 + IotCoapUpstreamHandler upstreamHandler = new IotCoapUpstreamHandler();
  62 + IotCoapUpstreamTopicResource topicResource = new IotCoapUpstreamTopicResource(this, upstreamHandler);
  63 + coapServer.add(topicResource);
  64 +
  65 + // 3. 启动服务器
  66 + coapServer.start();
  67 + log.info("[start][IoT 网关 CoAP 协议启动成功,端口:{},资源:/auth, /topic]", coapProperties.getPort());
  68 + } catch (Exception e) {
  69 + log.error("[start][IoT 网关 CoAP 协议启动失败]", e);
  70 + throw e;
  71 + }
  72 + }
  73 +
  74 + @PreDestroy
  75 + public void stop() {
  76 + if (coapServer != null) {
  77 + try {
  78 + coapServer.stop();
  79 + log.info("[stop][IoT 网关 CoAP 协议已停止]");
  80 + } catch (Exception e) {
  81 + log.error("[stop][IoT 网关 CoAP 协议停止失败]", e);
  82 + }
  83 + }
  84 + }
  85 +
  86 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/package-info.java 0 → 100644
  1 +/**
  2 + * CoAP 协议实现包
  3 + * <p>
  4 + * 提供基于 Eclipse Californium 的 IoT 设备连接和消息处理功能
  5 + * <p>
  6 + * URI 路径:
  7 + * - 认证:POST /auth
  8 + * - 属性上报:POST /topic/sys/{productKey}/{deviceName}/thing/property/post
  9 + * - 事件上报:POST /topic/sys/{productKey}/{deviceName}/thing/event/post
  10 + * <p>
  11 + * Token 通过 CoAP Option 2088 携带
  12 + */
  13 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap;
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/router/IotCoapAuthHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.router;
  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.core.util.StrUtil;
  7 +import cn.hutool.extra.spring.SpringUtil;
  8 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  9 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  10 +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
  11 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
  12 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  13 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
  14 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapUpstreamProtocol;
  15 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.util.IotCoapUtils;
  16 +import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
  17 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.eclipse.californium.core.coap.CoAP;
  20 +import org.eclipse.californium.core.server.resources.CoapExchange;
  21 +
  22 +import java.util.Map;
  23 +
  24 +/**
  25 + * IoT 网关 CoAP 协议的【认证】处理器
  26 + *
  27 + * 参考 {@link cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAuthHandler}
  28 + *
  29 + * @author 芋道源码
  30 + */
  31 +@Slf4j
  32 +public class IotCoapAuthHandler {
  33 +
  34 + private final IotDeviceTokenService deviceTokenService;
  35 + private final IotDeviceCommonApi deviceApi;
  36 + private final IotDeviceMessageService deviceMessageService;
  37 +
  38 + public IotCoapAuthHandler() {
  39 + this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
  40 + this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
  41 + this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
  42 + }
  43 +
  44 + /**
  45 + * 处理认证请求
  46 + *
  47 + * @param exchange CoAP 交换对象
  48 + * @param protocol 协议对象
  49 + */
  50 + @SuppressWarnings("unchecked")
  51 + public void handle(CoapExchange exchange, IotCoapUpstreamProtocol protocol) {
  52 + try {
  53 + // 1.1 解析请求体
  54 + byte[] payload = exchange.getRequestPayload();
  55 + if (payload == null || payload.length == 0) {
  56 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "请求体不能为空");
  57 + return;
  58 + }
  59 + Map<String, Object> body;
  60 + try {
  61 + body = JsonUtils.parseObject(new String(payload), Map.class);
  62 + } catch (Exception e) {
  63 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "请求体 JSON 格式错误");
  64 + return;
  65 + }
  66 + // 1.2 解析参数
  67 + String clientId = MapUtil.getStr(body, "clientId");
  68 + if (StrUtil.isEmpty(clientId)) {
  69 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "clientId 不能为空");
  70 + return;
  71 + }
  72 + String username = MapUtil.getStr(body, "username");
  73 + if (StrUtil.isEmpty(username)) {
  74 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "username 不能为空");
  75 + return;
  76 + }
  77 + String password = MapUtil.getStr(body, "password");
  78 + if (StrUtil.isEmpty(password)) {
  79 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "password 不能为空");
  80 + return;
  81 + }
  82 +
  83 + // 2.1 执行认证
  84 + CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO()
  85 + .setClientId(clientId).setUsername(username).setPassword(password));
  86 + if (result.isError()) {
  87 + log.warn("[handle][认证失败,clientId: {}, 错误: {}]", clientId, result.getMsg());
  88 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.UNAUTHORIZED, "认证失败:" + result.getMsg());
  89 + return;
  90 + }
  91 + if (!BooleanUtil.isTrue(result.getData())) {
  92 + log.warn("[handle][认证失败,clientId: {}]", clientId);
  93 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.UNAUTHORIZED, "认证失败");
  94 + return;
  95 + }
  96 + // 2.2 生成 Token
  97 + IotDeviceAuthUtils.DeviceInfo deviceInfo = deviceTokenService.parseUsername(username);
  98 + Assert.notNull(deviceInfo, "设备信息不能为空");
  99 + String token = deviceTokenService.createToken(deviceInfo.getProductKey(), deviceInfo.getDeviceName());
  100 + Assert.notBlank(token, "生成 token 不能为空");
  101 +
  102 + // 3. 执行上线
  103 + IotDeviceMessage message = IotDeviceMessage.buildStateUpdateOnline();
  104 + deviceMessageService.sendDeviceMessage(message,
  105 + deviceInfo.getProductKey(), deviceInfo.getDeviceName(), protocol.getServerId());
  106 +
  107 + // 4. 返回成功响应
  108 + log.info("[handle][认证成功,productKey: {}, deviceName: {}]",
  109 + deviceInfo.getProductKey(), deviceInfo.getDeviceName());
  110 + IotCoapUtils.respondSuccess(exchange, MapUtil.of("token", token));
  111 + } catch (Exception e) {
  112 + log.error("[handle][认证处理异常]", e);
  113 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.INTERNAL_SERVER_ERROR, "服务器内部错误");
  114 + }
  115 + }
  116 +
  117 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/router/IotCoapAuthResource.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.router;
  2 +
  3 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapUpstreamProtocol;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.eclipse.californium.core.CoapResource;
  6 +import org.eclipse.californium.core.server.resources.CoapExchange;
  7 +
  8 +/**
  9 + * IoT 网关 CoAP 协议的认证资源(/auth)
  10 + *
  11 + * 设备通过此资源进行认证,获取 Token
  12 + *
  13 + * @author 芋道源码
  14 + */
  15 +@Slf4j
  16 +public class IotCoapAuthResource extends CoapResource {
  17 +
  18 + public static final String PATH = "auth";
  19 +
  20 + private final IotCoapUpstreamProtocol protocol;
  21 + private final IotCoapAuthHandler authHandler;
  22 +
  23 + public IotCoapAuthResource(IotCoapUpstreamProtocol protocol,
  24 + IotCoapAuthHandler authHandler) {
  25 + super(PATH);
  26 + this.protocol = protocol;
  27 + this.authHandler = authHandler;
  28 + log.info("[IotCoapAuthResource][创建 CoAP 认证资源: /{}]", PATH);
  29 + }
  30 +
  31 + @Override
  32 + public void handlePOST(CoapExchange exchange) {
  33 + log.debug("[handlePOST][收到 /auth POST 请求]");
  34 + authHandler.handle(exchange, protocol);
  35 + }
  36 +
  37 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/router/IotCoapUpstreamHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.router;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.core.map.MapUtil;
  6 +import cn.hutool.core.text.StrPool;
  7 +import cn.hutool.core.util.ArrayUtil;
  8 +import cn.hutool.core.util.ObjUtil;
  9 +import cn.hutool.core.util.StrUtil;
  10 +import cn.hutool.extra.spring.SpringUtil;
  11 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  12 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
  13 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapUpstreamProtocol;
  14 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.util.IotCoapUtils;
  15 +import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
  16 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  17 +import lombok.extern.slf4j.Slf4j;
  18 +import org.eclipse.californium.core.coap.CoAP;
  19 +import org.eclipse.californium.core.server.resources.CoapExchange;
  20 +
  21 +import java.util.List;
  22 +
  23 +/**
  24 + * IoT 网关 CoAP 协议的【上行】处理器
  25 + *
  26 + * 处理设备通过 CoAP 协议发送的上行消息,包括:
  27 + * 1. 属性上报:POST /topic/sys/{productKey}/{deviceName}/thing/property/post
  28 + * 2. 事件上报:POST /topic/sys/{productKey}/{deviceName}/thing/event/post
  29 + *
  30 + * Token 通过自定义 CoAP Option 2088 携带
  31 + *
  32 + * @author 芋道源码
  33 + */
  34 +@Slf4j
  35 +public class IotCoapUpstreamHandler {
  36 +
  37 + private final IotDeviceTokenService deviceTokenService;
  38 + private final IotDeviceMessageService deviceMessageService;
  39 +
  40 + public IotCoapUpstreamHandler() {
  41 + this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
  42 + this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
  43 + }
  44 +
  45 + /**
  46 + * 处理 CoAP 请求
  47 + *
  48 + * @param exchange CoAP 交换对象
  49 + * @param protocol 协议对象
  50 + */
  51 + public void handle(CoapExchange exchange, IotCoapUpstreamProtocol protocol) {
  52 + try {
  53 + // 1. 解析通用参数
  54 + List<String> uriPath = exchange.getRequestOptions().getUriPath();
  55 + String productKey = CollUtil.get(uriPath, 2);
  56 + String deviceName = CollUtil.get(uriPath, 3);
  57 + byte[] payload = exchange.getRequestPayload();
  58 + if (StrUtil.isEmpty(productKey)) {
  59 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "productKey 不能为空");
  60 + return;
  61 + }
  62 + if (StrUtil.isEmpty(deviceName)) {
  63 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "deviceName 不能为空");
  64 + return;
  65 + }
  66 + if (ArrayUtil.isEmpty(payload)) {
  67 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "请求体不能为空");
  68 + return;
  69 + }
  70 +
  71 + // 2. 认证:从自定义 Option 获取 token
  72 + String token = IotCoapUtils.getTokenFromOption(exchange, IotCoapUtils.OPTION_TOKEN);
  73 + if (StrUtil.isEmpty(token)) {
  74 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.UNAUTHORIZED, "token 不能为空");
  75 + return;
  76 + }
  77 + // 验证 token
  78 + IotDeviceAuthUtils.DeviceInfo deviceInfo = deviceTokenService.verifyToken(token);
  79 + if (deviceInfo == null) {
  80 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.UNAUTHORIZED, "token 无效或已过期");
  81 + return;
  82 + }
  83 + // 验证设备信息匹配
  84 + if (ObjUtil.notEqual(productKey, deviceInfo.getProductKey())
  85 + || ObjUtil.notEqual(deviceName, deviceInfo.getDeviceName())) {
  86 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.FORBIDDEN, "设备信息与 token 不匹配");
  87 + return;
  88 + }
  89 +
  90 + // 2.1 解析 method:deviceName 后面的路径,用 . 拼接
  91 + // 路径格式:[topic, sys, productKey, deviceName, thing, property, post]
  92 + String method = String.join(StrPool.DOT, uriPath.subList(4, uriPath.size()));
  93 +
  94 + // 2.2 解码消息
  95 + IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(payload, productKey, deviceName);
  96 + Assert.equals(method, message.getMethod(), "method 不匹配");
  97 + // 2.3 发送消息到消息总线
  98 + deviceMessageService.sendDeviceMessage(message, productKey, deviceName, protocol.getServerId());
  99 +
  100 + // 3. 返回成功响应
  101 + IotCoapUtils.respondSuccess(exchange, MapUtil.of("messageId", message.getId()));
  102 + } catch (Exception e) {
  103 + log.error("[handle][CoAP 请求处理异常]", e);
  104 + IotCoapUtils.respondError(exchange, CoAP.ResponseCode.INTERNAL_SERVER_ERROR, "服务器内部错误");
  105 + }
  106 + }
  107 +
  108 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/router/IotCoapUpstreamTopicResource.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.router;
  2 +
  3 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapUpstreamProtocol;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.eclipse.californium.core.CoapResource;
  6 +import org.eclipse.californium.core.server.resources.CoapExchange;
  7 +import org.eclipse.californium.core.server.resources.Resource;
  8 +
  9 +/**
  10 + * IoT 网关 CoAP 协议的【上行】Topic 资源
  11 + *
  12 + * 支持任意深度的路径匹配:
  13 + * - /topic/sys/{productKey}/{deviceName}/thing/property/post
  14 + * - /topic/sys/{productKey}/{deviceName}/thing/event/{eventId}/post
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +@Slf4j
  19 +public class IotCoapUpstreamTopicResource extends CoapResource {
  20 +
  21 + public static final String PATH = "topic";
  22 +
  23 + private final IotCoapUpstreamProtocol protocol;
  24 + private final IotCoapUpstreamHandler upstreamHandler;
  25 +
  26 + /**
  27 + * 创建根资源(/topic)
  28 + */
  29 + public IotCoapUpstreamTopicResource(IotCoapUpstreamProtocol protocol,
  30 + IotCoapUpstreamHandler upstreamHandler) {
  31 + this(PATH, protocol, upstreamHandler);
  32 + log.info("[IotCoapUpstreamTopicResource][创建 CoAP 上行 Topic 资源: /{}]", PATH);
  33 + }
  34 +
  35 + /**
  36 + * 创建子资源(动态路径)
  37 + */
  38 + private IotCoapUpstreamTopicResource(String name,
  39 + IotCoapUpstreamProtocol protocol,
  40 + IotCoapUpstreamHandler upstreamHandler) {
  41 + super(name);
  42 + this.protocol = protocol;
  43 + this.upstreamHandler = upstreamHandler;
  44 + }
  45 +
  46 + @Override
  47 + public Resource getChild(String name) {
  48 + // 递归创建动态子资源,支持任意深度路径
  49 + return new IotCoapUpstreamTopicResource(name, protocol, upstreamHandler);
  50 + }
  51 +
  52 + @Override
  53 + public void handleGET(CoapExchange exchange) {
  54 + upstreamHandler.handle(exchange, protocol);
  55 + }
  56 +
  57 + @Override
  58 + public void handlePOST(CoapExchange exchange) {
  59 + upstreamHandler.handle(exchange, protocol);
  60 + }
  61 +
  62 + @Override
  63 + public void handlePUT(CoapExchange exchange) {
  64 + upstreamHandler.handle(exchange, protocol);
  65 + }
  66 +
  67 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/util/IotCoapUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.util;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  6 +import org.eclipse.californium.core.coap.CoAP;
  7 +import org.eclipse.californium.core.coap.MediaTypeRegistry;
  8 +import org.eclipse.californium.core.coap.Option;
  9 +import org.eclipse.californium.core.server.resources.CoapExchange;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
  12 +
  13 +/**
  14 + * IoT CoAP 协议工具类
  15 + *
  16 + * @author 芋道源码
  17 + */
  18 +public class IotCoapUtils {
  19 +
  20 + /**
  21 + * 自定义 CoAP Option 编号,用于携带 Token
  22 + * <p>
  23 + * CoAP Option 范围 2048-65535 属于实验/自定义范围
  24 + */
  25 + public static final int OPTION_TOKEN = 2088;
  26 +
  27 + /**
  28 + * 返回成功响应
  29 + *
  30 + * @param exchange CoAP 交换对象
  31 + * @param data 响应数据
  32 + */
  33 + public static void respondSuccess(CoapExchange exchange, Object data) {
  34 + CommonResult<Object> result = CommonResult.success(data);
  35 + String json = JsonUtils.toJsonString(result);
  36 + exchange.respond(CoAP.ResponseCode.CONTENT, json, MediaTypeRegistry.APPLICATION_JSON);
  37 + }
  38 +
  39 + /**
  40 + * 返回错误响应
  41 + *
  42 + * @param exchange CoAP 交换对象
  43 + * @param code CoAP 响应码
  44 + * @param message 错误消息
  45 + */
  46 + public static void respondError(CoapExchange exchange, CoAP.ResponseCode code, String message) {
  47 + int errorCode = mapCoapCodeToErrorCode(code);
  48 + CommonResult<Object> result = CommonResult.error(errorCode, message);
  49 + String json = JsonUtils.toJsonString(result);
  50 + exchange.respond(code, json, MediaTypeRegistry.APPLICATION_JSON);
  51 + }
  52 +
  53 + /**
  54 + * 从自定义 CoAP Option 中获取 Token
  55 + *
  56 + * @param exchange CoAP 交换对象
  57 + * @param optionNumber Option 编号
  58 + * @return Token 值,如果不存在则返回 null
  59 + */
  60 + public static String getTokenFromOption(CoapExchange exchange, int optionNumber) {
  61 + Option option = CollUtil.findOne(exchange.getRequestOptions().getOthers(),
  62 + o -> o.getNumber() == optionNumber);
  63 + return option != null ? new String(option.getValue()) : null;
  64 + }
  65 +
  66 + /**
  67 + * 将 CoAP 响应码映射到业务错误码
  68 + *
  69 + * @param code CoAP 响应码
  70 + * @return 业务错误码
  71 + */
  72 + public static int mapCoapCodeToErrorCode(CoAP.ResponseCode code) {
  73 + if (code == CoAP.ResponseCode.BAD_REQUEST) {
  74 + return BAD_REQUEST.getCode();
  75 + } else if (code == CoAP.ResponseCode.UNAUTHORIZED) {
  76 + return UNAUTHORIZED.getCode();
  77 + } else if (code == CoAP.ResponseCode.FORBIDDEN) {
  78 + return FORBIDDEN.getCode();
  79 + } else {
  80 + return INTERNAL_SERVER_ERROR.getCode();
  81 + }
  82 + }
  83 +
  84 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/emqx/package-info.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.emqx;
0 \ No newline at end of file 2 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/package-info.java 0 → 100644
  1 +// TODO @AI:参考 /Users/yunai/Java/ruoyi-vue-pro-jdk25/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/package-info.java 完善注释;
  2 +package cn.iocoder.yudao.module.iot.gateway.protocol.http;
0 \ No newline at end of file 3 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/package-info.java 0 → 100644
  1 +// TODO @AI:参考 /Users/yunai/Java/ruoyi-vue-pro-jdk25/yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/package-info.java
  2 +package cn.iocoder.yudao.module.iot.gateway.protocol.tcp;
0 \ No newline at end of file 3 \ No newline at end of file
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
@@ -127,6 +127,15 @@ yudao: @@ -127,6 +127,15 @@ yudao:
127 keep-alive-timeout-seconds: 300 # 保持连接超时时间(秒) 127 keep-alive-timeout-seconds: 300 # 保持连接超时时间(秒)
128 ssl-enabled: false # 是否启用 SSL(wss://) 128 ssl-enabled: false # 是否启用 SSL(wss://)
129 sub-protocol: mqtt # WebSocket 子协议 129 sub-protocol: mqtt # WebSocket 子协议
  130 + # ====================================
  131 + # 针对引入的 CoAP 组件的配置
  132 + # ====================================
  133 + coap:
  134 + enabled: true # 是否启用 CoAP 协议
  135 + port: 5683 # CoAP 服务端口(默认 5683)
  136 + max-message-size: 1024 # 最大消息大小(字节)
  137 + ack-timeout: 2000 # ACK 超时时间(毫秒)
  138 + max-retransmit: 4 # 最大重传次数
130 139
131 --- #################### 日志相关配置 #################### 140 --- #################### 日志相关配置 ####################
132 141
@@ -147,6 +156,7 @@ logging: @@ -147,6 +156,7 @@ logging:
147 cn.iocoder.yudao.module.iot.gateway.protocol.http: DEBUG 156 cn.iocoder.yudao.module.iot.gateway.protocol.http: DEBUG
148 cn.iocoder.yudao.module.iot.gateway.protocol.mqtt: DEBUG 157 cn.iocoder.yudao.module.iot.gateway.protocol.mqtt: DEBUG
149 cn.iocoder.yudao.module.iot.gateway.protocol.mqttws: DEBUG 158 cn.iocoder.yudao.module.iot.gateway.protocol.mqttws: DEBUG
  159 + cn.iocoder.yudao.module.iot.gateway.protocol.coap: DEBUG
150 # 根日志级别 160 # 根日志级别
151 root: INFO 161 root: INFO
152 162
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapProtocolIntegrationTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap;
  2 +
  3 +import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.core.util.IdUtil;
  5 +import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
  6 +import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  7 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.util.IotCoapUtils;
  8 +import lombok.extern.slf4j.Slf4j;
  9 +import org.eclipse.californium.core.CoapClient;
  10 +import org.eclipse.californium.core.CoapResponse;
  11 +import org.eclipse.californium.core.coap.MediaTypeRegistry;
  12 +import org.eclipse.californium.core.coap.Option;
  13 +import org.eclipse.californium.core.coap.Request;
  14 +import org.eclipse.californium.core.config.CoapConfig;
  15 +import org.eclipse.californium.elements.config.Configuration;
  16 +import org.eclipse.californium.elements.config.UdpConfig;
  17 +import org.junit.jupiter.api.BeforeAll;
  18 +import org.junit.jupiter.api.Test;
  19 +
  20 +/**
  21 + * IoT 网关 CoAP 协议集成测试(手动测试)
  22 + *
  23 + * <p>使用步骤:
  24 + * <ol>
  25 + * <li>启动 yudao-module-iot-gateway 服务(CoAP 端口 5683)</li>
  26 + * <li>运行 {@link #testAuth()} 获取 token,将返回的 token 粘贴到 {@link #TOKEN} 常量</li>
  27 + * <li>运行 {@link #testPropertyPost()} 测试属性上报,或运行 {@link #testEventPost()} 测试事件上报</li>
  28 + * </ol>
  29 + *
  30 + * @author 芋道源码
  31 + */
  32 +@Slf4j
  33 +public class IotCoapProtocolIntegrationTest {
  34 +
  35 + private static final String SERVER_HOST = "127.0.0.1";
  36 + private static final int SERVER_PORT = 5683;
  37 +
  38 + // 设备信息(根据实际情况修改 PRODUCT_KEY、DEVICE_NAME、PASSWORD)
  39 + private static final String PRODUCT_KEY = "4aymZgOTOOCrDKRT";
  40 + private static final String DEVICE_NAME = "small";
  41 + private static final String PASSWORD = "509e2b08f7598eb139d276388c600435913ba4c94cd0d50aebc5c0d1855bcb75";
  42 +
  43 + private static final String CLIENT_ID = PRODUCT_KEY + "." + DEVICE_NAME;
  44 + private static final String USERNAME = DEVICE_NAME + "&" + PRODUCT_KEY;
  45 +
  46 + /**
  47 + * 设备 Token:从 {@link #testAuth()} 方法获取后,粘贴到这里
  48 + */
  49 + private static final String TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJwcm9kdWN0S2V5IjoiNGF5bVpnT1RPT0NyREtSVCIsImV4cCI6MTc2OTMwNTA1NSwiZGV2aWNlTmFtZSI6InNtYWxsIn0.mf3MEATCn5bp6cXgULunZjs8d00RGUxj96JEz0hMS7k";
  50 +
  51 + @BeforeAll
  52 + public static void initCaliforniumConfig() {
  53 + // 注册 Californium 配置定义
  54 + CoapConfig.register();
  55 + UdpConfig.register();
  56 + // 创建默认配置
  57 + Configuration.setStandard(Configuration.createStandardWithoutFile());
  58 + }
  59 +
  60 + /**
  61 + * 认证测试:获取设备 Token
  62 + */
  63 + @Test
  64 + @SuppressWarnings("deprecation")
  65 + public void testAuth() throws Exception {
  66 + String uri = String.format("coap://%s:%d/auth", SERVER_HOST, SERVER_PORT);
  67 + String payload = JsonUtils.toJsonString(MapUtil.builder()
  68 + .put("clientId", CLIENT_ID)
  69 + .put("username", USERNAME)
  70 + .put("password", PASSWORD)
  71 + .build());
  72 +
  73 + CoapClient client = new CoapClient(uri);
  74 + try {
  75 + log.info("[testAuth][请求 URI: {}]", uri);
  76 + log.info("[testAuth][请求体: {}]", payload);
  77 +
  78 + CoapResponse response = client.post(payload, MediaTypeRegistry.APPLICATION_JSON);
  79 +
  80 + log.info("[testAuth][响应码: {}]", response.getCode());
  81 + log.info("[testAuth][响应体: {}]", response.getResponseText());
  82 + log.info("[testAuth][请将返回的 token 复制到 TOKEN 常量中]");
  83 + } finally {
  84 + client.shutdown();
  85 + }
  86 + }
  87 +
  88 + /**
  89 + * 属性上报测试
  90 + */
  91 + @Test
  92 + @SuppressWarnings("deprecation")
  93 + public void testPropertyPost() throws Exception {
  94 + String uri = String.format("coap://%s:%d/topic/sys/%s/%s/thing/property/post",
  95 + SERVER_HOST, SERVER_PORT, PRODUCT_KEY, DEVICE_NAME);
  96 + String payload = JsonUtils.toJsonString(MapUtil.builder()
  97 + .put("id", IdUtil.fastSimpleUUID())
  98 + .put("method", IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod())
  99 + .put("version", "1.0")
  100 + .put("params", MapUtil.builder()
  101 + .put("width", 1)
  102 + .put("height", "2")
  103 + .build())
  104 + .build());
  105 +
  106 + CoapClient client = new CoapClient(uri);
  107 + try {
  108 + Request request = Request.newPost();
  109 + request.setURI(uri);
  110 + request.setPayload(payload);
  111 + request.getOptions().setContentFormat(MediaTypeRegistry.APPLICATION_JSON);
  112 + request.getOptions().addOption(new Option(IotCoapUtils.OPTION_TOKEN, TOKEN));
  113 +
  114 + log.info("[testPropertyPost][请求 URI: {}]", uri);
  115 + log.info("[testPropertyPost][请求体: {}]", payload);
  116 +
  117 + CoapResponse response = client.advanced(request);
  118 +
  119 + log.info("[testPropertyPost][响应码: {}]", response.getCode());
  120 + log.info("[testPropertyPost][响应体: {}]", response.getResponseText());
  121 + } finally {
  122 + client.shutdown();
  123 + }
  124 + }
  125 +
  126 + /**
  127 + * 事件上报测试
  128 + */
  129 + @Test
  130 + @SuppressWarnings("deprecation")
  131 + public void testEventPost() throws Exception {
  132 + String uri = String.format("coap://%s:%d/topic/sys/%s/%s/thing/event/post",
  133 + SERVER_HOST, SERVER_PORT, PRODUCT_KEY, DEVICE_NAME);
  134 + String payload = JsonUtils.toJsonString(MapUtil.builder()
  135 + .put("id", IdUtil.fastSimpleUUID())
  136 + .put("method", IotDeviceMessageMethodEnum.EVENT_POST.getMethod())
  137 + .put("version", "1.0")
  138 + .put("params", MapUtil.builder()
  139 + .put("identifier", "eat")
  140 + .put("value", MapUtil.builder()
  141 + .put("width", 1)
  142 + .put("height", "2")
  143 + .put("oneThree", "3")
  144 + .build())
  145 + .put("time", System.currentTimeMillis())
  146 + .build())
  147 + .build());
  148 +
  149 + CoapClient client = new CoapClient(uri);
  150 + try {
  151 + Request request = Request.newPost();
  152 + request.setURI(uri);
  153 + request.setPayload(payload);
  154 + request.getOptions().setContentFormat(MediaTypeRegistry.APPLICATION_JSON);
  155 + request.getOptions().addOption(new Option(IotCoapUtils.OPTION_TOKEN, TOKEN));
  156 +
  157 + log.info("[testEventPost][请求 URI: {}]", uri);
  158 + log.info("[testEventPost][请求体: {}]", payload);
  159 +
  160 + CoapResponse response = client.advanced(request);
  161 +
  162 + log.info("[testEventPost][响应码: {}]", response.getCode());
  163 + log.info("[testEventPost][响应体: {}]", response.getResponseText());
  164 + } finally {
  165 + client.shutdown();
  166 + }
  167 + }
  168 +
  169 +}