Commit a28a15295cdb60f340f7157a5bbd18a76fd4ccbb

Authored by YunaiV
1 parent 8e4b4cf2

feat(iot):【协议改造】coap 初步改造(100%)

Showing 30 changed files with 955 additions and 960 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
@@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.iot.gateway.config; @@ -2,8 +2,6 @@ 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.IotProtocolManager; 4 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolManager;
5 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapDownstreamSubscriber;  
6 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapUpstreamProtocol;  
7 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol; 5 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxAuthEventProtocol;
8 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscriber; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxDownstreamSubscriber;
9 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol; 7 import cn.iocoder.yudao.module.iot.gateway.protocol.emqx.IotEmqxUpstreamProtocol;
@@ -11,12 +9,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttDownstreamSubscr @@ -11,12 +9,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttDownstreamSubscr
11 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol; 9 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
12 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager; 10 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
13 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttDownstreamHandler; 11 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttDownstreamHandler;
14 -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketDownstreamSubscriber;  
15 -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketUpstreamProtocol;  
16 -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager;  
17 -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.router.IotWebSocketDownstreamHandler;  
18 import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager; 12 import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager;
19 -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;  
20 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 13 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
21 import io.vertx.core.Vertx; 14 import io.vertx.core.Vertx;
22 import lombok.extern.slf4j.Slf4j; 15 import lombok.extern.slf4j.Slf4j;
@@ -111,63 +104,4 @@ public class IotGatewayConfiguration { @@ -111,63 +104,4 @@ public class IotGatewayConfiguration {
111 104
112 } 105 }
113 106
114 - /**  
115 - * IoT 网关 CoAP 协议配置类  
116 - */  
117 - @Configuration  
118 - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.coap", name = "enabled", havingValue = "true")  
119 - @Slf4j  
120 - public static class CoapProtocolConfiguration {  
121 -  
122 - @Bean  
123 - public IotCoapUpstreamProtocol iotCoapUpstreamProtocol(IotGatewayProperties gatewayProperties) {  
124 - return new IotCoapUpstreamProtocol(gatewayProperties.getProtocol().getCoap());  
125 - }  
126 -  
127 - @Bean  
128 - public IotCoapDownstreamSubscriber iotCoapDownstreamSubscriber(IotCoapUpstreamProtocol coapUpstreamProtocol,  
129 - IotMessageBus messageBus) {  
130 - return new IotCoapDownstreamSubscriber(coapUpstreamProtocol, messageBus);  
131 - }  
132 -  
133 - }  
134 -  
135 - /**  
136 - * IoT 网关 WebSocket 协议配置类  
137 - */  
138 - @Configuration  
139 - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.websocket", name = "enabled", havingValue = "true")  
140 - @Slf4j  
141 - public static class WebSocketProtocolConfiguration {  
142 -  
143 - @Bean(name = "websocketVertx", destroyMethod = "close")  
144 - public Vertx websocketVertx() {  
145 - return Vertx.vertx();  
146 - }  
147 -  
148 - @Bean  
149 - public IotWebSocketUpstreamProtocol iotWebSocketUpstreamProtocol(IotGatewayProperties gatewayProperties,  
150 - IotDeviceService deviceService,  
151 - IotDeviceMessageService messageService,  
152 - IotWebSocketConnectionManager connectionManager,  
153 - @Qualifier("websocketVertx") Vertx websocketVertx) {  
154 - return new IotWebSocketUpstreamProtocol(gatewayProperties.getProtocol().getWebsocket(),  
155 - deviceService, messageService, connectionManager, websocketVertx);  
156 - }  
157 -  
158 - @Bean  
159 - public IotWebSocketDownstreamHandler iotWebSocketDownstreamHandler(IotDeviceMessageService messageService,  
160 - IotWebSocketConnectionManager connectionManager) {  
161 - return new IotWebSocketDownstreamHandler(messageService, connectionManager);  
162 - }  
163 -  
164 - @Bean  
165 - public IotWebSocketDownstreamSubscriber iotWebSocketDownstreamSubscriber(IotWebSocketUpstreamProtocol protocolHandler,  
166 - IotWebSocketDownstreamHandler downstreamHandler,  
167 - IotMessageBus messageBus) {  
168 - return new IotWebSocketDownstreamSubscriber(protocolHandler, downstreamHandler, messageBus);  
169 - }  
170 -  
171 - }  
172 -  
173 } 107 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayProperties.java
1 package cn.iocoder.yudao.module.iot.gateway.config; 1 package cn.iocoder.yudao.module.iot.gateway.config;
2 2
3 import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; 3 import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
  4 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapConfig;
4 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpConfig; 5 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpConfig;
5 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConfig; 6 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpConfig;
6 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpConfig; 7 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpConfig;
  8 +import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketConfig;
7 import io.vertx.core.net.KeyCertOptions; 9 import io.vertx.core.net.KeyCertOptions;
8 import io.vertx.core.net.TrustOptions; 10 import io.vertx.core.net.TrustOptions;
9 import jakarta.validation.Valid; 11 import jakarta.validation.Valid;
@@ -90,16 +92,6 @@ public class IotGatewayProperties { @@ -90,16 +92,6 @@ public class IotGatewayProperties {
90 */ 92 */
91 private MqttProperties mqtt; 93 private MqttProperties mqtt;
92 94
93 - /**  
94 - * CoAP 组件配置  
95 - */  
96 - private CoapProperties coap;  
97 -  
98 - /**  
99 - * WebSocket 组件配置  
100 - */  
101 - private WebSocketProperties websocket;  
102 -  
103 } 95 }
104 96
105 @Data 97 @Data
@@ -344,93 +336,6 @@ public class IotGatewayProperties { @@ -344,93 +336,6 @@ public class IotGatewayProperties {
344 336
345 } 337 }
346 338
347 - @Data  
348 - public static class CoapProperties {  
349 -  
350 - /**  
351 - * 是否开启  
352 - */  
353 - @NotNull(message = "是否开启不能为空")  
354 - private Boolean enabled;  
355 -  
356 - /**  
357 - * 服务端口(CoAP 默认端口 5683)  
358 - */  
359 - @NotNull(message = "服务端口不能为空")  
360 - private Integer port = 5683;  
361 -  
362 - /**  
363 - * 最大消息大小(字节)  
364 - */  
365 - @NotNull(message = "最大消息大小不能为空")  
366 - private Integer maxMessageSize = 1024;  
367 -  
368 - /**  
369 - * ACK 超时时间(毫秒)  
370 - */  
371 - @NotNull(message = "ACK 超时时间不能为空")  
372 - private Integer ackTimeout = 2000;  
373 -  
374 - /**  
375 - * 最大重传次数  
376 - */  
377 - @NotNull(message = "最大重传次数不能为空")  
378 - private Integer maxRetransmit = 4;  
379 -  
380 - }  
381 -  
382 - @Data  
383 - public static class WebSocketProperties {  
384 -  
385 - /**  
386 - * 是否开启  
387 - */  
388 - @NotNull(message = "是否开启不能为空")  
389 - private Boolean enabled;  
390 -  
391 - /**  
392 - * 服务器端口(默认:8094)  
393 - */  
394 - private Integer port = 8094;  
395 -  
396 - /**  
397 - * WebSocket 路径(默认:/ws)  
398 - */  
399 - @NotEmpty(message = "WebSocket 路径不能为空")  
400 - private String path = "/ws";  
401 -  
402 - /**  
403 - * 最大消息大小(字节,默认 64KB)  
404 - */  
405 - private Integer maxMessageSize = 65536;  
406 -  
407 - /**  
408 - * 最大帧大小(字节,默认 64KB)  
409 - */  
410 - private Integer maxFrameSize = 65536;  
411 -  
412 - /**  
413 - * 空闲超时时间(秒,默认 60)  
414 - */  
415 - private Integer idleTimeoutSeconds = 60;  
416 -  
417 - /**  
418 - * 是否启用 SSL(wss://)  
419 - */  
420 - private Boolean sslEnabled = false;  
421 -  
422 - /**  
423 - * SSL 证书路径  
424 - */  
425 - private String sslCertPath;  
426 -  
427 - /**  
428 - * SSL 私钥路径  
429 - */  
430 - private String sslKeyPath;  
431 -  
432 - }  
433 -  
434 // TODO @AI:【暂时忽略】改成 ProtocolProperties 339 // TODO @AI:【暂时忽略】改成 ProtocolProperties
435 /** 340 /**
436 * 协议实例配置 341 * 协议实例配置
@@ -489,6 +394,18 @@ public class IotGatewayProperties { @@ -489,6 +394,18 @@ public class IotGatewayProperties {
489 @Valid 394 @Valid
490 private IotUdpConfig udp; 395 private IotUdpConfig udp;
491 396
  397 + /**
  398 + * CoAP 协议配置
  399 + */
  400 + @Valid
  401 + private IotCoapConfig coap;
  402 +
  403 + /**
  404 + * WebSocket 协议配置
  405 + */
  406 + @Valid
  407 + private IotWebSocketConfig websocket;
  408 +
492 } 409 }
493 410
494 } 411 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocolManager.java
@@ -4,9 +4,11 @@ import cn.hutool.core.collection.CollUtil; @@ -4,9 +4,11 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.util.BooleanUtil; 4 import cn.hutool.core.util.BooleanUtil;
5 import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum; 5 import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
6 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties; 6 import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
  7 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapProtocol;
