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 { 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 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 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 阿里云 - 一型一密 */ @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 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); } }