package cn.iocoder.yudao.module.iot.gateway.protocol.udp.handler.upstream; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.BooleanUtil; import cn.hutool.core.util.IdUtil; 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.udp.manager.IotUdpSessionManager; import cn.iocoder.yudao.module.iot.gateway.serialize.IotMessageSerializer; import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService; 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.buffer.Buffer; import io.vertx.core.datagram.DatagramPacket; import io.vertx.core.datagram.DatagramSocket; import lombok.extern.slf4j.Slf4j; import cn.hutool.core.lang.Assert; import java.net.InetSocketAddress; import java.util.Map; 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; /** * UDP 上行消息处理器 *
* 采用无状态 Token 机制(每次请求携带 token):
* 1. 认证请求:设备发送 auth 消息,携带 clientId、username、password
* 2. 返回 Token:服务端验证后返回 JWT token
* 3. 后续请求:每次请求在 params 中携带 token
* 4. 服务端验证:每次请求通过 IotDeviceTokenService.verifyToken() 验证
*
* @author 芋道源码
*/
@Slf4j
public class IotUdpUpstreamHandler {
private static final String AUTH_METHOD = "auth";
/**
* Token 参数 Key
*/
private static final String PARAM_KEY_TOKEN = "token";
/**
* Body 参数 Key(实际请求内容)
*/
private static final String PARAM_KEY_BODY = "body";
private final String serverId;
/**
* 消息序列化器(处理业务消息序列化/反序列化)
*/
private final IotMessageSerializer serializer;
/**
* UDP 会话管理器
*/
private final IotUdpSessionManager sessionManager;
private final IotDeviceMessageService deviceMessageService;
private final IotDeviceService deviceService;
private final IotDeviceTokenService deviceTokenService;
private final IotDeviceCommonApi deviceApi;
public IotUdpUpstreamHandler(String serverId,
IotUdpSessionManager sessionManager,
IotMessageSerializer serializer) {
Assert.notNull(serializer, "消息序列化器必须配置");
Assert.notNull(sessionManager, "会话管理器不能为空");
this.serverId = serverId;
this.sessionManager = sessionManager;
this.serializer = serializer;
this.deviceMessageService = SpringUtil.getBean(IotDeviceMessageService.class);
this.deviceService = SpringUtil.getBean(IotDeviceService.class);
this.deviceApi = SpringUtil.getBean(IotDeviceCommonApi.class);
this.deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class);
}
/**
* 处理 UDP 数据包
*
* @param packet 数据包
* @param socket UDP Socket
*/
public void handle(DatagramPacket packet, DatagramSocket socket) {
InetSocketAddress senderAddress = new InetSocketAddress(packet.sender().host(), packet.sender().port());
Buffer data = packet.data();
String addressKey = sessionManager.buildAddressKey(senderAddress);
log.debug("[handle][收到 UDP 数据包,来源: {},数据长度: {} 字节]", addressKey, data.length());
processMessage(data, senderAddress, socket);
}
/**
* 处理消息
*
* @param buffer 消息
* @param senderAddress 发送者地址
* @param socket UDP Socket
*/
private void processMessage(Buffer buffer, InetSocketAddress senderAddress, DatagramSocket socket) {
String addressKey = sessionManager.buildAddressKey(senderAddress);
// 1.1 基础检查
if (ArrayUtil.isEmpty(buffer)) {
return;
}
// 1.2 反序列化消息
IotDeviceMessage message = serializer.deserialize(buffer.getBytes());
if (message == null) {
sendErrorResponse(socket, senderAddress, null, null, BAD_REQUEST.getCode(), "消息反序列化失败");
return;
}
// 2. 根据消息类型路由处理
try {
if (AUTH_METHOD.equals(message.getMethod())) {
// 认证请求
handleAuthenticationRequest(message, senderAddress, socket);
} else if (IotDeviceMessageMethodEnum.DEVICE_REGISTER.getMethod().equals(message.getMethod())) {
// 设备动态注册请求
handleRegisterRequest(message, senderAddress, socket);
} else {
// 业务消息
handleBusinessRequest(message, senderAddress, socket);
}
} catch (ServiceException e) {
// 业务异常,返回对应的错误码和错误信息
log.warn("[processMessage][业务异常,来源: {},requestId: {},method: {},错误: {}]",
addressKey, message.getRequestId(), message.getMethod(), e.getMessage());
sendErrorResponse(socket, senderAddress, message.getRequestId(), message.getMethod(),
e.getCode(), e.getMessage());
} catch (IllegalArgumentException e) {
// 参数校验失败,返回 400
log.warn("[processMessage][参数校验失败,来源: {},requestId: {},method: {},错误: {}]",
addressKey, message.getRequestId(), message.getMethod(), e.getMessage());
sendErrorResponse(socket, senderAddress, message.getRequestId(), message.getMethod(),
BAD_REQUEST.getCode(), e.getMessage());
} catch (Exception e) {
// 其他异常,返回 500
log.error("[processMessage][处理消息失败,来源: {},requestId: {},method: {}]",
addressKey, message.getRequestId(), message.getMethod(), e);
sendErrorResponse(socket, senderAddress, message.getRequestId(), message.getMethod(),
INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
}
}
/**
* 处理认证请求
*
* @param message 消息信息
* @param senderAddress 发送者地址
* @param socket UDP Socket
*/
@SuppressWarnings("DuplicatedCode")
private void handleAuthenticationRequest(IotDeviceMessage message, InetSocketAddress senderAddress,
DatagramSocket socket) {
String clientId = IdUtil.simpleUUID();
// 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
* 请求参数格式:
* - token:JWT 令牌
* - body:实际请求内容(可以是 Map、List 或其他类型)
*
* @param message 消息信息
* @param senderAddress 发送者地址
* @param socket UDP Socket
*/
@SuppressWarnings("unchecked")
private void handleBusinessRequest(IotDeviceMessage message, InetSocketAddress senderAddress,
DatagramSocket socket) {
String addressKey = sessionManager.buildAddressKey(senderAddress);
// 1.1 从消息中提取 token 和 body
String token = null;
Object body = null;
if (message.getParams() instanceof Map) {
Map