Commit b87bc19116ffa22e0f0bb47a13a2fb92731b1738

Authored by YunaiV
1 parent 1b4ac9fb

feat:【iot】移除不再使用的 MQTT WebSocket 协议配置类(实现不够标准,使用 MQTT 即可)

yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/config/IotGatewayConfiguration.java
... ... @@ -12,10 +12,6 @@ import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttDownstreamSubscr
12 12 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.IotMqttUpstreamProtocol;
13 13 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
14 14 import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.router.IotMqttDownstreamHandler;
15   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.IotMqttWsDownstreamSubscriber;
16   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.IotMqttWsUpstreamProtocol;
17   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.manager.IotMqttWsConnectionManager;
18   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.router.IotMqttWsDownstreamHandler;
19 15 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpDownstreamSubscriber;
20 16 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.IotTcpUpstreamProtocol;
21 17 import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager;
... ... @@ -160,44 +156,6 @@ public class IotGatewayConfiguration {
160 156 }
161 157  
162 158 /**
163   - * IoT 网关 MQTT WebSocket 协议配置类
164   - */
165   - @Configuration
166   - @ConditionalOnProperty(prefix = "yudao.iot.gateway.protocol.mqtt-ws", name = "enabled", havingValue = "true")
167   - @Slf4j
168   - public static class MqttWsProtocolConfiguration {
169   -
170   - @Bean(name = "mqttWsVertx", destroyMethod = "close")
171   - public Vertx mqttWsVertx() {
172   - return Vertx.vertx();
173   - }
174   -
175   - @Bean
176   - public IotMqttWsUpstreamProtocol iotMqttWsUpstreamProtocol(IotGatewayProperties gatewayProperties,
177   - IotDeviceMessageService messageService,
178   - IotMqttWsConnectionManager connectionManager,
179   - @Qualifier("mqttWsVertx") Vertx mqttWsVertx) {
180   - return new IotMqttWsUpstreamProtocol(gatewayProperties.getProtocol().getMqttWs(),
181   - messageService, connectionManager, mqttWsVertx);
182   - }
183   -
184   - @Bean
185   - public IotMqttWsDownstreamHandler iotMqttWsDownstreamHandler(IotDeviceMessageService messageService,
186   - IotDeviceService deviceService,
187   - IotMqttWsConnectionManager connectionManager) {
188   - return new IotMqttWsDownstreamHandler(messageService, deviceService, connectionManager);
189   - }
190   -
191   - @Bean
192   - public IotMqttWsDownstreamSubscriber iotMqttWsDownstreamSubscriber(IotMqttWsUpstreamProtocol mqttWsUpstreamProtocol,
193   - IotMqttWsDownstreamHandler downstreamHandler,
194   - IotMessageBus messageBus) {
195   - return new IotMqttWsDownstreamSubscriber(mqttWsUpstreamProtocol, downstreamHandler, messageBus);
196   - }
197   -
198   - }
199   -
200   - /**
201 159 * IoT 网关 UDP 协议配置类
202 160 */
203 161 @Configuration
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqtt/manager/IotMqttConnectionManager.java
... ... @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager;
2 2  
3 3 import cn.hutool.core.util.StrUtil;
4 4 import io.netty.handler.codec.mqtt.MqttQoS;
  5 +import io.vertx.core.buffer.Buffer;
