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