7 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol; 8 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol;
8 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpProtocol; 9 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpProtocol;
9 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpProtocol; 10 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpProtocol;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketUpstreamProtocol;
10 import lombok.Getter; 12 import lombok.Getter;
11 import lombok.extern.slf4j.Slf4j; 13 import lombok.extern.slf4j.Slf4j;
12 import org.springframework.context.SmartLifecycle; 14 import org.springframework.context.SmartLifecycle;
@@ -100,6 +102,10 @@ public class IotProtocolManager implements SmartLifecycle { @@ -100,6 +102,10 @@ public class IotProtocolManager implements SmartLifecycle {
100 return createTcpProtocol(config); 102 return createTcpProtocol(config);
101 case UDP: 103 case UDP:
102 return createUdpProtocol(config); 104 return createUdpProtocol(config);
  105 + case COAP:
  106 + return createCoapProtocol(config);
  107 + case WEBSOCKET:
  108 + return createWebSocketProtocol(config);
103 default: 109 default:
104 throw new IllegalArgumentException(String.format( 110 throw new IllegalArgumentException(String.format(
105 "[createProtocol][协议实例 %s 的协议类型 %s 暂不支持]", config.getId(), protocolType)); 111 "[createProtocol][协议实例 %s 的协议类型 %s 暂不支持]", config.getId(), protocolType));
@@ -136,4 +142,24 @@ public class IotProtocolManager implements SmartLifecycle { @@ -136,4 +142,24 @@ public class IotProtocolManager implements SmartLifecycle {
136 return new IotUdpProtocol(config); 142 return new IotUdpProtocol(config);
137 } 143 }
138 144
  145 + /**
  146 + * 创建 CoAP 协议实例
  147 + *
  148 + * @param config 协议实例配置
  149 + * @return CoAP 协议实例
  150 + */
  151 + private IotCoapProtocol createCoapProtocol(IotGatewayProperties.ProtocolInstanceProperties config) {
  152 + return new IotCoapProtocol(config);
  153 + }
  154 +
  155 + /**
  156 + * 创建 WebSocket 协议实例
  157 + *
  158 + * @param config 协议实例配置
  159 + * @return WebSocket 协议实例
  160 + */
  161 + private IotWebSocketUpstreamProtocol createWebSocketProtocol(IotGatewayProperties.ProtocolInstanceProperties config) {
  162 + return new IotWebSocketUpstreamProtocol(config);
  163 + }
  164 +
139 } 165 }
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/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.ProtocolInstanceProperties;
  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.upstrem.IotCoapAuthHandler;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem.IotCoapAuthResource;
  12 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem.IotCoapRegisterHandler;
  13 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem.IotCoapRegisterResource;
  14 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem.IotCoapRegisterSubHandler;
  15 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem.IotCoapRegisterSubResource;
  16 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem.IotCoapUpstreamHandler;
  17 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem.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 org.springframework.util.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 ProtocolInstanceProperties 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 final IotCoapDownstreamSubscriber downstreamSubscriber;
  68 +
  69 + public IotCoapProtocol(ProtocolInstanceProperties 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 + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class);
  77 + this.downstreamSubscriber = new IotCoapDownstreamSubscriber(this, messageBus);
  78 + }
  79 +
  80 + @Override
  81 + public String getId() {
  82 + return properties.getId();
  83 + }
  84 +
  85 + @Override
  86 + public IotProtocolTypeEnum getType() {
  87 + return IotProtocolTypeEnum.COAP;
  88 + }
  89 +
  90 + @Override
  91 + public void start() {
  92 + if (running) {
  93 + log.warn("[start][IoT CoAP 协议 {} 已经在运行中]", getId());
  94 + return;
  95 + }
  96 +
  97 + IotCoapConfig coapConfig = properties.getCoap();
  98 + try {
  99 + // 1.1 创建 CoAP 配置
  100 + Configuration config = Configuration.createStandardWithoutFile();
  101 + config.set(CoapConfig.COAP_PORT, properties.getPort());
  102 + config.set(CoapConfig.MAX_MESSAGE_SIZE, coapConfig.getMaxMessageSize());
  103 + config.set(CoapConfig.ACK_TIMEOUT, coapConfig.getAckTimeoutMs(), TimeUnit.MILLISECONDS);
  104 + config.set(CoapConfig.MAX_RETRANSMIT, coapConfig.getMaxRetransmit());
  105 + // 1.2 创建 CoAP 服务器
  106 + coapServer = new CoapServer(config);
  107 +
  108 + // 2.1 添加 /auth 认证资源
  109 + IotCoapAuthHandler authHandler = new IotCoapAuthHandler(serverId);
  110 + IotCoapAuthResource authResource = new IotCoapAuthResource(authHandler);
  111 + coapServer.add(authResource);
  112 + // 2.2 添加 /auth/register/device 设备动态注册资源(一型一密)
  113 + IotCoapRegisterHandler registerHandler = new IotCoapRegisterHandler();
  114 + IotCoapRegisterResource registerResource = new IotCoapRegisterResource(registerHandler);
  115 + // 2.3 添加 /auth/register/sub-device/{productKey}/{deviceName} 子设备动态注册资源
  116 + IotCoapRegisterSubHandler registerSubHandler = new IotCoapRegisterSubHandler();
  117 + IotCoapRegisterSubResource registerSubResource = new IotCoapRegisterSubResource(registerSubHandler);
  118 + authResource.add(new CoapResource("register") {{
  119 + add(registerResource);
  120 + add(registerSubResource);
  121 + }});
  122 + // 2.4 添加 /topic 根资源(用于上行消息)
  123 + IotCoapUpstreamHandler upstreamHandler = new IotCoapUpstreamHandler(serverId);
  124 + IotCoapUpstreamTopicResource topicResource = new IotCoapUpstreamTopicResource(serverId, upstreamHandler);
  125 + coapServer.add(topicResource);
  126 +
  127 + // 3. 启动服务器
  128 + coapServer.start();
  129 + running = true;
  130 + log.info("[start][IoT CoAP 协议 {} 启动成功,端口:{},serverId:{}]",
  131 + getId(), properties.getPort(), serverId);
  132 +
  133 + // 4. 启动下行消息订阅者
  134 + this.downstreamSubscriber.start();
  135 + } catch (Exception e) {
  136 + log.error("[start][IoT CoAP 协议 {} 启动失败]", getId(), e);
  137 + if (coapServer != null) {
  138 + coapServer.destroy();
  139 + coapServer = null;
  140 + }
  141 + throw e;
  142 + }
  143 + }
  144 +
  145 + @Override
  146 + public void stop() {
  147 + if (!running) {
  148 + return;
  149 + }
  150 + // 1. 停止下行消息订阅者
  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 +
  158 + // 2. 关闭 CoAP 服务器
  159 + if (coapServer != null) {
  160 + try {
  161 + coapServer.stop();
  162 + coapServer.destroy();
  163 + coapServer = null;
  164 + log.info("[stop][IoT CoAP 协议 {} 服务器已停止]", getId());
  165 + } catch (Exception e) {
  166 + log.error("[stop][IoT CoAP 协议 {} 服务器停止失败]", getId(), e);
  167 + }
  168 + }
  169 + running = false;
  170 + log.info("[stop][IoT CoAP 协议 {} 已停止]", getId());
  171 + }
  172 +
  173 +}
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.enums.IotProtocolTypeEnum;  
4 -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;  
5 -import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;  
6 -import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;  
7 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapAuthHandler;  
8 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapAuthResource;  
9 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapRegisterHandler;  
10 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapRegisterResource;  
11 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapUpstreamTopicResource;  
12 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.router.IotCoapUpstreamHandler;  
13 -import jakarta.annotation.PostConstruct;  
14 -import jakarta.annotation.PreDestroy;  
15 -import lombok.Getter;  
16 -import lombok.extern.slf4j.Slf4j;  
17 -import org.eclipse.californium.core.CoapResource;  
18 -import org.eclipse.californium.core.CoapServer;  
19 -import org.eclipse.californium.core.config.CoapConfig;  
20 -import org.eclipse.californium.elements.config.Configuration;  
21 -  
22 -import java.util.concurrent.TimeUnit;  
23 -  
24 -/**  
25 - * IoT 网关 CoAP 协议:接收设备上行消息  
26 - *  
27 - * 基于 Eclipse Californium 实现,支持:  
28 - * 1. 认证:POST /auth  
29 - * 2. 属性上报:POST /topic/sys/{productKey}/{deviceName}/thing/property/post  
30 - * 3. 事件上报:POST /topic/sys/{productKey}/{deviceName}/thing/event/post  
31 - *  
32 - * @author 芋道源码  
33 - */  
34 -@Slf4j  
35 -public class IotCoapUpstreamProtocol implements IotProtocol {  
36 -  
37 - private static final String ID = "coap";  
38 -  
39 - private final IotGatewayProperties.CoapProperties coapProperties;  
40 -  
41 - private CoapServer coapServer;  
42 -  
43 - @Getter  
44 - private final String serverId;  
45 -  
46 - private volatile boolean running = false;  
47 -  
48 - public IotCoapUpstreamProtocol(IotGatewayProperties.CoapProperties coapProperties) {  
49 - this.coapProperties = coapProperties;  
50 - this.serverId = IotDeviceMessageUtils.generateServerId(coapProperties.getPort());  
51 - }  
52 -  
53 - @Override  
54 - public String getId() {  
55 - return ID;  
56 - }  
57 -  
58 - @Override  
59 - public IotProtocolTypeEnum getType() {  
60 - return IotProtocolTypeEnum.COAP;  
61 - }  
62 -  
63 - @Override  
64 - @PostConstruct  
65 - public void start() {  
66 - try {  
67 - // 1.1 创建网络配置(Californium 3.x API)  
68 - Configuration config = Configuration.createStandardWithoutFile();  
69 - config.set(CoapConfig.COAP_PORT, coapProperties.getPort());  
70 - config.set(CoapConfig.MAX_MESSAGE_SIZE, coapProperties.getMaxMessageSize());  
71 - config.set(CoapConfig.ACK_TIMEOUT, coapProperties.getAckTimeout(), TimeUnit.MILLISECONDS);  
72 - config.set(CoapConfig.MAX_RETRANSMIT, coapProperties.getMaxRetransmit());  
73 - // 1.2 创建 CoAP 服务器  
74 - coapServer = new CoapServer(config);  
75 -  
76 - // 2.1 添加 /auth 认证资源  
77 - IotCoapAuthHandler authHandler = new IotCoapAuthHandler();  
78 - IotCoapAuthResource authResource = new IotCoapAuthResource(this, authHandler);  
79 - coapServer.add(authResource);  
80 - // 2.2 添加 /auth/register/device 设备动态注册资源(一型一密)  
81 - IotCoapRegisterHandler registerHandler = new IotCoapRegisterHandler();  
82 - IotCoapRegisterResource registerResource = new IotCoapRegisterResource(registerHandler);  
83 - authResource.add(new CoapResource("register") {{  
84 - add(registerResource);  
85 - }});  
86 - // 2.3 添加 /topic 根资源(用于上行消息)  
87 - IotCoapUpstreamHandler upstreamHandler = new IotCoapUpstreamHandler();  
88 - IotCoapUpstreamTopicResource topicResource = new IotCoapUpstreamTopicResource(this, upstreamHandler);  
89 - coapServer.add(topicResource);  
90 -  
91 - // 3. 启动服务器  
92 - coapServer.start();  
93 - running = true;  
94 - log.info("[start][IoT 网关 CoAP 协议启动成功,端口:{},资源:/auth, /auth/register/device, /topic]", coapProperties.getPort());  
95 - } catch (Exception e) {  
96 - log.error("[start][IoT 网关 CoAP 协议启动失败]", e);  
97 - throw e;  
98 - }  
99 - }  
100 -  
101 - @Override  
102 - @PreDestroy  
103 - public void stop() {  
104 - if (coapServer != null) {  
105 - try {  
106 - coapServer.stop();  
107 - running = false;  
108 - log.info("[stop][IoT 网关 CoAP 协议已停止]");  
109 - } catch (Exception e) {  
110 - log.error("[stop][IoT 网关 CoAP 协议停止失败]", e);  
111 - }  
112 - }  
113 - }  
114 -  
115 - @Override  
116 - public boolean isRunning() {  
117 - return running;  
118 - }  
119 -  
120 -}  
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/IotCoapDownstreamSubscriber.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/downstream/IotCoapDownstreamSubscriber.java
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.coap; 1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.downstream;
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.core.mq.message.IotDeviceMessage; 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber; 5 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapProtocol;
6 import lombok.extern.slf4j.Slf4j; 7 import lombok.extern.slf4j.Slf4j;
7 8
8 /** 9 /**
@@ -13,7 +14,7 @@ import lombok.extern.slf4j.Slf4j; @@ -13,7 +14,7 @@ import lombok.extern.slf4j.Slf4j;
13 @Slf4j 14 @Slf4j
14 public class IotCoapDownstreamSubscriber extends IotProtocolDownstreamSubscriber { 15 public class IotCoapDownstreamSubscriber extends IotProtocolDownstreamSubscriber {
15 16
16 - public IotCoapDownstreamSubscriber(IotCoapUpstreamProtocol protocol, IotMessageBus messageBus) { 17 + public IotCoapDownstreamSubscriber(IotCoapProtocol protocol, IotMessageBus messageBus) {
17 super(protocol, messageBus); 18 super(protocol, messageBus);
18 } 19 }
19 20
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstrem/IotCoapAbstractHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem;
  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/upstrem/IotCoapAuthHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem;
  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 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/router/IotCoapAuthResource.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstrem/IotCoapAuthResource.java
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.coap.router; 1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem;
2 2
3 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapUpstreamProtocol;  
4 import lombok.extern.slf4j.Slf4j; 3 import lombok.extern.slf4j.Slf4j;
5 import org.eclipse.californium.core.CoapResource; 4 import org.eclipse.californium.core.CoapResource;
6 import org.eclipse.californium.core.server.resources.CoapExchange; 5 import org.eclipse.californium.core.server.resources.CoapExchange;
@@ -17,13 +16,10 @@ public class IotCoapAuthResource extends CoapResource { @@ -17,13 +16,10 @@ public class IotCoapAuthResource extends CoapResource {
17 16
18 public static final String PATH = "auth"; 17 public static final String PATH = "auth";
19 18
20 - private final IotCoapUpstreamProtocol protocol;  
21 private final IotCoapAuthHandler authHandler; 19 private final IotCoapAuthHandler authHandler;
22 20
23 - public IotCoapAuthResource(IotCoapUpstreamProtocol protocol,  
24 - IotCoapAuthHandler authHandler) { 21 + public IotCoapAuthResource(IotCoapAuthHandler authHandler) {
25 super(PATH); 22 super(PATH);
26 - this.protocol = protocol;  
27 this.authHandler = authHandler; 23 this.authHandler = authHandler;
28 log.info("[IotCoapAuthResource][创建 CoAP 认证资源: /{}]", PATH); 24 log.info("[IotCoapAuthResource][创建 CoAP 认证资源: /{}]", PATH);
29 } 25 }
@@ -31,7 +27,7 @@ public class IotCoapAuthResource extends CoapResource { @@ -31,7 +27,7 @@ public class IotCoapAuthResource extends CoapResource {
31 @Override 27 @Override
32 public void handlePOST(CoapExchange exchange) { 28 public void handlePOST(CoapExchange exchange) {
33 log.debug("[handlePOST][收到 /auth POST 请求]"); 29 log.debug("[handlePOST][收到 /auth POST 请求]");
34 - authHandler.handle(exchange, protocol); 30 + authHandler.handle(exchange);
35 } 31 }
36 32
37 } 33 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstrem/IotCoapRegisterHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.extra.spring.SpringUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
  7 +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
  8 +import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
  9 +import lombok.extern.slf4j.Slf4j;
  10 +import org.eclipse.californium.core.server.resources.CoapExchange;
  11 +
  12 +/**
  13 + * IoT 网关 CoAP 协议的【设备动态注册】处理器
  14 + * <p>
  15 + * 用于直连设备/网关的一型一密动态注册,不需要认证
  16 + *
  17 + * @author 芋道源码
  18 + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
  19 + */
  20 +@Slf4j
  21 +public class IotCoapRegisterHandler extends IotCoapAbstractHandler {
  22 +
  23 + private final IotDeviceCommonApi deviceApi;
  24 +
  25 + public IotCoapRegisterHandler() {
  26 + this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
  27 + }
  28 +
  29 + @Override
  30 + protected CommonResult<Object> handle0(CoapExchange exchange) {
  31 + // 1. 解析参数
  32 + IotDeviceRegisterReqDTO request = deserializeRequest(exchange, IotDeviceRegisterReqDTO.class);
  33 + Assert.notNull(request, "请求体不能为空");
  34 + Assert.notBlank(request.getProductKey(), "productKey 不能为空");
  35 + Assert.notBlank(request.getDeviceName(), "deviceName 不能为空");
  36 + Assert.notBlank(request.getProductSecret(), "productSecret 不能为空");
  37 +
  38 + // 2. 调用动态注册
  39 + CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(request);
  40 + result.checkError();
  41 +
  42 + // 3. 构建响应数据
  43 + return CommonResult.success(result.getData());
  44 + }
  45 +
  46 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/router/IotCoapRegisterResource.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstrem/IotCoapRegisterResource.java
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.coap.router; 1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem;
2 2
3 import lombok.extern.slf4j.Slf4j; 3 import lombok.extern.slf4j.Slf4j;
4 import org.eclipse.californium.core.CoapResource; 4 import org.eclipse.californium.core.CoapResource;
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstrem/IotCoapRegisterSubHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.hutool.extra.spring.SpringUtil;
  6 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  7 +import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
  8 +import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO;
  9 +import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO;
  10 +import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO;
  11 +import lombok.Data;
  12 +import lombok.extern.slf4j.Slf4j;
  13 +import org.eclipse.californium.core.server.resources.CoapExchange;
  14 +
  15 +import java.util.List;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  18 +
  19 +/**
  20 + * IoT 网关 CoAP 协议的【子设备动态注册】处理器
  21 + * <p>
  22 + * 用于子设备的动态注册,需要网关认证
  23 + *
  24 + * @author 芋道源码
  25 + * @see <a href="https://help.aliyun.com/zh/iot/user-guide/register-devices">阿里云 - 动态注册子设备</a>
  26 + */
  27 +@Slf4j
  28 +public class IotCoapRegisterSubHandler extends IotCoapAbstractHandler {
  29 +
  30 + private final IotDeviceCommonApi deviceApi;
  31 +
  32 + public IotCoapRegisterSubHandler() {
  33 + this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
  34 + }
  35 +
  36 + @Override
  37 + @SuppressWarnings("DuplicatedCode")
  38 + protected CommonResult<Object> handle0(CoapExchange exchange) {
  39 + // 1.1 解析通用参数(从 URI 路径获取网关设备信息)
  40 + List<String> uriPath = exchange.getRequestOptions().getUriPath();
  41 + String productKey = getProductKey(uriPath);
  42 + String deviceName = getDeviceName(uriPath);
  43 + // 1.2 解析子设备列表
  44 + SubDeviceRegisterRequest request = deserializeRequest(exchange, SubDeviceRegisterRequest.class);
  45 + Assert.notNull(request, "请求参数不能为空");
  46 + Assert.notEmpty(request.getParams(), "params 不能为空");
  47 +
  48 + // 2. 调用子设备动态注册
  49 + IotSubDeviceRegisterFullReqDTO reqDTO = new IotSubDeviceRegisterFullReqDTO()
  50 + .setGatewayProductKey(productKey)
  51 + .setGatewayDeviceName(deviceName)
  52 + .setSubDevices(request.getParams());
  53 + CommonResult<List<IotSubDeviceRegisterRespDTO>> result = deviceApi.registerSubDevices(reqDTO);
  54 + result.checkError();
  55 +
  56 + // 3. 返回结果
  57 + return success(result.getData());
  58 + }
  59 +
  60 + @Override
  61 + protected boolean requiresAuthentication() {
  62 + return true;
  63 + }
  64 +
  65 + @Override
  66 + protected String getProductKey(List<String> uriPath) {
  67 + // 路径格式:/auth/register/sub-device/{productKey}/{deviceName}
  68 + return CollUtil.get(uriPath, 3);
  69 + }
  70 +
  71 + @Override
  72 + protected String getDeviceName(List<String> uriPath) {
  73 + // 路径格式:/auth/register/sub-device/{productKey}/{deviceName}
  74 + return CollUtil.get(uriPath, 4);
  75 + }
  76 +
  77 + @Data
  78 + public static class SubDeviceRegisterRequest {
  79 +
  80 + private List<IotSubDeviceRegisterReqDTO> params;
  81 +
  82 + }
  83 +
  84 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstrem/IotCoapRegisterSubResource.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem;
  2 +
  3 +import lombok.extern.slf4j.Slf4j;
  4 +import org.eclipse.californium.core.CoapResource;
  5 +import org.eclipse.californium.core.server.resources.CoapExchange;
  6 +import org.eclipse.californium.core.server.resources.Resource;
  7 +
  8 +/**
  9 + * IoT 网关 CoAP 协议的子设备动态注册资源(/auth/register/sub-device/{productKey}/{deviceName})
  10 + * <p>
  11 + * 用于子设备的动态注册,需要网关认证
  12 + * <p>
  13 + * 支持动态路径匹配:productKey 和 deviceName 是网关设备的标识
  14 + *
  15 + * @author 芋道源码
  16 + */
  17 +@Slf4j
  18 +public class IotCoapRegisterSubResource extends CoapResource {
  19 +
  20 + public static final String PATH = "sub-device";
  21 +
  22 + private final IotCoapRegisterSubHandler registerSubHandler;
  23 +
  24 + /**
  25 + * 创建根资源(/auth/register/sub-device)
  26 + */
  27 + public IotCoapRegisterSubResource(IotCoapRegisterSubHandler registerSubHandler) {
  28 + this(PATH, registerSubHandler);
  29 + log.info("[IotCoapRegisterSubResource][创建 CoAP 子设备动态注册资源: /auth/register/{}]", PATH);
  30 + }
  31 +
  32 + /**
  33 + * 创建子资源(动态路径)
  34 + */
  35 + private IotCoapRegisterSubResource(String name, IotCoapRegisterSubHandler registerSubHandler) {
  36 + super(name);
  37 + this.registerSubHandler = registerSubHandler;
  38 + }
  39 +
  40 + @Override
  41 + public Resource getChild(String name) {
  42 + // 递归创建动态子资源,支持 /sub-device/{productKey}/{deviceName} 路径
  43 + return new IotCoapRegisterSubResource(name, registerSubHandler);
  44 + }
  45 +
  46 + @Override
  47 + public void handlePOST(CoapExchange exchange) {
  48 + log.debug("[handlePOST][收到子设备动态注册请求]");
  49 + registerSubHandler.handle(exchange);
  50 + }
  51 +
  52 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstrem/IotCoapUpstreamHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem;
  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.extra.spring.SpringUtil;
  8 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  9 +import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  10 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +import org.eclipse.californium.core.server.resources.CoapExchange;
  13 +
  14 +import java.util.List;
  15 +
  16 +/**
  17 + * IoT 网关 CoAP 协议的【上行】处理器
  18 + *
  19 + * 处理设备通过 CoAP 协议发送的上行消息,包括:
  20 + * 1. 属性上报:POST /topic/sys/{productKey}/{deviceName}/thing/property/post
  21 + * 2. 事件上报:POST /topic/sys/{productKey}/{deviceName}/thing/event/post
  22 + *
  23 + * Token 通过自定义 CoAP Option 2088 携带
  24 + *
  25 + * @author 芋道源码
  26 + */
  27 +@Slf4j
  28 +public class IotCoapUpstreamHandler extends IotCoapAbstractHandler {
  29 +
  30 + private final String serverId;
  31 +
  32 + private final IotDeviceMessageService deviceMessageService;
  33 +
  34 + public IotCoapUpstreamHandler(String serverId) {
  35 + this.serverId = serverId;
  36 + this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
  37 + }
  38 +
  39 + @Override
  40 + @SuppressWarnings("DuplicatedCode")
  41 + protected CommonResult<Object> handle0(CoapExchange exchange) {
  42 + // 1.1 解析通用参数
  43 + List<String> uriPath = exchange.getRequestOptions().getUriPath();
  44 + String productKey = getProductKey(uriPath);
  45 + String deviceName = getDeviceName(uriPath);
  46 + String method = String.join(StrPool.DOT, uriPath.subList(4, uriPath.size()));
  47 + // 1.2 解析消息
  48 + IotDeviceMessage message = deserializeRequest(exchange, IotDeviceMessage.class);
  49 + Assert.notNull(message, "请求参数不能为空");
  50 + Assert.equals(method, message.getMethod(), "method 不匹配");
  51 +
  52 + // 2. 发送消息
  53 + deviceMessageService.sendDeviceMessage(message, productKey, deviceName, serverId);
  54 +
  55 + // 3. 返回结果
  56 + return CommonResult.success(MapUtil.of("messageId", message.getId()));
  57 + }
  58 +
  59 + @Override
  60 + protected boolean requiresAuthentication() {
  61 + return true;
  62 + }
  63 +
  64 + @Override
  65 + protected String getProductKey(List<String> uriPath) {
  66 + // 路径格式:/topic/sys/{productKey}/{deviceName}/...
  67 + return CollUtil.get(uriPath, 2);
  68 + }
  69 +
  70 + @Override
  71 + protected String getDeviceName(List<String> uriPath) {
  72 + // 路径格式:/topic/sys/{productKey}/{deviceName}/...
  73 + return CollUtil.get(uriPath, 3);
  74 + }
  75 +
  76 +}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/router/IotCoapUpstreamTopicResource.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/handler/upstrem/IotCoapUpstreamTopicResource.java
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.coap.router; 1 +package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem;
2 2
3 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapUpstreamProtocol;  
4 import lombok.extern.slf4j.Slf4j; 3 import lombok.extern.slf4j.Slf4j;
5 import org.eclipse.californium.core.CoapResource; 4 import org.eclipse.californium.core.CoapResource;
6 import org.eclipse.californium.core.server.resources.CoapExchange; 5 import org.eclipse.californium.core.server.resources.CoapExchange;
@@ -20,15 +19,15 @@ public class IotCoapUpstreamTopicResource extends CoapResource { @@ -20,15 +19,15 @@ public class IotCoapUpstreamTopicResource extends CoapResource {
20 19
21 public static final String PATH = "topic"; 20 public static final String PATH = "topic";
22 21
23 - private final IotCoapUpstreamProtocol protocol; 22 + private final String serverId;
24 private final IotCoapUpstreamHandler upstreamHandler; 23 private final IotCoapUpstreamHandler upstreamHandler;
25 24
26 /** 25 /**
27 * 创建根资源(/topic) 26 * 创建根资源(/topic)
28 */ 27 */
29 - public IotCoapUpstreamTopicResource(IotCoapUpstreamProtocol protocol, 28 + public IotCoapUpstreamTopicResource(String serverId,
30 IotCoapUpstreamHandler upstreamHandler) { 29 IotCoapUpstreamHandler upstreamHandler) {
31 - this(PATH, protocol, upstreamHandler); 30 + this(PATH, serverId, upstreamHandler);
32 log.info("[IotCoapUpstreamTopicResource][创建 CoAP 上行 Topic 资源: /{}]", PATH); 31 log.info("[IotCoapUpstreamTopicResource][创建 CoAP 上行 Topic 资源: /{}]", PATH);
33 } 32 }
34 33
@@ -36,32 +35,32 @@ public class IotCoapUpstreamTopicResource extends CoapResource { @@ -36,32 +35,32 @@ public class IotCoapUpstreamTopicResource extends CoapResource {
36 * 创建子资源(动态路径) 35 * 创建子资源(动态路径)
37 */ 36 */
38 private IotCoapUpstreamTopicResource(String name, 37 private IotCoapUpstreamTopicResource(String name,
39 - IotCoapUpstreamProtocol protocol, 38 + String serverId,
40 IotCoapUpstreamHandler upstreamHandler) { 39 IotCoapUpstreamHandler upstreamHandler) {
41 super(name); 40 super(name);
42 - this.protocol = protocol; 41 + this.serverId = serverId;
43 this.upstreamHandler = upstreamHandler; 42 this.upstreamHandler = upstreamHandler;
44 } 43 }
45 44
46 @Override 45 @Override
47 public Resource getChild(String name) { 46 public Resource getChild(String name) {
48 // 递归创建动态子资源,支持任意深度路径 47 // 递归创建动态子资源,支持任意深度路径
49 - return new IotCoapUpstreamTopicResource(name, protocol, upstreamHandler); 48 + return new IotCoapUpstreamTopicResource(name, serverId, upstreamHandler);
50 } 49 }
51 50
52 @Override 51 @Override
53 public void handleGET(CoapExchange exchange) { 52 public void handleGET(CoapExchange exchange) {
54 - upstreamHandler.handle(exchange, protocol); 53 + upstreamHandler.handle(exchange);
55 } 54 }
56 55
57 @Override 56 @Override
58 public void handlePOST(CoapExchange exchange) { 57 public void handlePOST(CoapExchange exchange) {
59 - upstreamHandler.handle(exchange, protocol); 58 + upstreamHandler.handle(exchange);
60 } 59 }
61 60
62 @Override 61 @Override
63 public void handlePUT(CoapExchange exchange) { 62 public void handlePUT(CoapExchange exchange) {
64 - upstreamHandler.handle(exchange, protocol); 63 + upstreamHandler.handle(exchange);
65 } 64 }
66 65
67 } 66 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/package-info.java
@@ -2,12 +2,5 @@ @@ -2,12 +2,5 @@
2 * CoAP 协议实现包 2 * CoAP 协议实现包
3 * <p> 3 * <p>
4 * 提供基于 Eclipse Californium 的 IoT 设备连接和消息处理功能 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 */ 5 */
13 package cn.iocoder.yudao.module.iot.gateway.protocol.coap; 6 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 deleted
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.topic.IotDeviceIdentity;  
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 - * @author 芋道源码  
28 - */  
29 -@Slf4j  
30 -public class IotCoapAuthHandler {  
31 -  
32 - private final IotDeviceTokenService deviceTokenService;  
33 - private final IotDeviceCommonApi deviceApi;  
34 - private final IotDeviceMessageService deviceMessageService;  
35 -  
36 - public IotCoapAuthHandler() {  
37 - this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);  
38 - this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);  
39 - this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);  
40 - }  
41 -  
42 - /**  
43 - * 处理认证请求  
44 - *  
45 - * @param exchange CoAP 交换对象  
46 - * @param protocol 协议对象  
47 - */  
48 - @SuppressWarnings("unchecked")  
49 - public void handle(CoapExchange exchange, IotCoapUpstreamProtocol protocol) {  
50 - try {  
51 - // 1.1 解析请求体  
52 - byte[] payload = exchange.getRequestPayload();  
53 - if (payload == null || payload.length == 0) {  
54 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "请求体不能为空");  
55 - return;  
56 - }  
57 - Map<String, Object> body;  
58 - try {  
59 - body = JsonUtils.parseObject(new String(payload), Map.class);  
60 - } catch (Exception e) {  
61 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "请求体 JSON 格式错误");  
62 - return;  
63 - }  
64 - // 1.2 解析参数  
65 - String clientId = MapUtil.getStr(body, "clientId");  
66 - if (StrUtil.isEmpty(clientId)) {  
67 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "clientId 不能为空");  
68 - return;  
69 - }  
70 - String username = MapUtil.getStr(body, "username");  
71 - if (StrUtil.isEmpty(username)) {  
72 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "username 不能为空");  
73 - return;  
74 - }  
75 - String password = MapUtil.getStr(body, "password");  
76 - if (StrUtil.isEmpty(password)) {  
77 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "password 不能为空");  
78 - return;  
79 - }  
80 -  
81 - // 2.1 执行认证  
82 - CommonResult<Boolean> result = deviceApi.authDevice(new IotDeviceAuthReqDTO()  
83 - .setClientId(clientId).setUsername(username).setPassword(password));  
84 - if (result.isError()) {  
85 - log.warn("[handle][认证失败,clientId: {}, 错误: {}]", clientId, result.getMsg());  
86 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.UNAUTHORIZED, "认证失败:" + result.getMsg());  
87 - return;  
88 - }  
89 - if (!BooleanUtil.isTrue(result.getData())) {  
90 - log.warn("[handle][认证失败,clientId: {}]", clientId);  
91 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.UNAUTHORIZED, "认证失败");  
92 - return;  
93 - }  
94 - // 2.2 生成 Token  
95 - IotDeviceIdentity deviceInfo = deviceTokenService.parseUsername(username);  
96 - Assert.notNull(deviceInfo, "设备信息不能为空");  
97 - String token = deviceTokenService.createToken(deviceInfo.getProductKey(), deviceInfo.getDeviceName());  
98 - Assert.notBlank(token, "生成 token 不能为空");  
99 -  
100 - // 3. 执行上线  
101 - IotDeviceMessage message = IotDeviceMessage.buildStateUpdateOnline();  
102 - deviceMessageService.sendDeviceMessage(message,  
103 - deviceInfo.getProductKey(), deviceInfo.getDeviceName(), protocol.getServerId());  
104 -  
105 - // 4. 返回成功响应  
106 - log.info("[handle][认证成功,productKey: {}, deviceName: {}]",  
107 - deviceInfo.getProductKey(), deviceInfo.getDeviceName());  
108 - IotCoapUtils.respondSuccess(exchange, MapUtil.of("token", token));  
109 - } catch (Exception e) {  
110 - log.error("[handle][认证处理异常]", e);  
111 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.INTERNAL_SERVER_ERROR, "服务器内部错误");  
112 - }  
113 - }  
114 -  
115 -}  
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/router/IotCoapRegisterHandler.java deleted
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.coap.router;  
2 -  
3 -import cn.hutool.core.map.MapUtil;  
4 -import cn.hutool.core.util.StrUtil;  
5 -import cn.hutool.extra.spring.SpringUtil;  
6 -import cn.iocoder.yudao.framework.common.pojo.CommonResult;  
7 -import cn.iocoder.yudao.framework.common.util.json.JsonUtils;  
8 -import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;  
9 -import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;  
10 -import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;  
11 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.util.IotCoapUtils;  
12 -import lombok.extern.slf4j.Slf4j;  
13 -import org.eclipse.californium.core.coap.CoAP;  
14 -import org.eclipse.californium.core.server.resources.CoapExchange;  
15 -  
16 -import java.util.Map;  
17 -  
18 -/**  
19 - * IoT 网关 CoAP 协议的【设备动态注册】处理器  
20 - * <p>  
21 - * 用于直连设备/网关的一型一密动态注册,不需要认证  
22 - *  
23 - * @author 芋道源码  
24 - * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>  
25 - */  
26 -@Slf4j  
27 -public class IotCoapRegisterHandler {  
28 -  
29 - private final IotDeviceCommonApi deviceApi;  
30 -  
31 - public IotCoapRegisterHandler() {  
32 - this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);  
33 - }  
34 -  
35 - /**  
36 - * 处理设备动态注册请求  
37 - *  
38 - * @param exchange CoAP 交换对象  
39 - */  
40 - @SuppressWarnings("unchecked")  
41 - public void handle(CoapExchange exchange) {  
42 - try {  
43 - // 1.1 解析请求体  
44 - byte[] payload = exchange.getRequestPayload();  
45 - if (payload == null || payload.length == 0) {  
46 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "请求体不能为空");  
47 - return;  
48 - }  
49 - Map<String, Object> body;  
50 - try {  
51 - body = JsonUtils.parseObject(new String(payload), Map.class);  
52 - } catch (Exception e) {  
53 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "请求体 JSON 格式错误");  
54 - return;  
55 - }  
56 -  
57 - // 1.2 解析参数  
58 - String productKey = MapUtil.getStr(body, "productKey");  
59 - if (StrUtil.isEmpty(productKey)) {  
60 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "productKey 不能为空");  
61 - return;  
62 - }  
63 - String deviceName = MapUtil.getStr(body, "deviceName");  
64 - if (StrUtil.isEmpty(deviceName)) {  
65 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "deviceName 不能为空");  
66 - return;  
67 - }  
68 - String productSecret = MapUtil.getStr(body, "productSecret");  
69 - if (StrUtil.isEmpty(productSecret)) {  
70 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "productSecret 不能为空");  
71 - return;  
72 - }  
73 -  
74 - // 2. 调用动态注册  
75 - IotDeviceRegisterReqDTO reqDTO = new IotDeviceRegisterReqDTO()  
76 - .setProductKey(productKey)  
77 - .setDeviceName(deviceName)  
78 - .setProductSecret(productSecret);  
79 - CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(reqDTO);  
80 - if (result.isError()) {  
81 - log.warn("[handle][设备动态注册失败,productKey: {}, deviceName: {}, 错误: {}]",  
82 - productKey, deviceName, result.getMsg());  
83 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST,  
84 - "设备动态注册失败:" + result.getMsg());  
85 - return;  
86 - }  
87 -  
88 - // 3. 返回成功响应  
89 - log.info("[handle][设备动态注册成功,productKey: {}, deviceName: {}]", productKey, deviceName);  
90 - IotCoapUtils.respondSuccess(exchange, result.getData());  
91 - } catch (Exception e) {  
92 - log.error("[handle][设备动态注册处理异常]", e);  
93 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.INTERNAL_SERVER_ERROR, "服务器内部错误");  
94 - }  
95 - }  
96 -  
97 -}  
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/router/IotCoapUpstreamHandler.java deleted
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.coap.router;  
2 -  
3 -import cn.hutool.core.collection.CollUtil;  
4 -import cn.hutool.core.map.MapUtil;  
5 -import cn.hutool.core.text.StrPool;  
6 -import cn.hutool.core.util.ArrayUtil;  
7 -import cn.hutool.core.util.ObjUtil;  
8 -import cn.hutool.core.util.StrUtil;  
9 -import cn.hutool.extra.spring.SpringUtil;  
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.protocol.coap.IotCoapUpstreamProtocol;  
13 -import cn.iocoder.yudao.module.iot.gateway.protocol.coap.util.IotCoapUtils;  
14 -import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;  
15 -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;  
16 -import lombok.extern.slf4j.Slf4j;  
17 -import org.eclipse.californium.core.coap.CoAP;  
18 -import org.eclipse.californium.core.server.resources.CoapExchange;  
19 -  
20 -import java.util.List;  
21 -  
22 -/**  
23 - * IoT 网关 CoAP 协议的【上行】处理器  
24 - *  
25 - * 处理设备通过 CoAP 协议发送的上行消息,包括:  
26 - * 1. 属性上报:POST /topic/sys/{productKey}/{deviceName}/thing/property/post  
27 - * 2. 事件上报:POST /topic/sys/{productKey}/{deviceName}/thing/event/post  
28 - *  
29 - * Token 通过自定义 CoAP Option 2088 携带  
30 - *  
31 - * @author 芋道源码  
32 - */  
33 -@Slf4j  
34 -public class IotCoapUpstreamHandler {  
35 -  
36 - private final IotDeviceTokenService deviceTokenService;  
37 - private final IotDeviceMessageService deviceMessageService;  
38 -  
39 - public IotCoapUpstreamHandler() {  
40 - this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);  
41 - this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);  
42 - }  
43 -  
44 - /**  
45 - * 处理 CoAP 请求  
46 - *  
47 - * @param exchange CoAP 交换对象  
48 - * @param protocol 协议对象  
49 - */  
50 - public void handle(CoapExchange exchange, IotCoapUpstreamProtocol protocol) {  
51 - try {  
52 - // 1. 解析通用参数  
53 - List<String> uriPath = exchange.getRequestOptions().getUriPath();  
54 - String productKey = CollUtil.get(uriPath, 2);  
55 - String deviceName = CollUtil.get(uriPath, 3);  
56 - byte[] payload = exchange.getRequestPayload();  
57 - if (StrUtil.isEmpty(productKey)) {  
58 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "productKey 不能为空");  
59 - return;  
60 - }  
61 - if (StrUtil.isEmpty(deviceName)) {  
62 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "deviceName 不能为空");  
63 - return;  
64 - }  
65 - if (ArrayUtil.isEmpty(payload)) {  
66 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "请求体不能为空");  
67 - return;  
68 - }  
69 -  
70 - // 2. 认证:从自定义 Option 获取 token  
71 - String token = IotCoapUtils.getTokenFromOption(exchange, IotCoapUtils.OPTION_TOKEN);  
72 - if (StrUtil.isEmpty(token)) {  
73 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.UNAUTHORIZED, "token 不能为空");  
74 - return;  
75 - }  
76 - // 验证 token  
77 - IotDeviceIdentity deviceInfo = deviceTokenService.verifyToken(token);  
78 - if (deviceInfo == null) {  
79 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.UNAUTHORIZED, "token 无效或已过期");  
80 - return;  
81 - }  
82 - // 验证设备信息匹配  
83 - if (ObjUtil.notEqual(productKey, deviceInfo.getProductKey())  
84 - || ObjUtil.notEqual(deviceName, deviceInfo.getDeviceName())) {  
85 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.FORBIDDEN, "设备信息与 token 不匹配");  
86 - return;  
87 - }  
88 -  
89 - // 2.1 解析 method:deviceName 后面的路径,用 . 拼接  
90 - // 路径格式:[topic, sys, productKey, deviceName, thing, property, post]  
91 - String method = String.join(StrPool.DOT, uriPath.subList(4, uriPath.size()));  
92 -  
93 - // 2.2 解码消息  
94 - IotDeviceMessage message = deviceMessageService.decodeDeviceMessage(payload, productKey, deviceName);  
95 - if (ObjUtil.notEqual(method, message.getMethod())) {  
96 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.BAD_REQUEST, "method 不匹配");  
97 - return;  
98 - }  
99 - // 2.3 发送消息到消息总线  
100 - deviceMessageService.sendDeviceMessage(message, productKey, deviceName, protocol.getServerId());  
101 -  
102 - // 3. 返回成功响应  
103 - IotCoapUtils.respondSuccess(exchange, MapUtil.of("messageId", message.getId()));  
104 - } catch (Exception e) {  
105 - log.error("[handle][CoAP 请求处理异常]", e);  
106 - IotCoapUtils.respondError(exchange, CoAP.ResponseCode.INTERNAL_SERVER_ERROR, "服务器内部错误");  
107 - }  
108 - }  
109 -  
110 -}  
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/coap/util/IotCoapUtils.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.coap.util; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.coap.util;
2 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.*; 3 +import cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstrem.IotCoapAbstractHandler;
12 4
13 /** 5 /**
14 * IoT CoAP 协议工具类 6 * IoT CoAP 协议工具类
@@ -22,63 +14,6 @@ public class IotCoapUtils { @@ -22,63 +14,6 @@ public class IotCoapUtils {
22 * <p> 14 * <p>
23 * CoAP Option 范围 2048-65535 属于实验/自定义范围 15 * CoAP Option 范围 2048-65535 属于实验/自定义范围
24 */ 16 */
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 - } 17 + public static final int OPTION_TOKEN = IotCoapAbstractHandler.OPTION_TOKEN;
83 18
84 } 19 }
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/router/IotHttpAbstractHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/upstream/IotHttpAbstractHandler.java
1 -package cn.iocoder.yudao.module.iot.gateway.protocol.http.router; 1 +package cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream;
2 2
3 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.util.ArrayUtil; 4 import cn.hutool.core.util.ArrayUtil;
@@ -10,9 +10,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -10,9 +10,6 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
10 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 10 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
11 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 11 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
12 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; 12 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
13 -import cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream.IotHttpAuthHandler;  
14 -import cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream.IotHttpRegisterHandler;  
15 -import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager;  
16 import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService; 13 import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
17 import io.vertx.core.Handler; 14 import io.vertx.core.Handler;
18 import io.vertx.core.http.HttpHeaders; 15 import io.vertx.core.http.HttpHeaders;
@@ -20,8 +17,7 @@ import io.vertx.ext.web.RoutingContext; @@ -20,8 +17,7 @@ import io.vertx.ext.web.RoutingContext;
20 import lombok.extern.slf4j.Slf4j; 17 import lombok.extern.slf4j.Slf4j;
21 import org.springframework.http.MediaType; 18 import org.springframework.http.MediaType;
22 19
23 -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.FORBIDDEN;  
24 -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR; 20 +import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
25 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 21 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException; 22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
27 23
@@ -35,8 +31,6 @@ public abstract class IotHttpAbstractHandler implements Handler&lt;RoutingContext&gt; @@ -35,8 +31,6 @@ public abstract class IotHttpAbstractHandler implements Handler&lt;RoutingContext&gt;
35 31
36 private final IotDeviceTokenService deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class); 32 private final IotDeviceTokenService deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
37 33
38 - private final IotMessageSerializerManager serializerManager = SpringUtil.getBean(IotMessageSerializerManager.class);  
39 -  
40 @Override 34 @Override
41 public final void handle(RoutingContext context) { 35 public final void handle(RoutingContext context) {
42 try { 36 try {
@@ -47,15 +41,31 @@ public abstract class IotHttpAbstractHandler implements Handler&lt;RoutingContext&gt; @@ -47,15 +41,31 @@ public abstract class IotHttpAbstractHandler implements Handler&lt;RoutingContext&gt;
47 CommonResult<Object> result = handle0(context); 41 CommonResult<Object> result = handle0(context);
48 writeResponse(context, result); 42 writeResponse(context, result);
49 } catch (ServiceException e) { 43 } catch (ServiceException e) {
  44 + // 已知异常,返回对应的错误码和错误信息
50 writeResponse(context, CommonResult.error(e.getCode(), e.getMessage())); 45 writeResponse(context, CommonResult.error(e.getCode(), e.getMessage()));
  46 + } catch (IllegalArgumentException e) {
  47 + // 参数校验异常,返回 400 错误
  48 + writeResponse(context, CommonResult.error(BAD_REQUEST.getCode(), e.getMessage()));
51 } catch (Exception e) { 49 } catch (Exception e) {
  50 + // 其他未知异常,返回 500 错误
52 log.error("[handle][path({}) 处理异常]", context.request().path(), e); 51 log.error("[handle][path({}) 处理异常]", context.request().path(), e);
53 writeResponse(context, CommonResult.error(INTERNAL_SERVER_ERROR)); 52 writeResponse(context, CommonResult.error(INTERNAL_SERVER_ERROR));
54 } 53 }
55 } 54 }
56 55
  56 + /**
  57 + * 处理 HTTP 请求(子类实现)
  58 + *
  59 + * @param context RoutingContext 对象
  60 + * @return 处理结果
  61 + */
