2026-06-24 f4bd1f3c89d906131495a0aca5aaf82966378510
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
package cn.iocoder.yudao.module.iot.gateway.protocol.tcp.handler.upstream;
 
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.iot.core.biz.IotDeviceCommonApi;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
import cn.iocoder.yudao.module.iot.core.biz.dto.IotDeviceRespDTO;
import cn.iocoder.yudao.module.iot.core.enums.IotDeviceMessageMethodEnum;
import cn.iocoder.yudao.module.iot.core.mq.message.IotDeviceMessage;
import cn.iocoder.yudao.module.iot.core.topic.IotDeviceIdentity;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterReqDTO;
import cn.iocoder.yudao.module.iot.core.topic.auth.IotDeviceRegisterRespDTO;
import cn.iocoder.yudao.module.iot.core.util.IotDeviceAuthUtils;
import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.codec.IotTcpFrameCodec;
import cn.iocoder.yudao.module.iot.gateway.protocol.tcp.manager.IotTcpConnectionManager;
import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer;
import cn.iocoder.yudao.module.iot.gateway.service.device.IotDeviceService;
import cn.iocoder.yudao.module.iot.gateway.service.device.message.IotDeviceMessageService;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.net.NetSocket;
import io.vertx.core.parsetools.RecordParser;
import lombok.extern.slf4j.Slf4j;
import cn.hutool.core.lang.Assert;
 
import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.iot.gateway.enums.ErrorCodeConstants.DEVICE_AUTH_FAIL;
 
/**
 * TCP 上行消息处理器
 *
 * @author 芋道源码
 */
@Slf4j
public class IotTcpUpstreamHandler implements Handler<NetSocket> {
 
    private static final String AUTH_METHOD = "auth";
 
    private final String serverId;
 
    /**
     * TCP 帧编解码器(处理粘包/拆包)
     */
    private final IotTcpFrameCodec codec;
    /**
     * 消息序列化器(处理业务消息序列化/反序列化)
     */
    private final IotMessageSerializer serializer;
    /**
     * TCP 连接管理器
     */
    private final IotTcpConnectionManager connectionManager;
 
    private final IotDeviceMessageService deviceMessageService;
    private final IotDeviceService deviceService;
    private final IotDeviceCommonApi deviceApi;
 
    public IotTcpUpstreamHandler(String serverId,
                                 IotTcpFrameCodec codec,
                                 IotMessageSerializer serializer,
                                 IotTcpConnectionManager connectionManager) {
        Assert.notNull(codec, "TCP FrameCodec 必须配置");
        Assert.notNull(serializer, "消息序列化器必须配置");
        Assert.notNull(connectionManager, "连接管理器不能为空");
        this.serverId = serverId;
        this.codec = codec;
        this.serializer = serializer;
        this.connectionManager = connectionManager;
        this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
        this.deviceService = SpringUtil.getBean(IotDeviceService.class);
        this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
    }
 
    @Override
    @SuppressWarnings("DuplicatedCode")
    public void handle(NetSocket socket) {
        String remoteAddress = String.valueOf(socket.remoteAddress());
        log.debug("[handle][设备连接,地址: {}]", remoteAddress);
 
        // 1. 设置异常和关闭处理器
        socket.exceptionHandler(ex -> {
            log.warn("[handle][连接异常,地址: {}]", remoteAddress, ex);
            socket.close();
        });
        socket.closeHandler(v -> {
            log.debug("[handle][连接关闭,地址: {}]", remoteAddress);
            cleanupConnection(socket);
        });
 
        // 2.1 设置消息处理器
        Handler<Buffer> messageHandler = buffer -> {
            try {
                processMessage(buffer, socket);
            } catch (Exception e) {
                log.error("[handle][消息处理失败,地址: {}]", remoteAddress, e);
                socket.close();
            }
        };
        // 2.2 使用拆包器处理粘包/拆包
        RecordParser parser = codec.createDecodeParser(messageHandler);
        socket.handler(parser);
        log.debug("[handle][启用 {} 拆包器,地址: {}]", codec.getType(), remoteAddress);
    }
 
