refactor(iot-gateway): JT808编解码器代码重构
- 提取 enrichParamsWithMetadata 方法统一处理元数据 - 提取 extractPhoneFromMap 和 extractFlowIdFromMap 方法 - 优化 getParamsAsMap 方法,同时支持 params 和 data 字段 - 优化代码注释格式 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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.heartbeat(JT808 专用,不映射到物模型)
|
||||
* - 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user