57 protected abstract CommonResult<Object> handle0(RoutingContext context); 62 protected abstract CommonResult<Object> handle0(RoutingContext context);
58 63
  64 + /**
  65 + * 前置处理:认证等
  66 + *
  67 + * @param context RoutingContext 对象
  68 + */
59 private void beforeHandle(RoutingContext context) { 69 private void beforeHandle(RoutingContext context) {
60 // 如果不需要认证,则不走前置处理 70 // 如果不需要认证,则不走前置处理
61 String path = context.request().path(); 71 String path = context.request().path();
@@ -102,7 +112,7 @@ public abstract class IotHttpAbstractHandler implements Handler&lt;RoutingContext&gt; @@ -102,7 +112,7 @@ public abstract class IotHttpAbstractHandler implements Handler&lt;RoutingContext&gt;
102 } 112 }
103 113
104 @SuppressWarnings("deprecation") 114 @SuppressWarnings("deprecation")
105 - public static void writeResponse(RoutingContext context, Object data) { 115 + public static void writeResponse(RoutingContext context, CommonResult<?> data) {
106 context.response() 116 context.response()
107 .setStatusCode(200) 117 .setStatusCode(200)
108 .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE) 118 .putHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE)
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/upstream/IotHttpAuthHandler.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream; @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream;
3 import cn.hutool.core.lang.Assert; 3 import cn.hutool.core.lang.Assert;
4 import cn.hutool.core.map.MapUtil; 4 import cn.hutool.core.map.MapUtil;
5 import cn.hutool.core.util.BooleanUtil; 5 import cn.hutool.core.util.BooleanUtil;
6 -import cn.hutool.core.util.StrUtil;  
7 import cn.hutool.extra.spring.SpringUtil; 6 import cn.hutool.extra.spring.SpringUtil;
8 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 7 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
9 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; 8 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
@@ -11,13 +10,11 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO; @@ -11,13 +10,11 @@ import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
11 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 10 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
12 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity; 11 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
13 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol; 12 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol;
14 -import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAbstractHandler;  
15 import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService; 13 import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService;
16 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 14 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
17 import io.vertx.ext.web.RoutingContext; 15 import io.vertx.ext.web.RoutingContext;
18 16
19 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 17 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
20 -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;  
21 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 18 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
22 import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_AUTH_FAIL; 19 import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_AUTH_FAIL;
23 20
@@ -48,23 +45,19 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler { @@ -48,23 +45,19 @@ public class IotHttpAuthHandler extends IotHttpAbstractHandler {
48 } 45 }
49 46
50 @Override 47 @Override
  48 + @SuppressWarnings("DuplicatedCode")
