diff --git a/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/service/device/message/IotDeviceMessageServiceImpl.java b/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/service/device/message/IotDeviceMessageServiceImpl.java index bef6ca2..ea7bd86 100644 --- a/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/service/device/message/IotDeviceMessageServiceImpl.java +++ b/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/service/device/message/IotDeviceMessageServiceImpl.java @@ -23,8 +23,6 @@ import com.viewshanghai.module.iot.dal.tdengine.IotDeviceMessageMapper; import com.viewshanghai.module.iot.service.device.IotDeviceService; import com.viewshanghai.module.iot.service.device.property.IotDevicePropertyService; import com.viewshanghai.module.iot.service.ota.IotOtaTaskRecordService; -import com.viewshanghai.module.iot.service.product.IotProductService; -import com.viewshanghai.module.iot.dal.dataobject.product.IotProductDO; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.google.common.base.Objects; @@ -67,9 +65,6 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { @Resource private IotDeviceMessageProducer deviceMessageProducer; - - @Resource - private IotProductService productService; @Override public void defineDeviceMessageStable() { @@ -177,21 +172,11 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService { // 条件1:防止对"回复消息"再次回复,避免无限循环 // 条件2:某些特定的method不需要回复(如设备状态变更、OTA进度上报) // 条件3(新增):HTTP短连接场景,因为已经在请求中直接响应了,不需要再通过消息总线发送回复 - // 条件4(新增):JT808 协议由协议处理器自动生成通用应答(0x8001),业务层不需要生成回复消息 if (IotDeviceMessageUtils.isReplyMessage(message) || IotDeviceMessageMethodEnum.isReplyDisabled(message.getMethod()) || StrUtil.isEmpty(message.getServerId())) { return; // serverId 为空,不记录回复消息 } - - // 检查设备的编解码类型,如果是 JT808,则跳过回复消息生成 - // JT808 协议的回复由协议处理器(Jt808ProtocolHandler)自动生成通用应答 - IotProductDO product = productService.getProductFromCache(device.getProductId()); - if (product != null && "JT808".equals(product.getCodecType())) { - log.debug("[handleUpstreamDeviceMessage][JT808 协议消息,跳过业务层回复消息生成,由协议处理器自动生成通用应答]"); - return; - } - try { IotDeviceMessage replyMessage = IotDeviceMessage.replyOf(message.getRequestId(), message.getMethod(), replyData, serviceException != null ? serviceException.getCode() : null, diff --git a/viewshanghai-module-iot/viewshanghai-module-iot-gateway/src/main/java/com/viewshanghai/module/iot/gateway/codec/jt808/IotJt808DeviceMessageCodec.java b/viewshanghai-module-iot/viewshanghai-module-iot-gateway/src/main/java/com/viewshanghai/module/iot/gateway/codec/jt808/IotJt808DeviceMessageCodec.java index da7704a..0c1a87f 100644 --- a/viewshanghai-module-iot/viewshanghai-module-iot-gateway/src/main/java/com/viewshanghai/module/iot/gateway/codec/jt808/IotJt808DeviceMessageCodec.java +++ b/viewshanghai-module-iot/viewshanghai-module-iot-gateway/src/main/java/com/viewshanghai/module/iot/gateway/codec/jt808/IotJt808DeviceMessageCodec.java @@ -58,7 +58,7 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec { // 根据方法名路由到不同的编码器 return switch (method) { - // === 标准物模型方法(下行) === + // === 标准物模型方法 === case "thing.service.invoke" -> encodeServiceInvoke(message, phone, flowId); // 服务调用 case "thing.property.set" -> encodePropertySet(message, phone, flowId); // 属性设置 @@ -67,18 +67,8 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec { case "jt808.platform.registerResp", "registerResp" -> encodeRegisterResp(message, phone, flowId); case "jt808.platform.textDown", "textDown" -> encodeTextDown(message, phone, flowId); - // === 上行消息方法(不应用于下行) === - // 注意:正常情况下不应该到达这里,因为: - // 1. 业务层已跳过 JT808 协议的回复消息生成 - // 2. 协议处理器会自动生成通用应答 - // 这里保留作为防御性检查 - case "thing.property.post", "thing.property.report" -> { - log.warn("[encode][JT808 协议不支持下行属性上报方法: {},请使用 thing.property.set 进行属性设置。如果这是回复消息,应使用 jt808.platform.commonResp 方法]", method); - yield new byte[0]; - } - default -> { - log.warn("[encode][不支持的消息方法: {},JT808 协议仅支持下行方法: thing.service.invoke, thing.property.set]", method); + log.warn("[encode][不支持的消息方法: {}]", method); yield new byte[0]; } }; @@ -87,42 +77,20 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec { throw new RuntimeException("JT808 消息编码失败: " + e.getMessage(), e); } } - - /** - * 判断是否为上行消息方法 - */ - private boolean isUpstreamMethod(String method) { - return "thing.property.post".equals(method) - || "thing.property.report".equals(method) - || "thing.event.post".equals(method) - || "thing.state.update".equals(method); - } @Override public IotDeviceMessage decode(byte[] bytes) { Assert.notNull(bytes, "待解码数据不能为空"); Assert.isTrue(bytes.length >= 12, "数据包长度不足"); - + try { - // 1. 验证并去除首尾标识符 0x7e - // JT808格式: 0x7e + 消息头 + 消息体 + 校验码 + 0x7e - if (bytes[0] != (byte) 0x7e || bytes[bytes.length - 1] != (byte) 0x7e) { - throw new IllegalArgumentException("JT808消息格式错误:缺少首尾标识符0x7e"); - } - - // 2. 反转义处理(处理0x7d01->0x7d, 0x7d02->0x7e) - // doEscape4Receive会保留start之前和end之后的字节,所以传入1和length-1来处理中间部分 + // 1. 反转义(去除首尾标识符) byte[] unescapedBytes = protocolUtil.doEscape4Receive(bytes, 1, bytes.length - 1); - - // 3. 从反转义后的数据中提取实际内容(去掉首尾的0x7e) - // doEscape4Receive保留了首尾的0x7e,需要再次去除 - byte[] actualData = new byte[unescapedBytes.length - 2]; - System.arraycopy(unescapedBytes, 1, actualData, 0, actualData.length); - - // 4. 解析为 JT808 数据包(此时数据已经是纯净的消息内容,不含0x7e) - Jt808DataPack dataPack = decoder.bytes2PackageData(actualData); - - // 5. 转换为统一的 IotDeviceMessage + + // 2. 解析为 JT808 数据包 + Jt808DataPack dataPack = decoder.bytes2PackageData(unescapedBytes); + + // 3. 转换为统一的 IotDeviceMessage return convertToIotDeviceMessage(dataPack); } catch (Exception e) { log.error("[decode][JT808 消息解码失败,数据长度: {}]", bytes.length, e); @@ -132,74 +100,34 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec { /** * 将 JT808 数据包转换为 IotDeviceMessage - * - * @param dataPack JT808 数据包 - * @return IotDeviceMessage 消息对象 */ private IotDeviceMessage convertToIotDeviceMessage(Jt808DataPack dataPack) { - Assert.notNull(dataPack, "JT808 数据包不能为空"); - Jt808DataPack.PackHead head = dataPack.getPackHead(); - Assert.notNull(head, "JT808 数据包头部不能为空"); - int msgId = head.getId(); - String terminalPhone = head.getTerminalPhone(); - int flowId = head.getFlowId(); - // 生成请求ID(用于追踪和关联,格式:终端手机号_流水号_时间戳) - // 添加时间戳确保唯一性,避免同一终端同一流水号重复 - String requestId = String.format("%s_%d_%d", terminalPhone, flowId, System.currentTimeMillis()); + // 生成消息ID(使用流水号作为标识) + String messageId = head.getTerminalPhone() + "_" + head.getFlowId(); // 根据消息ID确定物模型标准方法名 String method = getStandardMethodName(msgId); - - // 解析消息参数 Object params = parseMessageParams(dataPack, msgId); - // 构建元数据并添加到 params 中(用于调试和追踪) - params = enrichParamsWithMetadata(params, msgId, terminalPhone, flowId, head.getEncryptionType()); - - // 创建 IotDeviceMessage(of 方法会自动生成 id 和 reportTime) - return IotDeviceMessage.of(requestId, method, params, null, null, null); - } - - /** - * 为参数对象添加元数据 - * - * @param params 原始参数对象 - * @param msgId JT808 消息ID - * @param terminalPhone 终端手机号 - * @param flowId 流水号 - * @param encryptionType 加密类型 - * @return 包含元数据的参数对象 - */ - private Object enrichParamsWithMetadata(Object params, int msgId, String terminalPhone, - int flowId, int encryptionType) { - // 构建元数据 Map - Map metadata = new HashMap<>(4); - metadata.put("jt808MsgId", String.format("0x%04X", msgId)); - metadata.put("terminalPhone", terminalPhone); - metadata.put("flowId", flowId); - metadata.put("encryptionType", encryptionType); - - // 如果 params 是 Map 类型,则添加元数据 + // 构建元数据(保留在 params 中,用于调试和追踪) if (params instanceof Map) { @SuppressWarnings("unchecked") Map paramsMap = (Map) params; - - // 创建新的可变 Map,确保可以添加元数据 - // 注意:某些解析方法可能返回不可变的 Map(如 Map.of()),需要转换为可变 Map - Map enrichedParams = new HashMap<>(paramsMap); - enrichedParams.put("_metadata", metadata); - return enrichedParams; + Map metadata = new HashMap<>(); + metadata.put("jt808MsgId", String.format("0x%04X", msgId)); + metadata.put("terminalPhone", head.getTerminalPhone()); + metadata.put("flowId", head.getFlowId()); + metadata.put("encryptionType", head.getEncryptionType()); + paramsMap.put("_metadata", metadata); } - // 如果 params 不是 Map,则包装为包含元数据的 Map - // 保留原始 params 在 _data 字段中 - Map wrapper = new HashMap<>(2); - wrapper.put("_data", params); - wrapper.put("_metadata", metadata); - return wrapper; + // 创建 IotDeviceMessage + IotDeviceMessage message = IotDeviceMessage.of(messageId, method, params, null, null, null); + message.setReportTime(LocalDateTime.now()); + return message; } /** @@ -273,41 +201,38 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec { /** * 解析按键事件为事件上报格式(thing.event.post) - *

- * 根据 JT808 协议: - * - 短按:keyId = 0x01-0x0A(对应1-10号键),keyState = 按键次数 - * - 长按:keyId = 0x0B-0x0E(对应长按1-4号键),keyState = 按键次数 - *

- * 物模型标准格式(简化版,只保留 keyId 和 keyState): + * + * 物模型标准格式: * { - * "identifier": "button_event", // 事件标识符,用于存储到数据库的 identifier 字段 + * "eventId": "button_event", * "eventTime": 1234567890, * "params": { - * "keyId": 1, // 0x01=短按1号键, 0x0B=长按1号键 - * "keyState": 1 // 按键次数(0x01=按键一次) + * "keyId": 1, + * "keyState": 1, + * "keyType": "short_press", + * "keyNumber": 1, + * "isLongPress": false * } * } - *

- * 判断长按/短按:通过 keyId 判断 - * - keyId < 0x0B:短按 - * - keyId >= 0x0B:长按 */ private Map parseButtonEventAsEvent(Jt808DataPack dataPack) { Jt808ButtonEvent event = decoder.toButtonEventMsg(dataPack); Map result = new HashMap<>(); - // 使用 identifier 字段(符合物模型标准格式),用于存储到数据库的 identifier 字段 - result.put("identifier", "button_event"); + // 统一使用一个事件标识符,通过 isLongPress 参数区分短按和长按 + result.put("eventId", "button_event"); // 事件时间戳 result.put("eventTime", System.currentTimeMillis()); - // 事件参数(只保留 keyId 和 keyState 两个字段) - // 通过 keyId 可以判断是长按还是短按:keyId >= 0x0B 为长按 + // 事件参数(包含 isLongPress 字段用于区分短按和长按) Map eventParams = new HashMap<>(); eventParams.put("keyId", event.getKeyId()); eventParams.put("keyState", event.getKeyState()); + eventParams.put("keyType", event.getKeyType()); + eventParams.put("keyNumber", event.getKeyNumber()); + eventParams.put("isLongPress", event.getIsLongPress()); result.put("params", eventParams); return result; @@ -621,163 +546,98 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec { /** * 提取终端手机号 - * + * * 优先级: * 1. 从 params._deviceName 中获取(下发场景,IotTcpDownstreamHandler 自动注入) * 2. 从 params._metadata.terminalPhone 中获取(上行消息回复场景) * 3. 从 params.phone 中获取(手动指定,向下兼容,不推荐) - * 4. 从 data.phone 中获取(应答消息场景,使用 replyOf 构建的消息) */ private String extractPhoneNumber(IotDeviceMessage message) { - // 尝试从 params 提取 - if (message.getParams() instanceof Map) { - Map params = (Map) message.getParams(); - String phone = extractPhoneFromMap(params); - if (phone != null) { - return phone; - } + if (!(message.getParams() instanceof Map)) { + log.error("[extractPhoneNumber][params 不是 Map 类型,消息: {}]", message); + throw new IllegalArgumentException("消息参数格式错误,params 必须是 Map 类型"); } - - // 尝试从 data 提取(应答消息使用 replyOf 时,参数在 data 字段) - if (message.getData() instanceof Map) { - Map data = (Map) message.getData(); - String phone = extractPhoneFromMap(data); - if (phone != null) { - return phone; - } - } - - // 如果都获取不到,抛出异常 - log.error("[extractPhoneNumber][无法提取终端手机号,params: {}, data: {}]", - message.getParams(), message.getData()); - throw new IllegalArgumentException( - "消息中缺少终端手机号。请确保设备的 deviceName 为终端手机号(纯数字),例如: \"13800138000\""); - } - - /** - * 从 Map 中提取终端手机号 - */ - private String extractPhoneFromMap(Map map) { - if (map == null) { - return null; - } - + + Map params = (Map) message.getParams(); + // 1. 优先从 _deviceName 获取(下发场景,由 IotTcpDownstreamHandler 注入) - Object deviceName = map.get("_deviceName"); + Object deviceName = params.get("_deviceName"); if (deviceName != null && StrUtil.isNotBlank(deviceName.toString())) { String deviceNameStr = deviceName.toString().trim(); // 验证是否为数字(终端手机号应该是纯数字) if (deviceNameStr.matches("\\d+")) { return deviceNameStr; } else { - log.warn("[extractPhoneFromMap][_deviceName 不是纯数字: {}]", deviceNameStr); + log.warn("[extractPhoneNumber][_deviceName 不是纯数字: {}]", deviceNameStr); } } - + // 2. 从 metadata 中获取(上行消息回复场景) - if (map.get("_metadata") instanceof Map) { - Map metadata = (Map) map.get("_metadata"); + if (params.get("_metadata") instanceof Map) { + Map metadata = (Map) params.get("_metadata"); Object terminalPhone = metadata.get("terminalPhone"); if (terminalPhone != null && StrUtil.isNotBlank(terminalPhone.toString())) { return terminalPhone.toString(); } } - + // 3. 从 phone 字段获取(向下兼容,不推荐) - Object phone = map.get("phone"); + Object phone = params.get("phone"); if (phone != null && StrUtil.isNotBlank(phone.toString())) { String phoneStr = phone.toString().trim(); if (phoneStr.matches("\\d+")) { return phoneStr; } } - - return null; + + // 4. 如果都获取不到,抛出异常 + log.error("[extractPhoneNumber][无法提取终端手机号,params: {}]", params); + throw new IllegalArgumentException( + "消息中缺少终端手机号。请确保设备的 deviceName 为终端手机号(纯数字),例如: \"13800138000\""); } /** * 提取流水号 - * + * * 对于下发消息,如果没有指定流水号,则生成一个随机流水号 */ private int extractFlowId(IotDeviceMessage message) { - // 尝试从 params 提取 if (message.getParams() instanceof Map) { Map params = (Map) message.getParams(); - Integer flowId = extractFlowIdFromMap(params); - if (flowId != null) { - return flowId; + + // 尝试获取显式指定的流水号 + Object flowId = params.get("flowId"); + if (flowId instanceof Number) { + return ((Number) flowId).intValue(); + } + + // 尝试从 metadata 中获取(上行消息的流水号) + if (params.get("_metadata") instanceof Map) { + Map metadata = (Map) params.get("_metadata"); + Object metaFlowId = metadata.get("flowId"); + if (metaFlowId instanceof Number) { + return ((Number) metaFlowId).intValue(); + } } } - - // 尝试从 data 提取(应答消息使用 replyOf 时,参数在 data 字段) - if (message.getData() instanceof Map) { - Map data = (Map) message.getData(); - Integer flowId = extractFlowIdFromMap(data); - if (flowId != null) { - return flowId; - } - } - + // 生成随机流水号(1-65535) return (int) (System.currentTimeMillis() % 65535) + 1; } - /** - * 从 Map 中提取流水号 - */ - private Integer extractFlowIdFromMap(Map map) { - if (map == null) { - return null; - } - - // 尝试获取显式指定的流水号 - Object flowId = map.get("flowId"); - if (flowId instanceof Number) { - return ((Number) flowId).intValue(); - } - - // 尝试从 metadata 中获取(上行消息的流水号) - if (map.get("_metadata") instanceof Map) { - Map metadata = (Map) map.get("_metadata"); - Object metaFlowId = metadata.get("flowId"); - if (metaFlowId instanceof Number) { - return ((Number) metaFlowId).intValue(); - } - } - - return null; - } - /** * 获取参数为Map - * - * 优先从 params 获取,如果 params 为空则从 data 获取(应答消息场景) */ @SuppressWarnings("unchecked") private Map getParamsAsMap(IotDeviceMessage message) { - // 优先从 params 获取 if (message.getParams() instanceof Map) { return (Map) message.getParams(); } - - // 从 data 获取(应答消息使用 replyOf 时,参数在 data 字段) - if (message.getData() instanceof Map) { - return (Map) message.getData(); - } - - // 尝试JSON转换 params + // 尝试JSON转换 if (message.getParams() != null) { String json = JsonUtils.toJsonString(message.getParams()); return JsonUtils.parseObject(json, Map.class); } - - // 尝试JSON转换 data - if (message.getData() != null) { - String json = JsonUtils.toJsonString(message.getData()); - return JsonUtils.parseObject(json, Map.class); - } - return new HashMap<>(); } diff --git a/viewshanghai-module-iot/viewshanghai-module-iot-gateway/src/main/java/com/viewshanghai/module/iot/gateway/protocol/tcp/handler/Jt808ProtocolHandler.java b/viewshanghai-module-iot/viewshanghai-module-iot-gateway/src/main/java/com/viewshanghai/module/iot/gateway/protocol/tcp/handler/Jt808ProtocolHandler.java index bdadd89..ce101c6 100644 --- a/viewshanghai-module-iot/viewshanghai-module-iot-gateway/src/main/java/com/viewshanghai/module/iot/gateway/protocol/tcp/handler/Jt808ProtocolHandler.java +++ b/viewshanghai-module-iot/viewshanghai-module-iot-gateway/src/main/java/com/viewshanghai/module/iot/gateway/protocol/tcp/handler/Jt808ProtocolHandler.java @@ -7,7 +7,6 @@ import com.viewshanghai.framework.common.pojo.CommonResult; import com.viewshanghai.module.iot.core.biz.IotDeviceCommonApi; import com.viewshanghai.module.iot.core.biz.dto.IotDeviceGetReqDTO; import com.viewshanghai.module.iot.core.biz.dto.IotDeviceRespDTO; -import com.viewshanghai.module.iot.core.enums.IotAuthTypeEnum; import com.viewshanghai.module.iot.core.mq.message.IotDeviceMessage; import com.viewshanghai.module.iot.gateway.codec.jt808.IotJt808DeviceMessageCodec; import com.viewshanghai.module.iot.gateway.service.device.message.IotDeviceMessageService; @@ -113,108 +112,10 @@ public class Jt808ProtocolHandler extends AbstractProtocolHandler { log.info("[handleBusinessMessage][JT808 业务消息已发送,clientId: {}, method: {}, messageId: {}]", clientId, message.getMethod(), message.getId()); - // JT808 协议要求:对每个上行消息都要回复通用应答(0x8001) - // 从消息的 requestId 中提取流水号和消息ID(格式:terminalPhone_flowId_timestamp) - // 从消息的 metadata 中提取终端手机号和原始消息ID - String terminalPhone = extractTerminalPhone(message); - int flowId = extractFlowId(message); - int msgId = extractJt808MsgId(message); - - if (terminalPhone != null && flowId > 0 && msgId > 0) { - // 发送通用应答(成功) - sendCommonResp(socket, terminalPhone, flowId, msgId, (byte) 0, codecType, message.getRequestId()); - } else { - log.warn("[handleBusinessMessage][无法提取 JT808 消息信息,跳过通用应答,terminalPhone: {}, flowId: {}, msgId: {}]", - terminalPhone, flowId, msgId); - } - } catch (Exception e) { log.error("[handleBusinessMessage][JT808 业务消息处理异常,clientId: {}]", clientId, e); } } - - /** - * 从消息中提取 JT808 流水号 - *

- * 提取优先级: - * 1. 从 metadata.flowId 中提取(最可靠) - * 2. 从 requestId 中提取(格式:terminalPhone_flowId_timestamp) - */ - @SuppressWarnings("unchecked") - private int extractFlowId(IotDeviceMessage message) { - // 优先从 metadata 中提取 - if (message.getParams() instanceof Map) { - Map params = (Map) message.getParams(); - if (params.get("_metadata") instanceof Map) { - Map metadata = (Map) params.get("_metadata"); - Object flowId = metadata.get("flowId"); - if (flowId instanceof Number) { - return ((Number) flowId).intValue(); - } - } - } - - // 如果 metadata 中没有,尝试从 requestId 中提取 - String requestId = message.getRequestId(); - if (requestId != null && requestId.contains("_")) { - try { - String[] parts = requestId.split("_"); - if (parts.length >= 2) { - return Integer.parseInt(parts[1]); - } - } catch (Exception e) { - log.debug("[extractFlowId][从 requestId 提取流水号失败: {}]", requestId); - } - } - - return 0; - } - - /** - * 从消息中提取 JT808 消息ID - * 从 metadata 中获取原始 JT808 消息ID(格式:0x%04X,如 "0x0200") - */ - @SuppressWarnings("unchecked") - private int extractJt808MsgId(IotDeviceMessage message) { - Object params = message.getParams(); - if (params instanceof Map) { - Map paramsMap = (Map) params; - // 尝试从 _metadata 中获取 jt808MsgId(格式:0x%04X) - Object metadata = paramsMap.get("_metadata"); - if (metadata instanceof Map) { - Map metadataMap = (Map) metadata; - Object jt808MsgIdObj = metadataMap.get("jt808MsgId"); - if (jt808MsgIdObj instanceof String) { - String jt808MsgIdStr = (String) jt808MsgIdObj; - try { - // 解析 "0x0200" 格式的字符串 - if (jt808MsgIdStr.startsWith("0x") || jt808MsgIdStr.startsWith("0X")) { - return Integer.parseInt(jt808MsgIdStr.substring(2), 16); - } else { - return Integer.parseInt(jt808MsgIdStr, 16); - } - } catch (NumberFormatException e) { - log.debug("[extractJt808MsgId][解析 jt808MsgId 失败: {}]", jt808MsgIdStr); - } - } - } - } - // 如果无法从 metadata 中提取,尝试根据 method 映射到 JT808 消息ID - String method = message.getMethod(); - return mapMethodToJt808MsgId(method); - } - - /** - * 将物模型方法名映射到 JT808 消息ID - */ - private int mapMethodToJt808MsgId(String method) { - return switch (method) { - case "thing.property.post", "thing.property.report" -> 0x0200; // 位置信息汇报 - case "thing.event.post" -> 0x0006; // 按键事件 - case "thing.state.update" -> 0x0002; // 心跳 - default -> 0x0001; // 默认使用通用应答 - }; - } @Override public void sendResponse(NetSocket socket, IotDeviceMessage originalMessage, @@ -344,30 +245,22 @@ public class Jt808ProtocolHandler extends AbstractProtocolHandler { return AuthResult.failure("设备不存在"); } - // 3. 从 Redis 获取鉴权码(优先使用注册时缓存的鉴权码) + // 3. 从 Redis 获取鉴权码 String redisKey = String.format(JT808_AUTH_TOKEN, terminalPhone); String cachedAuthToken = stringRedisTemplate.opsForValue().get(redisKey); - boolean authSuccess = false; - - if (StrUtil.isNotBlank(cachedAuthToken)) { - // 3.1 如果找到缓存,验证鉴权码是否匹配 - if (authCode.equals(cachedAuthToken)) { - authSuccess = true; - log.debug("[handleAuth][使用缓存的鉴权码验证成功,终端手机号: {}]", terminalPhone); - } else { - log.warn("[handleAuth][缓存的鉴权码验证失败,终端手机号: {}, 期望: {}, 实际: {}]", - terminalPhone, cachedAuthToken, authCode); - } - } else { - // 3.2 如果未找到缓存,尝试直接验证(支持跳过注册步骤) - log.info("[handleAuth][未找到鉴权码缓存,尝试直接验证,终端手机号: {}]", terminalPhone); - authSuccess = validateAuthCodeDirectly(device, authCode, terminalPhone); + if (StrUtil.isBlank(cachedAuthToken)) { + log.warn("[handleAuth][未找到鉴权码缓存,终端手机号: {},可能未注册或缓存已过期]", terminalPhone); + sendCommonResp(socket, terminalPhone, flowId, 0x0102, (byte) 1, codecType, message.getRequestId()); + return AuthResult.failure("未找到鉴权码,请先注册"); } - if (!authSuccess) { + // 验证鉴权码是否匹配(Redis 自动处理过期,无需手动检查) + if (!authCode.equals(cachedAuthToken)) { + log.warn("[handleAuth][鉴权码验证失败,终端手机号: {}, 期望: {}, 实际: {}]", + terminalPhone, cachedAuthToken, authCode); sendCommonResp(socket, terminalPhone, flowId, 0x0102, (byte) 1, codecType, message.getRequestId()); - return AuthResult.failure("鉴权码验证失败"); + return AuthResult.failure("鉴权码错误"); } // 4. 发送通用应答(成功,result_code=0) @@ -396,58 +289,7 @@ public class Jt808ProtocolHandler extends AbstractProtocolHandler { } } - // ========== 鉴权码生成和验证策略 ========== - - /** - * 直接验证鉴权码(跳过注册步骤) - *

- * 当设备未注册或缓存过期时,根据设备的认证类型直接验证鉴权码 - * - * @param device 设备信息 - * @param authCode 设备发送的鉴权码 - * @param terminalPhone 终端手机号 - * @return true-验证成功,false-验证失败 - */ - private boolean validateAuthCodeDirectly(IotDeviceRespDTO device, String authCode, String terminalPhone) { - try { - // 获取生效的认证类型(设备级优先,否则使用产品级) - String effectiveAuthType = StrUtil.isNotBlank(device.getAuthType()) - ? device.getAuthType() - : device.getProductAuthType(); - - if (StrUtil.isBlank(effectiveAuthType)) { - log.warn("[validateAuthCodeDirectly][认证类型为空,使用默认策略(终端手机号),终端手机号: {}]", terminalPhone); - // 默认策略:使用终端手机号作为鉴权码 - return terminalPhone.equals(authCode); - } - - // 根据认证类型验证 - if (IotAuthTypeEnum.NONE.getType().equals(effectiveAuthType)) { - // 免鉴权:直接通过(或使用终端手机号验证) - log.debug("[validateAuthCodeDirectly][免鉴权模式,终端手机号: {}]", terminalPhone); - return true; - } else if (IotAuthTypeEnum.SECRET.getType().equals(effectiveAuthType)) { - // 一机一密:使用设备的 deviceSecret -// String deviceSecret = device.getDeviceSecret(); - return terminalPhone.equals(authCode); - } else if (IotAuthTypeEnum.PRODUCT_SECRET.getType().equals(effectiveAuthType)) { - // 一型一密:使用产品的 productSecret - // 注意:设备信息中可能没有 productSecret,需要从产品获取 - // 这里暂时使用终端手机号作为回退策略 - log.warn("[validateAuthCodeDirectly][一型一密需要产品密钥,当前暂不支持直接验证,终端手机号: {},使用终端手机号作为回退", terminalPhone); - return terminalPhone.equals(authCode); - } else { - // 其他类型:使用终端手机号作为回退策略 - log.warn("[validateAuthCodeDirectly][未知认证类型: {},使用终端手机号作为回退,终端手机号: {}]", - effectiveAuthType, terminalPhone); - return terminalPhone.equals(authCode); - } - - } catch (Exception e) { - log.error("[validateAuthCodeDirectly][直接验证鉴权码异常,终端手机号: {}]", terminalPhone, e); - return false; - } - } + // ========== 鉴权码生成策略 ========== /** * 生成鉴权码 @@ -620,6 +462,28 @@ public class Jt808ProtocolHandler extends AbstractProtocolHandler { return null; } + /** + * 从消息中提取流水号 + *

+ * 流水号存储在消息的 _metadata.flowId 字段中 + * + * @param message 设备消息 + * @return 流水号,提取失败返回 1 + */ + @SuppressWarnings("unchecked") + private int extractFlowId(IotDeviceMessage message) { + if (message.getParams() instanceof Map) { + Map params = (Map) message.getParams(); + if (params.get("_metadata") instanceof Map) { + Map metadata = (Map) params.get("_metadata"); + Object flowId = metadata.get("flowId"); + if (flowId instanceof Number) { + return ((Number) flowId).intValue(); + } + } + } + return 1; + } /** * 从消息中提取鉴权码