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
package cn.iocoder.yudao.module.iot.gateway.protocol.websocket.handler.upstream;
 
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
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.websocket.manager.IotWebSocketConnectionManager;
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.http.ServerWebSocket;
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;
 
 
/**
 * WebSocket 上行消息处理器
 *
 * @author 芋道源码
 */
@Slf4j
public class IotWebSocketUpstreamHandler implements Handler<ServerWebSocket> {
 
    private static final String AUTH_METHOD = "auth";
 
    private final String serverId;
 
    /**
     * 消息序列化器(处理业务消息序列化/反序列化)
     */
    private final IotMessageSerializer serializer;
    /**
     * 连接管理器
     */
    private final IotWebSocketConnectionManager connectionManager;
 
    private final IotDeviceMessageService deviceMessageService;
    private final IotDeviceService deviceService;
    private final IotDeviceCommonApi deviceApi;
 
    public IotWebSocketUpstreamHandler(String serverId,
                                       IotMessageSerializer serializer,
                                       IotWebSocketConnectionManager connectionManager) {
        this.serverId = serverId;
        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(ServerWebSocket 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. 设置消息处理器(仅支持文本帧)
        socket.textMessageHandler(message -> {
            try {
                processMessage(StrUtil.utf8Bytes(message), socket);
            } catch (Exception e) {
                log.error("[handle][消息解码失败,断开连接,地址: {},错误: {}]", remoteAddress, e.getMessage());
                socket.close();
            }
        });
    }
 
    /**
     * 处理消息
     *
     * @param payload  消息负载
     * @param socket   WebSocket 连接
     */
    private void processMessage(byte[] payload, ServerWebSocket socket) {
        IotDeviceMessage message = null;
        try {
            // 1.1 基础检查
            if (ArrayUtil.isEmpty(payload)) {
                return;
            }
            // 1.2 解码消息
            message = serializer.deserialize(payload);
            Assert.notNull(message, "消息反序列化失败");
            Assert.notBlank(message.getMethod(), "method 不能为空");
 
            // 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][业务异常,错误: {}]", 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) {
            log.warn("[processMessage][参数校验失败,错误: {}]", 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) {
            log.error("[processMessage][处理消息失败]", 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   WebSocket 连接
     */
    @SuppressWarnings("DuplicatedCode")
    private void handleAuthenticationRequest(IotDeviceMessage message, ServerWebSocket 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   WebSocket 连接
     * @see <a href="https://help.aliyun.com/zh/iot/user-guide/unique-certificate-per-product-verification">阿里云 - 一型一密</a>
     */
    @SuppressWarnings("DuplicatedCode")
    private void handleRegisterRequest(IotDeviceMessage message, ServerWebSocket 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. 发送成功响应(包含 deviceSecret)
        sendSuccessResponse(socket, message.getRequestId(),
                IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod(), result.getData());
        log.info("[handleRegisterRequest][注册成功,设备名: {}]", params.getDeviceName());
    }
 
    /**
     * 处理业务请求
     *
     * @param message  消息信息
     * @param socket   WebSocket 连接
     */
    private void handleBusinessRequest(IotDeviceMessage message, ServerWebSocket socket) {
        // 1. 获取认证信息并处理业务消息
        IotWebSocketConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket);
        if (connectionInfo == null) {
            log.warn("[handleBusinessRequest][连接未认证,拒绝处理业务消息]");
            sendErrorResponse(socket, message.getRequestId(), message.getMethod(),
                    UNAUTHORIZED.getCode(), "设备未认证,无法处理业务消息");
            return;
        }
 
        // 2. 发送消息到消息总线
        deviceMessageService.sendDeviceMessage(message, connectionInfo.getProductKey(),
                connectionInfo.getDeviceName(), serverId);
        log.info("[handleBusinessRequest][发送消息到消息总线,消息: {}]", message);
    }
 
    /**
     * 注册连接信息
     *
     * @param socket   WebSocket 连接
     * @param device   设备
     */
    private void registerConnection(ServerWebSocket socket, IotDeviceRespDTO device) {
        IotWebSocketConnectionManager.ConnectionInfo connectionInfo = new IotWebSocketConnectionManager.ConnectionInfo()
                .setDeviceId(device.getId())
                .setProductKey(device.getProductKey())
                .setDeviceName(device.getDeviceName());
        connectionManager.registerConnection(socket, device.getId(), connectionInfo);
    }
 
    /**
     * 发送设备上线消息
     *
     * @param device 设备信息
     */
    private void sendOnlineMessage(IotDeviceRespDTO device) {
        try {
            IotDeviceMessage onlineMessage = IotDeviceMessage.buildStateUpdateOnline();
            deviceMessageService.sendDeviceMessage(onlineMessage, device.getProductKey(),
                    device.getDeviceName(), serverId);
        } catch (Exception e) {
            log.error("[sendOnlineMessage][发送上线消息失败,设备: {}]", device.getDeviceName(), e);
        }
    }
 
    /**
     * 清理连接
     *
     * @param socket WebSocket 连接
     */
    private void cleanupConnection(ServerWebSocket socket) {
        try {
            // 1. 发送离线消息(如果已认证)
            IotWebSocketConnectionManager.ConnectionInfo connectionInfo = connectionManager.getConnectionInfo(socket);
            if (connectionInfo != null) {
                IotDeviceMessage offlineMessage = IotDeviceMessage.buildStateOffline();
                deviceMessageService.sendDeviceMessage(offlineMessage, connectionInfo.getProductKey(),
                        connectionInfo.getDeviceName(), serverId);
            }
 
            // 2. 注销连接
            connectionManager.unregisterConnection(socket);
        } catch (Exception e) {
            log.error("[cleanupConnection][清理连接失败]", e);
        }
    }
 
    // ===================== 发送响应消息 =====================
 
    /**
     * 发送响应消息
     *
     * @param socket    WebSocket 连接
     * @param requestId 请求 ID
     * @param method    请求方法
     * @param data      响应数据
     */
    private void sendSuccessResponse(ServerWebSocket socket, String requestId, String method, Object data) {
        IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, method, data, SUCCESS.getCode(), null);
        writeResponse(socket, responseMessage);
    }
 
    private void sendErrorResponse(ServerWebSocket socket, String requestId, String method, Integer code, String msg) {
        IotDeviceMessage responseMessage = IotDeviceMessage.replyOf(requestId, method, null, code, msg);
        writeResponse(socket, responseMessage);
    }
 
    /**
     * 写入响应消息
     */
    private void writeResponse(ServerWebSocket socket, IotDeviceMessage responseMessage) {
        byte[] payload = serializer.serialize(responseMessage);
        socket.writeTextMessage(StrUtil.utf8Str(payload));
    }
 
}