51 public CommonResult<Object> handle0(RoutingContext context) { 49 public CommonResult<Object> handle0(RoutingContext context) {
52 // 1. 解析参数 50 // 1. 解析参数
53 IotDeviceAuthReqDTO request = deserializeRequest(context, IotDeviceAuthReqDTO.class); 51 IotDeviceAuthReqDTO request = deserializeRequest(context, IotDeviceAuthReqDTO.class);
54 - if (StrUtil.isEmpty(request.getClientId())) {  
55 - throw invalidParamException("clientId 不能为空");  
56 - }  
57 - if (StrUtil.isEmpty(request.getUsername())) {  
58 - throw invalidParamException("username 不能为空");  
59 - }  
60 - if (StrUtil.isEmpty(request.getPassword())) {  
61 - throw invalidParamException("password 不能为空");  
62 - } 52 + Assert.notNull(request, "请求参数不能为空");
  53 + Assert.notBlank(request.getClientId(), "clientId 不能为空");
  54 + Assert.notBlank(request.getUsername(), "username 不能为空");
  55 + Assert.notBlank(request.getPassword(), "password 不能为空");
63 56
64 // 2.1 执行认证 57 // 2.1 执行认证
65 CommonResult<Boolean> result = deviceApi.authDevice(request); 58 CommonResult<Boolean> result = deviceApi.authDevice(request);
66 result.checkError(); 59 result.checkError();
67 - if (!BooleanUtil.isTrue(result.getData())) { 60 + if (BooleanUtil.isFalse(result.getData())) {
68 throw exception(DEVICE_AUTH_FAIL); 61 throw exception(DEVICE_AUTH_FAIL);
69 } 62 }
70 // 2.2 生成 Token 63 // 2.2 生成 Token
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/upstream/IotHttpRegisterHandler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream;
2 2
3 -import cn.hutool.core.util.StrUtil; 3 +import cn.hutool.core.lang.Assert;
4 import cn.hutool.extra.spring.SpringUtil; 4 import cn.hutool.extra.spring.SpringUtil;
5 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; 6 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
7 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO; 7 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
8 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO; 8 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
9 -import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAbstractHandler;  
10 import io.vertx.ext.web.RoutingContext; 9 import io.vertx.ext.web.RoutingContext;
11 10
12 -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;  
13 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 11 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
14 12
15 /** 13 /**
@@ -34,15 +32,10 @@ public class IotHttpRegisterHandler extends IotHttpAbstractHandler { @@ -34,15 +32,10 @@ public class IotHttpRegisterHandler extends IotHttpAbstractHandler {
34 public CommonResult<Object> handle0(RoutingContext context) { 32 public CommonResult<Object> handle0(RoutingContext context) {
35 // 1. 解析参数 33 // 1. 解析参数
36 IotDeviceRegisterReqDTO request = deserializeRequest(context, IotDeviceRegisterReqDTO.class); 34 IotDeviceRegisterReqDTO request = deserializeRequest(context, IotDeviceRegisterReqDTO.class);
37 - if (StrUtil.isEmpty(request.getProductKey())) {  
38 - throw invalidParamException("productKey 不能为空");  
39 - }  
40 - if (StrUtil.isEmpty(request.getDeviceName())) {  
41 - throw invalidParamException("deviceName 不能为空");  
42 - }  
43 - if (StrUtil.isEmpty(request.getProductSecret())) {  
44 - throw invalidParamException("productSecret 不能为空");  
45 - } 35 + Assert.notNull(request, "请求参数不能为空");
  36 + Assert.notBlank(request.getProductKey(), "productKey 不能为空");
  37 + Assert.notBlank(request.getDeviceName(), "deviceName 不能为空");
  38 + Assert.notBlank(request.getProductSecret(), "productSecret 不能为空");
46 39
47 // 2. 调用动态注册 40 // 2. 调用动态注册
48 CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(request); 41 CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(request);
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/upstream/IotHttpRegisterSubHandler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.http.handler.upstream;
2 2
3 -import cn.hutool.core.collection.CollUtil; 3 +import cn.hutool.core.lang.Assert;
4 import cn.hutool.extra.spring.SpringUtil; 4 import cn.hutool.extra.spring.SpringUtil;
5 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; 6 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
7 import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO; 7 import cn.iocoder.yudao.module.iot.core.biz.dto.IotSubDeviceRegisterFullReqDTO;
8 import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO; 8 import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterReqDTO;
9 import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO; 9 import cn.iocoder.yudao.module.iot.core.topic.auth.IotSubDeviceRegisterRespDTO;
10 -import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAbstractHandler;  
11 import io.vertx.ext.web.RoutingContext; 10 import io.vertx.ext.web.RoutingContext;
12 import lombok.Data; 11 import lombok.Data;
13 12
14 import java.util.List; 13 import java.util.List;
15 14
16 -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;  
17 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 15 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
18 16
19 /** 17 /**
@@ -46,9 +44,8 @@ public class IotHttpRegisterSubHandler extends IotHttpAbstractHandler { @@ -46,9 +44,8 @@ public class IotHttpRegisterSubHandler extends IotHttpAbstractHandler {
46 String deviceName = context.pathParam("deviceName"); 44 String deviceName = context.pathParam("deviceName");
47 // 1.2 解析子设备列表 45 // 1.2 解析子设备列表
48 SubDeviceRegisterRequest request = deserializeRequest(context, SubDeviceRegisterRequest.class); 46 SubDeviceRegisterRequest request = deserializeRequest(context, SubDeviceRegisterRequest.class);
49 - if (CollUtil.isEmpty(request.getParams())) {  
50 - throw invalidParamException("params 不能为空");  
51 - } 47 + Assert.notNull(request, "请求参数不能为空");
  48 + Assert.notEmpty(request.getParams(), "params 不能为空");
52 49
53 // 2. 调用子设备动态注册 50 // 2. 调用子设备动态注册
54 IotSubDeviceRegisterFullReqDTO reqDTO = new IotSubDeviceRegisterFullReqDTO() 51 IotSubDeviceRegisterFullReqDTO reqDTO = new IotSubDeviceRegisterFullReqDTO()
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/http/handler/upstream/IotHttpUpstreamHandler.java
@@ -7,7 +7,6 @@ import cn.hutool.extra.spring.SpringUtil; @@ -7,7 +7,6 @@ import cn.hutool.extra.spring.SpringUtil;
7 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 7 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage; 8 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
9 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol; 9 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol;
10 -import cn.iocoder.yudao.module.iot.gateway.protocol.http.router.IotHttpAbstractHandler;  
11 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService; 10 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
12 import io.vertx.ext.web.RoutingContext; 11 import io.vertx.ext.web.RoutingContext;
13 import lombok.extern.slf4j.Slf4j; 12 import lombok.extern.slf4j.Slf4j;
@@ -33,15 +32,16 @@ public class IotHttpUpstreamHandler extends IotHttpAbstractHandler { @@ -33,15 +32,16 @@ public class IotHttpUpstreamHandler extends IotHttpAbstractHandler {
33 32
34 @Override 33 @Override
35 protected CommonResult<Object> handle0(RoutingContext context) { 34 protected CommonResult<Object> handle0(RoutingContext context) {
36 - // 1. 解析通用参数 35 + // 1.1 解析通用参数
37 String productKey = context.pathParam("productKey"); 36 String productKey = context.pathParam("productKey");
38 String deviceName = context.pathParam("deviceName"); 37 String deviceName = context.pathParam("deviceName");
39 String method = context.pathParam("*").replaceAll(StrPool.SLASH, StrPool.DOT); 38 String method = context.pathParam("*").replaceAll(StrPool.SLASH, StrPool.DOT);
40 -  
41 - // 2.1 根据 Content-Type 反序列化消息 39 + // 1.2 根据 Content-Type 反序列化消息
42 IotDeviceMessage message = deserializeRequest(context, IotDeviceMessage.class); 40 IotDeviceMessage message = deserializeRequest(context, IotDeviceMessage.class);
  41 + Assert.notNull(message, "请求参数不能为空");
43 Assert.equals(method, message.getMethod(), "method 不匹配"); 42 Assert.equals(method, message.getMethod(), "method 不匹配");
44 - // 2.2 发送消息 43 +
  44 + // 2. 发送消息
45 deviceMessageService.sendDeviceMessage(message, 45 deviceMessageService.sendDeviceMessage(message,
46 productKey, deviceName, serverId); 46 productKey, deviceName, serverId);
47 47
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/handler/upstream/IotTcpUpstreamHandler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.handler.upstream; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.handler.upstream;
2 2
  3 +import cn.hutool.core.util.BooleanUtil;
3 import cn.hutool.core.util.IdUtil; 4 import cn.hutool.core.util.IdUtil;
4 import cn.hutool.extra.spring.SpringUtil; 5 import cn.hutool.extra.spring.SpringUtil;
  6 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
5 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 7 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 8 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; 9 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
@@ -26,7 +28,8 @@ import lombok.extern.slf4j.Slf4j; @@ -26,7 +28,8 @@ import lombok.extern.slf4j.Slf4j;
26 import org.springframework.util.Assert; 28 import org.springframework.util.Assert;
27 29
28 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*; 30 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
29 -import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED; 31 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  32 +import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_AUTH_FAIL;
30 33
31 /** 34 /**
32 * TCP 上行消息处理器 35 * TCP 上行消息处理器
@@ -132,6 +135,12 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -132,6 +135,12 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
132 // 业务消息 135 // 业务消息
133 handleBusinessRequest(clientId, message, socket); 136 handleBusinessRequest(clientId, message, socket);
134 } 137 }
  138 + } catch (ServiceException e) {
  139 + // 业务异常,返回对应的错误码和错误信息
  140 + log.warn("[processMessage][业务异常,客户端 ID: {},错误: {}]", clientId, e.getMessage());
  141 + String requestId = message != null ? message.getRequestId() : null;
  142 + String method = message != null ? message.getMethod() : null;
  143 + sendErrorResponse(socket, requestId, method, e.getCode(), e.getMessage());
135 } catch (IllegalArgumentException e) { 144 } catch (IllegalArgumentException e) {
136 // 参数校验失败,返回 400 145 // 参数校验失败,返回 400
137 log.warn("[processMessage][参数校验失败,客户端 ID: {},错误: {}]", clientId, e.getMessage()); 146 log.warn("[processMessage][参数校验失败,客户端 ID: {},错误: {}]", clientId, e.getMessage());
@@ -166,10 +175,9 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -166,10 +175,9 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
166 175
167 // 2.1 执行认证 176 // 2.1 执行认证
168 CommonResult<Boolean> authResult = deviceApi.authDevice(authParams); 177 CommonResult<Boolean> authResult = deviceApi.authDevice(authParams);
169 - if (authResult.isError()) {  
170 - log.warn("[handleAuthenticationRequest][认证失败,客户端 ID: {},username: {}]", clientId, authParams.getUsername());  
171 - sendErrorResponse(socket, message.getRequestId(), AUTH_METHOD, authResult.getCode(), authResult.getMsg());  
172 - return; 178 + authResult.checkError();
  179 + if (BooleanUtil.isFalse(authResult.getData())) {
  180 + throw exception(DEVICE_AUTH_FAIL);
173 } 181 }
174 // 2.2 解析设备信息 182 // 2.2 解析设备信息
175 IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(authParams.getUsername()); 183 IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(authParams.getUsername());
@@ -205,12 +213,7 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; { @@ -205,12 +213,7 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
205 213
206 // 2. 调用动态注册 214 // 2. 调用动态注册
207 CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params); 215 CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params);
208 - if (result.isError()) {  
209 - log.warn("[handleRegisterRequest][注册失败,客户端 ID: {},错误: {}]", clientId, result.getMsg());  
210 - sendErrorResponse(socket, message.getRequestId(), IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(),  
211 - result.getCode(), result.getMsg());  
212 - return;  
213 - } 216 + result.checkError();
214 217
215 // 3. 发送成功响应 218 // 3. 发送成功响应
216 sendSuccessResponse(socket, message.getRequestId(), 219 sendSuccessResponse(socket, message.getRequestId(),
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/handler/upstream/IotUdpUpstreamHandler.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp.handler.upstream; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp.handler.upstream;
2 2
  3 +import cn.hutool.core.map.MapUtil;
3 import cn.hutool.core.util.ArrayUtil; 4 import cn.hutool.core.util.ArrayUtil;
  5 +import cn.hutool.core.util.BooleanUtil;
4 import cn.hutool.core.util.IdUtil; 6 import cn.hutool.core.util.IdUtil;
5 import cn.hutool.core.util.StrUtil; 7 import cn.hutool.core.util.StrUtil;
6 import cn.hutool.extra.spring.SpringUtil; 8 import cn.hutool.extra.spring.SpringUtil;
  9 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
7 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 10 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
8 import cn.iocoder.yudao.framework.common.util.json.JsonUtils; 11 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
9 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi; 12 import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
@@ -27,10 +30,11 @@ import lombok.extern.slf4j.Slf4j; @@ -27,10 +30,11 @@ import lombok.extern.slf4j.Slf4j;
27 import org.springframework.util.Assert; 30 import org.springframework.util.Assert;
28 31
29 import java.net.InetSocketAddress; 32 import java.net.InetSocketAddress;
30 -import java.time.LocalDateTime;  
31 import java.util.Map; 33 import java.util.Map;
32 34
33 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*; 35 import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
  36 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  37 +import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_AUTH_FAIL;
34 38
35 /** 39 /**
36 * UDP 上行消息处理器 40 * UDP 上行消息处理器
@@ -87,7 +91,7 @@ public class IotUdpUpstreamHandler { @@ -87,7 +91,7 @@ public class IotUdpUpstreamHandler {
87 this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class); 91 this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
88 } 92 }
89 93
90 - // TODO @AI:vertx 有 udp 的实现么? 94 + // TODO done @AI:vertx 有 udp 的实现么?当前已使用 Vert.x DatagramSocket 实现
91 /** 95 /**
92 * 处理 UDP 数据包 96 * 处理 UDP 数据包
93 * 97 *
@@ -99,18 +103,7 @@ public class IotUdpUpstreamHandler { @@ -99,18 +103,7 @@ public class IotUdpUpstreamHandler {
99 Buffer data = packet.data(); 103 Buffer data = packet.data();
100 String addressKey = sessionManager.buildAddressKey(senderAddress); 104 String addressKey = sessionManager.buildAddressKey(senderAddress);
101 log.debug("[handle][收到 UDP 数据包,来源: {},数据长度: {} 字节]", addressKey, data.length()); 105 log.debug("[handle][收到 UDP 数据包,来源: {},数据长度: {} 字节]", addressKey, data.length());
102 - try {  
103 - processMessage(data, senderAddress, socket);  
104 - } catch (IllegalArgumentException e) {  
105 - // 参数校验失败,返回 400  
106 - log.warn("[handle][参数校验失败,来源: {},错误: {}]", addressKey, e.getMessage());  
107 - sendErrorResponse(socket, senderAddress, null, null, BAD_REQUEST.getCode(), e.getMessage());  
108 - } catch (Exception e) {  
109 - // 其他异常,返回 500  
110 - log.error("[handle][处理消息失败,来源: {}]", addressKey, e);  
111 - sendErrorResponse(socket, senderAddress, null, null,  
112 - INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());  
113 - } 106 + processMessage(data, senderAddress, socket);
114 } 107 }
115 108
116 /** 109 /**
@@ -121,6 +114,7 @@ public class IotUdpUpstreamHandler { @@ -121,6 +114,7 @@ public class IotUdpUpstreamHandler {
121 * @param socket UDP Socket 114 * @param socket UDP Socket
122 */ 115 */
123 private void processMessage(Buffer buffer, InetSocketAddress senderAddress, DatagramSocket socket) { 116 private void processMessage(Buffer buffer, InetSocketAddress senderAddress, DatagramSocket socket) {
  117 + String addressKey = sessionManager.buildAddressKey(senderAddress);
124 // 1.1 基础检查 118 // 1.1 基础检查
125 if (ArrayUtil.isEmpty(buffer)) { 119 if (ArrayUtil.isEmpty(buffer)) {
126 return; 120 return;
@@ -133,15 +127,35 @@ public class IotUdpUpstreamHandler { @@ -133,15 +127,35 @@ public class IotUdpUpstreamHandler {
133 } 127 }
134 128
135 // 2. 根据消息类型路由处理 129 // 2. 根据消息类型路由处理
136 - if (AUTH_METHOD.equals(message.getMethod())) {  
137 - // 认证请求  
138 - handleAuthenticationRequest(message, senderAddress, socket);  
139 - } else if (IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod().equals(message.getMethod())) {  
140 - // 设备动态注册请求  
141 - handleRegisterRequest(message, senderAddress, socket);  
142 - } else {  
143 - // 业务消息  
144 - handleBusinessRequest(message, senderAddress, socket); 130 + try {
  131 + if (AUTH_METHOD.equals(message.getMethod())) {
  132 + // 认证请求
  133 + handleAuthenticationRequest(message, senderAddress, socket);
  134 + } else if (IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod().equals(message.getMethod())) {
  135 + // 设备动态注册请求
  136 + handleRegisterRequest(message, senderAddress, socket);
  137 + } else {
  138 + // 业务消息
  139 + handleBusinessRequest(message, senderAddress, socket);
  140 + }
  141 + } catch (ServiceException e) {
  142 + // 业务异常,返回对应的错误码和错误信息
  143 + log.warn("[processMessage][业务异常,来源: {},requestId: {},method: {},错误: {}]",
  144 + addressKey, message.getRequestId(), message.getMethod(), e.getMessage());
  145 + sendErrorResponse(socket, senderAddress, message.getRequestId(), message.getMethod(),
  146 + e.getCode(), e.getMessage());
  147 + } catch (IllegalArgumentException e) {
  148 + // 参数校验失败,返回 400
  149 + log.warn("[processMessage][参数校验失败,来源: {},requestId: {},method: {},错误: {}]",
  150 + addressKey, message.getRequestId(), message.getMethod(), e.getMessage());
  151 + sendErrorResponse(socket, senderAddress, message.getRequestId(), message.getMethod(),
  152 + BAD_REQUEST.getCode(), e.getMessage());
  153 + } catch (Exception e) {
  154 + // 其他异常,返回 500
  155 + log.error("[processMessage][处理消息失败,来源: {},requestId: {},method: {}]",
  156 + addressKey, message.getRequestId(), message.getMethod(), e);
  157 + sendErrorResponse(socket, senderAddress, message.getRequestId(), message.getMethod(),
  158 + INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
145 } 159 }
146 } 160 }
147 161
@@ -164,12 +178,9 @@ public class IotUdpUpstreamHandler { @@ -164,12 +178,9 @@ public class IotUdpUpstreamHandler {
164 178
165 // 2.1 执行认证 179 // 2.1 执行认证
166 CommonResult<Boolean> authResult = deviceApi.authDevice(authParams); 180 CommonResult<Boolean> authResult = deviceApi.authDevice(authParams);
167 - if (authResult.isError()) {  
168 - log.warn("[handleAuthenticationRequest][认证失败,客户端 ID: {},username: {}]",  
169 - clientId, authParams.getUsername());  
170 - sendErrorResponse(socket, senderAddress, message.getRequestId(), AUTH_METHOD,  
171 - authResult.getCode(), authResult.getMsg());  
172 - return; 181 + authResult.checkError();
  182 + if (!BooleanUtil.isTrue(authResult.getData())) {
  183 + throw exception(DEVICE_AUTH_FAIL);
173 } 184 }
174 // 2.2 解析设备信息 185 // 2.2 解析设备信息
175 IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(authParams.getUsername()); 186 IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(authParams.getUsername());
@@ -187,7 +198,8 @@ public class IotUdpUpstreamHandler { @@ -187,7 +198,8 @@ public class IotUdpUpstreamHandler {
187 // 4.2 发送上线消息 198 // 4.2 发送上线消息
188 sendOnlineMessage(device); 199 sendOnlineMessage(device);
189 // 4.3 发送成功响应(包含 token) 200 // 4.3 发送成功响应(包含 token)
190 - sendAuthSuccessResponse(socket, senderAddress, message.getRequestId(), token); 201 + sendSuccessResponse(socket, senderAddress, message.getRequestId(), AUTH_METHOD,
  202 + MapUtil.of("token", token));
191 log.info("[handleAuthenticationRequest][认证成功,设备 ID: {},设备名: {},来源: {}]", 203 log.info("[handleAuthenticationRequest][认证成功,设备 ID: {},设备名: {},来源: {}]",
192 device.getId(), device.getDeviceName(), sessionManager.buildAddressKey(senderAddress)); 204 device.getId(), device.getDeviceName(), sessionManager.buildAddressKey(senderAddress));
193 } 205 }
@@ -211,13 +223,7 @@ public class IotUdpUpstreamHandler { @@ -211,13 +223,7 @@ public class IotUdpUpstreamHandler {
211 223
212 // 2. 调用动态注册 224 // 2. 调用动态注册
213 CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params); 225 CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params);
214 - if (result.isError()) {  
215 - log.warn("[handleRegisterRequest][注册失败,来源: {},错误: {}]",  
216 - sessionManager.buildAddressKey(senderAddress), result.getMsg());  
217 - sendErrorResponse(socket, senderAddress, message.getRequestId(),  
218 - IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), result.getCode(), result.getMsg());  
219 - return;  
220 - } 226 + result.checkError();
221 227
222 // 3. 发送成功响应 228 // 3. 发送成功响应
223 sendSuccessResponse(socket, senderAddress, message.getRequestId(), 229 sendSuccessResponse(socket, senderAddress, message.getRequestId(),
@@ -274,17 +280,8 @@ public class IotUdpUpstreamHandler { @@ -274,17 +280,8 @@ public class IotUdpUpstreamHandler {
274 return; 280 return;
275 } 281 }
276 282
277 - // 2. 更新会话活跃时间和地址  
278 - // TODO @AI:是不是合并到 sessionManager 里面更好?  
279 - IotUdpSessionManager.SessionInfo sessionInfo = sessionManager.getSessionInfo(device.getId());  
280 - if (sessionInfo != null) {  
281 - // 检查地址是否变化,变化则更新  
282 - if (!senderAddress.equals(sessionInfo.getAddress())) {  
283 - sessionManager.updateSessionAddress(device.getId(), senderAddress);  
284 - } else {  
285 - sessionManager.updateSessionActivity(device.getId());  
286 - }  
287 - } 283 + // 2. 更新会话地址(如有变化)
  284 + sessionManager.updateSessionAddress(device.getId(), senderAddress);
288 285
289 // 3. 将 body 设置为实际的 params,发送消息到消息总线 286 // 3. 将 body 设置为实际的 params,发送消息到消息总线
290 message.setParams(body); 287 message.setParams(body);
@@ -306,8 +303,7 @@ public class IotUdpUpstreamHandler { @@ -306,8 +303,7 @@ public class IotUdpUpstreamHandler {
306 .setDeviceId(device.getId()) 303 .setDeviceId(device.getId())
307 .setProductKey(device.getProductKey()) 304 .setProductKey(device.getProductKey())
308 .setDeviceName(device.getDeviceName()) 305 .setDeviceName(device.getDeviceName())
309 - .setAddress(address)  
310 - .setLastActiveTime(LocalDateTime.now()); 306 + .setAddress(address);
311 sessionManager.registerSession(device.getId(), sessionInfo); 307 sessionManager.registerSession(device.getId(), sessionInfo);
312 } 308 }
313 309
@@ -325,21 +321,6 @@ public class IotUdpUpstreamHandler { @@ -325,21 +321,6 @@ public class IotUdpUpstreamHandler {
325 // ===================== 发送响应消息 ===================== 321 // ===================== 发送响应消息 =====================
326 322
327 /** 323 /**
328 - * 发送认证成功响应(包含 token)  
329 - *  
330 - * @param socket UDP Socket  
331 - * @param address 目标地址  
332 - * @param requestId 请求 ID  
333 - * @param token JWT Token  
334 - */  
335 - private void sendAuthSuccessResponse(DatagramSocket socket, InetSocketAddress address,  
336 - String requestId, String token) {  
337 - IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, AUTH_METHOD, token,  
338 - SUCCESS.getCode(), null);  
339 - writeResponse(socket, address, responseMessage);  
340 - }  
341 -  
342 - /**  
343 * 发送成功响应 324 * 发送成功响应
344 * 325 *
345 * @param socket UDP Socket 326 * @param socket UDP Socket
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/manager/IotUdpSessionManager.java
1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager; 1 package cn.iocoder.yudao.module.iot.gateway.protocol.udp.manager;
2 2
  3 +import cn.hutool.core.util.ObjUtil;
  4 +import com.google.common.cache.Cache;
  5 +import com.google.common.cache.CacheBuilder;
3 import io.vertx.core.buffer.Buffer; 6 import io.vertx.core.buffer.Buffer;
4 import io.vertx.core.datagram.DatagramSocket; 7 import io.vertx.core.datagram.DatagramSocket;
5 import lombok.Data; 8 import lombok.Data;
6 import lombok.extern.slf4j.Slf4j; 9 import lombok.extern.slf4j.Slf4j;
7 10
8 import java.net.InetSocketAddress; 11 import java.net.InetSocketAddress;
9 -import java.time.LocalDateTime;  
10 -import java.util.ArrayList;  
11 -import java.util.Iterator;  
12 -import java.util.List;  
13 -import java.util.Map;  
14 -import java.util.concurrent.ConcurrentHashMap; 12 +import java.util.concurrent.TimeUnit;
15 13
16 /** 14 /**
17 * IoT 网关 UDP 会话管理器 15 * IoT 网关 UDP 会话管理器
18 * <p> 16 * <p>
19 - * 统一管理 UDP 会话的认证状态、设备会话和消息发送功能:  
20 - * 1. 管理 UDP 会话的认证状态  
21 - * 2. 管理设备会话和在线状态  
22 - * 3. 管理消息发送到设备 17 + * 基于 Guava Cache 实现会话的自动过期清理:
  18 + * 1. 管理设备会话信息(设备 ID -> 地址映射)
  19 + * 2. 自动清理超时会话(expireAfterAccess)
  20 + * 3. 限制最大会话数(maximumSize)
23 * 21 *
24 * @author 芋道源码 22 * @author 芋道源码
25 */ 23 */
@@ -27,109 +25,76 @@ import java.util.concurrent.ConcurrentHashMap; @@ -27,109 +25,76 @@ import java.util.concurrent.ConcurrentHashMap;
27 public class IotUdpSessionManager { 25 public class IotUdpSessionManager {
28 26
29 /** 27 /**
30 - * 最大会话数 28 + * 设备会话缓存:设备 ID -> 会话信息
  29 + * <p>
  30 + * 使用 Guava Cache 自动管理过期:expireAfterAccess:每次访问(get/put)自动刷新过期时间
31 */ 31 */
32 - private final int maxSessions; 32 + private final Cache<Long, SessionInfo> deviceSessionCache;
33 33
34 - /**  
35 - * 设备 ID -> 会话信息  
36 - */  
37 - private final Map<Long, SessionInfo> deviceSessionMap = new ConcurrentHashMap<>();  
38 -  
39 - /**  
40 - * 设备地址 Key -> 设备 ID(反向映射,用于清理时同步)  
41 - */  
42 - // TODO @AI:1)这个变量是否必须?2)unregisterSession 这个方法是否必须?  
43 - private final Map<String, Long> addressDeviceMap = new ConcurrentHashMap<>(); 34 + private final int maxSessions;
44 35
45 - public IotUdpSessionManager(int maxSessions) { 36 + public IotUdpSessionManager(int maxSessions, long sessionTimeoutMs) {
46 this.maxSessions = maxSessions; 37 this.maxSessions = maxSessions;
  38 + this.deviceSessionCache = CacheBuilder.newBuilder()
  39 + .maximumSize(maxSessions)
  40 + .expireAfterAccess(sessionTimeoutMs, TimeUnit.MILLISECONDS)
  41 + .build();
47 } 42 }
48 43
49 /** 44 /**
50 - * 注册设备会话(包含认证信息) 45 + * 注册设备会话
51 * 46 *
52 * @param deviceId 设备 ID 47 * @param deviceId 设备 ID
53 * @param sessionInfo 会话信息 48 * @param sessionInfo 会话信息
54 */ 49 */
55 public void registerSession(Long deviceId, SessionInfo sessionInfo) { 50 public void registerSession(Long deviceId, SessionInfo sessionInfo) {
56 - // 检查会话数是否已达上限  
57 - if (deviceSessionMap.size() >= maxSessions) { 51 + // 检查是否为新设备,且会话数已达上限
  52 + if (deviceSessionCache.getIfPresent(deviceId) == null
  53 + && deviceSessionCache.size() >= maxSessions) {
58 throw new IllegalStateException("会话数已达上限: " + maxSessions); 54 throw new IllegalStateException("会话数已达上限: " + maxSessions);
59 } 55 }
60 - // 如果设备已有其他会话,先清理旧会话  
61 - SessionInfo oldSessionInfo = deviceSessionMap.get(deviceId);  
62 - if (oldSessionInfo != null) {  
63 - String oldAddressKey = buildAddressKey(oldSessionInfo.getAddress());  
64 - addressDeviceMap.remove(oldAddressKey, deviceId);  
65 - log.info("[registerSession][设备已有其他会话,清理旧会话,设备 ID: {},旧地址: {}]",  
66 - deviceId, oldAddressKey);  
67 - }  
68 -  
69 - // 注册新会话  
70 - String addressKey = buildAddressKey(sessionInfo.getAddress());  
71 - deviceSessionMap.put(deviceId, sessionInfo);  
72 - addressDeviceMap.put(addressKey, deviceId);  
73 - log.info("[registerSession][注册设备会话,设备 ID: {},地址: {},product key: {},device name: {}]",  
74 - deviceId, addressKey, sessionInfo.getProductKey(), sessionInfo.getDeviceName()); 56 + // 注册会话
  57 + deviceSessionCache.put(deviceId, sessionInfo);
  58 + log.info("[registerSession][注册设备会话,设备 ID: {},地址: {},productKey: {},deviceName: {}]",
  59 + deviceId, buildAddressKey(sessionInfo.getAddress()),
  60 + sessionInfo.getProductKey(), sessionInfo.getDeviceName());
75 } 61 }
76 62
77 /** 63 /**
78 - * 注销设备会话 64 + * 获取会话信息
  65 + * <p>
  66 + * 注意:调用此方法会自动刷新会话的过期时间
79 * 67 *
80 * @param deviceId 设备 ID 68 * @param deviceId 设备 ID
  69 + * @return 会话信息,不存在则返回 null
81 */ 70 */
82 - public void unregisterSession(Long deviceId) {  
83 - SessionInfo sessionInfo = deviceSessionMap.remove(deviceId);  
84 - if (sessionInfo == null) {  
85 - return;  
86 - }  
87 - String addressKey = buildAddressKey(sessionInfo.getAddress());  
88 - // 仅当 addressDeviceMap 中的 deviceId 是当前 deviceId 时才移除,避免误删新会话  
89 - addressDeviceMap.remove(addressKey, deviceId);  
90 - log.info("[unregisterSession][注销设备会话,设备 ID: {},地址: {}]", deviceId, addressKey);  
91 - }  
92 -  
93 - /**  
94 - * 更新会话活跃时间(每次收到上行消息时调用)  
95 - *  
96 - * @param deviceId 设备 ID  
97 - */  
98 - public void updateSessionActivity(Long deviceId) {  
99 - SessionInfo sessionInfo = deviceSessionMap.get(deviceId);  
100 - if (sessionInfo != null) {  
101 - sessionInfo.setLastActiveTime(LocalDateTime.now());  
102 - } 71 + public SessionInfo getSession(Long deviceId) {
  72 + return deviceSessionCache.getIfPresent(deviceId);
103 } 73 }
104 74
105 /** 75 /**
106 * 更新设备会话地址(设备地址变更时调用) 76 * 更新设备会话地址(设备地址变更时调用)
  77 + * <p>
  78 + * 注意:getIfPresent 已自动刷新过期时间,无需重新 put
107 * 79 *
108 * @param deviceId 设备 ID 80 * @param deviceId 设备 ID
109 * @param newAddress 新地址 81 * @param newAddress 新地址
110 */ 82 */
111 public void updateSessionAddress(Long deviceId, InetSocketAddress newAddress) { 83 public void updateSessionAddress(Long deviceId, InetSocketAddress newAddress) {
112 - SessionInfo sessionInfo = deviceSessionMap.get(deviceId); 84 + // 地址未变化,无需更新
  85 + SessionInfo sessionInfo = deviceSessionCache.getIfPresent(deviceId);
113 if (sessionInfo == null) { 86 if (sessionInfo == null) {
114 return; 87 return;
115 } 88 }
116 - // 清理旧地址映射  
117 - String oldAddressKey = buildAddressKey(sessionInfo.getAddress());  
118 - addressDeviceMap.remove(oldAddressKey, deviceId); 89 + if (ObjUtil.equals(newAddress, sessionInfo.getAddress())) {
  90 + return;
  91 + }
119 92
120 - // 更新新地址  
121 - String newAddressKey = buildAddressKey(newAddress); 93 + // 更新地址
  94 + String oldAddressKey = buildAddressKey(sessionInfo.getAddress());
122 sessionInfo.setAddress(newAddress); 95 sessionInfo.setAddress(newAddress);
123 - sessionInfo.setLastActiveTime(LocalDateTime.now());  
124 - addressDeviceMap.put(newAddressKey, deviceId);  
125 - log.debug("[updateSessionAddress][更新设备地址,设备 ID: {},新地址: {}]", deviceId, newAddressKey);  
126 - }  
127 -  
128 - /**  
129 - * 获取会话信息  
130 - */  
131 - public SessionInfo getSessionInfo(Long deviceId) {  
132 - return deviceSessionMap.get(deviceId); 96 + log.debug("[updateSessionAddress][更新设备地址,设备 ID: {},旧地址: {},新地址: {}]",
  97 + deviceId, oldAddressKey, buildAddressKey(newAddress));
133 } 98 }
134 99
135 /** 100 /**
@@ -141,7 +106,7 @@ public class IotUdpSessionManager { @@ -141,7 +106,7 @@ public class IotUdpSessionManager {
141 * @return 是否发送成功 106 * @return 是否发送成功
142 */ 107 */
143 public boolean sendToDevice(Long deviceId, byte[] data, DatagramSocket socket) { 108 public boolean sendToDevice(Long deviceId, byte[] data, DatagramSocket socket) {
144 - SessionInfo sessionInfo = deviceSessionMap.get(deviceId); 109 + SessionInfo sessionInfo = deviceSessionCache.getIfPresent(deviceId);
145 if (sessionInfo == null || sessionInfo.getAddress() == null) { 110 if (sessionInfo == null || sessionInfo.getAddress() == null) {
146 log.warn("[sendToDevice][设备会话不存在,设备 ID: {}]", deviceId); 111 log.warn("[sendToDevice][设备会话不存在,设备 ID: {}]", deviceId);
147 return false; 112 return false;
@@ -165,38 +130,7 @@ public class IotUdpSessionManager { @@ -165,38 +130,7 @@ public class IotUdpSessionManager {
165 } 130 }
166 131
167 /** 132 /**
168 - * 定期清理不活跃的设备会话  
169 - *  
170 - * @param timeoutMs 超时时间(毫秒)  
171 - * @return 清理的设备 ID 列表(用于发送离线消息)  
172 - */  
173 - public List<Long> cleanExpiredSessions(long timeoutMs) {  
174 - List<Long> offlineDeviceIds = new ArrayList<>();  
175 - LocalDateTime now = LocalDateTime.now();  
176 - LocalDateTime expireTime = now.minusNanos(timeoutMs * 1_000_000);  
177 - Iterator<Map.Entry<Long, SessionInfo>> iterator = deviceSessionMap.entrySet().iterator();  
178 - // TODO @AI:改成 for each 会不会更好?  
179 - while (iterator.hasNext()) {  
180 - Map.Entry<Long, SessionInfo> entry = iterator.next();  
181 - SessionInfo sessionInfo = entry.getValue();  
182 - // 未过期,跳过  
183 - if (sessionInfo.getLastActiveTime().isAfter(expireTime)) {  
184 - continue;  
185 - }  
186 - // 过期处理:记录离线设备 ID  
187 - Long deviceId = entry.getKey();  
188 - String addressKey = buildAddressKey(sessionInfo.getAddress());  
189 - addressDeviceMap.remove(addressKey, deviceId);  
190 - offlineDeviceIds.add(deviceId);  
191 - log.debug("[cleanExpiredSessions][清理超时设备,设备 ID: {},地址: {},最后活跃时间: {}]",  
192 - deviceId, addressKey, sessionInfo.getLastActiveTime());  
193 - iterator.remove();  
194 - }  
195 - return offlineDeviceIds;  
196 - }  
197 -  
198 - /**  
199 - * 构建地址 Key 133 + * 构建地址 Key(用于日志输出)
200 * 134 *
201 * @param address 地址 135 * @param address 地址
202 * @return 地址 Key 136 * @return 地址 Key
@@ -206,7 +140,7 @@ public class IotUdpSessionManager { @@ -206,7 +140,7 @@ public class IotUdpSessionManager {
206 } 140 }
207 141
208 /** 142 /**
209 - * 会话信息(包含认证信息) 143 + * 会话信息
210 */ 144 */
211 @Data 145 @Data
212 public static class SessionInfo { 146 public static class SessionInfo {
@@ -228,10 +162,6 @@ public class IotUdpSessionManager { @@ -228,10 +162,6 @@ public class IotUdpSessionManager {
228 * 设备地址 162 * 设备地址
229 */ 163 */
230 private InetSocketAddress address; 164 private InetSocketAddress address;
231 - /**  
232 - * 最后活跃时间  
233 - */  
234 - private LocalDateTime lastActiveTime;  
235 165
236 } 166 }
237 167
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
@@ -81,13 +81,38 @@ yudao: @@ -81,13 +81,38 @@ yudao:
81 - id: udp-json 81 - id: udp-json
82 type: udp 82 type: udp
83 port: 8093 83 port: 8093
84 - enabled: true 84 + enabled: false
85 serialize: json 85 serialize: json
86 udp: 86 udp:
87 max-sessions: 1000 # 最大会话数 87 max-sessions: 1000 # 最大会话数
88 session-timeout-ms: 60000 # 会话超时时间(毫秒),基于 Guava Cache 自动过期 88 session-timeout-ms: 60000 # 会话超时时间(毫秒),基于 Guava Cache 自动过期
89 receive-buffer-size: 65536 # 接收缓冲区大小(字节) 89 receive-buffer-size: 65536 # 接收缓冲区大小(字节)
90 send-buffer-size: 65536 # 发送缓冲区大小(字节) 90 send-buffer-size: 65536 # 发送缓冲区大小(字节)
  91 + # ====================================
  92 + # 针对引入的 WebSocket 组件的配置
  93 + # ====================================
  94 + - id: websocket-json
  95 + type: websocket
  96 + port: 8094
  97 + enabled: false
  98 + serialize: json
  99 + websocket:
  100 + path: /ws
  101 + max-message-size: 65536 # 最大消息大小(字节,默认 64KB)
  102 + max-frame-size: 65536 # 最大帧大小(字节,默认 64KB)
  103 + idle-timeout-seconds: 60 # 空闲超时时间(秒,默认 60)
  104 + ssl-enabled: false # 是否启用 SSL(wss://)
  105 + # ====================================
  106 + # 针对引入的 CoAP 组件的配置
  107 + # ====================================
  108 + - id: coap-json
  109 + type: coap
  110 + port: 5683
  111 + enabled: true
  112 + coap:
  113 + max-message-size: 1024 # 最大消息大小(字节)
  114 + ack-timeout-ms: 2000 # ACK 超时时间(毫秒)
  115 + max-retransmit: 4 # 最大重传次数
91 116
92 # 协议配置(旧版,保持兼容) 117 # 协议配置(旧版,保持兼容)
93 protocol: 118 protocol:
@@ -134,27 +159,6 @@ yudao: @@ -134,27 +159,6 @@ yudao:
134 max-message-size: 8192 159 max-message-size: 8192
135 connect-timeout-seconds: 60 160 connect-timeout-seconds: 60
136 ssl-enabled: false 161 ssl-enabled: false
137 - # ====================================  
138 - # 针对引入的 CoAP 组件的配置  
139 - # ====================================  
140 - coap:  
141 - enabled: false # 是否启用 CoAP 协议  
142 - port: 5683 # CoAP 服务端口(默认 5683)  
143 - max-message-size: 1024 # 最大消息大小(字节)  
144 - ack-timeout: 2000 # ACK 超时时间(毫秒)  
145 - max-retransmit: 4 # 最大重传次数  
146 - # ====================================  
147 - # 针对引入的 WebSocket 组件的配置  
148 - # ====================================  
149 - websocket:  
150 - enabled: false # 是否启用 WebSocket 协议  
151 - port: 8094 # WebSocket 服务端口(默认 8094)  
152 - path: /ws # WebSocket 路径(默认 /ws)  
153 - max-message-size: 65536 # 最大消息大小(字节,默认 64KB)  
154 - max-frame-size: 65536 # 最大帧大小(字节,默认 64KB)  
155 - idle-timeout-seconds: 60 # 空闲超时时间(秒,默认 60)  
156 - ssl-enabled: false # 是否启用 SSL(wss://)  
157 -  
158 --- #################### 日志相关配置 #################### 162 --- #################### 日志相关配置 ####################
159 163
160 # 基础日志配置 164 # 基础日志配置