352 lines
16 KiB
Markdown
352 lines
16 KiB
Markdown
# Vehicle Ingest TDengine 链路验收手册
|
||
|
||
这份手册用于验收当前 32960/JT808 接入重构链路:
|
||
|
||
1. 协议 App 接收 TCP 报文。
|
||
2. 协议 App 归档原始帧元数据,并把事件信封发布到 Kafka。
|
||
3. `vehicle-history-app` 消费 Kafka。
|
||
4. history 写入 raw、location 到 TDengine;按需开启 telemetry field 逐字段宽表。
|
||
5. Swagger/API 通过 TDengine 做历史分页查询。
|
||
|
||
每一步都必须执行命令并检查输出后,才能标记为已验证。
|
||
|
||
## 必需运行参数
|
||
|
||
启动服务前先设置:
|
||
|
||
```bash
|
||
export KAFKA_BROKERS=114.55.58.251:9092
|
||
export TDENGINE_HISTORY_ENABLED=true
|
||
export TDENGINE_HISTORY_DATABASE=vehicle_ts
|
||
export TDENGINE_JDBC_URL='jdbc:TAOS-WS://<tdengine-host>:6041/vehicle_ts'
|
||
export TDENGINE_DRIVER_CLASS_NAME=com.taosdata.jdbc.ws.WebSocketDriver
|
||
export TDENGINE_USERNAME=root
|
||
export TDENGINE_PASSWORD='<tdengine-password>'
|
||
export TDENGINE_MIN_IDLE=0
|
||
export TDENGINE_MAX_POOL_SIZE=32
|
||
export EVENT_FILE_STORE_ENABLED=false
|
||
export TDENGINE_TELEMETRY_FIELDS_ENABLED=false
|
||
```
|
||
|
||
`TDENGINE_MIN_IDLE=0` 用于减少冷启动时 TDengine 连接重试日志。TDengine 地址稳定后,再按生产并发调大连接池。
|
||
|
||
高吞吐生产验收默认关闭 `EVENT_FILE_STORE_ENABLED` 和 `TDENGINE_TELEMETRY_FIELDS_ENABLED`,验证 `raw_frames`、`vehicle_locations`、raw archive 文件和分页查询闭环。GB32960 snapshot/fields 接口会通过 TDengine `raw_frames` 索引找到 `rawUri`,再回读共享 archive 的原始 `.bin` 即时解析;需要兼容旧 `/api/event-history/records` 低层记录接口或逐字段趋势宽表时,再显式开启对应开关,并单独压测写入放大。
|
||
|
||
## 构建
|
||
|
||
```bash
|
||
mvn -pl modules/apps/jt808-ingest-app,modules/apps/gb32960-ingest-app,modules/apps/vehicle-history-app -am package -DskipTests
|
||
```
|
||
|
||
预期产物:
|
||
|
||
- `modules/apps/jt808-ingest-app/target/jt808-ingest-app.jar`
|
||
- `modules/apps/gb32960-ingest-app/target/gb32960-ingest-app.jar`
|
||
- `modules/apps/vehicle-history-app/target/vehicle-history-app.jar`
|
||
|
||
## 启动 JT808 接入服务,监听 808 端口
|
||
|
||
```bash
|
||
HTTP_PORT=20482 \
|
||
JT808_PORT=808 \
|
||
KAFKA_CONSUMER_ENABLED=false \
|
||
java -jar modules/apps/jt808-ingest-app/target/jt808-ingest-app.jar
|
||
```
|
||
|
||
验证:
|
||
|
||
```bash
|
||
curl -sS http://127.0.0.1:20482/actuator/health
|
||
lsof -nP -iTCP:808 -sTCP:LISTEN
|
||
```
|
||
|
||
预期:健康检查为 `UP`,并且 Java 进程监听 TCP `808`。
|
||
|
||
## 启动 GB32960 接入服务
|
||
|
||
```bash
|
||
HTTP_PORT=20100 \
|
||
GB32960_PORT=32960 \
|
||
KAFKA_CONSUMER_ENABLED=false \
|
||
java -jar modules/apps/gb32960-ingest-app/target/gb32960-ingest-app.jar
|
||
```
|
||
|
||
验证:
|
||
|
||
```bash
|
||
curl -sS http://127.0.0.1:20100/actuator/health
|
||
lsof -nP -iTCP:32960 -sTCP:LISTEN
|
||
```
|
||
|
||
## 启动 TDengine 版历史服务
|
||
|
||
```bash
|
||
HTTP_PORT=20200 \
|
||
KAFKA_CONSUMER_ENABLED=true \
|
||
EVENT_FILE_STORE_ENABLED=false \
|
||
EVENT_FILE_STORE_PATH=./target/tdengine-verification/event-store \
|
||
TDENGINE_TELEMETRY_FIELDS_ENABLED=false \
|
||
SINK_ARCHIVE_PATH=./target/tdengine-verification/archive \
|
||
java -jar modules/apps/vehicle-history-app/target/vehicle-history-app.jar
|
||
```
|
||
|
||
验证:
|
||
|
||
```bash
|
||
curl -sS http://127.0.0.1:20200/actuator/health
|
||
curl -sS http://127.0.0.1:20200/v3/api-docs \
|
||
| grep -E '/api/event-history/jt808/locations|/api/event-history/gb32960/snapshots|/api/event-history/telemetry/fields'
|
||
```
|
||
|
||
预期:健康检查为 `UP`,OpenAPI 中包含 JT808 TDengine 分页查询接口和 GB32960 snapshot 接口。`/api/event-history/telemetry/fields` 会存在,但只有在 `TDENGINE_TELEMETRY_FIELDS_ENABLED=true` 后才会持续写入新字段数据。
|
||
|
||
## 本机 launchctl 部署
|
||
|
||
当前本机生产验证建议使用仓库模板生成 plist:
|
||
|
||
```bash
|
||
deploy/local/launchctl/
|
||
```
|
||
|
||
关键约束:
|
||
|
||
- `jt808-ingest-app` 监听 TCP `808`,HTTP `20482`。
|
||
- `gb32960-ingest-app` 监听 TCP `32960`,HTTP `20100`。
|
||
- `vehicle-history-app` 监听 HTTP `20200`。
|
||
- 三个服务必须共用同一个 `SINK_ARCHIVE_PATH`,否则 TDengine 中的 `raw_uri` 能入库,但 history API 无法按 `archive://...` 读取原始帧。
|
||
- 替换 jar 前先 `launchctl bootout`,再复制 jar 和 `launchctl bootstrap`,避免 KeepAlive 在 jar 拷贝中途重启。
|
||
|
||
JT808 注册身份绑定:
|
||
|
||
- 默认可以使用 `VEHICLE_IDENTITY_STORE=file` 或 `memory`。
|
||
- 生产需要把 0x0100 注册信息维护到 MySQL 时,设置 `VEHICLE_IDENTITY_STORE=mysql`。
|
||
- 需要提供 `VEHICLE_IDENTITY_MYSQL_JDBC_URL`、`VEHICLE_IDENTITY_MYSQL_USERNAME`、`VEHICLE_IDENTITY_MYSQL_PASSWORD`。
|
||
- 服务启动时自动创建 `vehicle_identity_binding` 和 `vehicle_identity_binding_registration` 两张表。`vehicle_identity_binding` 按 `protocol + identifier_type + identifier_value` 维护已确认 VIN 绑定;`vehicle_identity_binding_registration` 按 `protocol + phone` 保存 JT808 0x0100 注册字段。
|
||
- 注册帧无真实 VIN 时也会写入 registration 表,`vin` 默认为 `unknown`。外部系统识别车辆后可反写真实 VIN,例如:`UPDATE vehicle_identity_binding_registration SET vin = 'LNVIN000000000001' WHERE protocol = 'JT808' AND phone = '13079960003';`
|
||
- MySQL 绑定在启动时加载到内存索引,registration 表反写 VIN 后会按 `VEHICLE_IDENTITY_MYSQL_REFRESH_INTERVAL` 周期刷新,默认 `60s`;帧解析热路径只查内存,不按帧访问 MySQL。
|
||
- 绑定/注册类型包括 `PHONE`、`DEVICE_ID`、`PLATE`;后续同一终端上报 raw/event 时会优先解析成已绑定或已反写 VIN。
|
||
|
||
健康检查:
|
||
|
||
```bash
|
||
curl -sS http://127.0.0.1:20482/actuator/health
|
||
curl -sS http://127.0.0.1:20100/actuator/health
|
||
curl -sS http://127.0.0.1:20200/actuator/health
|
||
```
|
||
|
||
Swagger:
|
||
|
||
- JT808 ingest: `http://127.0.0.1:20482/swagger-ui/index.html`
|
||
- GB32960 ingest: `http://127.0.0.1:20100/swagger-ui/index.html`
|
||
- History query: `http://127.0.0.1:20200/swagger-ui/index.html`
|
||
|
||
## Kafka Topic 验证
|
||
|
||
历史服务必须消费事件 topic 和 raw topic:
|
||
|
||
- `vehicle.event.gb32960.v1`
|
||
- `vehicle.raw.gb32960.v1`
|
||
- `vehicle.event.jt808.v1`
|
||
- `vehicle.raw.jt808.v1`
|
||
|
||
使用 Kafka CLI 或管理工具检查 topic 是否存在,并确认 history consumer group 在测试流量后没有持续堆积。默认 `KAFKA_GROUP_HISTORY=vehicle-history` 时,实际 group 会拆成:
|
||
|
||
- `vehicle-history-gb32960-event`
|
||
- `vehicle-history-gb32960-raw`
|
||
- `vehicle-history-jt808-event`
|
||
- `vehicle-history-jt808-raw`
|
||
|
||
## JT808 实时转发验收
|
||
|
||
如果外部平台已经把 JT808 报文转发到本机 TCP `808`,至少观察 60 秒日志和 Kafka 数据。
|
||
|
||
必须拿到以下证据:
|
||
|
||
- `jt808-ingest-app` 日志显示接入连接或解析到上游消息 ID。
|
||
- Kafka 的 `vehicle.event.jt808.v1` 和 `vehicle.raw.jt808.v1` 有新增记录。
|
||
- `vehicle-history-app` 日志没有持续出现 consumer 或 TDengine 写入失败。
|
||
|
||
消费完成后,使用已知终端手机号查询:
|
||
|
||
```bash
|
||
curl -sS 'http://127.0.0.1:20200/api/event-history/jt808/locations?phone=<phone>&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&limit=10'
|
||
```
|
||
|
||
预期:响应包含 `items`,并且每条记录包含 `eventTime`、`phone`、`longitude`、`latitude`、`speedKmh`、`rawUri`、`metadataJson`。
|
||
|
||
## GB32960 Snapshot / 字段投影验收
|
||
|
||
默认高吞吐模式下,GB32960 全字段查询不依赖 telemetry_fields 宽表。history 先查 TDengine `raw_frames`,再按 `rawUri` 读取共享 archive 中的原始 `.bin` 并即时解析:
|
||
|
||
```bash
|
||
curl -sS 'http://127.0.0.1:20200/api/event-history/gb32960/snapshots?vin=<vin>&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&limit=10'
|
||
|
||
curl -sS 'http://127.0.0.1:20200/api/event-history/gb32960/snapshots/fields?vin=<vin>&fields=VEHICLE.speedKmh,VEHICLE.totalMileageKm,POSITION_V2016.longitude,POSITION_V2016.latitude&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&limit=10'
|
||
```
|
||
|
||
预期:snapshot 返回 `sourceFrames.rawArchiveUri` 和解析后的 `blocks`;字段投影返回所选字段。新增协议字段后,只要解析器和字典补齐,就可以基于历史 raw 重新查询。
|
||
|
||
## Telemetry Field 查询验收
|
||
|
||
查询 GB32960 字段历史。该能力需要 `TDENGINE_TELEMETRY_FIELDS_ENABLED=true` 并确认压测后无持续 lag:
|
||
|
||
```bash
|
||
curl -sS 'http://127.0.0.1:20200/api/event-history/telemetry/fields?protocol=GB32960&vin=<vin>&fieldKey=<field-key>&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&limit=10'
|
||
```
|
||
|
||
查询 JT808 字段历史。没有 VIN 映射时,优先使用 `phone`。该能力同样需要 `TDENGINE_TELEMETRY_FIELDS_ENABLED=true`:
|
||
|
||
```bash
|
||
curl -sS 'http://127.0.0.1:20200/api/event-history/telemetry/fields?protocol=JT808&phone=<phone>&fieldKey=location.speed_kmh&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&limit=10'
|
||
```
|
||
|
||
预期:响应包含 `items`,可能包含 `nextCursor`。下一页使用 `nextCursor` 里的 `cursorTs` 和 `cursorId` 查询。
|
||
|
||
## TDengine 直接检查
|
||
|
||
使用 TDengine CLI 或 JDBC 客户端检查超级表和子表是否创建:
|
||
|
||
```sql
|
||
USE vehicle_ts;
|
||
SHOW STABLES;
|
||
SELECT COUNT(*) FROM raw_frames;
|
||
SELECT COUNT(*) FROM vehicle_locations;
|
||
-- 仅在 TDENGINE_TELEMETRY_FIELDS_ENABLED=true 时检查:
|
||
SELECT COUNT(*) FROM telemetry_fields;
|
||
```
|
||
|
||
预期:
|
||
|
||
- `raw_frames`、`vehicle_locations` 存在并持续增长。
|
||
- `telemetry_fields` 表结构会创建;默认高吞吐模式不会持续增长,除非显式开启 `TDENGINE_TELEMETRY_FIELDS_ENABLED=true`。
|
||
- 有实时流量后,计数持续增加。
|
||
|
||
## 失败语义
|
||
|
||
- TDengine 不可达时,TDengine 查询接口应该返回 HTTP `503`,消息类似 `tdengine history query failed`。
|
||
- TDengine 未启用时,TDengine 专属接口不应该出现在 OpenAPI 中。
|
||
- 路径不存在时,API 层应该返回 HTTP `404`,而不是泛化的存储失败。
|
||
|
||
## 2026-06-29 本机实时链路验证结果
|
||
|
||
运行服务:
|
||
|
||
- `com.lingniu.jt808.808`: `jt808-ingest-app`,TCP `808`,HTTP `20482`
|
||
- `com.lingniu.gb32960.32960`: `gb32960-ingest-app`,TCP `32960`,HTTP `20100`
|
||
- `com.lingniu.vehicle-history.jt808`: `vehicle-history-app`,HTTP `20200`
|
||
|
||
健康检查已确认三个服务均返回 `{"status":"UP"}`。
|
||
|
||
JT808 真实转发链路已验证:
|
||
|
||
- 本机 TCP `808` 有外部平台持续发送 JT808 报文。
|
||
- `vehicle.raw.jt808.v1` 和 `vehicle.event.jt808.v1` 被 history 消费。
|
||
- `raw_frames` 中 `protocol='JT808'` 的真实行数已超过 `6000`。
|
||
- `vehicle_locations` 中 `protocol='JT808'` 的位置行数已超过 `1900`。
|
||
- 在旧的字段宽表开启模式下,`telemetry_fields` 中 `protocol='JT808'` 的字段行数曾超过 `15000`;当前高吞吐默认不要求该表持续增长。
|
||
- 最新样例包含终端号 `13079963310`、消息 ID `512`、`archive://2026/06/29/JT808/...` 原始帧引用。
|
||
|
||
## JT808 本机 smoke / 轻压测工具
|
||
|
||
仓库提供 `tools/jt808_e2e_smoke.py`,用于重复验证 TCP `808` 到 TDengine history 的闭环:
|
||
|
||
```bash
|
||
export TDENGINE_REST_URL='http://<tdengine-host>:6041/rest/sql/vehicle_ts'
|
||
export TDENGINE_USERNAME='root'
|
||
export TDENGINE_PASSWORD='<tdengine-password>'
|
||
|
||
python3 tools/jt808_e2e_smoke.py \
|
||
--connection-mode session \
|
||
--start-phone 13079962000 \
|
||
--frames 3 \
|
||
--expect-history-count 3 \
|
||
--verify-pagination \
|
||
--archive-root "$PROJECT_ROOT/data/archive-jt808" \
|
||
--tdengine-rest-url "$TDENGINE_REST_URL" \
|
||
--tdengine-username "$TDENGINE_USERNAME" \
|
||
--tdengine-password "$TDENGINE_PASSWORD"
|
||
```
|
||
|
||
默认 `--connection-mode per-frame`,适合单帧连接 smoke。生产验收建议使用 `--connection-mode session` 覆盖同一连接内连续位置帧。脚本会输出发送数量、history 可见数量、分页验证结果、raw archive 检查结果和 `tdengineRawFrames`。`tdengineRawFrames` 表示本次可见位置记录中的唯一 `rawUri` 有多少个已确认写入 TDengine `raw_frames`。
|
||
- 合成 0x0100 注册帧终端号 `13079969999` 已验证:TCP `808` 返回 `0x8100` 注册 ACK,`raw_frames.metadata_json` 包含 `jt808.register.province`、`jt808.register.city`、`jt808.register.maker`、`jt808.register.deviceType`、`jt808.register.deviceId`、`jt808.register.plateColor`、`jt808.register.plate`,对应 raw archive 文件存在。
|
||
|
||
复查 SQL:
|
||
|
||
```sql
|
||
USE vehicle_ts;
|
||
SELECT COUNT(*) FROM raw_frames WHERE protocol = 'JT808';
|
||
SELECT COUNT(*) FROM vehicle_locations WHERE protocol = 'JT808';
|
||
-- 仅在 TDENGINE_TELEMETRY_FIELDS_ENABLED=true 时复查:
|
||
SELECT COUNT(*) FROM telemetry_fields WHERE protocol = 'JT808';
|
||
SELECT event_time, vehicle_key, phone, message_id, raw_uri
|
||
FROM raw_frames
|
||
WHERE protocol = 'JT808'
|
||
ORDER BY event_time DESC
|
||
LIMIT 5;
|
||
SELECT ts, frame_id, phone, message_id, metadata_json, raw_uri
|
||
FROM raw_frames
|
||
WHERE protocol = 'JT808' AND phone = '13079969999' AND message_id = 256
|
||
ORDER BY ts DESC
|
||
LIMIT 3;
|
||
```
|
||
|
||
复查 API:
|
||
|
||
```bash
|
||
curl -sS 'http://127.0.0.1:20200/api/event-history/jt808/locations?phone=13079963296&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&limit=10'
|
||
```
|
||
|
||
GB32960 链路已用合成实时帧验证:
|
||
|
||
- TCP `32960` 返回 GB32960 `2323` ACK。
|
||
- `raw_frames` 中测试 VIN `LTEST202606290001` 已有 `2` 条 raw 记录。
|
||
- 在旧的字段宽表开启模式下,`telemetry_fields` 中测试 VIN `LTEST202606290001` 曾有 `34` 条字段记录;当前默认通过 GB32960 snapshot/fields 即时解析 raw,不要求该表增长。
|
||
- latest raw 包含 `archive://2026/06/29/GB32960/LTEST202606290001/...`。
|
||
- snapshots API 可查到 `speedKmh=62.4`、`totalMileageKm=223456.7`、`longitude=116.397128`、`latitude=39.916527`。
|
||
|
||
复查 SQL:
|
||
|
||
```sql
|
||
USE vehicle_ts;
|
||
SELECT COUNT(*) FROM raw_frames
|
||
WHERE protocol = 'GB32960' AND vin = 'LTEST202606290001';
|
||
-- 仅在 TDENGINE_TELEMETRY_FIELDS_ENABLED=true 时复查:
|
||
SELECT COUNT(*) FROM telemetry_fields
|
||
WHERE protocol = 'GB32960' AND vin = 'LTEST202606290001';
|
||
SELECT event_time, vin, raw_uri, frame_id
|
||
FROM raw_frames
|
||
WHERE protocol = 'GB32960' AND vin = 'LTEST202606290001'
|
||
ORDER BY event_time DESC
|
||
LIMIT 5;
|
||
```
|
||
|
||
也可以直接运行仓库 smoke 工具验证 TCP `32960`、GB32960 snapshot/fields、TDengine `raw_frames` 和 raw archive:
|
||
|
||
```bash
|
||
export TDENGINE_REST_URL='http://<tdengine-host>:6041/rest/sql/vehicle_ts'
|
||
export TDENGINE_USERNAME='root'
|
||
export TDENGINE_PASSWORD='<tdengine-password>'
|
||
|
||
python3 tools/gb32960_e2e_smoke.py \
|
||
--archive-root "$PROJECT_ROOT/data/archive-jt808" \
|
||
--tdengine-rest-url "$TDENGINE_REST_URL" \
|
||
--tdengine-username "$TDENGINE_USERNAME" \
|
||
--tdengine-password "$TDENGINE_PASSWORD"
|
||
```
|
||
|
||
预期输出包含 `records >= 1`、`fieldCounts` 中关键字段均大于 0、`tdengineRawFrames` 等于本次可见唯一 `rawUri` 数量、`archiveChecked >= 1`。这里的 `records` 字段表示 snapshot 数量,用于兼容早期脚本输出名。
|
||
|
||
复查 API:
|
||
|
||
```bash
|
||
curl -sS 'http://127.0.0.1:20200/api/event-history/gb32960/snapshots?vin=LTEST202606290001&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&limit=10'
|
||
curl -sS 'http://127.0.0.1:20200/api/event-history/gb32960/snapshots/fields?vin=LTEST202606290001&fields=VEHICLE.speedKmh,VEHICLE.totalMileageKm,POSITION_V2016.longitude,POSITION_V2016.latitude&dateFrom=2026-06-29T00:00:00%2B08:00&dateTo=2026-06-29T23:59:59%2B08:00&limit=10'
|
||
```
|
||
|
||
## 当前注意事项
|
||
|
||
- 当前本机使用 TDengine WebSocket JDBC,例如 `jdbc:TAOS-WS://<tdengine-host>:6041/vehicle_ts`。
|
||
- `KAFKA_CONSUMER_AUTO_OFFSET_RESET=latest` 适合生产接入新流量;如果要回放历史 Kafka 数据,需要切换 consumer group 或重置 offset。
|
||
- `vehicle-history-app` raw 和 event 使用不同 consumer binding;raw consumer 必须开启,否则 `raw_frames` 不会持续增长。
|
||
- JT808 设备没有 VIN 映射时,`vehicle_key` 使用 `jt808:<phone>`;启用 MySQL identity store 后,registration 表中已反写 VIN 的 phone/deviceId/plate 会用于后续 raw/event 的 VIN 解析。
|