2026-06-26 20b96473f2520590a0dca6b775b81e3ea06a77a0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
package cn.iocoder.yudao.module.iot.gateway.protocol.mqtt;
 
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
import cn.iocoder.yudao.module.iot.core.enums.IotProtocolTypeEnum;
import cn.iocoder.yudao.module.iot.core.messagebus.core.IotMessageBus;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceMessageUtils;
import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties;
import cn.iocoder.yudao.module.iot.gateway.config.IotGatewayProperties.ProtocolProperties;
import cn.iocoder.yudao.module.iot.gateway.protocol.IotProtocol;
import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream.IotMqttDownstreamHandler;
import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.downstream.IotMqttDownstreamSubscriber;
import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttAuthHandler;
import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttRegisterHandler;
import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.handler.upstream.IotMqttUpstreamHandler;
import cn.iocoder.yudao.module.iot.gateway.protocol.mqtt.manager.IotMqttConnectionManager;
import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
import cn.iocoder.yudao.module.iot.gateway.util.IotMqttTopicUtils;
import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
import io.netty.handler.codec.mqtt.MqttQoS;
import io.vertx.core.Vertx;
import io.vertx.core.net.PemKeyCertOptions;
import io.vertx.mqtt.MqttEndpoint;
import io.vertx.mqtt.MqttServer;
import io.vertx.mqtt.MqttServerOptions;
import io.vertx.mqtt.MqttTopicSubscription;
import io.vertx.mqtt.messages.MqttPublishMessage;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
 
import java.util.ArrayList;
import java.util.List;
 
/**
 * IoT 网关 MQTT 协议:接收设备上行消息
 *
 * @author 芋道源码
 */
@Slf4j
public class IotMqttProtocol implements IotProtocol {
 
    /**
     * 注册连接的 clientId 标识
     *
     * @see #handleEndpoint(MqttEndpoint)
     */
    private static final String AUTH_TYPE_REGISTER = "|authType=register|";
 
    /**
     * 协议配置
     */
    private final ProtocolProperties properties;
    /**
     * 服务器 ID(用于消息追踪,全局唯一)
     */
    @Getter
    private final String serverId;
 
    /**
     * 运行状态
     */
    @Getter
    private volatile boolean running = false;
 
    /**
     * Vert.x 实例
     */
    private Vertx vertx;
    /**
     * MQTT 服务器
     */
    private MqttServer mqttServer;
 
    /**
     * 连接管理器
     */
    private final IotMqttConnectionManager connectionManager;
    /**
     * 下行消息订阅者
     */
    private IotMqttDownstreamSubscriber downstreamSubscriber;
 
    private final IotDeviceMessageService deviceMessageService;
 
    private final IotMqttAuthHandler authHandler;
    private final IotMqttRegisterHandler registerHandler;
    private final IotMqttUpstreamHandler upstreamHandler;
 
    public IotMqttProtocol(ProtocolProperties properties) {
        IotMqttConfig mqttConfig = properties.getMqtt();
        Assert.notNull(mqttConfig, "MQTT 协议配置(mqtt)不能为空");
        this.properties = properties;
        this.serverId = IotDeviceMessageUtils.generateServerId(properties.getPort());
 
        // 初始化连接管理器
        this.connectionManager = new IotMqttConnectionManager();
 
        // 初始化 Handler
        this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
        IotDeviceCommonApi deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
        this.authHandler = new IotMqttAuthHandler(connectionManager, deviceMessageService, deviceApi, serverId);
        this.registerHandler = new IotMqttRegisterHandler(connectionManager, deviceMessageService);
        this.upstreamHandler = new IotMqttUpstreamHandler(connectionManager, deviceMessageService, serverId);
    }
 
    @Override
    public String getId() {
        return properties.getId();
    }
 
    @Override
    public IotProtocolTypeEnum getType() {
        return IotProtocolTypeEnum.MQTT;
    }
 
