Commit a28a15295cdb60f340f7157a5bbd18a76fd4ccbb
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<RoutingContext> | @@ -35,8 +31,6 @@ public abstract class IotHttpAbstractHandler implements Handler<RoutingContext> | ||
| 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<RoutingContext> | @@ -47,15 +41,31 @@ public abstract class IotHttpAbstractHandler implements Handler<RoutingContext> | ||
| 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<RoutingContext> | @@ -102,7 +112,7 @@ public abstract class IotHttpAbstractHandler implements Handler<RoutingContext> | ||
| 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<NetSocket> { | @@ -132,6 +135,12 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | ||
| 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<NetSocket> { | @@ -166,10 +175,9 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | ||
| 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<NetSocket> { | @@ -205,12 +213,7 @@ public class IotTcpUpstreamHandler implements Handler<NetSocket> { | ||
| 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 | # 基础日志配置 |