diff --git a/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/api/device/IoTDeviceApiImpl.java b/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/api/device/IoTDeviceApiImpl.java
index 3b72e99..1cbec9f 100644
--- a/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/api/device/IoTDeviceApiImpl.java
+++ b/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/api/device/IoTDeviceApiImpl.java
@@ -53,6 +53,7 @@ public class IoTDeviceApiImpl implements IotDeviceCommonApi {
IotProductDO product = productService.getProductFromCache(deviceDTO.getProductId());
if (product != null) {
deviceDTO.setCodecType(product.getCodecType());
+ deviceDTO.setProductAuthType(product.getAuthType());
}
}));
}
diff --git a/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/dal/dataobject/device/IotDeviceDO.java b/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/dal/dataobject/device/IotDeviceDO.java
index c0ecc5d..3a76ad9 100644
--- a/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/dal/dataobject/device/IotDeviceDO.java
+++ b/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/dal/dataobject/device/IotDeviceDO.java
@@ -124,9 +124,11 @@ public class IotDeviceDO extends TenantBaseDO {
*/
private String deviceSecret;
/**
- * 认证类型(如一机一密、动态注册)
+ * 认证类型
+ *
+ * 如果为空,则继承产品的 authType
+ * 枚举 {@link com.viewshanghai.module.iot.core.enums.IotAuthTypeEnum}
*/
- // TODO @haohao:是不是要枚举哈
private String authType;
/**
diff --git a/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/dal/dataobject/product/IotProductDO.java b/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/dal/dataobject/product/IotProductDO.java
index 6acd98f..a0d2b9f 100644
--- a/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/dal/dataobject/product/IotProductDO.java
+++ b/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/dal/dataobject/product/IotProductDO.java
@@ -84,4 +84,17 @@ public class IotProductDO extends TenantBaseDO {
*/
private String codecType;
+ /**
+ * 认证类型(认证策略)
+ *
+ * 默认认证方式,可被设备级配置覆盖
+ * 枚举 {@link com.viewshanghai.module.iot.core.enums.IotAuthTypeEnum}
+ */
+ private String authType;
+
+ /**
+ * 产品密钥 (一型一密认证需要)
+ */
+ private String productSecret;
+
}
\ No newline at end of file
diff --git a/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/service/device/IotDeviceServiceImpl.java b/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/service/device/IotDeviceServiceImpl.java
index fff7111..df1157f 100644
--- a/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/service/device/IotDeviceServiceImpl.java
+++ b/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/service/device/IotDeviceServiceImpl.java
@@ -13,6 +13,7 @@ import com.viewshanghai.framework.tenant.core.aop.TenantIgnore;
import com.viewshanghai.framework.tenant.core.util.TenantUtils;
import com.viewshanghai.module.iot.controller.admin.device.vo.device.*;
import com.viewshanghai.module.iot.core.biz.dto.IotDeviceAuthReqDTO;
+import com.viewshanghai.module.iot.core.enums.IotAuthTypeEnum;
import com.viewshanghai.module.iot.core.enums.IotDeviceStateEnum;
import com.viewshanghai.module.iot.core.util.IotDeviceAuthUtils;
import com.viewshanghai.module.iot.dal.dataobject.device.IotDeviceDO;
@@ -398,9 +399,31 @@ public class IotDeviceServiceImpl implements IotDeviceService {
@Override
public IotDeviceAuthInfoRespVO getDeviceAuthInfo(Long id) {
IotDeviceDO device = validateDeviceExists(id);
+
+ // 获取生效的认证类型
+ String effectiveAuthType = device.getAuthType();
+ String targetSecret = device.getDeviceSecret();
+
+ if (StrUtil.isEmpty(effectiveAuthType)) {
+ IotProductDO product = productService.getProductFromCache(device.getProductId());
+ if (product != null) {
+ effectiveAuthType = product.getAuthType();
+ // 如果是产品级一型一密,需要获取 ProductSecret
+ if (IotAuthTypeEnum.PRODUCT_SECRET.getType().equals(effectiveAuthType)) {
+ targetSecret = product.getProductSecret();
+ }
+ }
+ } else if (IotAuthTypeEnum.PRODUCT_SECRET.getType().equals(effectiveAuthType)) {
+ // 如果设备级强制设为一型一密(罕见但逻辑上兼容)
+ IotProductDO product = productService.getProductFromCache(device.getProductId());
+ if (product != null) {
+ targetSecret = product.getProductSecret();
+ }
+ }
+
// 使用 IotDeviceAuthUtils 生成认证信息
IotDeviceAuthUtils.AuthInfo authInfo = IotDeviceAuthUtils.getAuthInfo(
- device.getProductKey(), device.getDeviceName(), device.getDeviceSecret());
+ device.getProductKey(), device.getDeviceName(), targetSecret);
return BeanUtils.toBean(authInfo, IotDeviceAuthInfoRespVO.class);
}
@@ -455,13 +478,68 @@ public class IotDeviceServiceImpl implements IotDeviceService {
String deviceName = deviceInfo.getDeviceName();
String productKey = deviceInfo.getProductKey();
IotDeviceDO device = getSelf().getDeviceFromCache(productKey, deviceName);
+
+ // 1.1 处理动态注册 (Dynamic Registration)
+ if (device == null) {
+ // 获取产品信息
+ IotProductDO product = productService.getProductByProductKey(productKey);
+ if (product != null && IotAuthTypeEnum.DYNAMIC.getType().equals(product.getAuthType())) {
+ // 自动创建设备
+ IotDeviceSaveReqVO createReq = new IotDeviceSaveReqVO()
+ .setProductId(product.getId())
+ .setDeviceName(deviceName);
+ try {
+ // TODO: 考虑并发问题,这里简单处理
+ getSelf().createDevice(createReq);
+ // 重新获取设备
+ device = getSelf().getDeviceFromCache(productKey, deviceName);
+ log.info("[authDevice][动态注册设备成功: {}/{}]", productKey, deviceName);
+ } catch (Exception e) {
+ log.error("[authDevice][动态注册设备失败: {}/{}]", productKey, deviceName, e);
+ return false;
+ }
+ }
+ }
+
if (device == null) {
log.warn("[authDevice][设备({}/{}) 不存在]", productKey, deviceName);
return false;
}
// 2. 校验密码
- IotDeviceAuthUtils.AuthInfo authInfo = IotDeviceAuthUtils.getAuthInfo(productKey, deviceName, device.getDeviceSecret());
+ // 2.1 获取生效的认证类型
+ String effectiveAuthType = device.getAuthType();
+ if (StrUtil.isEmpty(effectiveAuthType)) {
+ IotProductDO product = productService.getProductFromCache(device.getProductId());
+ if (product != null) {
+ effectiveAuthType = product.getAuthType();
+ }
+ }
+
+ // 2.2 根据类型校验
+ String targetSecret = device.getDeviceSecret(); // 默认为一机一密
+
+ if (IotAuthTypeEnum.PRODUCT_SECRET.getType().equals(effectiveAuthType)) {
+ // 一型一密:使用 ProductSecret
+ IotProductDO product = productService.getProductFromCache(device.getProductId());
+ if (product == null || StrUtil.isEmpty(product.getProductSecret())) {
+ log.error("[authDevice][一型一密认证失败,ProductSecret 为空: {}]", productKey);
+ return false;
+ }
+ targetSecret = product.getProductSecret();
+ } else if (IotAuthTypeEnum.NONE.getType().equals(effectiveAuthType)) {
+ // 免鉴权:直接通过
+ return true;
+ } else if (IotAuthTypeEnum.DYNAMIC.getType().equals(effectiveAuthType)) {
+ // 动态注册后,通常转为一机一密或一型一密,这里假设动态注册使用一型一密校验
+ // 或者是免鉴权。具体策略视业务而定。这里暂定为一型一密。
+ IotProductDO product = productService.getProductFromCache(device.getProductId());
+ if (product != null) {
+ targetSecret = product.getProductSecret();
+ }
+ }
+
+ IotDeviceAuthUtils.AuthInfo authInfo = IotDeviceAuthUtils.getAuthInfo(productKey, deviceName, targetSecret);
if (ObjUtil.notEqual(authInfo.getPassword(), authReqDTO.getPassword())) {
log.error("[authDevice][设备({}/{}) 密码不正确]", productKey, deviceName);
return false;
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 a2494f1..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
@@ -169,10 +169,13 @@ public class IotDeviceMessageServiceImpl implements IotDeviceMessageService {
getSelf().createDeviceLogAsync(message);
// 3. 回复消息。前提:非 _reply 消息,并且非禁用回复的消息
+ // 条件1:防止对"回复消息"再次回复,避免无限循环
+ // 条件2:某些特定的method不需要回复(如设备状态变更、OTA进度上报)
+ // 条件3(新增):HTTP短连接场景,因为已经在请求中直接响应了,不需要再通过消息总线发送回复
if (IotDeviceMessageUtils.isReplyMessage(message)
|| IotDeviceMessageMethodEnum.isReplyDisabled(message.getMethod())
|| StrUtil.isEmpty(message.getServerId())) {
- return;
+ return; // serverId 为空,不记录回复消息
}
try {
IotDeviceMessage replyMessage = IotDeviceMessage.replyOf(message.getRequestId(), message.getMethod(), replyData,
diff --git a/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/service/product/IotProductServiceImpl.java b/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/service/product/IotProductServiceImpl.java
index f2f1098..fe48ea1 100644
--- a/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/service/product/IotProductServiceImpl.java
+++ b/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/java/com/viewshanghai/module/iot/service/product/IotProductServiceImpl.java
@@ -1,5 +1,6 @@
package com.viewshanghai.module.iot.service.product;
+import cn.hutool.core.util.IdUtil;
import cn.hutool.core.collection.CollUtil;
import com.viewshanghai.framework.common.pojo.PageResult;
import com.viewshanghai.framework.common.util.object.BeanUtils;
@@ -53,7 +54,8 @@ public class IotProductServiceImpl implements IotProductService {
// 2. 插入
IotProductDO product = BeanUtils.toBean(createReqVO, IotProductDO.class)
- .setStatus(IotProductStatusEnum.UNPUBLISHED.getStatus());
+ .setStatus(IotProductStatusEnum.UNPUBLISHED.getStatus())
+ .setProductSecret(IdUtil.fastSimpleUUID()); // 生成 ProductSecret
productMapper.insert(product);
return product.getId();
}
diff --git a/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/resources/mapper/device/IotDeviceMessageMapper.xml b/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/resources/mapper/device/IotDeviceMessageMapper.xml
index 261ea92..2a0a10e 100644
--- a/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/resources/mapper/device/IotDeviceMessageMapper.xml
+++ b/viewshanghai-module-iot/viewshanghai-module-iot-biz/src/main/resources/mapper/device/IotDeviceMessageMapper.xml
@@ -38,7 +38,7 @@
USING device_message
TAGS (#{deviceId})
VALUES (
- NOW, #{id}, #{reportTime}, #{tenantId}, #{serverId},
+ #{reportTime}, #{id}, #{reportTime}, #{tenantId}, #{serverId},
#{upstream}, #{reply}, #{identifier}, #{requestId}, #{method},
#{params}, #{data}, #{code}, #{msg}
)
diff --git a/viewshanghai-module-iot/viewshanghai-module-iot-core/src/main/java/com/viewshanghai/module/iot/core/biz/dto/IotDeviceRespDTO.java b/viewshanghai-module-iot/viewshanghai-module-iot-core/src/main/java/com/viewshanghai/module/iot/core/biz/dto/IotDeviceRespDTO.java
index d3bb943..765f67c 100644
--- a/viewshanghai-module-iot/viewshanghai-module-iot-core/src/main/java/com/viewshanghai/module/iot/core/biz/dto/IotDeviceRespDTO.java
+++ b/viewshanghai-module-iot/viewshanghai-module-iot-core/src/main/java/com/viewshanghai/module/iot/core/biz/dto/IotDeviceRespDTO.java
@@ -38,4 +38,16 @@ public class IotDeviceRespDTO {
*/
private String codecType;
+ /**
+ * 设备级认证类型
+ * 枚举 {@link com.viewshanghai.module.iot.core.enums.IotAuthTypeEnum}
+ */
+ private String authType;
+
+ /**
+ * 产品级认证类型 (兜底策略)
+ * 枚举 {@link com.viewshanghai.module.iot.core.enums.IotAuthTypeEnum}
+ */
+ private String productAuthType;
+
}
\ No newline at end of file
diff --git a/viewshanghai-module-iot/viewshanghai-module-iot-core/src/main/java/com/viewshanghai/module/iot/core/enums/IotAuthTypeEnum.java b/viewshanghai-module-iot/viewshanghai-module-iot-core/src/main/java/com/viewshanghai/module/iot/core/enums/IotAuthTypeEnum.java
new file mode 100644
index 0000000..7085735
--- /dev/null
+++ b/viewshanghai-module-iot/viewshanghai-module-iot-core/src/main/java/com/viewshanghai/module/iot/core/enums/IotAuthTypeEnum.java
@@ -0,0 +1,58 @@
+package com.viewshanghai.module.iot.core.enums;
+
+import com.viewshanghai.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * IoT 认证类型枚举
+ *
+ * 用于产品(Product)和设备(Device)的认证策略配置
+ *
+ * @author 芋道源码
+ */
+@AllArgsConstructor
+@Getter
+public enum IotAuthTypeEnum implements ArrayValuable {
+
+ /**
+ * 一机一密:每个设备有独立的 DeviceSecret
+ */
+ SECRET("SECRET", "一机一密"),
+
+ /**
+ * 一型一密:同产品下所有设备共用 ProductSecret
+ */
+ PRODUCT_SECRET("PRODUCT_SECRET", "一型一密"),
+
+ /**
+ * 动态注册:设备初次连接时自动创建,通常配合一型一密或免鉴权使用
+ * 注意:这通常是一个"能力开关"而非单纯的"认证方式",但在某些简单模型下可作为一种策略标识
+ * TODO: 暂未实现动态注册
+ */
+ DYNAMIC("DYNAMIC", "动态注册"),
+
+ /**
+ * 免鉴权:仅校验 ProductKey 和 DeviceName 存在
+ */
+ NONE("NONE", "免鉴权");
+
+ public static final String[] ARRAYS = Arrays.stream(values()).map(IotAuthTypeEnum::getType).toArray(String[]::new);
+
+ /**
+ * 类型代码
+ */
+ private final String type;
+ /**
+ * 名字
+ */
+ private final String name;
+
+ @Override
+ public String[] array() {
+ return ARRAYS;
+ }
+
+}
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
new file mode 100644
index 0000000..0c1a87f
--- /dev/null
+++ b/viewshanghai-module-iot/viewshanghai-module-iot-gateway/src/main/java/com/viewshanghai/module/iot/gateway/codec/jt808/IotJt808DeviceMessageCodec.java
@@ -0,0 +1,672 @@
+package com.viewshanghai.module.iot.gateway.codec.jt808;
+
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.StrUtil;
+import com.viewshanghai.framework.common.util.json.JsonUtils;
+import com.viewshanghai.module.iot.core.enums.IotDeviceMessageMethodEnum;
+import com.viewshanghai.module.iot.core.mq.message.IotDeviceMessage;
+import com.viewshanghai.module.iot.gateway.codec.IotDeviceMessageCodec;
+import com.viewshanghai.module.iot.gateway.codec.jt808.entity.*;
+import com.viewshanghai.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;
+import java.util.Map;
+
+/**
+ * JT808 协议 {@link IotDeviceMessage} 编解码器
+ *
+ * 将 JT808 标准协议转换为统一的 IotDeviceMessage 格式
+ *
+ * @author lzh
+ */
+@Slf4j
+@Component
+public class IotJt808DeviceMessageCodec implements IotDeviceMessageCodec {
+
+ public static final String TYPE = "JT808";
+
+ private final Jt808Decoder decoder;
+ private final Jt808Encoder encoder;
+ private final Jt808ProtocolUtil protocolUtil;
+
+ public IotJt808DeviceMessageCodec() {
+ this.decoder = new Jt808Decoder();
+ this.encoder = new Jt808Encoder();
+ this.protocolUtil = new Jt808ProtocolUtil();
+ }
+
+ @Override
+ public String type() {
+ return TYPE;
+ }
+
+ @Override
+ public byte[] encode(IotDeviceMessage message) {
+ Assert.notNull(message, "消息不能为空");
+ Assert.notBlank(message.getMethod(), "消息方法不能为空");
+
+ try {
+ // 从消息中提取必要信息
+ String phone = extractPhoneNumber(message);
+ int flowId = extractFlowId(message);
+ String method = message.getMethod();
+
+ // 根据方法名路由到不同的编码器
+ return switch (method) {
+ // === 标准物模型方法 ===
+ case "thing.service.invoke" -> encodeServiceInvoke(message, phone, flowId); // 服务调用
+ case "thing.property.set" -> encodePropertySet(message, phone, flowId); // 属性设置
+
+ // === JT808 内部协议方法(向下兼容) ===
+ case "jt808.platform.commonResp", "commonResp" -> encodeCommonResp(message, phone, flowId);
+ case "jt808.platform.registerResp", "registerResp" -> encodeRegisterResp(message, phone, flowId);
+ case "jt808.platform.textDown", "textDown" -> encodeTextDown(message, phone, flowId);
+
+ default -> {
+ log.warn("[encode][不支持的消息方法: {}]", method);
+ yield new byte[0];
+ }
+ };
+ } catch (Exception e) {
+ log.error("[encode][JT808 消息编码失败,消息: {}]", message, e);
+ throw new RuntimeException("JT808 消息编码失败: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public IotDeviceMessage decode(byte[] bytes) {
+ Assert.notNull(bytes, "待解码数据不能为空");
+ Assert.isTrue(bytes.length >= 12, "数据包长度不足");
+
+ try {
+ // 1. 反转义(去除首尾标识符)
+ byte[] unescapedBytes = protocolUtil.doEscape4Receive(bytes, 1, bytes.length - 1);
+
+ // 2. 解析为 JT808 数据包
+ Jt808DataPack dataPack = decoder.bytes2PackageData(unescapedBytes);
+
+ // 3. 转换为统一的 IotDeviceMessage
+ return convertToIotDeviceMessage(dataPack);
+ } catch (Exception e) {
+ log.error("[decode][JT808 消息解码失败,数据长度: {}]", bytes.length, e);
+ throw new RuntimeException("JT808 消息解码失败: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * 将 JT808 数据包转换为 IotDeviceMessage
+ */
+ private IotDeviceMessage convertToIotDeviceMessage(Jt808DataPack dataPack) {
+ Jt808DataPack.PackHead head = dataPack.getPackHead();
+ int msgId = head.getId();
+
+ // 生成消息ID(使用流水号作为标识)
+ String messageId = head.getTerminalPhone() + "_" + head.getFlowId();
+
+ // 根据消息ID确定物模型标准方法名
+ String method = getStandardMethodName(msgId);
+ Object params = parseMessageParams(dataPack, msgId);
+
+ // 构建元数据(保留在 params 中,用于调试和追踪)
+ if (params instanceof Map) {
+ @SuppressWarnings("unchecked")
+ Map paramsMap = (Map) params;
+ 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);
+ }
+
+ // 创建 IotDeviceMessage
+ IotDeviceMessage message = IotDeviceMessage.of(messageId, method, params, null, null, null);
+ message.setReportTime(LocalDateTime.now());
+ return message;
+ }
+
+ /**
+ * 根据 JT808 消息ID获取物模型标准方法名
+ *
+ * 映射关系:
+ * - 0x0002 心跳 -> thing.state.update(设备状态更新)
+ * - 0x0200 位置上报 -> thing.property.post(属性上报)
+ * - 0x0704 批量位置上报 -> thing.property.post(属性上报)
+ * - 0x0006 按键事件 -> thing.event.post(事件上报)
+ * - 0x0100 注册 -> 内部认证流程,不使用标准方法
+ * - 0x0102 鉴权 -> 内部认证流程,不使用标准方法
+ * - 0x0001 通用应答 -> 内部应答,不使用标准方法
+ */
+ private String getStandardMethodName(int msgId) {
+ return switch (msgId) {
+ // 设备状态类
+ case 0x0002 -> IotDeviceMessageMethodEnum.STATE_UPDATE.getMethod(); // 心跳 -> 状态更新
+
+ // 属性上报类
+ case 0x0200 -> IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(); // 位置信息汇报 -> 属性上报
+ case 0x0704 -> IotDeviceMessageMethodEnum.PROPERTY_POST.getMethod(); // 批量位置上传 -> 属性上报
+
+ // 事件上报类
+ case 0x0006 -> IotDeviceMessageMethodEnum.EVENT_POST.getMethod(); // 按键事件 -> 事件上报
+
+ // 内部协议类(不映射到标准方法,使用 JT808 特定方法名)
+ case 0x0001 -> "jt808.terminal.commonResp"; // 终端通用应答
+ case 0x0100 -> "jt808.terminal.register"; // 终端注册
+ case 0x0102 -> "jt808.terminal.auth"; // 终端鉴权
+ case 0x0003 -> "jt808.terminal.logout"; // 终端注销
+
+ // 未知消息
+ default -> "jt808.unknown.0x" + Integer.toHexString(msgId);
+ };
+ }
+
+ /**
+ * 解析消息参数(根据物模型标准格式)
+ */
+ private Object parseMessageParams(Jt808DataPack dataPack, int msgId) {
+ byte[] bodyBytes = dataPack.getBodyBytes();
+
+ // 针对不同消息类型进行特殊解析,返回符合物模型标准的格式
+ return switch (msgId) {
+ // thing.property.post - 返回 properties
+ case 0x0200, 0x0704 -> parseLocationInfoAsProperties(dataPack);
+
+ // thing.state.update - 返回 state 信息
+ case 0x0002 -> parseHeartbeatAsState();
+
+ // thing.event.post - 返回 event 信息
+ case 0x0006 -> parseButtonEventAsEvent(dataPack);
+
+ // JT808 内部协议消息 - 保持原有格式
+ case 0x0100 -> parseRegisterInfo(dataPack);
+ case 0x0102 -> parseAuthInfo(bodyBytes);
+ default -> parseGenericParams(bodyBytes);
+ };
+ }
+
+ /**
+ * 解析心跳为状态信息(thing.state.update)
+ */
+ private Map parseHeartbeatAsState() {
+ Map result = new HashMap<>();
+ result.put("state", "online");
+ result.put("timestamp", System.currentTimeMillis());
+ return result;
+ }
+
+ /**
+ * 解析按键事件为事件上报格式(thing.event.post)
+ *
+ * 物模型标准格式:
+ * {
+ * "eventId": "button_event",
+ * "eventTime": 1234567890,
+ * "params": {
+ * "keyId": 1,
+ * "keyState": 1,
+ * "keyType": "short_press",
+ * "keyNumber": 1,
+ * "isLongPress": false
+ * }
+ * }
+ */
+ private Map parseButtonEventAsEvent(Jt808DataPack dataPack) {
+ Jt808ButtonEvent event = decoder.toButtonEventMsg(dataPack);
+
+ Map result = new HashMap<>();
+
+ // 统一使用一个事件标识符,通过 isLongPress 参数区分短按和长按
+ result.put("eventId", "button_event");
+
+ // 事件时间戳
+ result.put("eventTime", System.currentTimeMillis());
+
+ // 事件参数(包含 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;
+ }
+
+ /**
+ * 解析位置信息为属性上报格式(thing.property.post)
+ *
+ * 物模型标准格式:params 直接就是属性键值对
+ * {
+ * "latitude": 31.123456,
+ * "longitude": 121.123456,
+ * "batteryLevel": 80,
+ * ...
+ * }
+ */
+ private Map parseLocationInfoAsProperties(Jt808DataPack dataPack) {
+ Jt808LocationInfo locationInfo = decoder.toLocationInfoUploadMsg(dataPack);
+
+ // 物模型属性集合(所有属性平铺在同一层)
+ Map properties = new HashMap<>();
+
+ // === 基础位置信息(核心属性) ===
+ properties.put("latitude", locationInfo.getLatitude());
+ properties.put("longitude", locationInfo.getLongitude());
+ properties.put("elevation", locationInfo.getElevation());
+ properties.put("speed", locationInfo.getSpeed());
+ properties.put("direction", locationInfo.getDirection());
+ properties.put("time", locationInfo.getTime());
+
+ // 状态和告警字段(转为整数便于处理)
+ properties.put("warningFlag", locationInfo.getWarningFlagField());
+ properties.put("status", locationInfo.getStatusField());
+
+ // === 扩展信息(物模型属性) ===
+
+ // 电池信息
+ if (locationInfo.getBatteryInfo() != null) {
+ Jt808BatteryInfo batteryInfo = locationInfo.getBatteryInfo();
+ if (batteryInfo.getBatteryLevel() != null) {
+ properties.put("batteryLevel", batteryInfo.getBatteryLevel());
+ }
+ if (batteryInfo.getVersion() != null) {
+ properties.put("firmwareVersion", batteryInfo.getVersion());
+ }
+ if (batteryInfo.getIccid() != null) {
+ properties.put("iccid", batteryInfo.getIccid());
+ }
+ }
+
+ // 里程
+ if (locationInfo.getMileage() != null) {
+ properties.put("mileage", locationInfo.getMileage());
+ }
+
+ // 信号强度
+ if (locationInfo.getSignalStrength() != null) {
+ properties.put("signalStrength", locationInfo.getSignalStrength());
+ }
+
+ // 蓝牙设备列表
+ if (locationInfo.getBluetoothInfos() != null && !locationInfo.getBluetoothInfos().isEmpty()) {
+ List