5 6 import io.vertx.mqtt.MqttEndpoint;
6 7 import lombok.Data;
7 8 import lombok.extern.slf4j.Slf4j;
... ... @@ -166,7 +167,7 @@ public class IotMqttConnectionManager {
166 167 }
167 168  
168 169 try {
169   - endpoint.publish(topic, io.vertx.core.buffer.Buffer.buffer(payload), MqttQoS.valueOf(qos), false, retain);
  170 + endpoint.publish(topic, Buffer.buffer(payload), MqttQoS.valueOf(qos), false, retain);
170 171 log.debug("[sendToDevice][发送消息成功,设备 ID: {},主题: {},QoS: {}]", deviceId, topic, qos);
171 172 return true;
172 173 } catch (Exception e) {
... ...
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqttws/IotMqttWsDownstreamSubscriber.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.mqttws;
2   -
3   -import cn.hutool.core.util.StrUtil;
4   -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
5   -import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageSubscriber;
6   -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
7   -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
8   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.router.IotMqttWsDownstreamHandler;
9   -import jakarta.annotation.PostConstruct;
10   -import lombok.extern.slf4j.Slf4j;
11   -
12   -/**
13   - * IoT MQTT WebSocket 下行消息订阅器
14   - * <p>
15   - * 订阅消息总线的设备下行消息,并通过 WebSocket 发送到设备
16   - *
17   - * @author 芋道源码
18   - */
19   -@Slf4j
20   -public class IotMqttWsDownstreamSubscriber implements IotMessageSubscriber<IotDeviceMessage> {
21   -
22   - private final IotMqttWsUpstreamProtocol upstreamProtocol;
23   - private final IotMqttWsDownstreamHandler downstreamHandler;
24   - private final IotMessageBus messageBus;
25   -
26   - public IotMqttWsDownstreamSubscriber(IotMqttWsUpstreamProtocol upstreamProtocol,
27   - IotMqttWsDownstreamHandler downstreamHandler,
28   - IotMessageBus messageBus) {
29   - this.upstreamProtocol = upstreamProtocol;
30   - this.downstreamHandler = downstreamHandler;
31   - this.messageBus = messageBus;
32   - }
33   -
34   - @PostConstruct
35   - public void init() {
36   - messageBus.register(this);
37   - log.info("[init][MQTT WebSocket 下行消息订阅器已启动,topic: {}]", getTopic());
38   - }
39   -
40   - @Override
41   - public String getTopic() {
42   - return IotDeviceMessageUtils.buildMessageBusGatewayDeviceMessageTopic(upstreamProtocol.getServerId());
43   - }
44   -
45   - @Override
46   - public String getGroup() {
47   - // 保证点对点消费,需要保证独立的 Group,所以使用 Topic 作为 Group
48   - return getTopic();
49   - }
50   -
51   - @Override
52   - public void onMessage(IotDeviceMessage message) {
53   - log.debug("[onMessage][收到下行消息,deviceId: {},method: {}]",
54   - message.getDeviceId(), message.getMethod());
55   - try {
56   - // 1. 校验
57   - String method = message.getMethod();
58   - if (StrUtil.isBlank(method)) {
59   - log.warn("[onMessage][消息方法为空,deviceId: {}]", message.getDeviceId());
60   - return;
61   - }
62   -
63   - // 2. 委托给下行处理器处理业务逻辑
64   - boolean success = downstreamHandler.handleDownstreamMessage(message);
65   - if (success) {
66   - log.debug("[onMessage][下行消息处理成功,deviceId: {},method: {}]",
67   - message.getDeviceId(), message.getMethod());
68   - } else {
69   - log.warn("[onMessage][下行消息处理失败,deviceId: {},method: {}]",
70   - message.getDeviceId(), message.getMethod());
71   - }
72   - } catch (Exception e) {
73   - log.error("[onMessage][处理下行消息失败,deviceId: {},method: {}]",
74   - message.getDeviceId(), message.getMethod(), e);
75   - }
76   - }
77   -
78   -}
79   -
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqttws/IotMqttWsUpstreamProtocol.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.mqttws;
2   -
3   -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
4   -import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
5   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.manager.IotMqttWsConnectionManager;
6   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.router.IotMqttWsUpstreamHandler;
7   -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
8   -import io.vertx.core.Vertx;
9   -import io.vertx.core.http.HttpServer;
10   -import io.vertx.core.http.HttpServerOptions;
11   -import io.vertx.core.http.ServerWebSocket;
12   -import jakarta.annotation.PostConstruct;
13   -import jakarta.annotation.PreDestroy;
14   -import lombok.Getter;
15   -import lombok.extern.slf4j.Slf4j;
16   -
17   -/**
18   - * IoT 网关 MQTT WebSocket 协议:接收设备上行消息
19   - * <p>
20   - * 基于 Vert.x 实现 MQTT over WebSocket 服务端,支持:
21   - * - 标准 MQTT 3.1.1 协议
22   - * - WebSocket 协议升级
23   - * - SSL/TLS 加密(wss://)
24   - * - 设备认证与连接管理
25   - * - QoS 0/1/2 消息质量保证
26   - *
27   - * @author 芋道源码
28   - */
29   -@Slf4j
30   -public class IotMqttWsUpstreamProtocol {
31   -
32   - private final IotGatewayProperties.MqttWsProperties mqttWsProperties;
33   -
34   - private final IotDeviceMessageService messageService;
35   -
36   - private final IotMqttWsConnectionManager connectionManager;
37   -
38   - private final Vertx vertx;
39   -
40   - @Getter
41   - private final String serverId;
42   -
43   - private HttpServer httpServer;
44   -
45   - public IotMqttWsUpstreamProtocol(IotGatewayProperties.MqttWsProperties mqttWsProperties,
46   - IotDeviceMessageService messageService,
47   - IotMqttWsConnectionManager connectionManager,
48   - Vertx vertx) {
49   - this.mqttWsProperties = mqttWsProperties;
50   - this.messageService = messageService;
51   - this.connectionManager = connectionManager;
52   - this.vertx = vertx;
53   - this.serverId = IotDeviceMessageUtils.generateServerId(mqttWsProperties.getPort());
54   - }
55   -
56   - @PostConstruct
57   - public void start() {
58   - // 创建 HTTP 服务器选项
59   - HttpServerOptions options = new HttpServerOptions()
60   - .setPort(mqttWsProperties.getPort())
61   - .setIdleTimeout(mqttWsProperties.getKeepAliveTimeoutSeconds())
62   - .setMaxWebSocketFrameSize(mqttWsProperties.getMaxFrameSize())
63   - .setMaxWebSocketMessageSize(mqttWsProperties.getMaxMessageSize())
64   - // 配置 WebSocket 子协议支持
65   - .addWebSocketSubProtocol(mqttWsProperties.getSubProtocol());
66   -
67   - // 配置 SSL(如果启用)
68   - if (Boolean.TRUE.equals(mqttWsProperties.getSslEnabled())) {
69   - options.setSsl(true)
70   - .setKeyCertOptions(mqttWsProperties.getSslOptions().getKeyCertOptions())
71   - .setTrustOptions(mqttWsProperties.getSslOptions().getTrustOptions());
72   - log.info("[start][MQTT WebSocket 已启用 SSL/TLS (wss://)]");
73   - }
74   -
75   - // 创建 HTTP 服务器
76   - httpServer = vertx.createHttpServer(options);
77   -
78   - // 设置 WebSocket 处理器
79   - httpServer.webSocketHandler(this::handleWebSocketConnection);
80   -
81   - // 启动服务器
82   - try {
83   - httpServer.listen().result();
84   - log.info("[start][IoT 网关 MQTT WebSocket 协议启动成功,端口: {},路径: {},支持子协议: {}]",
85   - mqttWsProperties.getPort(), mqttWsProperties.getPath(),
86   - "mqtt, mqttv3.1, " + mqttWsProperties.getSubProtocol());
87   - } catch (Exception e) {
88   - log.error("[start][IoT 网关 MQTT WebSocket 协议启动失败]", e);
89   - throw e;
90   - }
91   - }
92   -
93   - @PreDestroy
94   - public void stop() {
95   - if (httpServer != null) {
96   - try {
97   - // 关闭所有连接
98   - connectionManager.closeAllConnections();
99   -
100   - // 关闭服务器
101   - httpServer.close().result();
102   - log.info("[stop][IoT 网关 MQTT WebSocket 协议已停止]");
103   - } catch (Exception e) {
104   - log.error("[stop][IoT 网关 MQTT WebSocket 协议停止失败]", e);
105   - }
106   - }
107   - }
108   -
109   - /**
110   - * 处理 WebSocket 连接请求
111   - *
112   - * @param socket WebSocket 连接
113   - */
114   - private void handleWebSocketConnection(ServerWebSocket socket) {
115   - String path = socket.path();
116   - String subProtocol = socket.subProtocol();
117   -
118   - log.info("[handleWebSocketConnection][收到 WebSocket 连接请求,path: {},subProtocol: {},remoteAddress: {}]",
119   - path, subProtocol, socket.remoteAddress());
120   -
121   - // 验证路径
122   - if (!mqttWsProperties.getPath().equals(path)) {
123   - log.warn("[handleWebSocketConnection][WebSocket 路径不匹配,拒绝连接,path: {},期望: {}]",
124   - path, mqttWsProperties.getPath());
125   - socket.close();
126   - return;
127   - }
128   -
129   - // 验证子协议
130   - // Vert.x 已经自动进行了子协议协商,这里只需要验证是否为 MQTT 相关协议
131   - if (subProtocol != null && !subProtocol.startsWith("mqtt")) {
132   - log.warn("[handleWebSocketConnection][WebSocket 子协议不支持,拒绝连接,subProtocol: {}]", subProtocol);
133   - socket.close();
134   - return;
135   - }
136   -
137   - log.info("[handleWebSocketConnection][WebSocket 连接已接受,remoteAddress: {},subProtocol: {}]",
138   - socket.remoteAddress(), subProtocol);
139   -
140   - // 创建处理器并处理连接
141   - IotMqttWsUpstreamHandler handler = new IotMqttWsUpstreamHandler(
142   - this, messageService, connectionManager);
143   - handler.handle(socket);
144   - }
145   -
146   -}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqttws/manager/IotMqttWsConnectionManager.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.manager;
2   -
3   -import cn.hutool.core.collection.CollUtil;
4   -import io.vertx.core.http.ServerWebSocket;
5   -import lombok.extern.slf4j.Slf4j;
6   -import org.springframework.stereotype.Component;
7   -
8   -import java.util.Map;
9   -import java.util.Set;
10   -import java.util.concurrent.ConcurrentHashMap;
11   -import java.util.concurrent.CopyOnWriteArraySet;
12   -
13   -/**
14   - * IoT MQTT WebSocket 连接管理器
15   - *
16   - * @author 芋道源码
17   - */
18   -@Slf4j
19   -@Component
20   -public class IotMqttWsConnectionManager {
21   -
22   - /**
23   - * 存储设备连接
24   - * Key: 设备标识(deviceKey)
25   - * Value: WebSocket 连接
26   - */
27   - private final Map<String, ServerWebSocket> connections = new ConcurrentHashMap<>();
28   -
29   - /**
30   - * 存储设备标识与 Socket ID 的映射
31   - * Key: 设备标识(deviceKey)
32   - * Value: Socket ID(UUID)
33   - */
34   - private final Map<String, String> deviceKeyToSocketId = new ConcurrentHashMap<>();
35   -
36   - /**
37   - * 存储 Socket ID 与设备标识的映射
38   - * Key: Socket ID(UUID)
39   - * Value: 设备标识(deviceKey)
40   - */
41   - private final Map<String, String> socketIdToDeviceKey = new ConcurrentHashMap<>();
42   -
43   - /**
44   - * 存储设备订阅的主题
45   - * Key: 设备标识(deviceKey)
46   - * Value: 订阅的主题集合
47   - */
48   - private final Map<String, Set<String>> deviceSubscriptions = new ConcurrentHashMap<>();
49   -
50   - /**
51   - * 添加连接
52   - *
53   - * @param deviceKey 设备标识
54   - * @param socket WebSocket 连接
55   - * @param socketId Socket ID(UUID)
56   - */
57   - public void addConnection(String deviceKey, ServerWebSocket socket, String socketId) {
58   - connections.put(deviceKey, socket);
59   - deviceKeyToSocketId.put(deviceKey, socketId);
60   - socketIdToDeviceKey.put(socketId, deviceKey);
61   - log.info("[addConnection][设备连接已添加,deviceKey: {},socketId: {},当前连接数: {}]",
62   - deviceKey, socketId, connections.size());
63   - }
64   -
65   - /**
66   - * 移除连接
67   - *
68   - * @param deviceKey 设备标识
69   - */
70   - public void removeConnection(String deviceKey) {
71   - ServerWebSocket socket = connections.remove(deviceKey);
72   - String socketId = deviceKeyToSocketId.remove(deviceKey);
73   - if (socketId != null) {
74   - socketIdToDeviceKey.remove(socketId);
75   - }
76   - if (socket != null) {
77   - log.info("[removeConnection][设备连接已移除,deviceKey: {},socketId: {},当前连接数: {}]",
78   - deviceKey, socketId, connections.size());
79   - }
80   - }
81   -
82   - /**
83   - * 根据 Socket ID 移除连接
84   - *
85   - * @param socketId WebSocket 文本框架 ID
86   - */
87   - public void removeConnectionBySocketId(String socketId) {
88   - String deviceKey = socketIdToDeviceKey.remove(socketId);
89   - if (deviceKey != null) {
90   - connections.remove(deviceKey);
91   - log.info("[removeConnectionBySocketId][设备连接已移除,socketId: {},deviceKey: {},当前连接数: {}]",
92   - socketId, deviceKey, connections.size());
93   - }
94   - }
95   -
96   - /**
97   - * 获取连接
98   - *
99   - * @param deviceKey 设备标识
100   - * @return WebSocket 连接
101   - */
102   - public ServerWebSocket getConnection(String deviceKey) {
103   - return connections.get(deviceKey);
104   - }
105   -
106   - /**
107   - * 根据 Socket ID 获取设备标识
108   - *
109   - * @param socketId WebSocket 文本框架 ID
110   - * @return 设备标识
111   - */
112   - public String getDeviceKeyBySocketId(String socketId) {
113   - return socketIdToDeviceKey.get(socketId);
114   - }
115   -
116   - /**
117   - * 检查设备是否在线
118   - *
119   - * @param deviceKey 设备标识
120   - * @return 是否在线
121   - */
122   - public boolean isOnline(String deviceKey) {
123   - return connections.containsKey(deviceKey);
124   - }
125   -
126   - /**
127   - * 获取当前连接数
128   - *
129   - * @return 连接数
130   - */
131   - public int getConnectionCount() {
132   - return connections.size();
133   - }
134   -
135   - /**
136   - * 关闭所有连接
137   - */
138   - public void closeAllConnections() {
139   - connections.forEach((deviceKey, socket) -> {
140   - try {
141   - socket.close();
142   - log.info("[closeAllConnections][关闭设备连接,deviceKey: {}]", deviceKey);
143   - } catch (Exception e) {
144   - log.error("[closeAllConnections][关闭设备连接失败,deviceKey: {}]", deviceKey, e);
145   - }
146   - });
147   - connections.clear();
148   - deviceKeyToSocketId.clear();
149   - socketIdToDeviceKey.clear();
150   - deviceSubscriptions.clear();
151   - log.info("[closeAllConnections][所有连接已关闭]");
152   - }
153   -
154   - // ==================== 订阅管理方法 ====================
155   -
156   - /**
157   - * 添加订阅
158   - *
159   - * @param deviceKey 设备标识
160   - * @param topic 订阅主题
161   - */
162   - public void addSubscription(String deviceKey, String topic) {
163   - deviceSubscriptions.computeIfAbsent(deviceKey, k -> new CopyOnWriteArraySet<>()).add(topic);
164   - log.debug("[addSubscription][设备订阅主题,deviceKey: {},topic: {}]", deviceKey, topic);
165   - }
166   -
167   - /**
168   - * 移除订阅
169   - *
170   - * @param deviceKey 设备标识
171   - * @param topic 订阅主题
172   - */
173   - public void removeSubscription(String deviceKey, String topic) {
174   - Set<String> topics = deviceSubscriptions.get(deviceKey);
175   - if (topics != null) {
176   - topics.remove(topic);
177   - log.debug("[removeSubscription][设备取消订阅,deviceKey: {},topic: {}]", deviceKey, topic);
178   - }
179   - }
180   -
181   - /**
182   - * 检查设备是否订阅了指定主题
183   - * 支持 MQTT 通配符匹配(+ 和 #)
184   - *
185   - * @param deviceKey 设备标识
186   - * @param topic 发布主题
187   - * @return 是否匹配
188   - */
189   - public boolean isSubscribed(String deviceKey, String topic) {
190   - Set<String> subscriptions = deviceSubscriptions.get(deviceKey);
191   - if (CollUtil.isEmpty(subscriptions)) {
192   - return false;
193   - }
194   -
195   - // 检查是否有匹配的订阅
196   - for (String subscription : subscriptions) {
197   - if (topicMatches(subscription, topic)) {
198   - return true;
199   - }
200   - }
201   - return false;
202   - }
203   -
204   - /**
205   - * 获取设备的所有订阅
206   - *
207   - * @param deviceKey 设备标识
208   - * @return 订阅主题集合
209   - */
210   - public Set<String> getSubscriptions(String deviceKey) {
211   - return deviceSubscriptions.get(deviceKey);
212   - }
213   -
214   - // TODO @haohao:这个方法,是不是也可以考虑抽到 IotMqttTopicUtils 里面去哈;感觉更简洁一点?
215   - /**
216   - * MQTT 主题匹配
217   - * 支持通配符:
218   - * - +:匹配单层主题
219   - * - #:匹配多层主题(必须在末尾)
220   - *
221   - * @param subscription 订阅主题(可能包含通配符)
222   - * @param topic 发布主题(不包含通配符)
223   - * @return 是否匹配
224   - */
225   - private boolean topicMatches(String subscription, String topic) {
226   - // 完全匹配
227   - if (subscription.equals(topic)) {
228   - return true;
229   - }
230   -
231   - // 不包含通配符
232   - // TODO @haohao:这里要不要枚举下哈;+ #
233   - if (!subscription.contains("+") && !subscription.contains("#")) {
234   - return false;
235   - }
236   -
237   - String[] subscriptionParts = subscription.split("/");
238   - String[] topicParts = topic.split("/");
239   - int i = 0;
240   - for (; i < subscriptionParts.length && i < topicParts.length; i++) {
241   - String subPart = subscriptionParts[i];
242   - String topicPart = topicParts[i];
243   -
244   - // # 匹配剩余所有层级,且必须在末尾
245   - if (subPart.equals("#")) {
246   - return i == subscriptionParts.length - 1;
247   - }
248   -
249   - // 不是通配符且不匹配
250   - if (!subPart.equals("+") && !subPart.equals(topicPart)) {
251   - return false;
252   - }
253   - }
254   -
255   - // 检查是否都匹配完
256   - return i == subscriptionParts.length && i == topicParts.length;
257   - }
258   -
259   -}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqttws/package-info.java deleted
1   -/**
2   - * IoT 网关 MQTT WebSocket 协议实现
3   - * <p>
4   - * 基于 Vert.x 实现 MQTT over WebSocket 服务端,支持:
5   - * - 标准 MQTT 3.1.1 协议
6   - * - WebSocket 协议升级
7   - * - SSL/TLS 加密(wss://)
8   - * - 设备认证与连接管理
9   - * - QoS 0/1/2 消息质量保证
10   - * - 双向消息通信(上行/下行)
11   - *
12   - * @author 芋道源码
13   - */
14   -package cn.iocoder.yudao.module.iot.gateway.protocol.mqttws;
15   -
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqttws/router/IotMqttWsDownstreamHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.router;
2   -
3   -import cn.hutool.core.util.StrUtil;
4   -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
5   -import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
6   -import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
7   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.manager.IotMqttWsConnectionManager;
8   -import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
9   -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
10   -import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils;
11   -import io.vertx.core.buffer.Buffer;
12   -import io.vertx.core.http.ServerWebSocket;
13   -import lombok.extern.slf4j.Slf4j;
14   -
15   -import java.util.concurrent.atomic.AtomicInteger;
16   -
17   -/**
18   - * IoT MQTT WebSocket 下行消息处理器
19   - * <p>
20   - * 处理从消息总线发送到设备的消息,包括:
21   - * - 属性设置
22   - * - 服务调用
23   - * - 事件通知
24   - *
25   - * @author 芋道源码
26   - */
27   -@Slf4j
28   -public class IotMqttWsDownstreamHandler {
29   -
30   - private final IotDeviceMessageService deviceMessageService;
31   -
32   - private final IotDeviceService deviceService;
33   -
34   - private final IotMqttWsConnectionManager connectionManager;
35   -
36   - /**
37   - * 消息 ID 生成器(用于发布消息)
38   - */
39   - private final AtomicInteger messageIdGenerator = new AtomicInteger(1);
40   -
41   - public IotMqttWsDownstreamHandler(IotDeviceMessageService deviceMessageService,
42   - IotDeviceService deviceService,
43   - IotMqttWsConnectionManager connectionManager) {
44   - this.deviceMessageService = deviceMessageService;
45   - this.deviceService = deviceService;
46   - this.connectionManager = connectionManager;
47   - }
48   -
49   - /**
50   - * 处理下行消息
51   - *
52   - * @param message 设备消息
53   - * @return 是否处理成功
54   - */
55   - public boolean handleDownstreamMessage(IotDeviceMessage message) {
56   - try {
57   - // 1. 基础校验
58   - if (message == null || message.getDeviceId() == null) {
59   - log.warn("[handleDownstreamMessage][消息或设备 ID 为空,忽略处理]");
60   - return false;
61   - }
62   -
63   - // 2. 获取设备信息
64   - IotDeviceRespDTO deviceInfo = deviceService.getDeviceFromCache(message.getDeviceId());
65   - if (deviceInfo == null) {
66   - log.warn("[handleDownstreamMessage][设备不存在,设备 ID:{}]", message.getDeviceId());
67   - return false;
68   - }
69   -
70   - // 3. 构建设备标识
71   - String deviceKey = deviceInfo.getProductKey() + ":" + deviceInfo.getDeviceName();
72   -
73   - // 4. 检查设备是否在线
74   - if (!connectionManager.isOnline(deviceKey)) {
75   - log.warn("[handleDownstreamMessage][设备离线,无法发送消息,deviceKey: {}]", deviceKey);
76   - return false;
77   - }
78   -
79   - // 5. 构建主题
80   - String topic = buildDownstreamTopic(message, deviceInfo);
81   - if (StrUtil.isBlank(topic)) {
82   - log.warn("[handleDownstreamMessage][主题构建失败,设备 ID:{},方法:{}]",
83   - message.getDeviceId(), message.getMethod());
84   - return false;
85   - }
86   -
87   - // 6. 检查设备是否订阅了该主题
88   - if (!connectionManager.isSubscribed(deviceKey, topic)) {
89   - log.warn("[handleDownstreamMessage][设备未订阅该主题,deviceKey: {},topic: {}]", deviceKey, topic);
90   - return false;
91   - }
92   -
93   - // 8. 编码消息
94   - byte[] payload = deviceMessageService.encodeDeviceMessage(message,
95   - deviceInfo.getProductKey(), deviceInfo.getDeviceName());
96   - if (payload == null || payload.length == 0) {
97   - log.warn("[handleDownstreamMessage][消息编码失败,设备 ID:{}]", message.getDeviceId());
98   - return false;
99   - }
100   -
101   - // 9. 发送消息到设备
102   - return sendMessageToDevice(deviceKey, topic, payload, 1);
103   - } catch (Exception e) {
104   - if (message != null) {
105   - log.error("[handleDownstreamMessage][处理下行消息异常,设备 ID:{},错误:{}]",
106   - message.getDeviceId(), e.getMessage(), e);
107   - }
108   - return false;
109   - }
110   - }
111   -
112   - /**
113   - * 构建下行消息主题
114   - *
115   - * @param message 设备消息
116   - * @param deviceInfo 设备信息
117   - * @return 主题
118   - */
119   - private String buildDownstreamTopic(IotDeviceMessage message, IotDeviceRespDTO deviceInfo) {
120   - String method = message.getMethod();
121   - if (StrUtil.isBlank(method)) {
122   - return null;
123   - }
124   -
125   - // 使用工具类构建主题,支持回复消息处理
126   - boolean isReply = IotDeviceMessageUtils.isReplyMessage(message);
127   - return IotMqttTopicUtils.buildTopicByMethod(method, deviceInfo.getProductKey(),
128   - deviceInfo.getDeviceName(), isReply);
129   - }
130   -
131   - /**
132   - * 发送消息到设备
133   - *
134   - * @param deviceKey 设备标识(productKey:deviceName)
135   - * @param topic 主题
136   - * @param payload 消息内容
137   - * @param qos QoS 级别
138   - * @return 是否发送成功
139   - */
140   - private boolean sendMessageToDevice(String deviceKey, String topic, byte[] payload, int qos) {
141   - // 获取设备连接
142   - ServerWebSocket socket = connectionManager.getConnection(deviceKey);
143   - if (socket == null) {
144   - log.warn("[sendMessageToDevice][设备未连接,deviceKey: {}]", deviceKey);
145   - return false;
146   - }
147   -
148   - try {
149   - int messageId = qos > 0 ? generateMessageId() : 0;
150   -
151   - // 手动编码 MQTT PUBLISH 消息
152   - io.netty.buffer.ByteBuf byteBuf = io.netty.buffer.Unpooled.buffer();
153   -
154   - // 固定头:消息类型(PUBLISH=3) + DUP(0) + QoS + RETAIN
155   - int fixedHeaderByte1 = 0x30 | (qos << 1); // PUBLISH类型
156   - byteBuf.writeByte(fixedHeaderByte1);
157   -
158   - // 计算剩余长度
159   - int topicLength = topic.getBytes().length;
160   - int remainingLength = 2 + topicLength + (qos > 0 ? 2 : 0) + payload.length;
161   -
162   - // 写入剩余长度(简化版本,假设小于 128 字节)
163   - if (remainingLength < 128) {
164   - byteBuf.writeByte(remainingLength);
165   - } else {
166   - // 处理大于 127 的情况
167   - int x = remainingLength;
168   - do {
169   - int encodedByte = x % 128;
170   - x = x / 128;
171   - if (x > 0) {
172   - encodedByte = encodedByte | 128;
173   - }
174   - byteBuf.writeByte(encodedByte);
175   - } while (x > 0);
176   - }
177   -
178   - // 可变头:主题名称
179   - byteBuf.writeShort(topicLength);
180   - byteBuf.writeBytes(topic.getBytes());
181   -
182   - // 可变头:消息 ID(仅 QoS > 0 时)
183   - if (qos > 0) {
184   - byteBuf.writeShort(messageId);
185   - }
186   -
187   - // 有效载荷
188   - byteBuf.writeBytes(payload);
189   -
190   - // 发送
191   - byte[] bytes = new byte[byteBuf.readableBytes()];
192   - byteBuf.readBytes(bytes);
193   - byteBuf.release();
194   - socket.writeBinaryMessage(Buffer.buffer(bytes));
195   -
196   - log.info("[sendMessageToDevice][消息已发送到设备,deviceKey: {},topic: {},qos: {},messageId: {}]",
197   - deviceKey, topic, qos, messageId);
198   - return true;
199   - } catch (Exception e) {
200   - log.error("[sendMessageToDevice][发送消息到设备失败,deviceKey: {},topic: {}]", deviceKey, topic, e);
201   - return false;
202   - }
203   - }
204   -
205   - /**
206   - * 生成消息 ID
207   - *
208   - * @return 消息 ID
209   - */
210   - private int generateMessageId() {
211   - int id = messageIdGenerator.getAndIncrement();
212   - // MQTT 消息 ID 范围是 1-65535
213   - // TODO @haohao:并发可能有问题;
214   - if (id > 65535) {
215   - messageIdGenerator.set(1);
216   - return 1;
217   - }
218   - return id;
219   - }
220   -
221   -}
yudao-module-iot/yudao-module-iot-gateway/src/main/java/cn/iocoder/yudao/module/iot/gateway/protocol/mqttws/router/IotMqttWsUpstreamHandler.java deleted
1   -package cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.router;
2   -
3   -import cn.hutool.core.util.BooleanUtil;
4   -import cn.hutool.core.util.IdUtil;
5   -import cn.hutool.core.util.StrUtil;
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.biz.dto.IotDeviceGetReqDTO;
11   -import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
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.core.util.IotDeviceAuthUtils;
15   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.IotMqttWsUpstreamProtocol;
16   -import cn.iocoder.yudao.module.iot.gateway.protocol.mqttws.manager.IotMqttWsConnectionManager;
17   -import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
18   -import io.netty.buffer.ByteBuf;
19   -import io.netty.buffer.Unpooled;
20   -import io.netty.channel.embedded.EmbeddedChannel;
21   -import io.netty.handler.codec.DecoderException;
22   -import io.netty.handler.codec.mqtt.*;
23   -import io.vertx.core.buffer.Buffer;
24   -import io.vertx.core.http.ServerWebSocket;
25   -import lombok.extern.slf4j.Slf4j;
26   -
27   -import java.nio.charset.StandardCharsets;
28   -import java.util.concurrent.ConcurrentHashMap;
29   -import java.util.concurrent.atomic.AtomicInteger;
30   -
31   -/**
32   - * IoT MQTT WebSocket 上行消息处理器
33   - * <p>
34   - * 处理来自设备的 MQTT 消息,包括:
35   - * - CONNECT:设备连接认证
36   - * - PUBLISH:设备发布消息
37   - * - SUBSCRIBE:设备订阅主题
38   - * - UNSUBSCRIBE:设备取消订阅
39   - * - PINGREQ:心跳请求
40   - * - DISCONNECT:设备断开连接
41   - *
42   - * @author 芋道源码
43   - */
44   -@Slf4j
45   -public class IotMqttWsUpstreamHandler {
46   -
47   - private final IotMqttWsUpstreamProtocol upstreamProtocol;
48   -
49   - private final IotDeviceCommonApi deviceApi;
50   -
51   - private final IotDeviceMessageService messageService;
52   -
53   - private final IotMqttWsConnectionManager connectionManager;
54   -
55   - /**
56   - * 存储 WebSocket 连接到 Socket ID 的映射
57   - * Key: WebSocket 对象
58   - * Value: Socket ID(UUID)
59   - */
60   - private final ConcurrentHashMap<ServerWebSocket, String> socketIdMap = new ConcurrentHashMap<>();
61   -
62   - /**
63   - * 存储 Socket ID 对应的设备信息
64   - * Key: Socket ID(UUID)
65   - * Value: 设备信息
66   - */
67   - private final ConcurrentHashMap<String, IotDeviceRespDTO> socketDeviceMap = new ConcurrentHashMap<>();
68   -
69   - /**
70   - * 存储设备的消息 ID 生成器(用于 QoS > 0 的消息)
71   - */
72   - private final ConcurrentHashMap<String, AtomicInteger> deviceMessageIdMap = new ConcurrentHashMap<>();
73   -
74   - /**
75   - * MQTT 解码通道(用于解析 WebSocket 中的 MQTT 二进制消息)
76   - */
77   - private final ThreadLocal<EmbeddedChannel> decoderChannelThreadLocal = ThreadLocal
78   - .withInitial(() -> new EmbeddedChannel(new MqttDecoder()));
79   -
80   - /**
81   - * MQTT 编码通道(用于编码 MQTT 响应消息)
82   - */
83   - private final ThreadLocal<EmbeddedChannel> encoderChannelThreadLocal = ThreadLocal
84   - .withInitial(() -> new EmbeddedChannel(MqttEncoder.INSTANCE));
85   -
86   - public IotMqttWsUpstreamHandler(IotMqttWsUpstreamProtocol upstreamProtocol,
87   - IotDeviceMessageService messageService,
88   - IotMqttWsConnectionManager connectionManager) {
89   - this.upstreamProtocol = upstreamProtocol;
90   - this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
91   - this.messageService = messageService;
92   - this.connectionManager = connectionManager;
93   - }
94   -
95   - /**
96   - * 处理 WebSocket 连接
97   - *
98   - * @param socket WebSocket 连接
99   - */
100   - public void handle(ServerWebSocket socket) {
101   - // 生成唯一的 Socket ID(因为 MQTT 使用二进制协议,textHandlerID() 会返回 null)
102   - String socketId = IdUtil.simpleUUID();
103   - socketIdMap.put(socket, socketId);
104   -
105   - log.info("[handle][WebSocket 连接建立,socketId: {},remoteAddress: {}]",
106   - socketId, socket.remoteAddress());
107   -
108   - // 设置二进制数据处理器
109   - socket.binaryMessageHandler(buffer -> {
110   - try {
111   - handleMqttMessage(socket, buffer);
112   - } catch (Exception e) {
113   - log.error("[handle][处理 MQTT 消息异常,socketId: {}]", socketId, e);
114   - socket.close();
115   - }
116   - });
117   -
118   - // 设置关闭处理器
119   - socket.closeHandler(v -> {
120   - socketIdMap.remove(socket);
121   - IotDeviceRespDTO device = socketDeviceMap.remove(socketId);
122   - if (device != null) {
123   - String deviceKey = device.getProductKey() + ":" + device.getDeviceName();
124   - connectionManager.removeConnection(deviceKey);
125   - deviceMessageIdMap.remove(deviceKey);
126   - // 发送设备离线消息
127   - sendOfflineMessage(device);
128   - log.info("[handle][WebSocket 连接关闭,deviceKey: {},socketId: {}]", deviceKey, socketId);
129   - }
130   - });
131   -
132   - // 设置异常处理器
133   - socket.exceptionHandler(e -> {
134   - log.error("[handle][WebSocket 连接异常,socketId: {}]", socketId, e);
135   - socketIdMap.remove(socket);
136   - IotDeviceRespDTO device = socketDeviceMap.remove(socketId);
137   - if (device != null) {
138   - String deviceKey = device.getProductKey() + ":" + device.getDeviceName();
139   - connectionManager.removeConnection(deviceKey);
140   - deviceMessageIdMap.remove(deviceKey);
141   - }
142   - socket.close();
143   - });
144   - }
145   -
146   - /**
147   - * 处理 MQTT 消息
148   - *
149   - * @param socket WebSocket 连接
150   - * @param buffer 消息缓冲区
151   - */
152   - private void handleMqttMessage(ServerWebSocket socket, Buffer buffer) {
153   - String socketId = socketIdMap.get(socket);
154   - ByteBuf byteBuf = Unpooled.wrappedBuffer(buffer.getBytes());
155   -
156   - try {
157   - // 使用 EmbeddedChannel 解码 MQTT 消息
158   - EmbeddedChannel decoderChannel = decoderChannelThreadLocal.get();
159   - decoderChannel.writeInbound(byteBuf.retain());
160   -
161   - // 读取解码后的消息
162   - MqttMessage mqttMessage = decoderChannel.readInbound();
163   - if (mqttMessage == null) {
164   - log.warn("[handleMqttMessage][MQTT 消息解码失败,socketId: {}]", socketId);
165   - return;
166   - }
167   -
168   - MqttMessageType messageType = mqttMessage.fixedHeader().messageType();
169   - log.debug("[handleMqttMessage][收到 MQTT 消息,类型: {},socketId: {}]", messageType, socketId);
170   -
171   - // 根据消息类型分发处理
172   - switch (messageType) {
173   - case CONNECT:
174   - handleConnect(socket, (MqttConnectMessage) mqttMessage);
175   - break;
176   - case PUBLISH:
177   - handlePublish(socket, (MqttPublishMessage) mqttMessage);
178   - break;
179   - case PUBACK:
180   - handlePubAck(socket, mqttMessage);
181   - break;
182   - case PUBREC:
183   - handlePubRec(socket, mqttMessage);
184   - break;
185   - case PUBREL:
186   - handlePubRel(socket, mqttMessage);
187   - break;
188   - case PUBCOMP:
189   - handlePubComp(socket, mqttMessage);
190   - break;
191   - case SUBSCRIBE:
192   - handleSubscribe(socket, (MqttSubscribeMessage) mqttMessage);
193   - break;
194   - case UNSUBSCRIBE:
195   - handleUnsubscribe(socket, (MqttUnsubscribeMessage) mqttMessage);
196   - break;
197   - case PINGREQ:
198   - handlePingReq(socket);
199   - break;
200   - case DISCONNECT:
201   - handleDisconnect(socket);
202   - break;
203   - default:
204   - log.warn("[handleMqttMessage][不支持的消息类型: {},socketId: {}]", messageType, socketId);
205   - }
206   - } catch (DecoderException e) {
207   - log.error("[handleMqttMessage][MQTT 消息解码异常,socketId: {}]", socketId, e);
208   - socket.close();
209   - } catch (Exception e) {
210   - log.error("[handleMqttMessage][处理 MQTT 消息失败,socketId: {}]", socketId, e);
211   - socket.close();
212   - } finally {
213   - byteBuf.release();
214   - }
215   - }
216   -
217   - /**
218   - * 处理 CONNECT 消息(设备认证)
219   - */
220   - private void handleConnect(ServerWebSocket socket, MqttConnectMessage message) {
221   - String socketId = socketIdMap.get(socket);
222   - try {
223   - // 1. 解析 CONNECT 消息
224   - MqttConnectPayload payload = message.payload();
225   - String clientId = payload.clientIdentifier();
226   - String username = payload.userName();
227   - String password = payload.passwordInBytes() != null
228   - ? new String(payload.passwordInBytes(), StandardCharsets.UTF_8)
229   - : null;
230   -
231   - log.info("[handleConnect][收到 CONNECT 消息,clientId: {},username: {},socketId: {}]",
232   - clientId, username, socketId);
233   -
234   - // 2. 设备认证
235   - IotDeviceRespDTO device = authenticateDevice(clientId, username, password);
236   - if (device == null) {
237   - log.warn("[handleConnect][设备认证失败,clientId: {},socketId: {}]", clientId, socketId);
238   - sendConnAck(socket, MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD);
239   - socket.close();
240   - return;
241   - }
242   -
243   - // 3. 保存设备信息
244   - socketDeviceMap.put(socketId, device);
245   - String deviceKey = device.getProductKey() + ":" + device.getDeviceName();
246   - connectionManager.addConnection(deviceKey, socket, socketId);
247   - deviceMessageIdMap.put(deviceKey, new AtomicInteger(1));
248   -
249   - log.info("[handleConnect][设备认证成功,deviceId: {},deviceKey: {},socketId: {}]",
250   - device.getId(), deviceKey, socketId);
251   -
252   - // 4. 发送 CONNACK
253   - sendConnAck(socket, MqttConnectReturnCode.CONNECTION_ACCEPTED);
254   -
255   - // 5. 发送设备上线消息
256   - sendOnlineMessage(device);
257   - } catch (Exception e) {
258   - log.error("[handleConnect][处理 CONNECT 消息失败,socketId: {}]", socketId, e);
259   - sendConnAck(socket, MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE);
260   - socket.close();
261   - }
262   - }
263   -
264   - /**
265   - * 处理 PUBLISH 消息(设备发布消息)
266   - */
267   - private void handlePublish(ServerWebSocket socket, MqttPublishMessage message) {
268   - String socketId = socketIdMap.get(socket);
269   - IotDeviceRespDTO device = socketDeviceMap.get(socketId);
270   -
271   - if (device == null) {
272   - log.warn("[handlePublish][设备未认证,socketId: {}]", socketId);
273   - socket.close();
274   - return;
275   - }
276   -
277   - try {
278   - // 1. 解析 PUBLISH 消息
279   - MqttFixedHeader fixedHeader = message.fixedHeader();
280   - MqttPublishVariableHeader variableHeader = message.variableHeader();
281   - ByteBuf payload = message.payload();
282   -
283   - String topic = variableHeader.topicName();
284   - int messageId = variableHeader.packetId();
285   - MqttQoS qos = fixedHeader.qosLevel();
286   -
287   - log.debug("[handlePublish][收到 PUBLISH 消息,topic: {},messageId: {},QoS: {},deviceId: {}]",
288   - topic, messageId, qos, device.getId());
289   -
290   - // 2. 读取 payload
291   - byte[] payloadBytes = new byte[payload.readableBytes()];
292   - payload.readBytes(payloadBytes);
293   -
294   - // 3. 解码并发送消息
295   - IotDeviceMessage deviceMessage = messageService.decodeDeviceMessage(payloadBytes,
296   - device.getProductKey(), device.getDeviceName());
297   - if (deviceMessage != null) {
298   - deviceMessage.setServerId(upstreamProtocol.getServerId());
299   - messageService.sendDeviceMessage(deviceMessage, device.getProductKey(),
300   - device.getDeviceName(), upstreamProtocol.getServerId());
301   - log.info("[handlePublish][设备消息已发送,method: {},deviceId: {}]",
302   - deviceMessage.getMethod(), device.getId());
303   - }
304   -
305   - // 4. 根据 QoS 级别发送相应的确认消息
306   - if (qos == MqttQoS.AT_LEAST_ONCE) {
307   - // QoS 1:发送 PUBACK
308   - sendPubAck(socket, messageId);
309   - } else if (qos == MqttQoS.EXACTLY_ONCE) {
310   - // QoS 2:发送 PUBREC
311   - sendPubRec(socket, messageId);
312   - }
313   - // QoS 0 无需确认
314   - } catch (Exception e) {
315   - log.error("[handlePublish][处理 PUBLISH 消息失败,deviceId: {}]", device.getId(), e);
316   - }
317   - }
318   -
319   - /**
320   - * 处理 PUBACK 消息(QoS 1 确认)
321   - */
322   - private void handlePubAck(ServerWebSocket socket, MqttMessage message) {
323   - String socketId = socketIdMap.get(socket);
324   - IotDeviceRespDTO device = socketDeviceMap.get(socketId);
325   - if (device == null) {
326   - log.warn("[handlePubAck][设备未认证,socketId: {}]", socketId);
327   - socket.close();
328   - return;
329   - }
330   -
331   - int messageId = ((MqttMessageIdVariableHeader) message.variableHeader()).messageId();
332   - log.debug("[handlePubAck][收到 PUBACK,messageId: {},deviceId: {}]", messageId, device.getId());
333   - }
334   -
335   - /**
336   - * 处理 PUBREC 消息(QoS 2 第一步确认)
337   - */
338   - private void handlePubRec(ServerWebSocket socket, MqttMessage message) {
339   - String socketId = socketIdMap.get(socket);
340   - IotDeviceRespDTO device = socketDeviceMap.get(socketId);
341   - if (device == null) {
342   - log.warn("[handlePubRec][设备未认证,socketId: {}]", socketId);
343   - socket.close();
344   - return;
345   - }
346   -
347   - int messageId = ((MqttMessageIdVariableHeader) message.variableHeader()).messageId();
348   - log.debug("[handlePubRec][收到 PUBREC,messageId: {},deviceId: {}]", messageId, device.getId());
349   - // 发送 PUBREL
350   - sendPubRel(socket, messageId);
351   - }
352   -
353   - /**
354   - * 处理 PUBREL 消息(QoS 2 第二步)
355   - */
356   - private void handlePubRel(ServerWebSocket socket, MqttMessage message) {
357   - String socketId = socketIdMap.get(socket);
358   - IotDeviceRespDTO device = socketDeviceMap.get(socketId);
359   -
360   - if (device == null) {
361   - log.warn("[handlePubRel][设备未认证,socketId: {}]", socketId);
362   - socket.close();
363   - return;
364   - }
365   -
366   - int messageId = ((MqttMessageIdVariableHeader) message.variableHeader()).messageId();
367   - log.debug("[handlePubRel][收到 PUBREL,messageId: {},deviceId: {}]", messageId, device.getId());
368   - // 发送 PUBCOMP
369   - sendPubComp(socket, messageId);
370   - }
371   -
372   - /**
373   - * 处理 PUBCOMP 消息(QoS 2 完成确认)
374   - */
375   - private void handlePubComp(ServerWebSocket socket, MqttMessage message) {
376   - String socketId = socketIdMap.get(socket);
377   - IotDeviceRespDTO device = socketDeviceMap.get(socketId);
378   -
379   - if (device == null) {
380   - log.warn("[handlePubComp][设备未认证,socketId: {}]", socketId);
381   - socket.close();
382   - return;
383   - }
384   -
385   - int messageId = ((MqttMessageIdVariableHeader) message.variableHeader()).messageId();
386   - log.debug("[handlePubComp][收到 PUBCOMP,messageId: {},deviceId: {}]", messageId, device.getId());
387   - }
388   -
389   - /**
390   - * 处理 SUBSCRIBE 消息(设备订阅主题)
391   - */
392   - private void handleSubscribe(ServerWebSocket socket, MqttSubscribeMessage message) {
393   - String socketId = socketIdMap.get(socket);
394   - IotDeviceRespDTO device = socketDeviceMap.get(socketId);
395   - if (device == null) {
396   - log.warn("[handleSubscribe][设备未认证,socketId: {}]", socketId);
397   - socket.close();
398   - return;
399   - }
400   -
401   - try {
402   - // 1. 解析 SUBSCRIBE 消息
403   - int messageId = message.variableHeader().messageId();
404   - MqttSubscribePayload payload = message.payload();
405   - String deviceKey = device.getProductKey() + ":" + device.getDeviceName();
406   -
407   - log.info("[handleSubscribe][设备订阅请求,deviceKey: {},messageId: {},主题数量: {}]",
408   - deviceKey, messageId, payload.topicSubscriptions().size());
409   -
410   - // 2. 构建 QoS 列表并记录订阅信息
411   - int[] grantedQosList = new int[payload.topicSubscriptions().size()];
412   - for (int i = 0; i < payload.topicSubscriptions().size(); i++) {
413   - MqttTopicSubscription subscription = payload.topicSubscriptions().get(i);
414   - String topic = subscription.topicFilter();
415   - grantedQosList[i] = subscription.qualityOfService().value();
416   -
417   - // 记录订阅信息到连接管理器
418   - connectionManager.addSubscription(deviceKey, topic);
419   -
420   - log.info("[handleSubscribe][订阅主题: {},QoS: {},deviceKey: {}]",
421   - topic, subscription.qualityOfService(), deviceKey);
422   - }
423   -
424   - // 3. 发送 SUBACK
425   - sendSubAck(socket, messageId, grantedQosList);
426   - } catch (Exception e) {
427   - log.error("[handleSubscribe][处理 SUBSCRIBE 消息失败,deviceId: {}]", device.getId(), e);
428   - }
429   - }
430   -
431   - /**
432   - * 处理 UNSUBSCRIBE 消息(设备取消订阅)
433   - */
434   - private void handleUnsubscribe(ServerWebSocket socket, MqttUnsubscribeMessage message) {
435   - String socketId = socketIdMap.get(socket);
436   - IotDeviceRespDTO device = socketDeviceMap.get(socketId);
437   - if (device == null) {
438   - log.warn("[handleUnsubscribe][设备未认证,socketId: {}]", socketId);
439   - socket.close();
440   - return;
441   - }
442   -
443   - try {
444   - // 1. 解析 UNSUBSCRIBE 消息
445   - int messageId = message.variableHeader().messageId();
446   - MqttUnsubscribePayload payload = message.payload();
447   - String deviceKey = device.getProductKey() + ":" + device.getDeviceName();
448   -
449   - log.info("[handleUnsubscribe][设备取消订阅,deviceKey: {},messageId: {},主题数量: {}]",
450   - deviceKey, messageId, payload.topics().size());
451   -
452   - // 2. 移除订阅信息
453   - for (String topic : payload.topics()) {
454   - connectionManager.removeSubscription(deviceKey, topic);
455   - log.info("[handleUnsubscribe][取消订阅主题: {},deviceKey: {}]", topic, deviceKey);
456   - }
457   -
458   - // 3. 发送 UNSUBACK
459   - sendUnsubAck(socket, messageId);
460   - } catch (Exception e) {
461   - log.error("[handleUnsubscribe][处理 UNSUBSCRIBE 消息失败,deviceId: {}]", device.getId(), e);
462   - }
463   - }
464   -
465   - /**
466   - * 处理 PINGREQ 消息(心跳请求)
467   - */
468   - private void handlePingReq(ServerWebSocket socket) {
469   - String socketId = socketIdMap.get(socket);
470   - IotDeviceRespDTO device = socketDeviceMap.get(socketId);
471   - if (device == null) {
472   - log.warn("[handlePingReq][设备未认证,socketId: {}]", socketId);
473   - socket.close();
474   - return;
475   - }
476   -
477   - log.debug("[handlePingReq][收到心跳请求,deviceId: {}]", device.getId());
478   - // 发送 PINGRESP
479   - sendPingResp(socket);
480   - }
481   -
482   - /**
483   - * 处理 DISCONNECT 消息(设备断开连接)
484   - */
485   - private void handleDisconnect(ServerWebSocket socket) {
486   - String socketId = socketIdMap.get(socket);
487   - IotDeviceRespDTO device = socketDeviceMap.remove(socketId);
488   - if (device != null) {
489   - String deviceKey = device.getProductKey() + ":" + device.getDeviceName();
490   - connectionManager.removeConnection(deviceKey);
491   - deviceMessageIdMap.remove(deviceKey);
492   - sendOfflineMessage(device);
493   - log.info("[handleDisconnect][设备主动断开连接,deviceKey: {}]", deviceKey);
494   - }
495   -
496   - socket.close();
497   - }
498   -
499   - // ==================== 设备认证和状态相关方法 ====================
500   -
501   - /**
502   - * 设备认证
503   - */
504   - private IotDeviceRespDTO authenticateDevice(String clientId, String username, String password) {
505   - try {
506   - // 1. 参数校验
507   - if (StrUtil.hasEmpty(clientId, username, password)) {
508   - log.warn("[authenticateDevice][认证参数不完整,clientId: {},username: {}]", clientId, username);
509   - return null;
510   - }
511   -
512   - // 2. 构建认证参数并调用 API
513   - IotDeviceAuthReqDTO authParams = new IotDeviceAuthReqDTO()
514   - .setClientId(clientId)
515   - .setUsername(username)
516   - .setPassword(password);
517   -
518   - CommonResult<Boolean> authResult = deviceApi.authDevice(authParams);
519   - if (!authResult.isSuccess() || !BooleanUtil.isTrue(authResult.getData())) {
520   - log.warn("[authenticateDevice][设备认证失败,clientId: {}]", clientId);
521   - return null;
522   - }
523   -
524   - // 3. 获取设备信息
525   - IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(username);
526   - if (deviceInfo == null) {
527   - log.warn("[authenticateDevice][用户名格式不正确,username: {}]", username);
528   - return null;
529   - }
530   -
531   - IotDeviceGetReqDTO getReqDTO = new IotDeviceGetReqDTO()
532   - .setProductKey(deviceInfo.getProductKey())
533   - .setDeviceName(deviceInfo.getDeviceName());
534   -
535   - CommonResult<IotDeviceRespDTO> deviceResult = deviceApi.getDevice(getReqDTO);
536   - if (!deviceResult.isSuccess() || deviceResult.getData() == null) {
537   - log.warn("[authenticateDevice][获取设备信息失败,username: {}]", username);
538   - return null;
539   - }
540   -
541   - return deviceResult.getData();
542   - } catch (Exception e) {
543   - log.error("[authenticateDevice][设备认证异常,clientId: {}]", clientId, e);
544   - return null;
545   - }
546   - }
547   -
548   - /**
549   - * 发送设备上线消息
550   - */
551   - private void sendOnlineMessage(IotDeviceRespDTO device) {
552   - try {
553   - IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline();
554   - messageService.sendDeviceMessage(onlineMessage, device.getProductKey(),
555   - device.getDeviceName(), upstreamProtocol.getServerId());
556   - log.info("[sendOnlineMessage][设备上线,deviceId: {}]", device.getId());
557   - } catch (Exception e) {
558   - log.error("[sendOnlineMessage][发送设备上线消息失败,deviceId: {}]", device.getId(), e);
559   - }
560   - }
561   -
562   - /**
563   - * 发送设备离线消息
564   - */
565   - private void sendOfflineMessage(IotDeviceRespDTO device) {
566   - try {
567   - IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline();
568   - messageService.sendDeviceMessage(offlineMessage, device.getProductKey(),
569   - device.getDeviceName(), upstreamProtocol.getServerId());
570   - log.info("[sendOfflineMessage][设备离线,deviceId: {}]", device.getId());
571   - } catch (Exception e) {
572   - log.error("[sendOfflineMessage][发送设备离线消息失败,deviceId: {}]", device.getId(), e);
573   - }
574   - }
575   -
576   - // ==================== 发送响应消息的辅助方法 ====================
577   -
578   - /**
579   - * 发送 CONNACK 消息
580   - */
581   - private void sendConnAck(ServerWebSocket socket, MqttConnectReturnCode returnCode) {
582   - try {
583   - // 构建 CONNACK 消息
584   - MqttFixedHeader fixedHeader = new MqttFixedHeader(
585   - MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
586   - MqttConnAckVariableHeader variableHeader = new MqttConnAckVariableHeader(returnCode, false);
587   - MqttConnAckMessage connAckMessage = new MqttConnAckMessage(fixedHeader, variableHeader);
588   -
589   - // 编码并发送
590   - sendMqttMessage(socket, connAckMessage);
591   - log.debug("[sendConnAck][发送 CONNACK 消息,returnCode: {}]", returnCode);
592   - } catch (Exception e) {
593   - log.error("[sendConnAck][发送 CONNACK 消息失败]", e);
594   - }
595   - }
596   -
597   - /**
598   - * 发送 PUBACK 消息(QoS 1 确认)
599   - */
600   - private void sendPubAck(ServerWebSocket socket, int messageId) {
601   - try {
602   - // 构建 PUBACK 消息
603   - MqttFixedHeader fixedHeader = new MqttFixedHeader(
604   - MqttMessageType.PUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
605   - MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(messageId);
606   - MqttMessage pubAckMessage = new MqttMessage(fixedHeader, variableHeader);
607   -
608   - // 编码并发送
609   - sendMqttMessage(socket, pubAckMessage);
610   - log.debug("[sendPubAck][发送 PUBACK 消息,messageId: {}]", messageId);
611   - } catch (Exception e) {
612   - log.error("[sendPubAck][发送 PUBACK 消息失败,messageId: {}]", messageId, e);
613   - }
614   - }
615   -
616   - /**
617   - * 发送 PUBREC 消息(QoS 2 第一步确认)
618   - */
619   - private void sendPubRec(ServerWebSocket socket, int messageId) {
620   - try {
621   - // 构建 PUBREC 消息
622   - MqttFixedHeader fixedHeader = new MqttFixedHeader(
623   - MqttMessageType.PUBREC, false, MqttQoS.AT_MOST_ONCE, false, 0);
624   - MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(messageId);
625   - MqttMessage pubRecMessage = new MqttMessage(fixedHeader, variableHeader);
626   -
627   - // 编码并发送
628   - sendMqttMessage(socket, pubRecMessage);
629   - log.debug("[sendPubRec][发送 PUBREC 消息,messageId: {}]", messageId);
630   - } catch (Exception e) {
631   - log.error("[sendPubRec][发送 PUBREC 消息失败,messageId: {}]", messageId, e);
632   - }
633   - }
634   -
635   - /**
636   - * 发送 PUBREL 消息(QoS 2 第二步)
637   - */
638   - private void sendPubRel(ServerWebSocket socket, int messageId) {
639   - try {
640   - // 构建 PUBREL 消息
641   - MqttFixedHeader fixedHeader = new MqttFixedHeader(
642   - MqttMessageType.PUBREL, false, MqttQoS.AT_LEAST_ONCE, false, 0);
643   - MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(messageId);
644   - MqttMessage pubRelMessage = new MqttMessage(fixedHeader, variableHeader);
645   -
646   - // 编码并发送
647   - sendMqttMessage(socket, pubRelMessage);
648   - log.debug("[sendPubRel][发送 PUBREL 消息,messageId: {}]", messageId);
649   - } catch (Exception e) {
650   - log.error("[sendPubRel][发送 PUBREL 消息失败,messageId: {}]", messageId, e);
651   - }
652   - }
653   -
654   - /**
655   - * 发送 PUBCOMP 消息(QoS 2 完成确认)
656   - */
657   - private void sendPubComp(ServerWebSocket socket, int messageId) {
658   - try {
659   - // 构建 PUBCOMP 消息
660   - MqttFixedHeader fixedHeader = new MqttFixedHeader(
661   - MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 0);
662   - MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(messageId);
663   - MqttMessage pubCompMessage = new MqttMessage(fixedHeader, variableHeader);
664   -
665   - // 编码并发送
666   - sendMqttMessage(socket, pubCompMessage);
667   - log.debug("[sendPubComp][发送 PUBCOMP 消息,messageId: {}]", messageId);
668   - } catch (Exception e) {
669   - log.error("[sendPubComp][发送 PUBCOMP 消息失败,messageId: {}]", messageId, e);
670   - }
671   - }
672   -
673   - /**
674   - * 发送 SUBACK 消息
675   - */
676   - private void sendSubAck(ServerWebSocket socket, int messageId, int[] grantedQosList) {
677   - try {
678   - // 构建 SUBACK 消息
679   - MqttFixedHeader fixedHeader = new MqttFixedHeader(
680   - MqttMessageType.SUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
681   - MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(messageId);
682   - MqttSubAckPayload payload = new MqttSubAckPayload(grantedQosList);
683   - MqttSubAckMessage subAckMessage = new MqttSubAckMessage(fixedHeader, variableHeader, payload);
684   -
685   - // 编码并发送
686   - sendMqttMessage(socket, subAckMessage);
687   - log.debug("[sendSubAck][发送 SUBACK 消息,messageId: {},主题数量: {}]", messageId, grantedQosList.length);
688   - } catch (Exception e) {
689   - log.error("[sendSubAck][发送 SUBACK 消息失败,messageId: {}]", messageId, e);
690   - }
691   - }
692   -
693   - /**
694   - * 发送 UNSUBACK 消息
695   - */
696   - private void sendUnsubAck(ServerWebSocket socket, int messageId) {
697   - try {
698   - // 构建 UNSUBACK 消息
699   - MqttFixedHeader fixedHeader = new MqttFixedHeader(
700   - MqttMessageType.UNSUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0);
701   - MqttMessageIdVariableHeader variableHeader = MqttMessageIdVariableHeader.from(messageId);
702   - MqttUnsubAckMessage unsubAckMessage = new MqttUnsubAckMessage(fixedHeader, variableHeader);
703   -
704   - // 编码并发送
705   - sendMqttMessage(socket, unsubAckMessage);
706   - log.debug("[sendUnsubAck][发送 UNSUBACK 消息,messageId: {}]", messageId);
707   - } catch (Exception e) {
708   - log.error("[sendUnsubAck][发送 UNSUBACK 消息失败,messageId: {}]", messageId, e);
709   - }
710   - }
711   -
712   - /**
713   - * 发送 PINGRESP 消息
714   - */
715   - private void sendPingResp(ServerWebSocket socket) {
716   - try {
717   - // 构建 PINGRESP 消息
718   - MqttFixedHeader fixedHeader = new MqttFixedHeader(
719   - MqttMessageType.PINGRESP, false, MqttQoS.AT_MOST_ONCE, false, 0);
720   - MqttMessage pingRespMessage = new MqttMessage(fixedHeader);
721   -
722   - // 编码并发送
723   - sendMqttMessage(socket, pingRespMessage);
724   - log.debug("[sendPingResp][发送 PINGRESP 消息]");
725   - } catch (Exception e) {
726   - log.error("[sendPingResp][发送 PINGRESP 消息失败]", e);
727   - }
728   - }
729   -
730   - /**
731   - * 发送 MQTT 消息到 WebSocket
732   - */
733   - private void sendMqttMessage(ServerWebSocket socket, MqttMessage mqttMessage) {
734   - ByteBuf byteBuf = null;
735   - try {
736   - // 使用 EmbeddedChannel 编码 MQTT 消息
737   - EmbeddedChannel encoderChannel = encoderChannelThreadLocal.get();
738   - encoderChannel.writeOutbound(mqttMessage);
739   -
740   - // 读取编码后的 ByteBuf
741   - byteBuf = encoderChannel.readOutbound();
742   - if (byteBuf != null) {
743   - byte[] bytes = new byte[byteBuf.readableBytes()];
744   - byteBuf.readBytes(bytes);
745   - socket.writeBinaryMessage(Buffer.buffer(bytes));
746   - }
747   - } finally {
748   - if (byteBuf != null) {
749   - byteBuf.release();
750   - }
751   - }
752   - }
753   -
754   -}
yudao-module-iot/yudao-module-iot-gateway/src/main/resources/application.yaml
... ... @@ -115,23 +115,10 @@ yudao:
115 115 connect-timeout-seconds: 60
116 116 ssl-enabled: false
117 117 # ====================================
118   - # 针对引入的 MQTT WebSocket 组件的配置
119   - # ====================================
120   - mqtt-ws:
121   - enabled: false # 是否启用 MQTT WebSocket
122   - port: 8083 # WebSocket 服务端口
123   - path: /mqtt # WebSocket 路径
124   - max-message-size: 8192 # 最大消息大小(字节)
125   - max-frame-size: 65536 # 最大帧大小(字节)
126   - connect-timeout-seconds: 60 # 连接超时时间(秒)
127   - keep-alive-timeout-seconds: 300 # 保持连接超时时间(秒)
128   - ssl-enabled: false # 是否启用 SSL(wss://)
129   - sub-protocol: mqtt # WebSocket 子协议
130   - # ====================================
131 118 # 针对引入的 CoAP 组件的配置
132 119 # ====================================
133 120 coap:
134   - enabled: true # 是否启用 CoAP 协议
  121 + enabled: false # 是否启用 CoAP 协议
135 122 port: 5683 # CoAP 服务端口(默认 5683)
136 123 max-message-size: 1024 # 最大消息大小(字节)
137 124 ack-timeout: 2000 # ACK 超时时间(毫秒)
... ...