diff --git a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandler.java b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandler.java
index 2a870318..e622988d 100644
--- a/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandler.java
+++ b/protocol-gb32960/src/main/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandler.java
@@ -7,18 +7,19 @@ import com.lingniu.ingest.api.annotation.ProtocolHandler;
import com.lingniu.ingest.api.annotation.RateLimited;
import com.lingniu.ingest.api.event.VehicleEvent;
import com.lingniu.ingest.protocol.gb32960.mapper.Gb32960EventMapper;
-import com.lingniu.ingest.protocol.gb32960.model.CommandType;
import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message;
import java.util.List;
/**
- * 示例 Handler:演示注解驱动的消息路由与事件产出。
+ * GB/T 32960 协议 Handler:Dispatcher 路由到此的所有 0x01~0x07 命令都委托给
+ * {@link Gb32960EventMapper} 转成 {@link VehicleEvent}。
*
- *
构造器注入 {@link Gb32960EventMapper},全部业务逻辑限制在纯函数里,不触碰数据库 / 不做 IO。
- * 产出的事件由 Dispatcher 统一投递到 Disruptor → Kafka。
+ *
这是 GB32960 协议在通用 Dispatcher 管线里的**唯一入口** Bean——移除将导致所有
+ * GB32960 帧产出的事件丢失。构造器注入 {@link Gb32960EventMapper},全部业务逻辑限制
+ * 在纯函数里,不触碰数据库 / 不做 IO。产出的事件由 Dispatcher 统一投递到 Disruptor → Kafka。
*/
-@ProtocolHandler(protocol = ProtocolId.GB32960, version = "2017")
+@ProtocolHandler(protocol = ProtocolId.GB32960)
public class Gb32960RealtimeHandler {
private final Gb32960EventMapper mapper;
@@ -42,11 +43,15 @@ public class Gb32960RealtimeHandler {
return mapper.toEvents(msg);
}
- /** 平台登入 / 登出:仅用于 OEM 平台间握手,不产出业务事件。 */
+ /**
+ * 平台登入 / 登出(0x05 / 0x06):外部下级平台握手,产出
+ * {@link VehicleEvent.Login} / {@link VehicleEvent.Logout},
+ * vin 字段形如 {@code platform:},metadata 带 {@code kind=platform},
+ * 下游消费者可据此区分车辆会话与平台会话。
+ */
@MessageMapping(command = {0x05, 0x06}, desc = "平台登入/登出")
+ @EventEmit({VehicleEvent.Login.class, VehicleEvent.Logout.class})
public List onPlatform(Gb32960Message msg) {
- CommandType cmd = msg.header().command();
- // 留个 hook:后续可在此触发 session-core 的平台会话事件
- return List.of();
+ return mapper.toEvents(msg);
}
}
diff --git a/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandlerTest.java b/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandlerTest.java
new file mode 100644
index 00000000..b46862d3
--- /dev/null
+++ b/protocol-gb32960/src/test/java/com/lingniu/ingest/protocol/gb32960/handler/Gb32960RealtimeHandlerTest.java
@@ -0,0 +1,79 @@
+package com.lingniu.ingest.protocol.gb32960.handler;
+
+import com.lingniu.ingest.api.event.VehicleEvent;
+import com.lingniu.ingest.protocol.gb32960.mapper.Gb32960EventMapper;
+import com.lingniu.ingest.protocol.gb32960.model.CommandBody;
+import com.lingniu.ingest.protocol.gb32960.model.CommandType;
+import com.lingniu.ingest.protocol.gb32960.model.EncryptType;
+import com.lingniu.ingest.protocol.gb32960.model.Gb32960Header;
+import com.lingniu.ingest.protocol.gb32960.model.Gb32960Message;
+import com.lingniu.ingest.protocol.gb32960.model.ProtocolVersion;
+import com.lingniu.ingest.protocol.gb32960.model.ResponseFlag;
+import org.junit.jupiter.api.Test;
+
+import java.time.Instant;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * 保障 {@link Gb32960RealtimeHandler} 正确委托给 {@link Gb32960EventMapper},
+ * 不吞掉任何需要上送下游的事件。
+ */
+class Gb32960RealtimeHandlerTest {
+
+ private final Gb32960RealtimeHandler handler = new Gb32960RealtimeHandler(new Gb32960EventMapper());
+
+ @Test
+ void onPlatform_login_emitsLoginEventWithKindPlatform() {
+ Gb32960Message msg = buildPlatformLoginMessage("lingniu");
+
+ List events = handler.onPlatform(msg);
+
+ assertThat(events).hasSize(1);
+ VehicleEvent first = events.get(0);
+ assertThat(first).isInstanceOf(VehicleEvent.Login.class);
+ assertThat(first.vin()).isEqualTo("platform:lingniu");
+ assertThat(first.metadata())
+ .containsEntry("kind", "platform")
+ .containsEntry("username", "lingniu");
+ }
+
+ @Test
+ void onPlatform_logout_emitsLogoutEventWithKindPlatform() {
+ Gb32960Message msg = new Gb32960Message(
+ new Gb32960Header(
+ ProtocolVersion.V2016,
+ CommandType.PLATFORM_LOGOUT,
+ ResponseFlag.COMMAND,
+ "00000000000000000",
+ EncryptType.UNENCRYPTED,
+ 0,
+ null),
+ new CommandBody.PlatformLogout(Instant.parse("2026-04-20T10:00:00Z"), 1));
+
+ List events = handler.onPlatform(msg);
+
+ assertThat(events).hasSize(1);
+ assertThat(events.get(0)).isInstanceOf(VehicleEvent.Logout.class);
+ assertThat(events.get(0).metadata()).containsEntry("kind", "platform");
+ }
+
+ private static Gb32960Message buildPlatformLoginMessage(String username) {
+ return new Gb32960Message(
+ new Gb32960Header(
+ ProtocolVersion.V2016,
+ CommandType.PLATFORM_LOGIN,
+ ResponseFlag.COMMAND,
+ "00000000000000000",
+ EncryptType.UNENCRYPTED,
+ 0,
+ null),
+ new CommandBody.PlatformLogin(
+ Instant.parse("2026-04-20T10:00:00Z"),
+ 1,
+ username,
+ "secret",
+ EncryptType.UNENCRYPTED));
+ }
+}