refactor(iot-gateway): JT808编解码器代码重构
Some checks failed
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled

- 提取 enrichParamsWithMetadata 方法统一处理元数据
- 提取 extractPhoneFromMap 和 extractFlowIdFromMap 方法
- 优化 getParamsAsMap 方法,同时支持 params 和 data 字段
- 优化代码注释格式

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-01-21 22:15:12 +08:00
parent 110cfc8cf8
commit 4e387e410c

View File

@@ -11,7 +11,6 @@ import com.viewsh.module.iot.gateway.codec.jt808.util.Jt808ProtocolUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@@ -115,36 +114,23 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec {
int msgId = head.getId(); int msgId = head.getId();
// 生成消息ID使用流水号作为标识 // 生成消息ID使用流水号作为标识
String messageId = head.getTerminalPhone() + "_" + head.getFlowId(); String terminalPhone = head.getTerminalPhone();
int flowId = head.getFlowId();
String messageId = terminalPhone + "_" + flowId;
// 根据消息ID确定物模型标准方法名 // 根据消息ID确定物模型标准方法名
String method = getStandardMethodName(msgId); String method = getStandardMethodName(msgId);
Object params = parseMessageParams(dataPack, msgId); Object params = parseMessageParams(dataPack, msgId);
// 构建元数据(保留在 params 中用于调试和追踪) // 构建元数据并添加到 params 中用于调试和追踪)
if (params instanceof Map) { params = enrichParamsWithMetadata(params, msgId, terminalPhone, flowId, head.getEncryptionType());
@SuppressWarnings("unchecked")
// 确保 paramsMap 是可变的,防止 parseMessageParams 返回不可变 Map 导致 put 报错
Map<String, Object> paramsMap = new HashMap<>((Map<String, Object>) params);
Map<String, Object> 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 引用,确保使用的是包含 metadata 的可变 Map
params = paramsMap;
}
// 创建 IotDeviceMessage return IotDeviceMessage.of(messageId, method, params, null, null, null);
IotDeviceMessage message = IotDeviceMessage.of(messageId, method, params, null, null, null);
message.setReportTime(LocalDateTime.now());
return message;
} }
/** /**
* 根据 JT808 消息ID获取物模型标准方法名 * 根据 JT808 消息ID获取物模型标准方法名
* * <p>
* 映射关系: * 映射关系:
* - 0x0002 心跳 -> jt808.terminal.heartbeatJT808 专用,不映射到物模型) * - 0x0002 心跳 -> jt808.terminal.heartbeatJT808 专用,不映射到物模型)
* - 0x0200 位置上报 -> thing.property.post属性上报 * - 0x0200 位置上报 -> thing.property.post属性上报
@@ -214,18 +200,18 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec {
/** /**
* 解析按键事件为事件上报格式thing.event.post * 解析按键事件为事件上报格式thing.event.post
* * <p>
* 物模型标准格式: * 物模型标准格式:
* { * {
* "identifier": "button_event", * "identifier": "button_event",
* "eventTime": 1234567890, * "eventTime": 1234567890,
* "params": { * "params": {
* "keyId": 1, * "keyId": 1,
* "keyState": 1, * "keyState": 1,
* "keyType": "short_press", * "keyType": "short_press",
* "keyNumber": 1, * "keyNumber": 1,
* "isLongPress": false * "isLongPress": false
* } * }
* } * }
*/ */
private Map<String, Object> parseButtonEventAsEvent(Jt808DataPack dataPack) { private Map<String, Object> parseButtonEventAsEvent(Jt808DataPack dataPack) {
@@ -233,20 +219,17 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec {
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
// 统一使用一个事件标识符,通过 isLongPress 参数区分短按和长按 // 使用 identifier 字段(符合物模型标准格式),用于存储到数据库的 identifier 字段
// 注意:必须使用 "identifier" 字段,以便 IotDeviceMessageUtils.getIdentifier() 正确提取
result.put("identifier", "button_event"); result.put("identifier", "button_event");
// 事件时间戳 // 事件时间戳
result.put("eventTime", System.currentTimeMillis()); result.put("eventTime", System.currentTimeMillis());
// 事件参数(包含 isLongPress 字段用于区分短按和长按 // 事件参数(只保留 keyId 和 keyState 两个字段
// 通过 keyId 可以判断是长按还是短按keyId >= 0x0B 为长按
Map<String, Object> eventParams = new HashMap<>(); Map<String, Object> eventParams = new HashMap<>();
eventParams.put("keyId", event.getKeyId()); eventParams.put("keyId", event.getKeyId());
eventParams.put("keyState", event.getKeyState()); eventParams.put("keyState", event.getKeyState());
eventParams.put("keyType", event.getKeyType());
eventParams.put("keyNumber", event.getKeyNumber());
eventParams.put("isLongPress", event.getIsLongPress());
result.put("params", eventParams); result.put("params", eventParams);
return result; return result;
@@ -254,13 +237,13 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec {
/** /**
* 解析位置信息为属性上报格式thing.property.post * 解析位置信息为属性上报格式thing.property.post
* * <p>
* 物模型标准格式params 直接就是属性键值对 * 物模型标准格式params 直接就是属性键值对
* { * {
* "latitude": 31.123456, * "latitude": 31.123456,
* "longitude": 121.123456, * "longitude": 121.123456,
* "batteryLevel": 80, * "batteryLevel": 80,
* ... * ...
* } * }
*/ */
private Map<String, Object> parseLocationInfoAsProperties(Jt808DataPack dataPack) { private Map<String, Object> parseLocationInfoAsProperties(Jt808DataPack dataPack) {
@@ -430,15 +413,15 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec {
/** /**
* 编码服务调用thing.service.invoke * 编码服务调用thing.service.invoke
* * <p>
* 根据服务标识符映射到不同的 JT808 指令 * 根据服务标识符映射到不同的 JT808 指令
* * <p>
* 消息格式: * 消息格式:
* { * {
* "identifier": "服务标识符", * "identifier": "服务标识符",
* "params": { * "params": {
* // 服务参数 * // 服务参数
* } * }
* } * }
*/ */
private byte[] encodeServiceInvoke(IotDeviceMessage message, String phone, int flowId) { private byte[] encodeServiceInvoke(IotDeviceMessage message, String phone, int flowId) {
@@ -483,15 +466,15 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec {
/** /**
* 编码属性设置thing.property.set * 编码属性设置thing.property.set
* * <p>
* 属性设置映射到 JT808 的参数设置指令 (0x8103) * 属性设置映射到 JT808 的参数设置指令 (0x8103)
* * <p>
* 消息格式: * 消息格式:
* { * {
* "properties": { * "properties": {
* "identifier1": value1, * "identifier1": value1,
* "identifier2": value2 * "identifier2": value2
* } * }
* } * }
*/ */
private byte[] encodePropertySet(IotDeviceMessage message, String phone, int flowId) { private byte[] encodePropertySet(IotDeviceMessage message, String phone, int flowId) {
@@ -520,7 +503,7 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec {
/** /**
* 将物模型属性映射到 JT808 参数 * 将物模型属性映射到 JT808 参数
* * <p>
* 映射关系参考 JT808 协议标准: * 映射关系参考 JT808 协议标准:
* - 0x0029: 心跳发送间隔 * - 0x0029: 心跳发送间隔
* - 0x0027: 位置汇报间隔 * - 0x0027: 位置汇报间隔
@@ -560,35 +543,61 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec {
/** /**
* 提取终端手机号 * 提取终端手机号
* * <p>
* 优先级: * 优先级:
* 1. 从 params._deviceName 中获取下发场景IotTcpDownstreamHandler 自动注入) * 1. 从 params._deviceName 中获取下发场景IotTcpDownstreamHandler 自动注入)
* 2. 从 params._metadata.terminalPhone 中获取(上行消息回复场景) * 2. 从 params._metadata.terminalPhone 中获取(上行消息回复场景)
* 3. 从 params.phone 中获取(手动指定,向下兼容,不推荐) * 3. 从 params.phone 中获取(手动指定,向下兼容,不推荐)
*/ */
private String extractPhoneNumber(IotDeviceMessage message) { private String extractPhoneNumber(IotDeviceMessage message) {
if (!(message.getParams() instanceof Map)) { // 尝试从 params 提取
log.error("[extractPhoneNumber][params 不是 Map 类型,消息: {}]", message); if (message.getParams() instanceof Map) {
throw new IllegalArgumentException("消息参数格式错误params 必须是 Map 类型"); Map<?, ?> params = (Map<?, ?>) message.getParams();
String phone = extractPhoneFromMap(params);
if (phone != null) {
return phone;
}
} }
Map<?, ?> params = (Map<?, ?>) message.getParams(); // 尝试从 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;
}
// 1. 优先从 _deviceName 获取(下发场景,由 IotTcpDownstreamHandler 注入) // 1. 优先从 _deviceName 获取(下发场景,由 IotTcpDownstreamHandler 注入)
Object deviceName = params.get("_deviceName"); Object deviceName = map.get("_deviceName");
if (deviceName != null && StrUtil.isNotBlank(deviceName.toString())) { if (deviceName != null && StrUtil.isNotBlank(deviceName.toString())) {
String deviceNameStr = deviceName.toString().trim(); String deviceNameStr = deviceName.toString().trim();
// 验证是否为数字(终端手机号应该是纯数字) // 验证是否为数字(终端手机号应该是纯数字)
if (deviceNameStr.matches("\\d+")) { if (deviceNameStr.matches("\\d+")) {
return deviceNameStr; return deviceNameStr;
} else { } else {
log.warn("[extractPhoneNumber][_deviceName 不是纯数字: {}]", deviceNameStr); log.warn("[extractPhoneFromMap][_deviceName 不是纯数字: {}]", deviceNameStr);
} }
} }
// 2. 从 metadata 中获取(上行消息回复场景) // 2. 从 metadata 中获取(上行消息回复场景)
if (params.get("_metadata") instanceof Map) { if (map.get("_metadata") instanceof Map) {
Map<?, ?> metadata = (Map<?, ?>) params.get("_metadata"); Map<?, ?> metadata = (Map<?, ?>) map.get("_metadata");
Object terminalPhone = metadata.get("terminalPhone"); Object terminalPhone = metadata.get("terminalPhone");
if (terminalPhone != null && StrUtil.isNotBlank(terminalPhone.toString())) { if (terminalPhone != null && StrUtil.isNotBlank(terminalPhone.toString())) {
return terminalPhone.toString(); return terminalPhone.toString();
@@ -596,7 +605,7 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec {
} }
// 3. 从 phone 字段获取(向下兼容,不推荐) // 3. 从 phone 字段获取(向下兼容,不推荐)
Object phone = params.get("phone"); Object phone = map.get("phone");
if (phone != null && StrUtil.isNotBlank(phone.toString())) { if (phone != null && StrUtil.isNotBlank(phone.toString())) {
String phoneStr = phone.toString().trim(); String phoneStr = phone.toString().trim();
if (phoneStr.matches("\\d+")) { if (phoneStr.matches("\\d+")) {
@@ -604,34 +613,30 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec {
} }
} }
// 4. 如果都获取不到,抛出异常 return null;
log.error("[extractPhoneNumber][无法提取终端手机号params: {}]", params);
throw new IllegalArgumentException(
"消息中缺少终端手机号。请确保设备的 deviceName 为终端手机号(纯数字),例如: \"13800138000\"");
} }
/** /**
* 提取流水号 * 提取流水号
* * <p>
* 对于下发消息,如果没有指定流水号,则生成一个随机流水号 * 对于下发消息,如果没有指定流水号,则生成一个随机流水号
*/ */
private int extractFlowId(IotDeviceMessage message) { private int extractFlowId(IotDeviceMessage message) {
// 尝试从 params 提取
if (message.getParams() instanceof Map) { if (message.getParams() instanceof Map) {
Map<?, ?> params = (Map<?, ?>) message.getParams(); Map<?, ?> params = (Map<?, ?>) message.getParams();
Integer flowId = extractFlowIdFromMap(params);
// 尝试获取显式指定的流水号 if (flowId != null) {
Object flowId = params.get("flowId"); return flowId;
if (flowId instanceof Number) {
return ((Number) flowId).intValue();
} }
}
// 尝试从 metadata 中获取(上行消息的流水号 // 尝试从 data 提取(应答消息使用 replyOf 时,参数在 data 字段
if (params.get("_metadata") instanceof Map) { if (message.getData() instanceof Map) {
Map<?, ?> metadata = (Map<?, ?>) params.get("_metadata"); Map<?, ?> data = (Map<?, ?>) message.getData();
Object metaFlowId = metadata.get("flowId"); Integer flowId = extractFlowIdFromMap(data);
if (metaFlowId instanceof Number) { if (flowId != null) {
return ((Number) metaFlowId).intValue(); return flowId;
}
} }
} }
@@ -639,19 +644,61 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec {
return (int) (System.currentTimeMillis() % 65535) + 1; 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 * 获取参数为Map
* <p>
* 优先从 params 获取,如果 params 为空则从 data 获取(应答消息场景)
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private Map<String, Object> getParamsAsMap(IotDeviceMessage message) { private Map<String, Object> getParamsAsMap(IotDeviceMessage message) {
// 优先从 params 获取
if (message.getParams() instanceof Map) { if (message.getParams() instanceof Map) {
return (Map<String, Object>) message.getParams(); return (Map<String, Object>) message.getParams();
} }
// 尝试JSON转换
// 从 data 获取(应答消息使用 replyOf 时,参数在 data 字段)
if (message.getData() instanceof Map) {
return (Map<String, Object>) message.getData();
}
// 尝试JSON转换 params
if (message.getParams() != null) { if (message.getParams() != null) {
String json = JsonUtils.toJsonString(message.getParams()); String json = JsonUtils.toJsonString(message.getParams());
return JsonUtils.parseObject(json, Map.class); 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<>(); return new HashMap<>();
} }
@@ -682,4 +729,42 @@ public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec {
&& data[data.length - 1] == (byte) Jt808Constants.PKG_DELIMITER; && data[data.length - 1] == (byte) Jt808Constants.PKG_DELIMITER;
} }
/**
* 为参数对象添加元数据
*
* @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<String, Object> 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 类型,则添加元数据
if (params instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> paramsMap = (Map<String, Object>) params;
// 创建新的可变 Map确保可以添加元数据
// 注意:某些解析方法可能返回不可变的 Map如 Map.of()),需要转换为可变 Map
Map<String, Object> enrichedParams = new HashMap<>(paramsMap);
enrichedParams.put("_metadata", metadata);
return enrichedParams;
}
// 如果 params 不是 Map则包装为包含元数据的 Map
// 保留原始 params 在 _data 字段中
Map<String, Object> wrapper = new HashMap<>(2);
wrapper.put("_data", params);
wrapper.put("_metadata", metadata);
return wrapper;
}
} }