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,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-codec-common</artifactId>
<name>ingest-codec-common</name>
<description>BCD / CRC / BCC / bit utils 等公共编解码工具。</description>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</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>

View File

@@ -0,0 +1,19 @@
package com.lingniu.ingest.codec;
/** BCC异或校验。GB/T 32960 与 JT/T 808 都使用此方式做帧尾校验。 */
public final class BccChecksum {
private BccChecksum() {}
public static byte compute(byte[] data, int offset, int length) {
byte sum = 0;
for (int i = offset; i < offset + length; i++) {
sum ^= data[i];
}
return sum;
}
public static boolean verify(byte[] data, int offset, int length, byte expected) {
return compute(data, offset, length) == expected;
}
}

View File

@@ -0,0 +1,30 @@
package com.lingniu.ingest.codec;
/** BCD (Binary-Coded Decimal) 编解码。JT/T 808 的手机号、时间戳常用。 */
public final class BcdCodec {
private BcdCodec() {}
public static byte[] encode(String decimal) {
int len = (decimal.length() + 1) / 2;
byte[] out = new byte[len];
int idx = decimal.length() % 2;
if (idx == 1) {
out[0] = (byte) Character.digit(decimal.charAt(0), 10);
}
for (int i = idx; i < decimal.length(); i += 2) {
int hi = Character.digit(decimal.charAt(i), 10);
int lo = Character.digit(decimal.charAt(i + 1), 10);
out[(i + idx) / 2] = (byte) ((hi << 4) | lo);
}
return out;
}
public static String decode(byte[] bcd) {
StringBuilder sb = new StringBuilder(bcd.length * 2);
for (byte b : bcd) {
sb.append((b >> 4) & 0x0F).append(b & 0x0F);
}
return sb.toString();
}
}

View File

@@ -0,0 +1,34 @@
package com.lingniu.ingest.codec;
/** 位运算工具:从字节数组读写无符号整数。 */
public final class BitUtils {
private BitUtils() {}
public static int readUint8(byte[] buf, int offset) {
return buf[offset] & 0xFF;
}
public static int readUint16BE(byte[] buf, int offset) {
return ((buf[offset] & 0xFF) << 8) | (buf[offset + 1] & 0xFF);
}
public static long readUint32BE(byte[] buf, int offset) {
return ((long) (buf[offset] & 0xFF) << 24)
| ((long) (buf[offset + 1] & 0xFF) << 16)
| ((long) (buf[offset + 2] & 0xFF) << 8)
| (buf[offset + 3] & 0xFF);
}
public static void writeUint16BE(byte[] buf, int offset, int value) {
buf[offset] = (byte) ((value >> 8) & 0xFF);
buf[offset + 1] = (byte) (value & 0xFF);
}
public static void writeUint32BE(byte[] buf, int offset, long value) {
buf[offset] = (byte) ((value >> 24) & 0xFF);
buf[offset + 1] = (byte) ((value >> 16) & 0xFF);
buf[offset + 2] = (byte) ((value >> 8) & 0xFF);
buf[offset + 3] = (byte) (value & 0xFF);
}
}

View File

@@ -0,0 +1,28 @@
package com.lingniu.ingest.codec;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
class BcdCodecTest {
@Test
void roundTripEvenLength() {
// 偶数位直接等值
byte[] bcd = BcdCodec.encode("1380013800");
assertThat(BcdCodec.decode(bcd)).isEqualTo("1380013800");
}
@Test
void roundTripOddLengthPadsLeadingZero() {
// 奇数位会在高位补 0
byte[] bcd = BcdCodec.encode("13800138000");
assertThat(BcdCodec.decode(bcd)).isEqualTo("013800138000");
}
@Test
void checksumMatches() {
byte[] data = {0x01, 0x02, 0x03, 0x04};
assertThat(BccChecksum.compute(data, 0, data.length)).isEqualTo((byte) 0x04);
}
}