    @Override
    public void start() {
        if (running) {
            log.warn("[start][IoT MQTT 协议 {} 已经在运行中]", getId());
            return;
        }
 
        // 1.1 创建 Vertx 实例
        this.vertx = Vertx.vertx();
 
        // 1.2 创建服务器选项
        IotMqttConfig mqttConfig = properties.getMqtt();
        MqttServerOptions options = new MqttServerOptions()
                .setPort(properties.getPort())
                .setMaxMessageSize(mqttConfig.getMaxMessageSize())
                .setTimeoutOnConnect(mqttConfig.getConnectTimeoutSeconds());
        IotGatewayProperties.SslConfig sslConfig = properties.getSsl();
        if (sslConfig != null && Boolean.TRUE.equals(sslConfig.getSsl())) {
            PemKeyCertOptions pemKeyCertOptions = new PemKeyCertOptions()
                    .setKeyPath(sslConfig.getSslKeyPath())
                    .setCertPath(sslConfig.getSslCertPath());
            options.setSsl(true).setKeyCertOptions(pemKeyCertOptions);
        }
 
        // 1.3 创建服务器并设置连接处理器
        mqttServer = MqttServer.create(vertx, options);
        mqttServer.endpointHandler(this::handleEndpoint);
 
        // 1.4 启动 MQTT 服务器
        try {
            mqttServer.listen().result();
            running = true;
            log.info("[start][IoT MQTT 协议 {} 启动成功,端口:{},serverId:{}]",
                    getId(), properties.getPort(), serverId);
 
            // 2. 启动下行消息订阅者
            IotMessageBus messageBus = SpringUtil.getBean(IotMessageBus.class);
            IotMqttDownstreamHandler downstreamHandler = new IotMqttDownstreamHandler(deviceMessageService, connectionManager);
            this.downstreamSubscriber = new IotMqttDownstreamSubscriber(this, downstreamHandler, messageBus);
            this.downstreamSubscriber.start();
        } catch (Exception e) {
            log.error("[start][IoT MQTT 协议 {} 启动失败]", getId(), e);
            stop0();
            throw e;
        }
    }
 
    @Override
    public void stop() {
        if (!running) {
            return;
        }
        stop0();
    }
 
    private void stop0() {
        // 1. 停止下行消息订阅者
        if (downstreamSubscriber != null) {
            try {
                downstreamSubscriber.stop();
                log.info("[stop][IoT MQTT 协议 {} 下行消息订阅者已停止]", getId());
            } catch (Exception e) {
                log.error("[stop][IoT MQTT 协议 {} 下行消息订阅者停止失败]", getId(), e);
            }
            downstreamSubscriber = null;
        }
 
        // 2.1 关闭所有连接
        connectionManager.closeAll();
        // 2.2 关闭 MQTT 服务器
        if (mqttServer != null) {
            try {
                mqttServer.close().result();
                log.info("[stop][IoT MQTT 协议 {} 服务器已停止]", getId());
            } catch (Exception e) {
                log.error("[stop][IoT MQTT 协议 {} 服务器停止失败]", getId(), e);
            }
            mqttServer = null;
        }
        // 2.3 关闭 Vertx 实例
        if (vertx != null) {
            try {
                vertx.close().result();
                log.info("[stop][IoT MQTT 协议 {} Vertx 已关闭]", getId());
            } catch (Exception e) {
                log.error("[stop][IoT MQTT 协议 {} Vertx 关闭失败]", getId(), e);
            }
            vertx = null;
        }
        running = false;
        log.info("[stop][IoT MQTT 协议 {} 已停止]", getId());
    }
 
    // ======================================= MQTT 连接处理 ======================================
 
