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:
lingniu-dev
2026-04-15 16:08:57 +08:00
commit 064ecc479c
220 changed files with 11874 additions and 0 deletions

View File

@@ -0,0 +1,84 @@
package com.lingniu.ingest.observability;
import com.lingniu.ingest.api.ProtocolId;
import com.lingniu.ingest.api.event.VehicleEvent;
import com.lingniu.ingest.api.pipeline.IngestContext;
import com.lingniu.ingest.api.pipeline.IngestInterceptor;
import com.lingniu.ingest.api.pipeline.RawFrame;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import org.springframework.core.Ordered;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* 指标拦截器:自动以 {@link IngestInterceptor} 形式接入拦截链,无侵入采集核心流量指标。
*
* <p>暴露指标:
* <ul>
* <li>{@code ingest_frames_total{protocol}} - 入站帧计数
* <li>{@code ingest_events_total{protocol,type}} - 事件产出计数
* <li>{@code ingest_errors_total{protocol}} - 异常计数
* <li>{@code ingest_frame_duration_seconds{protocol}} - 单帧处理耗时
* </ul>
*/
public class IngestMetrics implements IngestInterceptor, Ordered {
private static final String ATTR_TIMER_SAMPLE = "metrics.timer.sample";
private final MeterRegistry registry;
private final ConcurrentMap<ProtocolId, Counter> frameCounters = new ConcurrentHashMap<>();
private final ConcurrentMap<ProtocolId, Counter> errorCounters = new ConcurrentHashMap<>();
private final ConcurrentMap<ProtocolId, Timer> frameTimers = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Counter> eventCounters = new ConcurrentHashMap<>();
public IngestMetrics(MeterRegistry registry) {
this.registry = registry;
}
@Override
public boolean before(RawFrame frame, IngestContext ctx) {
ProtocolId p = frame.protocolId();
frameCounters.computeIfAbsent(p, k ->
Counter.builder("ingest_frames_total").tag("protocol", k.name()).register(registry)).increment();
Timer.Sample sample = Timer.start(registry);
ctx.attr(ATTR_TIMER_SAMPLE, sample);
return true;
}
@Override
public void after(VehicleEvent event, IngestContext ctx) {
String key = event.source().name() + ":" + event.getClass().getSimpleName();
eventCounters.computeIfAbsent(key, k -> {
String[] parts = k.split(":");
return Counter.builder("ingest_events_total")
.tags(Tags.of("protocol", parts[0], "type", parts[1]))
.register(registry);
}).increment();
Timer.Sample s = ctx.attr(ATTR_TIMER_SAMPLE);
if (s != null) {
Timer t = frameTimers.computeIfAbsent(event.source(), p ->
Timer.builder("ingest_frame_duration_seconds").tag("protocol", p.name()).register(registry));
s.stop(t);
ctx.attr(ATTR_TIMER_SAMPLE, null);
}
}
@Override
public void onError(Throwable error, IngestContext ctx) {
// 无协议上下文时归到 UNKNOWN
Counter c = errorCounters.computeIfAbsent(
ProtocolId.GB32960, // 默认桶:由上层拦截器设置具体协议更精确
p -> Counter.builder("ingest_errors_total").tag("protocol", "unknown").register(registry));
c.increment();
}
@Override
public int getOrder() {
return 10; // 尽早介入,晚于 Tracingorder=0
}
}

View File

@@ -0,0 +1,18 @@
package com.lingniu.ingest.observability;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnBean(MeterRegistry.class)
public class ObservabilityAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public IngestMetrics ingestMetrics(MeterRegistry registry) {
return new IngestMetrics(registry);
}
}

View File

@@ -0,0 +1 @@
com.lingniu.ingest.observability.ObservabilityAutoConfiguration