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)); + } +}