package cn.iocoder.yudao.module.iot.gateway.protocol.coap.handler.upstream; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.ObjUtil; 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.topic.IotDeviceIdentity; import cn.iocoder.yudao.module.iot.gateway.service.auth.IotDeviceTokenService; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.eclipse.californium.core.coap.Option; import org.eclipse.californium.core.server.resources.CoapExchange; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; /** * IoT 网关 CoAP 协议的处理器抽象基类:提供通用的前置处理(认证)、请求解析、响应处理、全局的异常捕获等 * * @author 芋道源码 */ @Slf4j public abstract class IotCoapAbstractHandler { /** * 自定义 CoAP Option 编号,用于携带 Token *

* CoAP Option 范围 2048-65535 属于实验/自定义范围 */ public static final int OPTION_TOKEN = 2088; private final IotDeviceTokenService deviceTokenService = SpringUtil.getBean(IotDeviceTokenService.class); /** * 处理 CoAP 请求(模板方法) * * @param exchange CoAP 交换对象 */ public final void handle(CoapExchange exchange) { try { // 1. 前置处理 beforeHandle(exchange); // 2. 执行业务逻辑 CommonResult result = handle0(exchange); writeResponse(exchange, result); } catch (ServiceException e) { // 业务异常,返回对应的错误码和消息 writeResponse(exchange, CommonResult.error(e.getCode(), e.getMessage())); } catch (IllegalArgumentException e) { // 参数校验异常(hutool Assert 抛出),返回 BAD_REQUEST writeResponse(exchange, CommonResult.error(BAD_REQUEST.getCode(), e.getMessage())); } catch (Exception e) { // 其他未知异常,返回 INTERNAL_SERVER_ERROR log.error("[handle][CoAP 请求处理异常]", e); writeResponse(exchange, CommonResult.error(INTERNAL_SERVER_ERROR)); } } /** * 处理 CoAP 请求(子类实现) * * @param exchange CoAP 交换对象 * @return 处理结果 */ protected abstract CommonResult handle0(CoapExchange exchange); /** * 前置处理:认证等 * * @param exchange CoAP 交换对象 */ private void beforeHandle(CoapExchange exchange) { // 1.1 如果不需要认证,则不走前置处理 if (!requiresAuthentication()) { return; } // 1.2 从自定义 Option 获取 token String token = getTokenFromOption(exchange); if (StrUtil.isEmpty(token)) { throw exception(UNAUTHORIZED); } // 1.3 校验 token IotDeviceIdentity deviceInfo = deviceTokenService.verifyToken(token); if (deviceInfo == null) { throw exception(UNAUTHORIZED); } // 2.1 解析 productKey 和 deviceName List uriPath = exchange.getRequestOptions().getUriPath(); String productKey = getProductKey(uriPath); String deviceName = getDeviceName(uriPath); if (StrUtil.isEmpty(productKey) || StrUtil.isEmpty(deviceName)) { throw exception(BAD_REQUEST); } // 2.2 校验设备信息是否匹配 if (ObjUtil.notEqual(productKey, deviceInfo.getProductKey()) || ObjUtil.notEqual(deviceName, deviceInfo.getDeviceName())) { throw exception(FORBIDDEN); } } // ========== Token 相关方法 ========== /** * 是否需要认证(子类可覆盖) *

* 默认不需要认证 * * @return 是否需要认证 */ protected boolean requiresAuthentication() { return false; } /** * 从 URI 路径中获取 productKey(子类实现) *

* 默认抛出异常,需要认证的子类必须实现此方法 * * @param uriPath URI 路径 * @return productKey */ protected String getProductKey(List uriPath) { throw new UnsupportedOperationException("子类需要实现 getProductKey 方法"); } /** * 从 URI 路径中获取 deviceName(子类实现) *

* 默认抛出异常,需要认证的子类必须实现此方法 * * @param uriPath URI 路径 * @return deviceName */ protected String getDeviceName(List uriPath) { throw new UnsupportedOperationException("子类需要实现 getDeviceName 方法"); } /** * 从自定义 CoAP Option 中获取 Token * * @param exchange CoAP 交换对象 * @return Token 值,如果不存在则返回 null */ protected String getTokenFromOption(CoapExchange exchange) { Option option = CollUtil.findOne(exchange.getRequestOptions().getOthers(), o -> o.getNumber() == OPTION_TOKEN); return option != null ? new String(option.getValue()) : null; } // ========== 序列化相关方法 ========== /** * 解析请求体为指定类型 * * @param exchange CoAP 交换对象 * @param clazz 目标类型 * @param 目标类型泛型 * @return 解析后的对象,解析失败返回 null */ protected T deserializeRequest(CoapExchange exchange, Class clazz) { byte[] payload = exchange.getRequestPayload(); if (ArrayUtil.isEmpty(payload)) { return null; } return JsonUtils.parseObject(payload, clazz); } private static String serializeResponse(Object data) { return JsonUtils.toJsonString(data); } protected void writeResponse(CoapExchange exchange, CommonResult data) { String json = serializeResponse(data); exchange.respond(CoAP.ResponseCode.CONTENT, json, MediaTypeRegistry.APPLICATION_JSON); } }