Commit d9a08094d9f9640f94f7b2e3cb94961019b8efef

Authored by YunaiV
1 parent 3db18709

feat(iot):【协议改造】websocket 初步改造(50%)

Showing 19 changed files with 518 additions and 298 deletions
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/IotProtocolManager.java
... ... @@ -8,7 +8,7 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.coap.IotCoapProtocol;
8 8 import cn.iocoder.yudao.module.iot.gateway.protocol.http.IotHttpProtocol;
9 9 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpProtocol;
10 10 import cn.iocoder.yudao.module.iot.gateway.protocol.udp.IotUdpProtocol;
11   -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketUpstreamProtocol;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketProtocol;
12 12 import lombok.Getter;
13 13 import lombok.extern.slf4j.Slf4j;
14 14 import org.springframework.context.SmartLifecycle;
... ... @@ -158,8 +158,8 @@ public class IotProtocolManager implements SmartLifecycle {
158 158 * @param config 协议实例配置
159 159 * @return WebSocket 协议实例
160 160 */
161   - private IotWebSocketUpstreamProtocol createWebSocketProtocol(IotGatewayProperties.ProtocolInstanceProperties config) {
162   - return new IotWebSocketUpstreamProtocol(config);
  161 + private IotWebSocketProtocol createWebSocketProtocol(IotGatewayProperties.ProtocolInstanceProperties config) {
  162 + return new IotWebSocketProtocol(config);
163 163 }
164 164  
165 165 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotTcpProtocol.java
... ... @@ -57,6 +57,10 @@ public class IotTcpProtocol implements IotProtocol {
57 57 * TCP 服务器
58 58 */
59 59 private NetServer tcpServer;
  60 + /**
  61 + * TCP 连接管理器
  62 + */
  63 + private final IotTcpConnectionManager connectionManager;
60 64  
61 65 /**
62 66 * 下行消息订阅者
... ... @@ -67,17 +71,11 @@ public class IotTcpProtocol implements IotProtocol {
67 71 * 消息序列化器
68 72 */
69 73 private final IotMessageSerializer serializer;
70   -
71 74 /**
72 75 * TCP 帧编解码器
73 76 */
74 77 private final IotTcpFrameCodec frameCodec;
75 78  
76   - /**
77   - * TCP 连接管理器
78   - */
79   - private final IotTcpConnectionManager connectionManager;
80   -
81 79 public IotTcpProtocol(ProtocolInstanceProperties properties) {
82 80 IotTcpConfig tcpConfig = properties.getTcp();
83 81 Assert.notNull(tcpConfig, "TCP 协议配置(tcp)不能为空");
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/codec/length/IotTcpLengthFieldFrameCodec.java
... ... @@ -29,6 +29,11 @@ import java.util.concurrent.atomic.AtomicReference;
29 29 @Slf4j
30 30 public class IotTcpLengthFieldFrameCodec implements IotTcpFrameCodec {
31 31  
  32 + /**
  33 + * 最大帧长度(64KB),防止 DoS 攻击
  34 + */
  35 + private static final int MAX_FRAME_LENGTH = 65536;
  36 +
32 37 private final int lengthFieldOffset;
33 38 private final int lengthFieldLength;
34 39 private final int lengthAdjustment;
... ... @@ -60,6 +65,7 @@ public class IotTcpLengthFieldFrameCodec implements IotTcpFrameCodec {
60 65 public RecordParser createDecodeParser(Handler<Buffer> handler) {
61 66 // 创建状态机:先读取头部,再读取消息体
62 67 RecordParser parser = RecordParser.newFixed(headerLength);
  68 + parser.maxRecordSize(MAX_FRAME_LENGTH); // 设置最大记录大小,防止 DoS 攻击
63 69 final AtomicReference<Integer> bodyLength = new AtomicReference<>(null); // 消息体长度,null 表示读取头部阶段
64 70 final AtomicReference<Buffer> headerBuffer = new AtomicReference<>(null); // 头部消息
65 71  
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/handler/upstream/IotTcpUpstreamHandler.java
... ... @@ -77,7 +77,9 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
77 77 }
78 78  
79 79 @Override
  80 + @SuppressWarnings("DuplicatedCode")
80 81 public void handle(NetSocket socket) {
  82 + // TODO @AI:clientId 去掉;其它模块也看看,怎么去掉下看看;
81 83 String clientId = IdUtil.simpleUUID();
82 84 log.debug("[handle][设备连接,客户端 ID: {},地址: {}]", clientId, socket.remoteAddress());
83 85  
... ... @@ -92,9 +94,10 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
92 94 });
93 95  
94 96 // 2.1 设置消息处理器
  97 + // TODO @AI:去掉 clientId;
95 98 Handler<Buffer> messageHandler = buffer -> {
96 99 try {
97   - processMessage(clientId, buffer, socket);
  100 + processMessage(buffer, socket);
98 101 } catch (Exception e) {
99 102 log.error("[handle][消息处理失败,客户端 ID: {},地址: {}]",
100 103 clientId, socket.remoteAddress(), e);
... ... @@ -110,11 +113,10 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
110 113 /**
111 114 * 处理消息
112 115 *
113   - * @param clientId 客户端 ID
114 116 * @param buffer 消息
115 117 * @param socket 网络连接
116 118 */
117   - private void processMessage(String clientId, Buffer buffer, NetSocket socket) {
  119 + private void processMessage(Buffer buffer, NetSocket socket) {
118 120 IotDeviceMessage message = null;
119 121 try {
120 122 // 1. 反序列化消息
... ... @@ -127,29 +129,29 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
127 129 // 2. 根据消息类型路由处理
128 130 if (AUTH_METHOD.equals(message.getMethod())) {
129 131 // 认证请求
130   - handleAuthenticationRequest(clientId, message, socket);
  132 + handleAuthenticationRequest(message, socket);
131 133 } else if (IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod().equals(message.getMethod())) {
132 134 // 设备动态注册请求
133   - handleRegisterRequest(clientId, message, socket);
  135 + handleRegisterRequest(message, socket);
134 136 } else {
135 137 // 业务消息
136   - handleBusinessRequest(clientId, message, socket);
  138 + handleBusinessRequest(null, message, socket);
137 139 }
138 140 } catch (ServiceException e) {
139 141 // 业务异常,返回对应的错误码和错误信息
140   - log.warn("[processMessage][业务异常,客户端 ID: {},错误: {}]", clientId, e.getMessage());
  142 + log.warn("[processMessage][业务异常,客户端 ID: {},错误: {}]", null, e.getMessage());
141 143 String requestId = message != null ? message.getRequestId() : null;
142 144 String method = message != null ? message.getMethod() : null;
143 145 sendErrorResponse(socket, requestId, method, e.getCode(), e.getMessage());
144 146 } catch (IllegalArgumentException e) {
145 147 // 参数校验失败,返回 400
146   - log.warn("[processMessage][参数校验失败,客户端 ID: {},错误: {}]", clientId, e.getMessage());
  148 + log.warn("[processMessage][参数校验失败,客户端 ID: {},错误: {}]", null, e.getMessage());
147 149 String requestId = message != null ? message.getRequestId() : null;
148 150 String method = message != null ? message.getMethod() : null;
149 151 sendErrorResponse(socket, requestId, method, BAD_REQUEST.getCode(), e.getMessage());
150 152 } catch (Exception e) {
151 153 // 其他异常,返回 500,并重新抛出让上层关闭连接
152   - log.error("[processMessage][处理消息失败,客户端 ID: {}]", clientId, e);
  154 + log.error("[processMessage][处理消息失败,客户端 ID: {}]", null, e);
153 155 String requestId = message != null ? message.getRequestId() : null;
154 156 String method = message != null ? message.getMethod() : null;
155 157 sendErrorResponse(socket, requestId, method,
... ... @@ -161,12 +163,11 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
161 163 /**
162 164 * 处理认证请求
163 165 *
164   - * @param clientId 客户端 ID
165 166 * @param message 消息信息
166 167 * @param socket 网络连接
167 168 */
168 169 @SuppressWarnings("DuplicatedCode")
169   - private void handleAuthenticationRequest(String clientId, IotDeviceMessage message, NetSocket socket) {
  170 + private void handleAuthenticationRequest(IotDeviceMessage message, NetSocket socket) {
170 171 // 1. 解析认证参数
171 172 IotDeviceAuthReqDTO authParams = JsonUtils.convertObject(message.getParams(), IotDeviceAuthReqDTO.class);
172 173 Assert.notNull(authParams, "认证参数不能为空");
... ... @@ -198,13 +199,12 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
198 199 /**
199 200 * 处理设备动态注册请求(一型一密,不需要认证)
200 201 *
201   - * @param clientId 客户端 ID
202 202 * @param message 消息信息
203 203 * @param socket 网络连接
204 204 * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
205 205 */
206 206 @SuppressWarnings("DuplicatedCode")
207   - private void handleRegisterRequest(String clientId, IotDeviceMessage message, NetSocket socket) {
  207 + private void handleRegisterRequest(IotDeviceMessage message, NetSocket socket) {
208 208 // 1. 解析注册参数
209 209 IotDeviceRegisterReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceRegisterReqDTO.class);
210 210 Assert.notNull(params, "注册参数不能为空");
... ... @@ -218,7 +218,7 @@ public class IotTcpUpstreamHandler implements Handler&lt;NetSocket&gt; {
218 218 // 3. 发送成功响应
219 219 sendSuccessResponse(socket, message.getRequestId(),
220 220 IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), result.getData());
221   - log.info("[handleRegisterRequest][注册成功,客户端 ID: {},设备名: {}]", clientId, params.getDeviceName());
  221 + log.info("[handleRegisterRequest][注册成功,客户端 ID: {},设备名: {}]", null, params.getDeviceName());
222 222 }
223 223  
224 224 /**
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/udp/IotUdpProtocol.java
... ... @@ -55,6 +55,10 @@ public class IotUdpProtocol implements IotProtocol {
55 55 */
56 56 @Getter
57 57 private DatagramSocket udpSocket;
  58 + /**
  59 + * UDP 会话管理器
  60 + */
  61 + private final IotUdpSessionManager sessionManager;
58 62  
59 63 /**
60 64 * 下行消息订阅者
... ... @@ -66,11 +70,6 @@ public class IotUdpProtocol implements IotProtocol {
66 70 */
67 71 private final IotMessageSerializer serializer;
68 72  
69   - /**
70   - * UDP 会话管理器
71   - */
72   - private final IotUdpSessionManager sessionManager;
73   -
74 73 public IotUdpProtocol(ProtocolInstanceProperties properties) {
75 74 IotUdpConfig udpConfig = properties.getUdp();
76 75 Assert.notNull(udpConfig, "UDP 协议配置(udp)不能为空");
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotWebSocketConfig.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.websocket;
  2 +
  3 +import jakarta.validation.constraints.NotEmpty;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +/**
  8 + * IoT WebSocket 协议配置
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Data
  13 +public class IotWebSocketConfig {
  14 +
  15 + /**
  16 + * WebSocket 路径(默认:/ws)
  17 + */
  18 + @NotEmpty(message = "WebSocket 路径不能为空")
  19 + private String path = "/ws";
  20 +
  21 + /**
  22 + * 最大消息大小(字节,默认 64KB)
  23 + */
  24 + @NotNull(message = "最大消息大小不能为空")
  25 + private Integer maxMessageSize = 65536;
  26 + /**
  27 + * 最大帧大小(字节,默认 64KB)
  28 + */
  29 + @NotNull(message = "最大帧大小不能为空")
  30 + private Integer maxFrameSize = 65536;
  31 +
  32 + /**
  33 + * 空闲超时时间(秒,默认 60)
  34 + */
  35 + @NotNull(message = "空闲超时时间不能为空")
  36 + private Integer idleTimeoutSeconds = 60;
  37 +
  38 + /**
  39 + * 是否启用 SSL(wss://)
  40 + */
  41 + @NotNull(message = "是否启用 SSL 不能为空")
  42 + private Boolean sslEnabled = false;
  43 +
  44 + /**
  45 + * SSL 证书路径
  46 + */
  47 + private String sslCertPath;
  48 +
  49 + /**
  50 + * SSL 私钥路径
  51 + */
  52 + private String sslKeyPath;
  53 +
  54 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotWebSocketProtocol.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.websocket;
  2 +
  3 +import cn.hutool.core.util.ObjUtil;
  4 +import cn.hutool.extra.spring.SpringUtil;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
  6 +import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum;
  7 +import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
  8 +import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
  9 +import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolInstanceProperties;
  10 +import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
  11 +import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.downstream.IotWebSocketDownstreamSubscriber;
  12 +import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager;
  13 +import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.downstream.IotWebSocketDownstreamHandler;
  14 +import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.upstream.IotWebSocketUpstreamHandler;
  15 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
  16 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializerManager;
  17 +import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
  18 +import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  19 +import io.vertx.core.Vertx;
  20 +import io.vertx.core.http.HttpServer;
  21 +import io.vertx.core.http.HttpServerOptions;
  22 +import io.vertx.core.net.PemKeyCertOptions;
  23 +import lombok.Getter;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.springframework.util.Assert;
  26 +
  27 +// TODO @AI:注释调整下,参考 IotTcpProtocol
  28 +/**
  29 + * IoT 网关 WebSocket 协议:接收设备上行消息
  30 + *
  31 + * @author 芋道源码
  32 + */
  33 +@Slf4j
  34 +public class IotWebSocketProtocol implements IotProtocol {
  35 +
  36 + /**
  37 + * 协议配置
  38 + */
  39 + private final ProtocolInstanceProperties properties;
  40 + /**
  41 + * 服务器 ID(用于消息追踪,全局唯一)
  42 + */
  43 + @Getter
  44 + private final String serverId;
  45 +
  46 + /**
  47 + * 运行状态
  48 + */
  49 + @Getter
  50 + private volatile boolean running = false;
  51 +
  52 + /**
  53 + * Vert.x 实例
  54 + */
  55 + private Vertx vertx;
  56 + /**
  57 + * WebSocket 服务器
  58 + */
  59 + private HttpServer httpServer;
  60 + /**
  61 + * WebSocket 连接管理器
  62 + */
  63 + private final IotWebSocketConnectionManager connectionManager;
  64 +
  65 + // TODO @AI:可以不用这个变量,从 properties 里面获取
  66 + /**
  67 + * WebSocket 配置
  68 + */
  69 + private final IotWebSocketConfig wsConfig;
  70 +
  71 + /**
  72 + * 下行消息订阅者
  73 + */
  74 + private final IotWebSocketDownstreamSubscriber downstreamSubscriber;
  75 +
  76 + /**
  77 + * 消息序列化器
  78 + */
  79 + private final IotMessageSerializer serializer;
  80 +
  81 + private final IotDeviceService deviceService;
  82 + private final IotDeviceMessageService messageService;
  83 +
  84 + public IotWebSocketProtocol(ProtocolInstanceProperties properties) {
  85 + Assert.notNull(properties, "协议实例配置不能为空");
  86 + Assert.notNull(properties.getWebsocket(), "WebSocket 协议配置(websocket)不能为空");
  87 + this.properties = properties;
  88 + this.wsConfig = properties.getWebsocket();
  89 + this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort());
  90 +
  91 + // 初始化序列化器
  92 + IotSerializeTypeEnum serializeType = IotSerializeTypeEnum.of(properties.getSerialize());
  93 + Assert.notNull(serializeType, "不支持的序列化类型:" + properties.getSerialize());
  94 + IotMessageSerializerManager serializerManager = SpringUtil.getBean(IotMessageSerializerManager.class);
  95 + this.serializer = serializerManager.get(serializeType);
  96 +
  97 + // 初始化基础依赖
  98 + this.deviceService = SpringUtil.getBean(IotDeviceService.class);
  99 + this.messageService = SpringUtil.getBean(IotDeviceMessageService.class);
  100 + this.connectionManager = new IotWebSocketConnectionManager();
  101 +
  102 + // 初始化下行消息订阅者
  103 + IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class);
  104 + IotWebSocketDownstreamHandler downstreamHandler = new IotWebSocketDownstreamHandler(serializer, connectionManager);
  105 + this.downstreamSubscriber = new IotWebSocketDownstreamSubscriber(this, downstreamHandler, messageBus);
  106 + }
  107 +
  108 + @Override
  109 + public String getId() {
  110 + return properties.getId();
  111 + }
  112 +
  113 + @Override
  114 + public IotProtocolTypeEnum getType() {
  115 + return IotProtocolTypeEnum.WEBSOCKET;
  116 + }
  117 +
  118 + @Override
  119 + @SuppressWarnings("deprecation")
  120 + public void start() {
  121 + if (running) {
  122 + log.warn("[start][IoT WebSocket 协议 {} 已经在运行中]", getId());
  123 + return;
  124 + }
  125 +
  126 + // 1.1 创建 Vertx 实例
  127 + this.vertx = Vertx.vertx();
  128 +
  129 + // 1.2 创建服务器选项
  130 + HttpServerOptions options = new HttpServerOptions()
  131 + .setPort(properties.getPort())
  132 + .setIdleTimeout(wsConfig.getIdleTimeoutSeconds())
  133 + .setMaxWebSocketFrameSize(wsConfig.getMaxFrameSize())
  134 + .setMaxWebSocketMessageSize(wsConfig.getMaxMessageSize());
  135 + if (Boolean.TRUE.equals(wsConfig.getSslEnabled())) {
  136 + PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions()
  137 + .setKeyPath(wsConfig.getSslKeyPath())
  138 + .setCertPath(wsConfig.getSslCertPath());
  139 + options.setSsl(true).setKeyCertOptions(pemKeyCertOptions);
  140 + }
  141 +
  142 + // 1.3 创建服务器并设置 WebSocket 处理器
  143 + httpServer = vertx.createHttpServer(options);
  144 + httpServer.webSocketHandler(socket -> {
  145 + // 验证路径
  146 + if (ObjUtil.notEqual(wsConfig.getPath(), socket.path())) {
  147 + log.warn("[webSocketHandler][WebSocket 路径不匹配,拒绝连接,路径: {},期望: {}]",
  148 + socket.path(), wsConfig.getPath());
  149 + socket.reject();
  150 + return;
  151 + }
  152 + // 创建上行处理器
  153 + IotWebSocketUpstreamHandler handler = new IotWebSocketUpstreamHandler(this,
  154 + messageService, deviceService, connectionManager, serializer);
  155 + handler.handle(socket);
  156 + });
  157 +
  158 + // 1.4 启动服务器
  159 + try {
  160 + httpServer.listen().result();
  161 + running = true;
  162 + log.info("[start][IoT WebSocket 协议 {} 启动成功,端口:{},路径:{},serverId:{}]",
  163 + getId(), properties.getPort(), wsConfig.getPath(), serverId);
  164 +
  165 + // 2. 启动下行消息订阅者
  166 + downstreamSubscriber.start();
  167 + } catch (Exception e) {
  168 + log.error("[start][IoT WebSocket 协议 {} 启动失败]", getId(), e);
  169 + if (httpServer != null) {
  170 + httpServer.close();
  171 + httpServer = null;
  172 + }
  173 + if (vertx != null) {
  174 + vertx.close();
  175 + vertx = null;
  176 + }
  177 + throw e;
  178 + }
  179 + }
  180 +
  181 + @Override
  182 + public void stop() {
  183 + if (!running) {
  184 + return;
  185 + }
  186 + // 1. 停止下行消息订阅者
  187 + try {
  188 + downstreamSubscriber.stop();
  189 + log.info("[stop][IoT WebSocket 协议 {} 下行消息订阅者已停止]", getId());
  190 + } catch (Exception e) {
  191 + log.error("[stop][IoT WebSocket 协议 {} 下行消息订阅者停止失败]", getId(), e);
  192 + }
  193 +
  194 + // 2.1 关闭 WebSocket 服务器
  195 + if (httpServer != null) {
  196 + try {
  197 + httpServer.close().result();
  198 + log.info("[stop][IoT WebSocket 协议 {} 服务器已停止]", getId());
  199 + } catch (Exception e) {
  200 + log.error("[stop][IoT WebSocket 协议 {} 服务器停止失败]", getId(), e);
  201 + }
  202 + httpServer = null;
  203 + }
  204 + // 2.2 关闭 Vertx 实例
  205 + if (vertx != null) {
  206 + try {
  207 + vertx.close().result();
  208 + log.info("[stop][IoT WebSocket 协议 {} Vertx 已关闭]", getId());
  209 + } catch (Exception e) {
  210 + log.error("[stop][IoT WebSocket 协议 {} Vertx 关闭失败]", getId(), e);
  211 + }
  212 + vertx = null;
  213 + }
  214 + running = false;
  215 + log.info("[stop][IoT WebSocket 协议 {} 已停止]", getId());
  216 + }
  217 +
  218 +}
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotWebSocketUpstreamProtocol.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.websocket;
2   -
3   -import cn.hutool.core.util.ObjUtil;
4   -import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
5   -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
6   -import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
7   -import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
8   -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager;
9   -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.router.IotWebSocketUpstreamHandler;
10   -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
11   -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
12   -import io.vertx.core.Vertx;
13   -import io.vertx.core.http.HttpServer;
14   -import io.vertx.core.http.HttpServerOptions;
15   -import io.vertx.core.net.PemKeyCertOptions;
16   -import jakarta.annotation.PostConstruct;
17   -import jakarta.annotation.PreDestroy;
18   -import lombok.Getter;
19   -import lombok.extern.slf4j.Slf4j;
20   -
21   -/**
22   - * IoT 网关 WebSocket 协议:接收设备上行消息
23   - *
24   - * @author 芋道源码
25   - */
26   -@Slf4j
27   -public class IotWebSocketUpstreamProtocol implements IotProtocol {
28   -
29   - private static final String ID = "websocket";
30   -
31   - private final IotGatewayProperties.WebSocketProperties wsProperties;
32   -
33   - private final IotDeviceService deviceService;
34   -
35   - private final IotDeviceMessageService messageService;
36   -
37   - private final IotWebSocketConnectionManager connectionManager;
38   -
39   - private final Vertx vertx;
40   -
41   - @Getter
42   - private final String serverId;
43   -
44   - private HttpServer httpServer;
45   -
46   - private volatile boolean running = false;
47   -
48   - public IotWebSocketUpstreamProtocol(IotGatewayProperties.WebSocketProperties wsProperties,
49   - IotDeviceService deviceService,
50   - IotDeviceMessageService messageService,
51   - IotWebSocketConnectionManager connectionManager,
52   - Vertx vertx) {
53   - this.wsProperties = wsProperties;
54   - this.deviceService = deviceService;
55   - this.messageService = messageService;
56   - this.connectionManager = connectionManager;
57   - this.vertx = vertx;
58   - this.serverId = IotDeviceMessageUtils.generateServerId(wsProperties.getPort());
59   - }
60   -
61   - @Override
62   - public String getId() {
63   - return ID;
64   - }
65   -
66   - @Override
67   - public IotProtocolTypeEnum getType() {
68   - return IotProtocolTypeEnum.WEBSOCKET;
69   - }
70   -
71   - @Override
72   - @PostConstruct
73   - @SuppressWarnings("deprecation")
74   - public void start() {
75   - // 1.1 创建服务器选项
76   - HttpServerOptions options = new HttpServerOptions()
77   - .setPort(wsProperties.getPort())
78   - .setIdleTimeout(wsProperties.getIdleTimeoutSeconds())
79   - .setMaxWebSocketFrameSize(wsProperties.getMaxFrameSize())
80   - .setMaxWebSocketMessageSize(wsProperties.getMaxMessageSize());
81   - // 1.2 配置 SSL(如果启用)
82   - if (Boolean.TRUE.equals(wsProperties.getSslEnabled())) {
83   - PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions()
84   - .setKeyPath(wsProperties.getSslKeyPath())
85   - .setCertPath(wsProperties.getSslCertPath());
86   - options.setSsl(true).setKeyCertOptions(pemKeyCertOptions);
87   - }
88   -
89   - // 2. 创建服务器并设置 WebSocket 处理器
90   - httpServer = vertx.createHttpServer(options);
91   - httpServer.webSocketHandler(socket -> {
92   - // 验证路径
93   - if (ObjUtil.notEqual(wsProperties.getPath(), socket.path())) {
94   - log.warn("[webSocketHandler][WebSocket 路径不匹配,拒绝连接,路径: {},期望: {}]",
95   - socket.path(), wsProperties.getPath());
96   - socket.reject();
97   - return;
98   - }
99   - // 创建上行处理器
100   - IotWebSocketUpstreamHandler handler = new IotWebSocketUpstreamHandler(this,
101   - messageService, deviceService, connectionManager);
102   - handler.handle(socket);
103   - });
104   -
105   - // 3. 启动服务器
106   - try {
107   - httpServer.listen().result();
108   - running = true;
109   - log.info("[start][IoT 网关 WebSocket 协议启动成功,端口:{},路径:{}]", wsProperties.getPort(), wsProperties.getPath());
110   - } catch (Exception e) {
111   - log.error("[start][IoT 网关 WebSocket 协议启动失败]", e);
112   - throw e;
113   - }
114   - }
115   -
116   - @Override
117   - @PreDestroy
118   - public void stop() {
119   - if (httpServer != null) {
120   - try {
121   - httpServer.close().result();
122   - running = false;
123   - log.info("[stop][IoT 网关 WebSocket 协议已停止]");
124   - } catch (Exception e) {
125   - log.error("[stop][IoT 网关 WebSocket 协议停止失败]", e);
126   - }
127   - }
128   - }
129   -
130   - @Override
131   - public boolean isRunning() {
132   - return running;
133   - }
134   -
135   -}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/router/IotWebSocketDownstreamHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/handler/downstream/IotWebSocketDownstreamHandler.java
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.websocket.router;
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.downstream;
2 2  
3   -import cn.hutool.core.util.StrUtil;
4 3 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
  4 +import cn.hutool.core.util.StrUtil;
  5 +import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum;
5 6 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager;
6   -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
  7 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
7 8 import lombok.RequiredArgsConstructor;
8 9 import lombok.extern.slf4j.Slf4j;
9 10  
... ... @@ -16,7 +17,7 @@ import lombok.extern.slf4j.Slf4j;
16 17 @RequiredArgsConstructor
17 18 public class IotWebSocketDownstreamHandler {
18 19  
19   - private final IotDeviceMessageService deviceMessageService;
  20 + private final IotMessageSerializer serializer;
20 21  
21 22 private final IotWebSocketConnectionManager connectionManager;
22 23  
... ... @@ -37,9 +38,15 @@ public class IotWebSocketDownstreamHandler {
37 38 }
38 39  
39 40 // 2. 编码消息并发送到设备
40   - byte[] bytes = deviceMessageService.encodeDeviceMessage(message, connectionInfo.getCodecType());
41   - String jsonMessage = StrUtil.utf8Str(bytes);
42   - boolean success = connectionManager.sendToDevice(message.getDeviceId(), jsonMessage);
  41 + byte[] bytes = serializer.serialize(message);
  42 + // TODO @AI:参考别的模块的做法,直接发?类似 tcp 这种;
  43 + boolean success;
  44 + if (serializer.getType() == IotSerializeTypeEnum.BINARY) {
  45 + success = connectionManager.sendToDevice(message.getDeviceId(), bytes);
  46 + } else {
  47 + String jsonMessage = StrUtil.utf8Str(bytes);
  48 + success = connectionManager.sendToDevice(message.getDeviceId(), jsonMessage);
  49 + }
43 50 if (success) {
44 51 log.info("[handle][下行消息发送成功,设备 ID: {},方法: {},消息 ID: {},数据长度: {} 字节]",
45 52 message.getDeviceId(), message.getMethod(), message.getId(), bytes.length);
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotWebSocketDownstreamSubscriber.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/handler/downstream/IotWebSocketDownstreamSubscriber.java
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.websocket;
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.downstream;
2 2  
3 3 import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
4 4 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
5 5 import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocolDownstreamSubscriber;
6   -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.router.IotWebSocketDownstreamHandler;
  6 +import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketProtocol;
7 7 import lombok.extern.slf4j.Slf4j;
8 8  
9 9 /**
... ... @@ -16,7 +16,7 @@ public class IotWebSocketDownstreamSubscriber extends IotProtocolDownstreamSubsc
16 16  
17 17 private final IotWebSocketDownstreamHandler downstreamHandler;
18 18  
19   - public IotWebSocketDownstreamSubscriber(IotWebSocketUpstreamProtocol protocol,
  19 + public IotWebSocketDownstreamSubscriber(IotWebSocketProtocol protocol,
20 20 IotWebSocketDownstreamHandler downstreamHandler,
21 21 IotMessageBus messageBus) {
22 22 super(protocol, messageBus);
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/router/IotWebSocketUpstreamHandler.java renamed to yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/handler/upstream/IotWebSocketUpstreamHandler.java
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.websocket.router;
  1 +package cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.upstream;
2 2  
3 3 import cn.hutool.core.map.MapUtil;
  4 +import cn.hutool.core.util.ArrayUtil;
4 5 import cn.hutool.core.util.BooleanUtil;
5 6 import cn.hutool.core.util.IdUtil;
6 7 import cn.hutool.core.util.StrUtil;
... ... @@ -11,17 +12,19 @@ import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
11 12 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
12 13 import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
13 14 import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
  15 +import cn.iocoder.yudao.module.iot.core.enums.IotSerializeTypeEnum;
14 16 import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
15 17 import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
16 18 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
17 19 import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
18 20 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
19   -import cn.iocoder.yudao.module.iot.gateway.codec.alink.IotAlinkDeviceMessageCodec;
20   -import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketUpstreamProtocol;
  21 +import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.IotWebSocketProtocol;
21 22 import cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager.IotWebSocketConnectionManager;
  23 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
22 24 import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
23 25 import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
24 26 import io.vertx.core.Handler;
  27 +import io.vertx.core.buffer.Buffer;
25 28 import io.vertx.core.http.ServerWebSocket;
26 29 import lombok.extern.slf4j.Slf4j;
27 30  
... ... @@ -36,82 +39,120 @@ import java.util.Map;
36 39 @Slf4j
37 40 public class IotWebSocketUpstreamHandler implements Handler<ServerWebSocket> {
38 41  
  42 + private static final String AUTH_METHOD = "auth";
  43 +
  44 + private final String serverId;
  45 +
39 46 /**
40   - * 默认消息编解码类型
  47 + * 消息序列化器(处理业务消息序列化/反序列化)
41 48 */
42   - private static final String CODEC_TYPE = IotAlinkDeviceMessageCodec.TYPE;
  49 + private final IotMessageSerializer serializer;
  50 + /**
  51 + * 连接管理器
  52 + */
  53 + private final IotWebSocketConnectionManager connectionManager;
43 54  
44   - private static final String AUTH_METHOD = "auth";
  55 + // TODO @AI:是不是可以去掉?
  56 + private final boolean binaryPayload;
45 57  
46 58 private final IotDeviceMessageService deviceMessageService;
47   -
48 59 private final IotDeviceService deviceService;
49   -
50   - private final IotWebSocketConnectionManager connectionManager;
51   -
52 60 private final IotDeviceCommonApi deviceApi;
53 61  
54   - private final String serverId;
55   -
56   - public IotWebSocketUpstreamHandler(IotWebSocketUpstreamProtocol protocol,
  62 + // TODO @AI:参数、顺序参考 IotTcpUpstreamHandler
  63 + public IotWebSocketUpstreamHandler(IotWebSocketProtocol protocol,
57 64 IotDeviceMessageService deviceMessageService,
58 65 IotDeviceService deviceService,
59   - IotWebSocketConnectionManager connectionManager) {
  66 + IotWebSocketConnectionManager connectionManager,
  67 + IotMessageSerializer serializer) {
60 68 this.deviceMessageService = deviceMessageService;
61 69 this.deviceService = deviceService;
62 70 this.connectionManager = connectionManager;
  71 + this.serializer = serializer;
  72 + this.binaryPayload = serializer.getType() == IotSerializeTypeEnum.BINARY;
63 73 this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
64 74 this.serverId = protocol.getServerId();
  75 + // TODO @AI:通过 springutil;deviceService、deviceMessageService;
65 76 }
66 77  
67 78 @Override
  79 + @SuppressWarnings("DuplicatedCode")
68 80 public void handle(ServerWebSocket socket) {
69 81 String clientId = IdUtil.simpleUUID();
70 82 log.debug("[handle][设备连接,客户端 ID: {},地址: {}]", clientId, socket.remoteAddress());
71 83  
72 84 // 1. 设置异常和关闭处理器
  85 + // TODO @AI:clientId 去掉;
73 86 socket.exceptionHandler(ex -> {
74 87 log.warn("[handle][连接异常,客户端 ID: {},地址: {}]", clientId, socket.remoteAddress());
75   - cleanupConnection(socket);
  88 + socket.close();
76 89 });
77 90 socket.closeHandler(v -> {
78 91 log.debug("[handle][连接关闭,客户端 ID: {},地址: {}]", clientId, socket.remoteAddress());
79 92 cleanupConnection(socket);
80 93 });
81 94  
82   - // 2. 设置文本消息处理器
83   - socket.textMessageHandler(message -> {
84   - try {
85   - processMessage(clientId, message, socket);
86   - } catch (Exception e) {
87   - log.error("[handle][消息解码失败,断开连接,客户端 ID: {},地址: {},错误: {}]",
88   - clientId, socket.remoteAddress(), e.getMessage());
  95 + // 2. 设置消息处理器(JSON 使用文本,BINARY 使用二进制)
  96 + // TODO @AI:是不是 text、binary 保持统一?用一个 mesagehandler?
  97 + if (binaryPayload) {
  98 + socket.binaryMessageHandler(buffer -> {
  99 + try {
  100 + processMessage(clientId, buffer.getBytes(), socket);
  101 + } catch (Exception e) {
  102 + log.error("[handle][消息解码失败,断开连接,客户端 ID: {},地址: {},错误: {}]",
  103 + clientId, socket.remoteAddress(), e.getMessage());
  104 + cleanupConnection(socket);
  105 + socket.close();
  106 + }
  107 + });
  108 + socket.textMessageHandler(message -> {
  109 + log.warn("[handle][收到文本帧但当前序列化为 BINARY,断开连接,客户端 ID: {},地址: {}]",
  110 + clientId, socket.remoteAddress());
89 111 cleanupConnection(socket);
90 112 socket.close();
91   - }
92   - });
  113 + });
  114 + } else {
  115 + socket.textMessageHandler(message -> {
  116 + try {
  117 + processMessage(clientId, StrUtil.utf8Bytes(message), socket);
  118 + } catch (Exception e) {
  119 + log.error("[handle][消息解码失败,断开连接,客户端 ID: {},地址: {},错误: {}]",
  120 + clientId, socket.remoteAddress(), e.getMessage());
  121 + // TODO @AI:是不是不用 cleanupConnection?closehandler 本身就吹了了;
  122 + cleanupConnection(socket);
  123 + socket.close();
  124 + }
  125 + });
  126 + socket.binaryMessageHandler(buffer -> {
  127 + try {
  128 + processMessage(clientId, buffer.getBytes(), socket);
  129 + } catch (Exception e) {
  130 + log.error("[handle][消息解码失败,断开连接,客户端 ID: {},地址: {},错误: {}]",
  131 + clientId, socket.remoteAddress(), e.getMessage());
  132 + cleanupConnection(socket);
  133 + socket.close();
  134 + }
  135 + });
  136 + }
93 137 }
94 138  
95 139 /**
96 140 * 处理消息
97 141 *
98 142 * @param clientId 客户端 ID
99   - * @param message 消息(JSON 字符串)
  143 + * @param payload 消息负载
100 144 * @param socket WebSocket 连接
101 145 * @throws Exception 消息解码失败时抛出异常
102 146 */
103   - private void processMessage(String clientId, String message, ServerWebSocket socket) throws Exception {
  147 + private void processMessage(String clientId, byte[] payload, ServerWebSocket socket) throws Exception {
104 148 // 1.1 基础检查
105   - if (StrUtil.isBlank(message)) {
  149 + if (ArrayUtil.isEmpty(payload)) {
106 150 return;
107 151 }
108   - // 1.2 解码消息(已认证连接使用其 codecType,未认证连接使用默认 CODEC_TYPE)
109   - IotWebSocketConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket);
110   - String codecType = connectionInfo != null ? connectionInfo.getCodecType() : CODEC_TYPE;
  152 + // 1.2 解码消息
111 153 IotDeviceMessage deviceMessage;
112 154 try {
113   - deviceMessage = deviceMessageService.decodeDeviceMessage(
114   - StrUtil.utf8Bytes(message), codecType);
  155 + deviceMessage = serializer.deserialize(payload);
115 156 if (deviceMessage == null) {
116 157 throw new Exception("解码后消息为空");
117 158 }
... ... @@ -132,6 +173,7 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
132 173 handleBusinessRequest(clientId, deviceMessage, socket);
133 174 }
134 175 } catch (Exception e) {
  176 + // TODO @AI:参考 IotTcpUpstreamHandler 处理;业务、参数、其它
135 177 log.error("[processMessage][处理消息失败,客户端 ID: {},消息方法: {}]",
136 178 clientId, deviceMessage.getMethod(), e);
137 179 // 发送错误响应,避免客户端一直等待
... ... @@ -153,6 +195,7 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
153 195 private void handleAuthenticationRequest(String clientId, IotDeviceMessage message, ServerWebSocket socket) {
154 196 try {
155 197 // 1.1 解析认证参数
  198 + // TODO @AI:参数解析;参考 tcp 对应的 handleAuthenticationRequest
156 199 IotDeviceAuthReqDTO authParams = parseAuthParams(message.getParams());
157 200 if (authParams == null) {
158 201 log.warn("[handleAuthenticationRequest][认证参数解析失败,客户端 ID: {}]", clientId);
... ... @@ -204,6 +247,7 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
204 247 * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
205 248 */
206 249 private void handleRegisterRequest(String clientId, IotDeviceMessage message, ServerWebSocket socket) {
  250 + // TODO @AI:参数解析;参考 tcp 对应的 handleRegisterRequest
207 251 try {
208 252 // 1. 解析注册参数
209 253 IotDeviceRegisterReqDTO params = parseRegisterParams(message.getParams());
... ... @@ -232,6 +276,7 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
232 276 }
233 277 }
234 278  
  279 + // TODO @AI:参考对应的 tcp 的 handleBusinessRequest
235 280 /**
236 281 * 处理业务请求
237 282 *
... ... @@ -270,10 +315,7 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
270 315 IotWebSocketConnectionManager.ConnectionInfo connectionInfo = new IotWebSocketConnectionManager.ConnectionInfo()
271 316 .setDeviceId(device.getId())
272 317 .setProductKey(device.getProductKey())
273   - .setDeviceName(device.getDeviceName())
274   - .setClientId(clientId)
275   - .setCodecType(CODEC_TYPE);
276   - // 注册连接
  318 + .setDeviceName(device.getDeviceName());
277 319 connectionManager.registerConnection(socket, device.getId(), connectionInfo);
278 320 }
279 321  
... ... @@ -314,6 +356,8 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
314 356 }
315 357 }
316 358  
  359 + // ===================== 发送响应消息 =====================
  360 +
317 361 /**
318 362 * 发送响应消息
319 363 *
... ... @@ -332,8 +376,7 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
332 376 int code = success ? 0 : 401;
333 377 IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, AUTH_METHOD, responseData, code, message);
334 378  
335   - byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, CODEC_TYPE);
336   - socket.writeTextMessage(StrUtil.utf8Str(encodedData));
  379 + writeResponse(socket, responseMessage);
337 380 } catch (Exception e) {
338 381 log.error("[sendResponse][发送响应失败,requestId: {}]", requestId, e);
339 382 }
... ... @@ -461,11 +504,22 @@ public class IotWebSocketUpstreamHandler implements Handler&lt;ServerWebSocket&gt; {
461 504 IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId,
462 505 IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerResp, 0, null);
463 506 // 2. 发送响应
464   - byte[] encodedData = deviceMessageService.encodeDeviceMessage(responseMessage, CODEC_TYPE);
465   - socket.writeTextMessage(StrUtil.utf8Str(encodedData));
  507 + writeResponse(socket, responseMessage);
466 508 } catch (Exception e) {
467 509 log.error("[sendRegisterSuccessResponse][发送注册成功响应失败,requestId: {}]", requestId, e);
468 510 }
469 511 }
470 512  
  513 + /**
  514 + * 写入响应消息
  515 + */
  516 + private void writeResponse(ServerWebSocket socket, IotDeviceMessage responseMessage) {
  517 + byte[] payload = serializer.serialize(responseMessage);
  518 + if (binaryPayload) {
  519 + socket.writeBinaryMessage(Buffer.buffer(payload));
  520 + } else {
  521 + socket.writeTextMessage(StrUtil.utf8Str(payload));
  522 + }
  523 + }
  524 +
471 525 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/manager/IotWebSocketConnectionManager.java
1 1 package cn.iocoder.yudao.module.iot.gateway.protocol.websocket.manager;
2 2  
  3 +import io.vertx.core.buffer.Buffer;
3 4 import io.vertx.core.http.ServerWebSocket;
4 5 import lombok.Data;
5 6 import lombok.experimental.Accessors;
6 7 import lombok.extern.slf4j.Slf4j;
7   -import org.springframework.stereotype.Component;
8 8  
9 9 import java.util.Map;
10 10 import java.util.concurrent.ConcurrentHashMap;
... ... @@ -20,7 +20,6 @@ import java.util.concurrent.ConcurrentHashMap;
20 20 * @author 芋道源码
21 21 */
22 22 @Slf4j
23   -@Component
24 23 public class IotWebSocketConnectionManager {
25 24  
26 25 /**
... ... @@ -69,7 +68,8 @@ public class IotWebSocketConnectionManager {
69 68 return;
70 69 }
71 70 Long deviceId = connectionInfo.getDeviceId();
72   - deviceSocketMap.remove(deviceId);
  71 + // 仅当 deviceSocketMap 中的 socket 是当前 socket 时才移除,避免误删新连接
  72 + deviceSocketMap.remove(deviceId, socket);
73 73 log.info("[unregisterConnection][注销设备连接,设备 ID: {},连接: {}]",
74 74 deviceId, socket.remoteAddress());
75 75 }
... ... @@ -115,6 +115,33 @@ public class IotWebSocketConnectionManager {
115 115 }
116 116 }
117 117  
  118 + // TODO @AI:没必要这里加一个;
  119 + /**
  120 + * 发送消息到设备(二进制消息)
  121 + *
  122 + * @param deviceId 设备 ID
  123 + * @param payload 二进制消息
  124 + * @return 是否发送成功
  125 + */
  126 + public boolean sendToDevice(Long deviceId, byte[] payload) {
  127 + ServerWebSocket socket = deviceSocketMap.get(deviceId);
  128 + if (socket == null) {
  129 + log.warn("[sendToDevice][设备未连接,设备 ID: {}]", deviceId);
  130 + return false;
  131 + }
  132 +
  133 + try {
  134 + socket.writeBinaryMessage(Buffer.buffer(payload));
  135 + log.debug("[sendToDevice][发送消息成功,设备 ID: {},数据长度: {} 字节]", deviceId, payload.length);
  136 + return true;
  137 + } catch (Exception e) {
  138 + log.error("[sendToDevice][发送消息失败,设备 ID: {}]", deviceId, e);
  139 + // 发送失败时清理连接
  140 + unregisterConnection(socket);
  141 + return false;
  142 + }
  143 + }
  144 +
118 145 /**
119 146 * 连接信息(包含认证信息)
120 147 */
... ... @@ -135,15 +162,6 @@ public class IotWebSocketConnectionManager {
135 162 */
136 163 private String deviceName;
137 164  
138   - /**
139   - * 客户端 ID
140   - */
141   - private String clientId;
142   - /**
143   - * 消息编解码类型(认证后确定)
144   - */
145   - private String codecType;
146   -
147 165 }
148 166  
149 167 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
... ... @@ -94,7 +94,7 @@ yudao:
94 94 - id: websocket-json
95 95 type: websocket
96 96 port: 8094
97   - enabled: false
  97 + enabled: true
98 98 serialize: json
99 99 websocket:
100 100 path: /ws
... ... @@ -159,6 +159,7 @@ yudao:
159 159 max-message-size: 8192
160 160 connect-timeout-seconds: 60
161 161 ssl-enabled: false
  162 +
162 163 --- #################### 日志相关配置 ####################
163 164  
164 165 # 基础日志配置
... ...
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotDirectDeviceTcpProtocolIntegrationTest.java
... ... @@ -270,7 +270,7 @@ public class IotDirectDeviceTcpProtocolIntegrationTest {
270 270 });
271 271 socket.handler(parser);
272 272  
273   - // 2.1 序列化 + 帧编码(复用 gateway 的编码逻辑)
  273 + // 2.1 序列化 + 帧编码
274 274 byte[] serializedData = SERIALIZER.serialize(request);
275 275 Buffer frameData = FRAME_CODEC.encode(serializedData);
276 276 log.info("[sendAndReceive][发送消息: {},数据长度: {} 字节]", request.getMethod(), frameData.length());
... ...
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotGatewayDeviceTcpProtocolIntegrationTest.java
... ... @@ -373,7 +373,7 @@ public class IotGatewayDeviceTcpProtocolIntegrationTest {
373 373 });
374 374 socket.handler(parser);
375 375  
376   - // 2.1 序列化 + 帧编码(复用 gateway 的编码逻辑)
  376 + // 2.1 序列化 + 帧编码
377 377 byte[] serializedData = SERIALIZER.serialize(request);
378 378 Buffer frameData = FRAME_CODEC.encode(serializedData);
379 379 log.info("[sendAndReceive][发送消息: {},数据长度: {} 字节]", request.getMethod(), frameData.length());
... ...
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/tcp/IotGatewaySubDeviceTcpProtocolIntegrationTest.java
... ... @@ -249,7 +249,7 @@ public class IotGatewaySubDeviceTcpProtocolIntegrationTest {
249 249 });
250 250 socket.handler(parser);
251 251  
252   - // 2.1 序列化 + 帧编码(复用 gateway 的编码逻辑)
  252 + // 2.1 序列化 + 帧编码
253 253 byte[] serializedData = SERIALIZER.serialize(request);
254 254 Buffer frameData = FRAME_CODEC.encode(serializedData);
255 255 log.info("[sendAndReceive][发送消息: {},数据长度: {} 字节]", request.getMethod(), frameData.length());
... ...
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotDirectDeviceWebSocketProtocolIntegrationTest.java
... ... @@ -10,8 +10,8 @@ import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
10 10 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
11 11 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
12 12 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
13   -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
14   -import cn.iocoder.yudao.module.iot.gateway.codec.alink.IotAlinkDeviceMessageCodec;
  13 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
  14 +import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer;
15 15 import io.vertx.core.Vertx;
16 16 import io.vertx.core.http.WebSocket;
17 17 import io.vertx.core.http.WebSocketClient;
... ... @@ -61,7 +61,7 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest {
61 61  
62 62 // ===================== 编解码器选择 =====================
63 63  
64   - private static final IotDeviceMessageCodec CODEC = new IotAlinkDeviceMessageCodec();
  64 + private static final IotMessageSerializer SERIALIZER = new IotJsonSerializer();
65 65  
66 66 // ===================== 直连设备信息(根据实际情况修改,从 iot_device 表查询) =====================
67 67  
... ... @@ -95,10 +95,10 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest {
95 95 .setUsername(authInfo.getUsername())
96 96 .setPassword(authInfo.getPassword());
97 97 IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
98   - // 1.2 编码
99   - byte[] payload = CODEC.encode(request);
  98 + // 1.2 序列化
  99 + byte[] payload = SERIALIZER.serialize(request);
100 100 String jsonMessage = StrUtil.utf8Str(payload);
101   - log.info("[testAuth][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  101 + log.info("[testAuth][Serialize: {}, 请求消息: {}]", SERIALIZER.getType(), request);
102 102  
103 103 // 2.1 创建 WebSocket 连接(同步)
104 104 WebSocket ws = createWebSocketConnection();
... ... @@ -109,7 +109,7 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest {
109 109  
110 110 // 3. 解码响应
111 111 if (response != null) {
112   - IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  112 + IotDeviceMessage responseMessage = SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
113 113 log.info("[testAuth][响应消息: {}]", responseMessage);
114 114 } else {
115 115 log.warn("[testAuth][未收到响应]");
... ... @@ -137,10 +137,10 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest {
137 137 registerReqDTO.setProductSecret("test-product-secret");
138 138 IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(),
139 139 IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), registerReqDTO, null, null, null);
140   - // 1.2 编码
141   - byte[] payload = CODEC.encode(request);
  140 + // 1.2 序列化
  141 + byte[] payload = SERIALIZER.serialize(request);
142 142 String jsonMessage = StrUtil.utf8Str(payload);
143   - log.info("[testDeviceRegister][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  143 + log.info("[testDeviceRegister][Serialize: {}, 请求消息: {}]", SERIALIZER.getType(), request);
144 144  
145 145 // 2.1 创建 WebSocket 连接(同步)
146 146 WebSocket ws = createWebSocketConnection();
... ... @@ -151,7 +151,7 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest {
151 151  
152 152 // 3. 解码响应
153 153 if (response != null) {
154   - IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  154 + IotDeviceMessage responseMessage = SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
155 155 log.info("[testDeviceRegister][响应消息: {}]", responseMessage);
156 156 log.info("[testDeviceRegister][成功后可使用返回的 deviceSecret 进行一机一密认证]");
157 157 } else {
... ... @@ -186,16 +186,16 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest {
186 186 .put("height", "2")
187 187 .build()),
188 188 null, null, null);
189   - // 2.2 编码
190   - byte[] payload = CODEC.encode(request);
  189 + // 2.2 序列化
  190 + byte[] payload = SERIALIZER.serialize(request);
191 191 String jsonMessage = StrUtil.utf8Str(payload);
192   - log.info("[testPropertyPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  192 + log.info("[testPropertyPost][Serialize: {}, 请求消息: {}]", SERIALIZER.getType(), request);
193 193  
194 194 // 3.1 发送并等待响应
195 195 String response = sendAndReceive(ws, jsonMessage);
196 196 // 3.2 解码响应
197 197 if (response != null) {
198   - IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  198 + IotDeviceMessage responseMessage = SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
199 199 log.info("[testPropertyPost][响应消息: {}]", responseMessage);
200 200 } else {
201 201 log.warn("[testPropertyPost][未收到响应]");
... ... @@ -229,16 +229,16 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest {
229 229 MapUtil.<String, Object>builder().put("rice", 3).build(),
230 230 System.currentTimeMillis()),
231 231 null, null, null);
232   - // 2.2 编码
233   - byte[] payload = CODEC.encode(request);
  232 + // 2.2 序列化
  233 + byte[] payload = SERIALIZER.serialize(request);
234 234 String jsonMessage = StrUtil.utf8Str(payload);
235   - log.info("[testEventPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  235 + log.info("[testEventPost][Serialize: {}, 请求消息: {}]", SERIALIZER.getType(), request);
236 236  
237 237 // 3.1 发送并等待响应
238 238 String response = sendAndReceive(ws, jsonMessage);
239 239 // 3.2 解码响应
240 240 if (response != null) {
241   - IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  241 + IotDeviceMessage responseMessage = SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
242 242 log.info("[testEventPost][响应消息: {}]", responseMessage);
243 243 } else {
244 244 log.warn("[testEventPost][未收到响应]");
... ... @@ -308,13 +308,13 @@ public class IotDirectDeviceWebSocketProtocolIntegrationTest {
308 308 .setPassword(authInfo.getPassword());
309 309 IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
310 310  
311   - byte[] payload = CODEC.encode(request);
  311 + byte[] payload = SERIALIZER.serialize(request);
312 312 String jsonMessage = StrUtil.utf8Str(payload);
313 313 log.info("[authenticate][发送认证请求: {}]", jsonMessage);
314 314  
315 315 String response = sendAndReceive(ws, jsonMessage);
316 316 if (response != null) {
317   - return CODEC.decode(StrUtil.utf8Bytes(response));
  317 + return SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
318 318 }
319 319 return null;
320 320 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotGatewayDeviceWebSocketProtocolIntegrationTest.java
... ... @@ -14,8 +14,8 @@ import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoAddReqDTO;
14 14 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoDeleteReqDTO;
15 15 import cn.iocoder.yudao.module.iot.core.topic.topo.IotDeviceTopoGetReqDTO;
16 16 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
17   -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
18   -import cn.iocoder.yudao.module.iot.gateway.codec.alink.IotAlinkDeviceMessageCodec;
  17 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
  18 +import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer;
19 19 import io.vertx.core.Vertx;
20 20 import io.vertx.core.http.WebSocket;
21 21 import io.vertx.core.http.WebSocketClient;
... ... @@ -67,9 +67,9 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
67 67  
68 68 private static Vertx vertx;
69 69  
70   - // ===================== 编解码器选择 =====================
  70 + // ===================== 序列化器选择 =====================
71 71  
72   - private static final IotDeviceMessageCodec CODEC = new IotAlinkDeviceMessageCodec();
  72 + private static final IotMessageSerializer SERIALIZER = new IotJsonSerializer();
73 73  
74 74 // ===================== 网关设备信息(根据实际情况修改,从 iot_device 表查询网关设备) =====================
75 75  
... ... @@ -110,10 +110,10 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
110 110 .setUsername(authInfo.getUsername())
111 111 .setPassword(authInfo.getPassword());
112 112 IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
113   - // 1.2 编码
114   - byte[] payload = CODEC.encode(request);
  113 + // 1.2 序列化
  114 + byte[] payload = SERIALIZER.serialize(request);
115 115 String jsonMessage = StrUtil.utf8Str(payload);
116   - log.info("[testAuth][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  116 + log.info("[testAuth][Serialize: {}, 请求消息: {}]", SERIALIZER.getType(), request);
117 117  
118 118 // 2.1 创建 WebSocket 连接(同步)
119 119 WebSocket ws = createWebSocketConnection();
... ... @@ -124,7 +124,7 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
124 124  
125 125 // 3. 解码响应
126 126 if (response != null) {
127   - IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  127 + IotDeviceMessage responseMessage = SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
128 128 log.info("[testAuth][响应消息: {}]", responseMessage);
129 129 } else {
130 130 log.warn("[testAuth][未收到响应]");
... ... @@ -164,16 +164,16 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
164 164 IotDeviceMessageMethodEnum.TOPO_ADD.getMethod(),
165 165 params,
166 166 null, null, null);
167   - // 2.3 编码
168   - byte[] payload = CODEC.encode(request);
  167 + // 2.3 序列化
  168 + byte[] payload = SERIALIZER.serialize(request);
169 169 String jsonMessage = StrUtil.utf8Str(payload);
170   - log.info("[testTopoAdd][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  170 + log.info("[testTopoAdd][Serialize: {}, 请求消息: {}]", SERIALIZER.getType(), request);
171 171  
172 172 // 3.1 发送并等待响应
173 173 String response = sendAndReceive(ws, jsonMessage);
174 174 // 3.2 解码响应
175 175 if (response != null) {
176   - IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  176 + IotDeviceMessage responseMessage = SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
177 177 log.info("[testTopoAdd][响应消息: {}]", responseMessage);
178 178 } else {
179 179 log.warn("[testTopoAdd][未收到响应]");
... ... @@ -205,16 +205,16 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
205 205 IotDeviceMessageMethodEnum.TOPO_DELETE.getMethod(),
206 206 params,
207 207 null, null, null);
208   - // 2.2 编码
209   - byte[] payload = CODEC.encode(request);
  208 + // 2.2 序列化
  209 + byte[] payload = SERIALIZER.serialize(request);
210 210 String jsonMessage = StrUtil.utf8Str(payload);
211   - log.info("[testTopoDelete][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  211 + log.info("[testTopoDelete][Serialize: {}, 请求消息: {}]", SERIALIZER.getType(), request);
212 212  
213 213 // 3.1 发送并等待响应
214 214 String response = sendAndReceive(ws, jsonMessage);
215 215 // 3.2 解码响应
216 216 if (response != null) {
217   - IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  217 + IotDeviceMessage responseMessage = SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
218 218 log.info("[testTopoDelete][响应消息: {}]", responseMessage);
219 219 } else {
220 220 log.warn("[testTopoDelete][未收到响应]");
... ... @@ -244,16 +244,16 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
244 244 IotDeviceMessageMethodEnum.TOPO_GET.getMethod(),
245 245 params,
246 246 null, null, null);
247   - // 2.2 编码
248   - byte[] payload = CODEC.encode(request);
  247 + // 2.2 序列化
  248 + byte[] payload = SERIALIZER.serialize(request);
249 249 String jsonMessage = StrUtil.utf8Str(payload);
250   - log.info("[testTopoGet][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  250 + log.info("[testTopoGet][Serialize: {}, 请求消息: {}]", SERIALIZER.getType(), request);
251 251  
252 252 // 3.1 发送并等待响应
253 253 String response = sendAndReceive(ws, jsonMessage);
254 254 // 3.2 解码响应
255 255 if (response != null) {
256   - IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  256 + IotDeviceMessage responseMessage = SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
257 257 log.info("[testTopoGet][响应消息: {}]", responseMessage);
258 258 } else {
259 259 log.warn("[testTopoGet][未收到响应]");
... ... @@ -287,16 +287,16 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
287 287 IotDeviceMessageMethodEnum.SUB_DEVICE_REGISTER.getMethod(),
288 288 Collections.singletonList(subDevice),
289 289 null, null, null);
290   - // 2.2 编码
291   - byte[] payload = CODEC.encode(request);
  290 + // 2.2 序列化
  291 + byte[] payload = SERIALIZER.serialize(request);
292 292 String jsonMessage = StrUtil.utf8Str(payload);
293   - log.info("[testSubDeviceRegister][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  293 + log.info("[testSubDeviceRegister][Serialize: {}, 请求消息: {}]", SERIALIZER.getType(), request);
294 294  
295 295 // 3.1 发送并等待响应
296 296 String response = sendAndReceive(ws, jsonMessage);
297 297 // 3.2 解码响应
298 298 if (response != null) {
299   - IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  299 + IotDeviceMessage responseMessage = SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
300 300 log.info("[testSubDeviceRegister][响应消息: {}]", responseMessage);
301 301 } else {
302 302 log.warn("[testSubDeviceRegister][未收到响应]");
... ... @@ -358,16 +358,16 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
358 358 IotDeviceMessageMethodEnum.PROPERTY_PACK_POST.getMethod(),
359 359 params,
360 360 null, null, null);
361   - // 2.7 编码
362   - byte[] payload = CODEC.encode(request);
  361 + // 2.7 序列化
  362 + byte[] payload = SERIALIZER.serialize(request);
363 363 String jsonMessage = StrUtil.utf8Str(payload);
364   - log.info("[testPropertyPackPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  364 + log.info("[testPropertyPackPost][Serialize: {}, 请求消息: {}]", SERIALIZER.getType(), request);
365 365  
366 366 // 3.1 发送并等待响应
367 367 String response = sendAndReceive(ws, jsonMessage);
368 368 // 3.2 解码响应
369 369 if (response != null) {
370   - IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  370 + IotDeviceMessage responseMessage = SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
371 371 log.info("[testPropertyPackPost][响应消息: {}]", responseMessage);
372 372 } else {
373 373 log.warn("[testPropertyPackPost][未收到响应]");
... ... @@ -438,13 +438,13 @@ public class IotGatewayDeviceWebSocketProtocolIntegrationTest {
438 438 .setPassword(authInfo.getPassword());
439 439 IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
440 440  
441   - byte[] payload = CODEC.encode(request);
  441 + byte[] payload = SERIALIZER.serialize(request);
442 442 String jsonMessage = StrUtil.utf8Str(payload);
443 443 log.info("[authenticate][发送认证请求: {}]", jsonMessage);
444 444  
445 445 String response = sendAndReceive(ws, jsonMessage);
446 446 if (response != null) {
447   - return CODEC.decode(StrUtil.utf8Bytes(response));
  447 + return SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
448 448 }
449 449 return null;
450 450 }
... ...
yudao-module-iot/yudao-module-iot-gateway/src/test/java/cn/iocoder/yudao/module/iot/gateway/protocol/websocket/IotGatewaySubDeviceWebSocketProtocolIntegrationTest.java
... ... @@ -9,8 +9,8 @@ import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
9 9 import cn.iocoder.yudao.module.iot.core.topic.event.IotDeviceEventPostReqDTO;
10 10 import cn.iocoder.yudao.module.iot.core.topic.property.IotDevicePropertyPostReqDTO;
11 11 import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
12   -import cn.iocoder.yudao.module.iot.gateway.codec.IotDeviceMessageCodec;
13   -import cn.iocoder.yudao.module.iot.gateway.codec.alink.IotAlinkDeviceMessageCodec;
  12 +import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
  13 +import cn.iocoder.yudao.module.iot.gateway.serialize.json.IotJsonSerializer;
14 14 import io.vertx.core.Vertx;
15 15 import io.vertx.core.http.WebSocket;
16 16 import io.vertx.core.http.WebSocketClient;
... ... @@ -60,9 +60,9 @@ public class IotGatewaySubDeviceWebSocketProtocolIntegrationTest {
60 60  
61 61 private static Vertx vertx;
62 62  
63   - // ===================== 编解码器选择 =====================
  63 + // ===================== 序列化器选择 =====================
64 64  
65   - private static final IotDeviceMessageCodec CODEC = new IotAlinkDeviceMessageCodec();
  65 + private static final IotMessageSerializer SERIALIZER = new IotJsonSerializer();
66 66  
67 67 // ===================== 网关子设备信息(根据实际情况修改,从 iot_device 表查询子设备) =====================
68 68  
... ... @@ -96,10 +96,10 @@ public class IotGatewaySubDeviceWebSocketProtocolIntegrationTest {
96 96 .setUsername(authInfo.getUsername())
97 97 .setPassword(authInfo.getPassword());
98 98 IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
99   - // 1.2 编码
100   - byte[] payload = CODEC.encode(request);
  99 + // 1.2 序列化
  100 + byte[] payload = SERIALIZER.serialize(request);
101 101 String jsonMessage = StrUtil.utf8Str(payload);
102   - log.info("[testAuth][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  102 + log.info("[testAuth][Serialize: {}, 请求消息: {}]", SERIALIZER.getType(), request);
103 103  
104 104 // 2.1 创建 WebSocket 连接(同步)
105 105 WebSocket ws = createWebSocketConnection();
... ... @@ -110,7 +110,7 @@ public class IotGatewaySubDeviceWebSocketProtocolIntegrationTest {
110 110  
111 111 // 3. 解码响应
112 112 if (response != null) {
113   - IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  113 + IotDeviceMessage responseMessage = SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
114 114 log.info("[testAuth][响应消息: {}]", responseMessage);
115 115 } else {
116 116 log.warn("[testAuth][未收到响应]");
... ... @@ -146,16 +146,16 @@ public class IotGatewaySubDeviceWebSocketProtocolIntegrationTest {
146 146 .put("temperature", 36.5)
147 147 .build()),
148 148 null, null, null);
149   - // 2.2 编码
150   - byte[] payload = CODEC.encode(request);
  149 + // 2.2 序列化
  150 + byte[] payload = SERIALIZER.serialize(request);
151 151 String jsonMessage = StrUtil.utf8Str(payload);
152   - log.info("[testPropertyPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  152 + log.info("[testPropertyPost][Serialize: {}, 请求消息: {}]", SERIALIZER.getType(), request);
153 153  
154 154 // 3.1 发送并等待响应
155 155 String response = sendAndReceive(ws, jsonMessage);
156 156 // 3.2 解码响应
157 157 if (response != null) {
158   - IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  158 + IotDeviceMessage responseMessage = SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
159 159 log.info("[testPropertyPost][响应消息: {}]", responseMessage);
160 160 } else {
161 161 log.warn("[testPropertyPost][未收到响应]");
... ... @@ -195,16 +195,16 @@ public class IotGatewaySubDeviceWebSocketProtocolIntegrationTest {
195 195 .build(),
196 196 System.currentTimeMillis()),
197 197 null, null, null);
198   - // 2.2 编码
199   - byte[] payload = CODEC.encode(request);
  198 + // 2.2 序列化
  199 + byte[] payload = SERIALIZER.serialize(request);
200 200 String jsonMessage = StrUtil.utf8Str(payload);
201   - log.info("[testEventPost][Codec: {}, 请求消息: {}]", CODEC.type(), request);
  201 + log.info("[testEventPost][Serialize: {}, 请求消息: {}]", SERIALIZER.getType(), request);
202 202  
203 203 // 3.1 发送并等待响应
204 204 String response = sendAndReceive(ws, jsonMessage);
205 205 // 3.2 解码响应
206 206 if (response != null) {
207   - IotDeviceMessage responseMessage = CODEC.decode(StrUtil.utf8Bytes(response));
  207 + IotDeviceMessage responseMessage = SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
208 208 log.info("[testEventPost][响应消息: {}]", responseMessage);
209 209 } else {
210 210 log.warn("[testEventPost][未收到响应]");
... ... @@ -274,13 +274,13 @@ public class IotGatewaySubDeviceWebSocketProtocolIntegrationTest {
274 274 .setPassword(authInfo.getPassword());
275 275 IotDeviceMessage request = IotDeviceMessage.of(IdUtil.fastSimpleUUID(), "auth", authReqDTO, null, null, null);
276 276  
277   - byte[] payload = CODEC.encode(request);
  277 + byte[] payload = SERIALIZER.serialize(request);
278 278 String jsonMessage = StrUtil.utf8Str(payload);
279 279 log.info("[authenticate][发送认证请求: {}]", jsonMessage);
280 280  
281 281 String response = sendAndReceive(ws, jsonMessage);
282 282 if (response != null) {
283   - return CODEC.decode(StrUtil.utf8Bytes(response));
  283 + return SERIALIZER.deserialize(StrUtil.utf8Bytes(response));
284 284 }
285 285 return null;
286 286 }
... ...