fix(gb32960): emit platform login/logout events instead of swallowing them
Gb32960RealtimeHandler.onPlatform 之前返回 List.of() 吞掉所有 0x05/0x06 事件, 而 Gb32960EventMapper.toEvents 里 PLATFORM_LOGIN / PLATFORM_LOGOUT 分支实际构造了 VehicleEvent.Login/Logout(vin="platform:<username>",metadata.kind="platform")。 两边语义冲突导致平台登入事件永远不会上送下游——mapper 里那段是死代码。 修正: - onPlatform 改为委托 mapper.toEvents(msg),让平台会话事件真正发布到 vehicle.session topic,下游按 metadata.kind 区分车辆/平台会话 - 加 @EventEmit 声明 Login/Logout 事件类,保持元数据一致 - 去掉 @ProtocolHandler(version = "2017"):2017 不是真实的 GB/T 32960 版本(标准 仅 2016 / 2025),且 version 属性在 HandlerRegistry 路由里未使用,纯噪音 - JavaDoc 从"示例 Handler:演示..."改为生产路径描述,说明它是 GB32960 在 Dispatcher 管线里的唯一入口 新增 Gb32960RealtimeHandlerTest 2 case 覆盖平台登入/登出事件产出。 全模块 41 tests 全绿(原 39 + 新 2),无回归。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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}。
|
||||
*
|
||||
* <p>构造器注入 {@link Gb32960EventMapper},全部业务逻辑限制在纯函数里,不触碰数据库 / 不做 IO。
|
||||
* 产出的事件由 Dispatcher 统一投递到 Disruptor → Kafka。
|
||||
* <p>这是 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:<username>},metadata 带 {@code kind=platform},
|
||||
* 下游消费者可据此区分车辆会话与平台会话。
|
||||
*/
|
||||
@MessageMapping(command = {0x05, 0x06}, desc = "平台登入/登出")
|
||||
@EventEmit({VehicleEvent.Login.class, VehicleEvent.Logout.class})
|
||||
public List<VehicleEvent> onPlatform(Gb32960Message msg) {
|
||||
CommandType cmd = msg.header().command();
|
||||
// 留个 hook:后续可在此触发 session-core 的平台会话事件
|
||||
return List.of();
|
||||
return mapper.toEvents(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<VehicleEvent> 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<VehicleEvent> 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user