feat: route gb32960 kafka topics
This commit is contained in:
@@ -43,13 +43,11 @@ public final class KafkaEventSink implements EventSink, AutoCloseable {
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒绝 {@link VehicleEvent.RawArchive} —— 当前生产 Kafka sink 只投递实时、会话、告警等轻量事件。
|
||||
* {@link EnvelopeMapper} 和 {@link TopicRouter} 已具备 RAW envelope/topic 的映射能力,但这里仍
|
||||
* 显式过滤;如需把 32960 raw 引用转发到 {@code vehicle.raw.archive},应先移除此过滤并补充测试。
|
||||
* Split-service mode uses Kafka for both normalized events and raw archive records.
|
||||
*/
|
||||
@Override
|
||||
public boolean accepts(VehicleEvent event) {
|
||||
return !(event instanceof VehicleEvent.RawArchive);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -57,16 +57,16 @@ public class SinkMqProperties {
|
||||
|
||||
public static class Topics {
|
||||
/** 实时遥测事件 topic;GB32960 RAW-only 架构下可逐步弱化该 topic。 */
|
||||
private String realtime = "vehicle.realtime";
|
||||
private String realtime = "vehicle.event.gb32960.v1";
|
||||
/** 位置事件 topic;通常可由 realtime/RAW 派生。 */
|
||||
private String location = "vehicle.location";
|
||||
private String alarm = "vehicle.alarm";
|
||||
private String session = "vehicle.session";
|
||||
private String mediaMeta = "vehicle.media.meta";
|
||||
private String location = "vehicle.event.gb32960.v1";
|
||||
private String alarm = "vehicle.event.gb32960.v1";
|
||||
private String session = "vehicle.event.gb32960.v1";
|
||||
private String mediaMeta = "vehicle.media.meta.v1";
|
||||
/** RAW 归档引用 topic,payload 应携带 archive URI/size,不建议携带完整 raw bytes。 */
|
||||
private String rawArchive = "vehicle.raw.archive";
|
||||
private String rawArchive = "vehicle.raw.gb32960.v1";
|
||||
/** producer 熔断或 consumer 处理失败时的死信 topic。 */
|
||||
private String dlq = "vehicle.dlq";
|
||||
private String dlq = "vehicle.dlq.gb32960.v1";
|
||||
|
||||
public String getRealtime() { return realtime; }
|
||||
public void setRealtime(String realtime) { this.realtime = realtime; }
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.lingniu.ingest.sink.mq;
|
||||
|
||||
import com.lingniu.ingest.api.ProtocolId;
|
||||
import com.lingniu.ingest.api.event.VehicleEvent;
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class KafkaEventSinkTest {
|
||||
|
||||
@Test
|
||||
void acceptsRawArchiveRecordsForSplitServiceKafkaBoundary() {
|
||||
KafkaEventSink sink = new KafkaEventSink(
|
||||
null,
|
||||
new EnvelopeMapper("node-1"),
|
||||
new TopicRouter(new SinkMqProperties.Topics()),
|
||||
"vehicle.dlq.gb32960.v1",
|
||||
CircuitBreaker.ofDefaults("kafka-test"));
|
||||
|
||||
assertThat(sink.accepts(rawArchive())).isTrue();
|
||||
}
|
||||
|
||||
private static VehicleEvent.RawArchive rawArchive() {
|
||||
return new VehicleEvent.RawArchive(
|
||||
"raw-1",
|
||||
"VIN001",
|
||||
ProtocolId.GB32960,
|
||||
Instant.parse("2026-06-23T07:00:00Z"),
|
||||
Instant.parse("2026-06-23T07:00:01Z"),
|
||||
"trace-raw-1",
|
||||
Map.of("platformAccount", "Hyundai"),
|
||||
0x02,
|
||||
0,
|
||||
new byte[] {0x23, 0x23});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.lingniu.ingest.sink.mq;
|
||||
|
||||
import com.lingniu.ingest.api.ProtocolId;
|
||||
import com.lingniu.ingest.api.event.AlarmPayload;
|
||||
import com.lingniu.ingest.api.event.LocationPayload;
|
||||
import com.lingniu.ingest.api.event.RealtimePayload;
|
||||
import com.lingniu.ingest.api.event.VehicleEvent;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TopicRouterTest {
|
||||
|
||||
@Test
|
||||
void rawArchiveRoutesToVersionedGb32960RawTopicByDefault() {
|
||||
TopicRouter router = new TopicRouter(new SinkMqProperties.Topics());
|
||||
|
||||
assertThat(router.route(rawArchive())).isEqualTo("vehicle.raw.gb32960.v1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void normalizedGb32960EventsRouteToVersionedEventTopicByDefault() {
|
||||
TopicRouter router = new TopicRouter(new SinkMqProperties.Topics());
|
||||
|
||||
assertThat(router.route(realtime())).isEqualTo("vehicle.event.gb32960.v1");
|
||||
assertThat(router.route(location())).isEqualTo("vehicle.event.gb32960.v1");
|
||||
assertThat(router.route(alarm())).isEqualTo("vehicle.event.gb32960.v1");
|
||||
assertThat(router.route(login())).isEqualTo("vehicle.event.gb32960.v1");
|
||||
assertThat(router.route(logout())).isEqualTo("vehicle.event.gb32960.v1");
|
||||
assertThat(router.route(heartbeat())).isEqualTo("vehicle.event.gb32960.v1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void dlqDefaultsToVersionedGb32960DlqTopic() {
|
||||
SinkMqProperties.Topics topics = new SinkMqProperties.Topics();
|
||||
|
||||
assertThat(topics.getDlq()).isEqualTo("vehicle.dlq.gb32960.v1");
|
||||
}
|
||||
|
||||
private static VehicleEvent.RawArchive rawArchive() {
|
||||
return new VehicleEvent.RawArchive(
|
||||
"raw-1",
|
||||
"VIN001",
|
||||
ProtocolId.GB32960,
|
||||
Instant.parse("2026-06-23T07:00:00Z"),
|
||||
Instant.parse("2026-06-23T07:00:01Z"),
|
||||
"trace-raw-1",
|
||||
Map.of("platformAccount", "Hyundai"),
|
||||
0x02,
|
||||
0,
|
||||
new byte[] {0x23, 0x23});
|
||||
}
|
||||
|
||||
private static VehicleEvent.Realtime realtime() {
|
||||
return new VehicleEvent.Realtime(
|
||||
"event-1",
|
||||
"VIN001",
|
||||
ProtocolId.GB32960,
|
||||
Instant.parse("2026-06-23T07:00:00Z"),
|
||||
Instant.parse("2026-06-23T07:00:01Z"),
|
||||
"trace-1",
|
||||
Map.of("platformAccount", "Hyundai"),
|
||||
new RealtimePayload(
|
||||
42.1, 1234.5, 80.0, null, null,
|
||||
null, null, null, null, null, null,
|
||||
RealtimePayload.VehicleState.STARTED,
|
||||
RealtimePayload.ChargingState.UNCHARGED,
|
||||
RealtimePayload.RunningMode.ELECTRIC,
|
||||
null, null, null,
|
||||
113.12, 23.45, null, null, null, null));
|
||||
}
|
||||
|
||||
private static VehicleEvent.Location location() {
|
||||
return new VehicleEvent.Location(
|
||||
"event-2",
|
||||
"VIN001",
|
||||
ProtocolId.GB32960,
|
||||
Instant.parse("2026-06-23T07:00:00Z"),
|
||||
Instant.parse("2026-06-23T07:00:01Z"),
|
||||
"trace-2",
|
||||
Map.of("platformAccount", "Hyundai"),
|
||||
new LocationPayload(113.12, 23.45, 0, 42.1, 90, 0, 0));
|
||||
}
|
||||
|
||||
private static VehicleEvent.Alarm alarm() {
|
||||
return new VehicleEvent.Alarm(
|
||||
"event-3",
|
||||
"VIN001",
|
||||
ProtocolId.GB32960,
|
||||
Instant.parse("2026-06-23T07:00:00Z"),
|
||||
Instant.parse("2026-06-23T07:00:01Z"),
|
||||
"trace-3",
|
||||
Map.of("platformAccount", "Hyundai"),
|
||||
new AlarmPayload(
|
||||
AlarmPayload.AlarmLevel.MINOR,
|
||||
1,
|
||||
"GB32960_ALARM",
|
||||
List.of("1"),
|
||||
Set.of("bit0"),
|
||||
113.12,
|
||||
23.45,
|
||||
AlarmPayload.SafetyCategory.GENERAL,
|
||||
false,
|
||||
AlarmPayload.HydrogenLeakLevel.NONE,
|
||||
false));
|
||||
}
|
||||
|
||||
private static VehicleEvent.Login login() {
|
||||
return new VehicleEvent.Login(
|
||||
"event-4",
|
||||
"VIN001",
|
||||
ProtocolId.GB32960,
|
||||
Instant.parse("2026-06-23T07:00:00Z"),
|
||||
Instant.parse("2026-06-23T07:00:01Z"),
|
||||
"trace-4",
|
||||
Map.of("platformAccount", "Hyundai"),
|
||||
"iccid-1",
|
||||
"V2016");
|
||||
}
|
||||
|
||||
private static VehicleEvent.Logout logout() {
|
||||
return new VehicleEvent.Logout(
|
||||
"event-5",
|
||||
"VIN001",
|
||||
ProtocolId.GB32960,
|
||||
Instant.parse("2026-06-23T07:00:00Z"),
|
||||
Instant.parse("2026-06-23T07:00:01Z"),
|
||||
"trace-5",
|
||||
Map.of("platformAccount", "Hyundai"));
|
||||
}
|
||||
|
||||
private static VehicleEvent.Heartbeat heartbeat() {
|
||||
return new VehicleEvent.Heartbeat(
|
||||
"event-6",
|
||||
"VIN001",
|
||||
ProtocolId.GB32960,
|
||||
Instant.parse("2026-06-23T07:00:00Z"),
|
||||
Instant.parse("2026-06-23T07:00:01Z"),
|
||||
"trace-6",
|
||||
Map.of("platformAccount", "Hyundai"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user