    /**
     * 处理 MQTT 连接端点
     *
     * @param endpoint MQTT 连接端点
     */
    private void handleEndpoint(MqttEndpoint endpoint) {
        // 1. 如果是注册请求,注册待认证连接;否则走正常认证流程
        String clientId = endpoint.clientIdentifier();
        if (StrUtil.endWith(clientId, AUTH_TYPE_REGISTER)) {
            // 情况一:设备注册请求
            registerHandler.handleRegister(endpoint);
            return;
        } else {
            // 情况二:普通认证请求
            if (!authHandler.handleAuthenticationRequest(endpoint)) {
                endpoint.reject(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD);
                return;
            }
        }
 
        // 2.1 设置异常和关闭处理器
        endpoint.exceptionHandler(ex -> {
            log.warn("[handleEndpoint][连接异常,客户端 ID: {},地址: {},异常: {}]",
                    clientId, connectionManager.getEndpointAddress(endpoint), ex.getMessage());
            endpoint.close();
        });
        endpoint.closeHandler(v -> cleanupConnection(endpoint)); // 处理底层连接关闭(网络中断、异常等)
        endpoint.disconnectHandler(v -> { // 处理 MQTT DISCONNECT 报文
            log.debug("[handleEndpoint][设备断开连接,客户端 ID: {}]", clientId);
            cleanupConnection(endpoint);
        });
        // 2.2 设置心跳处理器
        endpoint.pingHandler(v -> log.debug("[handleEndpoint][收到客户端心跳,客户端 ID: {}]", clientId));
 
        // 3.1 设置消息处理器
        endpoint.publishHandler(message -> processMessage(endpoint, message));
        // 3.2 设置 QoS 2 消息的 PUBREL 处理器
        endpoint.publishReleaseHandler(endpoint::publishComplete);
 
        // 4.1 设置订阅处理器(带 ACL 校验)
        endpoint.subscribeHandler(subscribe -> {
            IotMqttConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(endpoint);
            List<MqttQoS> grantedQoSLevels = new ArrayList<>();
            for (MqttTopicSubscription sub : subscribe.topicSubscriptions()) {
                String topicName = sub.topicName();
                // 校验主题是否属于当前设备
                if (connectionInfo != null && IotMqttTopicUtils.isTopicSubscribeAllowed(
                        topicName, connectionInfo.getProductKey(), connectionInfo.getDeviceName())) {
                    grantedQoSLevels.add(sub.qualityOfService());
                    log.debug("[handleEndpoint][订阅成功,客户端 ID: {},主题: {}]", clientId, topicName);
                } else {
                    log.warn("[handleEndpoint][订阅被拒绝,客户端 ID: {},主题: {}]", clientId, topicName);
                    grantedQoSLevels.add(MqttQoS.FAILURE);
                }
            }
            endpoint.subscribeAcknowledge(subscribe.messageId(), grantedQoSLevels);
        });
        // 4.2 设置取消订阅处理器
        endpoint.unsubscribeHandler(unsubscribe -> {
            log.debug("[handleEndpoint][设备取消订阅,客户端 ID: {},主题: {}]", clientId, unsubscribe.topics());
            endpoint.unsubscribeAcknowledge(unsubscribe.messageId());
        });
 
        // 5. 接受连接
        endpoint.accept(false);
    }
 
    /**
     * 处理消息(发布)
     *
     * @param endpoint MQTT 连接端点
     * @param message  发布消息
     */
    private void processMessage(MqttEndpoint endpoint, MqttPublishMessage message) {
        String clientId = endpoint.clientIdentifier();
        try {
            // 1. 处理业务消息
            String topic = message.topicName();
            byte[] payload = message.payload().getBytes();
            upstreamHandler.handleBusinessRequest(endpoint, topic, payload);
 
            // 2. 根据 QoS 级别发送相应的确认消息
            handleQoSAck(endpoint, message);
        } catch (Exception e) {
            log.error("[processMessage][消息处理失败,断开连接,客户端 ID: {},地址: {},错误: {}]",
                    clientId, connectionManager.getEndpointAddress(endpoint), e.getMessage());
            endpoint.close();
        }
    }
 
    /**
     * 处理 QoS 确认
     *
     * @param endpoint MQTT 连接端点
     * @param message  发布消息
     */
    private void handleQoSAck(MqttEndpoint endpoint, MqttPublishMessage message) {
        if (message.qosLevel() == MqttQoS.AT_LEAST_ONCE) {
            // QoS 1: 发送 PUBACK 确认
            endpoint.publishAcknowledge(message.messageId());
        } else if (message.qosLevel() == MqttQoS.EXACTLY_ONCE) {
            // QoS 2: 发送 PUBREC 确认
            endpoint.publishReceived(message.messageId());
        }
        // QoS 0 无需确认
    }
 
    /**
     * 清理连接
     *
     * @param endpoint MQTT 连接端点
     */
    private void cleanupConnection(MqttEndpoint endpoint) {
        try {
            // 1. 发送设备离线消息
            IotMqttConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(endpoint);
            if (connectionInfo != null) {
                IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline();
                deviceMessageService.sendDeviceMessage(offlineMessage, connectionInfo.getProductKey(),
                        connectionInfo.getDeviceName(), serverId);
            }
 
            // 2. 注销连接
            connectionManager.unregisterConnection(endpoint);
        } catch (Exception e) {
            log.error("[cleanupConnection][清理连接失败,客户端 ID: {},错误: {}]",
                    endpoint.clientIdentifier(), e.getMessage());
        }
    }
 
}