    /**
     * 处理消息
     *
     * @param buffer   消息
     * @param socket   网络连接
     */
    private void processMessage(Buffer buffer, NetSocket socket) {
        IotDeviceMessage message = null;
        try {
            // 1. 反序列化消息
            message = serializer.deserialize(buffer.getBytes());
            if (message == null) {
                sendErrorResponse(socket, null, null, BAD_REQUEST.getCode(), "消息反序列化失败");
                return;
            }
 
            // 2. 根据消息类型路由处理
            if (AUTH_METHOD.equals(message.getMethod())) {
                // 认证请求
                handleAuthenticationRequest(message, socket);
            } else if (IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod().equals(message.getMethod())) {
                // 设备动态注册请求
                handleRegisterRequest(message, socket);
            } else {
                // 业务消息
                handleBusinessRequest(message, socket);
            }
        } catch (ServiceException e) {
            // 业务异常,返回对应的错误码和错误信息
            log.warn("[processMessage][业务异常,地址: {},错误: {}]", socket.remoteAddress(), e.getMessage());
            String requestId = message != null ? message.getRequestId() : null;
            String method = message != null ? message.getMethod() : null;
            sendErrorResponse(socket, requestId, method, e.getCode(), e.getMessage());
        } catch (IllegalArgumentException e) {
            // 参数校验失败,返回 400
            log.warn("[processMessage][参数校验失败,地址: {},错误: {}]", socket.remoteAddress(), e.getMessage());
            String requestId = message != null ? message.getRequestId() : null;
            String method = message != null ? message.getMethod() : null;
            sendErrorResponse(socket, requestId, method, BAD_REQUEST.getCode(), e.getMessage());
        } catch (Exception e) {
            // 其他异常,返回 500,并重新抛出让上层关闭连接
            log.error("[processMessage][处理消息失败,地址: {}]", socket.remoteAddress(), e);
            String requestId = message != null ? message.getRequestId() : null;
            String method = message != null ? message.getMethod() : null;
            sendErrorResponse(socket, requestId, method,
                    INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
            throw e;
        }
    }
 
    /**
     * 处理认证请求
     *
     * @param message  消息信息
     * @param socket   网络连接
     */
    @SuppressWarnings("DuplicatedCode")
    private void handleAuthenticationRequest(IotDeviceMessage message, NetSocket socket) {
        // 1. 解析认证参数
        IotDeviceAuthReqDTO authParams = JsonUtils.convertObject(message.getParams(), IotDeviceAuthReqDTO.class);
        Assert.notNull(authParams, "认证参数不能为空");
        Assert.notBlank(authParams.getUsername(), "username 不能为空");
        Assert.notBlank(authParams.getPassword(), "password 不能为空");
 
        // 2.1 执行认证
        CommonResult<Boolean> authResult = deviceApi.authDevice(authParams);
        authResult.checkError();
        if (BooleanUtil.isFalse(authResult.getData())) {
            throw exception(DEVICE_AUTH_FAIL);
        }
        // 2.2 解析设备信息
        IotDeviceIdentity deviceInfo = IotDeviceAuthUtils.parseUsername(authParams.getUsername());
        Assert.notNull(deviceInfo, "解析设备信息失败");
        // 2.3 获取设备信息
        IotDeviceRespDTO device = deviceService.getDeviceFromCache(deviceInfo.getProductKey(), deviceInfo.getDeviceName());
        Assert.notNull(device, "设备不存在");
 
        // 3.1 注册连接
        registerConnection(socket, device);
        // 3.2 发送上线消息
        sendOnlineMessage(device);
        // 3.3 发送成功响应
        sendSuccessResponse(socket, message.getRequestId(), AUTH_METHOD, "认证成功");
        log.info("[handleAuthenticationRequest][认证成功,设备 ID: {},设备名: {}]", device.getId(), device.getDeviceName());
    }
 
    /**
     * 处理设备动态注册请求(一型一密,不需要认证)
     *
     * @param message  消息信息
     * @param socket   网络连接
     * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
     */
    @SuppressWarnings("DuplicatedCode")
    private void handleRegisterRequest(IotDeviceMessage message, NetSocket socket) {
        // 1. 解析注册参数
        IotDeviceRegisterReqDTO params = JsonUtils.convertObject(message.getParams(), IotDeviceRegisterReqDTO.class);
        Assert.notNull(params, "注册参数不能为空");
        Assert.notBlank(params.getProductKey(), "productKey 不能为空");
        Assert.notBlank(params.getDeviceName(), "deviceName 不能为空");
        Assert.notBlank(params.getSign(), "sign 不能为空");
 
        // 2. 调用动态注册
        CommonResult<IotDeviceRegisterRespDTO> result = deviceApi.registerDevice(params);
        result.checkError();
 
        // 3. 发送成功响应
        sendSuccessResponse(socket, message.getRequestId(),
                IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), result.getData());
        log.info("[handleRegisterRequest][注册成功,地址: {},设备名: {}]",
                socket.remoteAddress(), params.getDeviceName());
    }
 
