chore: initial import of lingniu-vehicle-ingest
Multi-module Spring Boot ingest service for vehicle telemetry. Modules: - ingest-api / ingest-core / ingest-codec-common: shared SPI, dispatcher, Disruptor event bus, BCC/BCD codec helpers - protocol-gb32960: GB/T 32960.3 inbound (Netty + per-version parser packages v2016/v2025), platform login auth, VIN whitelist, idle handler - protocol-jt808 / protocol-jt1078 / protocol-jsatl12: JT/T inbound - inbound-mqtt / inbound-xinda-push: alternative ingest channels - session-core: per-channel session state - sink-archive / sink-mq: persistence sinks (local file / Kafka) - command-gateway: terminal control command gateway - bootstrap-all: aggregator Spring Boot app - observability: Micrometer / Actuator wiring Includes hex-dump golden samples under protocol-gb32960/src/test/resources and the GB/T 32960.3-2016 / 2025 reference PDFs under reference/. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
29
ingest-api/pom.xml
Normal file
29
ingest-api/pom.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.lingniu.ingest</groupId>
|
||||
<artifactId>lingniu-vehicle-ingest</artifactId>
|
||||
<version>0.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>ingest-api</artifactId>
|
||||
<name>ingest-api</name>
|
||||
<description>SPI、sealed 领域事件、注解定义。零 Spring 依赖。</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.lingniu.ingest.api;
|
||||
|
||||
/**
|
||||
* 协议标识,作为所有 SPI / 注解 / 路由 key 的统一枚举。
|
||||
* 新增协议需要在此处登记,避免散落的字符串常量。
|
||||
*/
|
||||
public enum ProtocolId {
|
||||
GB32960,
|
||||
JT808,
|
||||
JT1078,
|
||||
JSATL12,
|
||||
MQTT_YUTONG,
|
||||
XINDA_PUSH
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.lingniu.ingest.api.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 批量异步处理:框架把多次同类调用聚合成 {@code List} 交给方法。
|
||||
*
|
||||
* <p>处理方法签名需为 {@code void foo(List<T> list)} 或返回 {@code List<VehicleEvent>}。
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface AsyncBatch {
|
||||
|
||||
int size() default 1000;
|
||||
|
||||
long waitMs() default 500;
|
||||
|
||||
int poolSize() default 2;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.lingniu.ingest.api.annotation;
|
||||
|
||||
import com.lingniu.ingest.api.event.VehicleEvent;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 声明当前 Handler 方法产出的事件类型。用于文档化和静态校验。
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface EventEmit {
|
||||
Class<? extends VehicleEvent>[] value();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.lingniu.ingest.api.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 声明幂等键,由 Dedup 拦截器使用。支持 SpEL 表达式,上下文根对象为 Handler 参数。
|
||||
*
|
||||
* <p>示例:{@code @IdempotentKey("#msg.vin + ':' + #msg.seq + ':' + #msg.eventTime")}
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface IdempotentKey {
|
||||
String value();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.lingniu.ingest.api.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 方法级路由注解:声明此方法处理哪些协议消息。
|
||||
*
|
||||
* <p>{@link #command} 和 {@link #infoType} 的含义由各协议自行解释,Dispatcher 只负责匹配:
|
||||
* <ul>
|
||||
* <li>GB/T 32960:{@code command} = 0x01 车辆登入 / 0x02 实时上报 / ...;{@code infoType} = 信息体 ID
|
||||
* <li>JT/T 808:{@code command} = 消息 ID(0x0100 / 0x0200 / ...);{@code infoType} 留空
|
||||
* <li>MQTT:{@code command} 可为 topic hash 或忽略
|
||||
* </ul>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface MessageMapping {
|
||||
|
||||
int[] command() default {};
|
||||
|
||||
int[] infoType() default {};
|
||||
|
||||
String desc() default "";
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.lingniu.ingest.api.annotation;
|
||||
|
||||
import com.lingniu.ingest.api.ProtocolId;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 标记一个类为协议 Handler Bean,由 {@code AnnotationHandlerProcessor} 自动扫描注册。
|
||||
*
|
||||
* <p>典型用法:
|
||||
* <pre>{@code
|
||||
* @ProtocolHandler(protocol = ProtocolId.GB32960)
|
||||
* public class Gb32960RealtimeHandler {
|
||||
* @MessageMapping(command = 0x02)
|
||||
* public VehicleEvent.Realtime handle(Gb32960Message msg) { ... }
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface ProtocolHandler {
|
||||
|
||||
ProtocolId protocol();
|
||||
|
||||
/** 可选:子协议版本(如 32960 的 2011 / 2017)。 */
|
||||
String version() default "";
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.lingniu.ingest.api.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 单 VIN 速率限制。超限消息直接进 DLQ 并打点。
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface RateLimited {
|
||||
|
||||
/** 单 VIN 每秒最大消息数。 */
|
||||
int perVin() default 50;
|
||||
|
||||
/** 全局 QPS 上限;<=0 表示不设限。 */
|
||||
int global() default -1;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.lingniu.ingest.api.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 为 Handler 方法开启 OpenTelemetry span。
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface TraceSpan {
|
||||
String value();
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.lingniu.ingest.api.event;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 报警事件载荷,跨协议归一。
|
||||
*
|
||||
* @param level 报警最高等级
|
||||
* @param alarmTypeCode 协议原始 alarm type 编码(按协议自定义),可用于联查协议规范
|
||||
* @param alarmTypeName 报警类型名称,如 {@code GB32960_ALARM}
|
||||
* @param faultCodes 故障代码列表(协议私有编码字符串化,便于下游去重/聚合)
|
||||
* @param activeBits 结构化的通用报警位集合(2016 版:0~15;2025 版:0~27),对照 GB/T 32960.3 表 24
|
||||
* @param longitude 经度(可选,若事件伴随位置)
|
||||
* @param latitude 纬度(可选)
|
||||
*/
|
||||
public record AlarmPayload(
|
||||
AlarmLevel level,
|
||||
int alarmTypeCode,
|
||||
String alarmTypeName,
|
||||
List<String> faultCodes,
|
||||
Set<String> activeBits,
|
||||
Double longitude,
|
||||
Double latitude
|
||||
) {
|
||||
/**
|
||||
* 报警等级(归一化的跨协议分类)。
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #INFO}:提示级(对应 GB/T 32960.3 0/无故障)
|
||||
* <li>{@link #MINOR}:1 级 —— 不影响车辆正常行驶
|
||||
* <li>{@link #MAJOR}:2 级 —— 影响车辆性能,需驾驶员限制行驶
|
||||
* <li>{@link #CRITICAL}:3 级(驾驶员应立即停车)或 4 级(热事件最高级)
|
||||
* </ul>
|
||||
*/
|
||||
public enum AlarmLevel { INFO, MINOR, MAJOR, CRITICAL }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.lingniu.ingest.api.event;
|
||||
|
||||
/** 位置事件载荷。坐标已统一转换为 WGS84 十进制度。 */
|
||||
public record LocationPayload(
|
||||
double longitude,
|
||||
double latitude,
|
||||
double altitudeM,
|
||||
double speedKmh,
|
||||
double directionDeg,
|
||||
long alarmFlag,
|
||||
long statusFlag
|
||||
) {}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.lingniu.ingest.api.event;
|
||||
|
||||
/**
|
||||
* 实时数据载荷(扁平 record,字段用 {@code Double} / {@code Integer} 允许 null,表达"未上报")。
|
||||
*
|
||||
* <p>不再复刻旧服务 {@code VehicleDataActual} 的 273 字段巨型类 —— 协议特有细节通过
|
||||
* {@link VehicleEvent#metadata()} 传递。此处只保留跨协议通用字段。
|
||||
*/
|
||||
public record RealtimePayload(
|
||||
// ===== 动力 & 能源 =====
|
||||
Double speedKmh,
|
||||
Double totalMileageKm,
|
||||
Double batterySoc,
|
||||
Double batteryVoltageV,
|
||||
Double batteryCurrentA,
|
||||
|
||||
// ===== 燃料电池 / 氢能 =====
|
||||
Double fcVoltageV,
|
||||
Double fcCurrentA,
|
||||
Double fcTempC,
|
||||
Double hydrogenRemainingKg,
|
||||
Double hydrogenHighPressureMpa,
|
||||
Double hydrogenLowPressureMpa,
|
||||
|
||||
// ===== 车辆状态 =====
|
||||
VehicleState vehicleState,
|
||||
ChargingState chargingState,
|
||||
RunningMode runningMode,
|
||||
Integer gearLevel,
|
||||
Double acceleratorPedal,
|
||||
Double brakePedal,
|
||||
|
||||
// ===== 位置(冗余一份便于单主题消费)=====
|
||||
Double longitude,
|
||||
Double latitude,
|
||||
Double altitudeM,
|
||||
Double directionDeg,
|
||||
|
||||
// ===== 环境 =====
|
||||
Double ambientTempC,
|
||||
Double coolantTempC
|
||||
) {
|
||||
|
||||
public enum VehicleState { STARTED, SHUTDOWN, OTHER, INVALID }
|
||||
|
||||
public enum ChargingState { UNCHARGED, PARKED_CHARGING, DRIVING_CHARGING, CHARGED, OTHER, INVALID }
|
||||
|
||||
public enum RunningMode { ELECTRIC, HYBRID, FUEL, OTHER, INVALID }
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package com.lingniu.ingest.api.event;
|
||||
|
||||
import com.lingniu.ingest.api.ProtocolId;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 领域事件根类型。所有协议归一到这里。
|
||||
*
|
||||
* <p>使用 sealed + record 表达有界闭合的事件集合,下游消费方可用 {@code switch} 模式匹配穷尽处理。
|
||||
*
|
||||
* <p>字段约定:
|
||||
* <ul>
|
||||
* <li>{@link #vin} 作为 Kafka 分区 key,保证单车顺序
|
||||
* <li>{@link #eventTime} 设备上报时间(而非服务器接收时间)
|
||||
* <li>{@link #ingestTime} 服务器接收时间,用于延迟监控
|
||||
* <li>{@link #traceId} W3C traceparent 上下文
|
||||
* </ul>
|
||||
*/
|
||||
public sealed interface VehicleEvent
|
||||
permits VehicleEvent.Realtime,
|
||||
VehicleEvent.Location,
|
||||
VehicleEvent.Alarm,
|
||||
VehicleEvent.Login,
|
||||
VehicleEvent.Logout,
|
||||
VehicleEvent.Heartbeat,
|
||||
VehicleEvent.MediaMeta,
|
||||
VehicleEvent.Passthrough {
|
||||
|
||||
String eventId();
|
||||
String vin();
|
||||
ProtocolId source();
|
||||
Instant eventTime();
|
||||
Instant ingestTime();
|
||||
String traceId();
|
||||
|
||||
/** 通用元数据:协议版本、终端手机号、网关 IP 等。 */
|
||||
Map<String, String> metadata();
|
||||
|
||||
// ===== 具体事件类型 =====
|
||||
|
||||
/** 整车实时数据(32960 实时上报 / MQTT 宇通 / JT808 位置扩展等都归一到这里)。 */
|
||||
record Realtime(
|
||||
String eventId,
|
||||
String vin,
|
||||
ProtocolId source,
|
||||
Instant eventTime,
|
||||
Instant ingestTime,
|
||||
String traceId,
|
||||
Map<String, String> metadata,
|
||||
RealtimePayload payload
|
||||
) implements VehicleEvent {}
|
||||
|
||||
/** 纯位置事件(808 的 0x0200 / Xinda 0200 映射到此)。 */
|
||||
record Location(
|
||||
String eventId,
|
||||
String vin,
|
||||
ProtocolId source,
|
||||
Instant eventTime,
|
||||
Instant ingestTime,
|
||||
String traceId,
|
||||
Map<String, String> metadata,
|
||||
LocationPayload payload
|
||||
) implements VehicleEvent {}
|
||||
|
||||
/** 报警事件。 */
|
||||
record Alarm(
|
||||
String eventId,
|
||||
String vin,
|
||||
ProtocolId source,
|
||||
Instant eventTime,
|
||||
Instant ingestTime,
|
||||
String traceId,
|
||||
Map<String, String> metadata,
|
||||
AlarmPayload payload
|
||||
) implements VehicleEvent {}
|
||||
|
||||
/** 设备登入 / 车辆登入。 */
|
||||
record Login(
|
||||
String eventId,
|
||||
String vin,
|
||||
ProtocolId source,
|
||||
Instant eventTime,
|
||||
Instant ingestTime,
|
||||
String traceId,
|
||||
Map<String, String> metadata,
|
||||
String iccid,
|
||||
String protocolVersion
|
||||
) implements VehicleEvent {}
|
||||
|
||||
/** 设备登出。 */
|
||||
record Logout(
|
||||
String eventId,
|
||||
String vin,
|
||||
ProtocolId source,
|
||||
Instant eventTime,
|
||||
Instant ingestTime,
|
||||
String traceId,
|
||||
Map<String, String> metadata
|
||||
) implements VehicleEvent {}
|
||||
|
||||
/** 心跳 / 链路保活。 */
|
||||
record Heartbeat(
|
||||
String eventId,
|
||||
String vin,
|
||||
ProtocolId source,
|
||||
Instant eventTime,
|
||||
Instant ingestTime,
|
||||
String traceId,
|
||||
Map<String, String> metadata
|
||||
) implements VehicleEvent {}
|
||||
|
||||
/** 多媒体元数据(大文件本体走对象存储,通过 archiveRef 引用)。 */
|
||||
record MediaMeta(
|
||||
String eventId,
|
||||
String vin,
|
||||
ProtocolId source,
|
||||
Instant eventTime,
|
||||
Instant ingestTime,
|
||||
String traceId,
|
||||
Map<String, String> metadata,
|
||||
String mediaId,
|
||||
String mediaType,
|
||||
long sizeBytes,
|
||||
String archiveRef
|
||||
) implements VehicleEvent {}
|
||||
|
||||
/** 透传 / 自定义扩展消息。 */
|
||||
record Passthrough(
|
||||
String eventId,
|
||||
String vin,
|
||||
ProtocolId source,
|
||||
Instant eventTime,
|
||||
Instant ingestTime,
|
||||
String traceId,
|
||||
Map<String, String> metadata,
|
||||
int passthroughType,
|
||||
byte[] data
|
||||
) implements VehicleEvent {}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* lingniu-vehicle-ingest 公共 API 模块。零 Spring 依赖,只定义 SPI、sealed 领域事件、注解。
|
||||
*
|
||||
* <p>分包:
|
||||
* <ul>
|
||||
* <li>{@code annotation} - Handler 注解体系
|
||||
* <li>{@code event} - sealed {@code VehicleEvent} + payload records
|
||||
* <li>{@code pipeline} - RawFrame / IngestContext / IngestInterceptor
|
||||
* <li>{@code sink} - EventSink SPI
|
||||
* <li>{@code spi} - ProtocolPlugin / FrameDecoder / FrameEncoder / EventMapper
|
||||
* </ul>
|
||||
*/
|
||||
package com.lingniu.ingest.api;
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.lingniu.ingest.api.pipeline;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 一次消息处理的上下文,贯穿整个拦截链与 Handler。线程不共享,不需要同步。
|
||||
*/
|
||||
public final class IngestContext {
|
||||
|
||||
private final String traceId;
|
||||
private final Map<String, Object> attributes = new HashMap<>(8);
|
||||
private volatile boolean aborted;
|
||||
private volatile String abortReason;
|
||||
|
||||
public IngestContext(String traceId) {
|
||||
this.traceId = traceId;
|
||||
}
|
||||
|
||||
public String traceId() {
|
||||
return traceId;
|
||||
}
|
||||
|
||||
public <T> T attr(String key) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T v = (T) attributes.get(key);
|
||||
return v;
|
||||
}
|
||||
|
||||
public void attr(String key, Object value) {
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
public void abort(String reason) {
|
||||
this.aborted = true;
|
||||
this.abortReason = reason;
|
||||
}
|
||||
|
||||
public boolean aborted() {
|
||||
return aborted;
|
||||
}
|
||||
|
||||
public String abortReason() {
|
||||
return abortReason;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.lingniu.ingest.api.pipeline;
|
||||
|
||||
import com.lingniu.ingest.api.event.VehicleEvent;
|
||||
|
||||
/**
|
||||
* 拦截器 SPI。内置实现:Auth / Dedup / RateLimit / CoordTransform / Tracing。
|
||||
* 实现 {@link org.springframework.core.Ordered}(或使用 {@code @Order})控制顺序。
|
||||
*/
|
||||
public interface IngestInterceptor {
|
||||
|
||||
/** 解码后、分发到 Handler 之前触发。返回 false 表示终止后续处理。 */
|
||||
default boolean before(RawFrame frame, IngestContext ctx) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Handler 成功产出事件后触发,允许修饰事件或取消投递。 */
|
||||
default void after(VehicleEvent event, IngestContext ctx) {}
|
||||
|
||||
/** 处理链任一环节抛出异常时触发。 */
|
||||
default void onError(Throwable error, IngestContext ctx) {}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.lingniu.ingest.api.pipeline;
|
||||
|
||||
import com.lingniu.ingest.api.ProtocolId;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 进入 Pipeline 的原始帧描述。
|
||||
*
|
||||
* @param protocolId 来源协议
|
||||
* @param command 协议主命令 / 消息 ID(由各协议自行定义)
|
||||
* @param infoType 可选子类型(如 32960 的信息体 ID)
|
||||
* @param payload 已经被 {@link com.lingniu.ingest.api.spi.FrameDecoder} 解析出的协议对象
|
||||
* @param rawBytes 原始字节,用于冷存与排障(可能为 null,按配置开关)
|
||||
* @param sourceMeta 来源元数据:peer ip、端口、session id、topic 等
|
||||
* @param receivedAt 服务器接收时刻
|
||||
*/
|
||||
public record RawFrame(
|
||||
ProtocolId protocolId,
|
||||
int command,
|
||||
int infoType,
|
||||
Object payload,
|
||||
byte[] rawBytes,
|
||||
Map<String, String> sourceMeta,
|
||||
Instant receivedAt
|
||||
) {}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.lingniu.ingest.api.sink;
|
||||
|
||||
import com.lingniu.ingest.api.event.VehicleEvent;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* 事件出口 SPI。Kafka、本地归档、指标打点都是一种 Sink。
|
||||
*
|
||||
* <p>实现应当线程安全、非阻塞(内部异步 IO)。由 Disruptor EventBus 扇出调用。
|
||||
*/
|
||||
public interface EventSink {
|
||||
|
||||
String name();
|
||||
|
||||
CompletableFuture<Void> publish(VehicleEvent event);
|
||||
|
||||
default CompletableFuture<Void> publishBatch(List<VehicleEvent> batch) {
|
||||
CompletableFuture<?>[] all = batch.stream().map(this::publish).toArray(CompletableFuture[]::new);
|
||||
return CompletableFuture.allOf(all);
|
||||
}
|
||||
|
||||
/** 是否接受此事件类型。默认全部接受。 */
|
||||
default boolean accepts(VehicleEvent event) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.lingniu.ingest.api.spi;
|
||||
|
||||
/** 解码阶段异常,指示数据体本身违反协议。应被 Dispatcher 路由到 DLQ。 */
|
||||
public class DecodeException extends RuntimeException {
|
||||
public DecodeException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DecodeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.lingniu.ingest.api.spi;
|
||||
|
||||
import com.lingniu.ingest.api.event.VehicleEvent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 协议消息 → 领域事件的映射器(Adapter 模式)。
|
||||
*
|
||||
* <p>实现类负责把"我这个协议特有的 Java 对象"翻译成统一的 {@link VehicleEvent}。
|
||||
* 允许一条协议消息展开为多条领域事件(例如 32960 的一次实时上报同时产出 Realtime + Location)。
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface EventMapper<I> {
|
||||
List<VehicleEvent> toEvents(I message);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.lingniu.ingest.api.spi;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/**
|
||||
* 协议帧解码器:字节流 → 协议消息对象。
|
||||
*
|
||||
* <p>实现类应是无状态的(拆包/粘包由上游 Netty 的帧解码器处理),此处只负责一条完整帧的解析。
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface FrameDecoder<I> {
|
||||
I decode(ByteBuffer buffer) throws DecodeException;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.lingniu.ingest.api.spi;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
/** 下行帧编码器。无下行的协议返回 null 即可。 */
|
||||
@FunctionalInterface
|
||||
public interface FrameEncoder<O> {
|
||||
ByteBuffer encode(O message);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.lingniu.ingest.api.spi;
|
||||
|
||||
import com.lingniu.ingest.api.event.VehicleEvent;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 编程式 Handler 接口。推荐使用 {@code @ProtocolHandler + @MessageMapping} 注解方式代替。
|
||||
* 此接口保留是为了 SPI 场景下的脚本式扩展(例如动态加载的 groovy handler)。
|
||||
*/
|
||||
public interface MessageHandler<I> {
|
||||
|
||||
boolean supports(I message);
|
||||
|
||||
List<VehicleEvent> handle(I message);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.lingniu.ingest.api.spi;
|
||||
|
||||
import com.lingniu.ingest.api.ProtocolId;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 协议插件 SPI。每个协议模块提供一个实现,由 {@code ProtocolPluginRegistry} 通过 ServiceLoader
|
||||
* 或 Spring Bean 方式发现并装配。
|
||||
*
|
||||
* @param <I> 协议解码后的消息类型
|
||||
* @param <O> 下行消息类型(无下行的协议用 {@link Void})
|
||||
*/
|
||||
public interface ProtocolPlugin<I, O> {
|
||||
|
||||
ProtocolId id();
|
||||
|
||||
FrameDecoder<I> decoder();
|
||||
|
||||
FrameEncoder<O> encoder();
|
||||
|
||||
EventMapper<I> eventMapper();
|
||||
|
||||
/**
|
||||
* 插件自带的协议特定 Handler 列表(可选)。
|
||||
* 通常推荐使用 {@code @ProtocolHandler} + Spring Bean 的方式注册,此处返回空列表。
|
||||
*/
|
||||
default List<MessageHandler<I>> builtinHandlers() {
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user