    /**
     * 处理业务请求
     *
     * @param message  消息信息
     * @param socket   网络连接
     */
    private void handleBusinessRequest(IotDeviceMessage message, NetSocket socket) {
        // 1. 获取认证信息并处理业务消息
        IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket);
        if (connectionInfo == null) {
            log.error("[handleBusinessRequest][无法获取连接信息,地址: {}]", socket.remoteAddress());
            sendErrorResponse(socket, message.getRequestId(), message.getMethod(),
                    UNAUTHORIZED.getCode(), "设备未认证,无法处理业务消息");
            return;
        }
 
        // 2. 发送消息到消息总线
        deviceMessageService.sendDeviceMessage(message, connectionInfo.getProductKey(),
                connectionInfo.getDeviceName(), serverId);
        log.info("[handleBusinessRequest][发送消息到消息总线,地址: {},消息: {}]", socket.remoteAddress(), message);
    }
 
    /**
     * 注册连接信息
     *
     * @param socket   网络连接
     * @param device   设备
     */
    private void registerConnection(NetSocket socket, IotDeviceRespDTO device) {
        IotTcpConnectionManager.ConnectionInfo connectionInfo = new IotTcpConnectionManager.ConnectionInfo()
                .setDeviceId(device.getId())
                .setProductKey(device.getProductKey())
                .setDeviceName(device.getDeviceName());
        connectionManager.registerConnection(socket, device.getId(), connectionInfo);
    }
 
    /**
     * 发送设备上线消息
     *
     * @param device 设备信息
     */
    private void sendOnlineMessage(IotDeviceRespDTO device) {
        IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline();
        deviceMessageService.sendDeviceMessage(onlineMessage, device.getProductKey(),
                device.getDeviceName(), serverId);
    }
 
    /**
     * 清理连接
     *
     * @param socket 网络连接
     */
    private void cleanupConnection(NetSocket socket) {
        // 1. 发送离线消息
        IotTcpConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket);
        if (connectionInfo != null) {
            IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline();
            deviceMessageService.sendDeviceMessage(offlineMessage, connectionInfo.getProductKey(),
                    connectionInfo.getDeviceName(), serverId);
        }
 
        // 2. 注销连接
        connectionManager.unregisterConnection(socket);
    }
 
    // ===================== 发送响应消息 =====================
 
    /**
     * 发送成功响应
     *
     * @param socket    网络连接
     * @param requestId 请求 ID
     * @param method    方法名
     * @param data      响应数据
     */
    private void sendSuccessResponse(NetSocket socket, String requestId, String method, Object data) {
        IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, method, data, SUCCESS.getCode(), null);
        writeResponse(socket, responseMessage);
    }
 
    /**
     * 发送错误响应
     *
     * @param socket       网络连接
     * @param requestId    请求 ID
     * @param method       方法名
     * @param code         错误码
     * @param msg          错误消息
     */
    private void sendErrorResponse(NetSocket socket, String requestId, String method, Integer code, String msg) {
        IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, method, null, code, msg);
        writeResponse(socket, responseMessage);
    }
 
    /**
     * 写入响应到 Socket
     *
     * @param socket          网络连接
     * @param responseMessage 响应消息
     */
    private void writeResponse(NetSocket socket, IotDeviceMessage responseMessage) {
        byte[] serializedData = serializer.serialize(responseMessage);
        Buffer frameData = codec.encode(serializedData);
        socket.write(frameData);
    }
 
}