Files
lingniu-vehicle-ingest/vehicle-data-platform/docs/open-platform-api.md
T
lingniu d6b957dfc8 feat: sync vehicle monitoring and accumulated platform updates
Add monitoring metrics, numeric sorting, report-time display, vehicle number labels, and map layout fixes. Include accumulated mileage, hydrogen, OneOS authentication, open platform, ingestion, deployment, and UI updates.

Exclude temporary files, generated reports, and credentials. Validate frontend 632 tests and production build, API packages, vehicle-map tests, and release installer tests. Gateway suite has one pre-existing date-sensitive identity throttling test failure reproduced on the parent commit.
2026-10-26 18:05:59 +08:00

20 KiB
Raw Blame History

车辆数据开放平台 API

在线文档:

  • 简洁中文说明:/open-api/docs/
  • Swagger UI:/open-api/swagger/
  • OpenAPI 3.0:/open-api/openapi.yaml

Swagger 的认证信息仅保存在当前页面内存中,页面刷新后清除,不会由平台预填或持久化。

模块边界

一期向合作方开放经过预统计的车辆单日用氢量、单日里程和指定时刻总里程,并提供:

  • 32 位无连字符 UUID appKey;
  • appKey 启停和起止有效期;
  • appKey 下的 VIN 白名单和逐车起止有效期;
  • 外部调用审计和管理员变更审计。

外部接口与平台后台登录令牌相互独立。appKey 明文只在创建或轮换时返回一次,数据库仅保存 SHA-256 和前 8 位展示前缀。

授权规则

Authorization: Bearer <32位appKey>
Content-Type: application/json

请求需要同时满足:

  1. appKey 状态为 enabled,当前调用时间在 Key 有效期内;
  2. 查询自然日完整落在 Key 有效期内;
  3. 每辆车的授权完整覆盖查询自然日;
  4. 车牌能映射到授权 VIN。

任一车辆未授权时整批返回 403 FORBIDDEN,不会返回部分结果。单日聚合不开放授权首日或结束日的半日数据。

综合运营数据批量查询

POST /api/v1/vehicles/operating-data/query
{"plateNumbers":["粤A09635F"],"dailyDate":"2026-10-23","dataTypes":["REALTIME","DAILY_MILEAGE","DAILY_HYDROGEN"],"protocolPriority":["GB32960","MQTT","JT808"]}

新端点车牌可选,省略或传空数组时查询所选时间范围内均有效授权的全部车辆(多范围取交集),按车牌排序;无授权车辆返回空数组。省略或空数组返回全部有效授权车辆,不受 200 辆限制,不分页、不截断。显式指定车牌最多 200 项,超出返回 HTTP 400 / BATCH_SIZE_EXCEEDED 并提示可省略或传空数组查询全部。规范化后重复车牌报错;最大请求体 64 KiB。dataTypes 省略默认全部,含日统计时 dailyDate 必填且不能为未来日期;仅实时可省略,响应 queryDate=null。protocolPriority 只允许在选择里程时提供。未知字段及显式 null 均拒绝。日期不设置新的历史下界,结果取决于授权和保留投影,不承诺历史完整性。

所有范围先授权:appKey 当前有效,实时车辆授权覆盖请求时点,日统计授权完整覆盖查询日;任一越权整批失败。按 VIN 合并,保持车牌顺序。realtime、dailyMileage、dailyHydrogen 仅返回所选块,字段及业务口径沿用独立接口,原可选缺失字段显式 null;时间转成带 +08:00 的 RFC3339。requestAt、generatedAt 不表示共同数据快照。

块内 queryStatus=SUCCESS 后仍需判断业务状态与质量。失败块仅含 queryStatus=FAILED、status=null、errorCode、errorMessage;失败不丢弃其他结果,全部子查询失败也可返回 HTTP 200。summary 按车辆统计所选块执行成功、部分成功或全失败,NO_DATA/异常不改变执行成功计数。保留 CARRIED_FORWARD、PRELIMINARY/FINAL、实时储氢逐字段状态及来源;补充查询已降级的 MISSING/UNKNOWN 无法严格区分无记录和补充存储失败。接口不计算百公里氢耗,同日、NORMAL 不证明数据区间可比。

内部三类集合批量并行查询,共享已授权 VIN 映射,不扫描新原始全日数据。总预算 6 秒,数据子查询共享最多 4.5 秒。独立复用现有应用限流器:每应用每实例 30 批/60 秒、最多 2 批并发;本端点全局每实例最多 16 批,超限 429 RATE_LIMIT_EXCEEDED 带 Retry-After。多实例统一配额及更长网关预算由部署配置,不宣称未经压测的性能目标。

完整请求、响应、字段和错误码见 /open-api/openapi.yaml 及 /open-api/docs/#operating-data。原三个独立接口保持兼容。

小程序精简模式

{"responseMode":"SIMPLE","plateNumbers":["沪A01933F"],"dailyDate":"2026-10-23"}

省略 responseMode 或指定 FULL 保持已上线响应;显式 SIMPLE 时,外层不变,每辆车固定返回 plateNumber、reportTime、offline、remainingHydrogenKg、socPercent、totalMileageKm、dailyHydrogenConsumptionKg、dailyMileageKm、hydrogenConsumptionPer100KmKg 九个字段,不输出 VIN、嵌套块或内部质量字段。

未知、未选或不满足质量的字段为 null,真实零及布尔 false 保留。剩余氢量要求逐字段 NORMAL、有可信采集时点且在300秒时效范围;不展示陈旧值。日用氢要求 NORMAL+OK,日里程要求 NORMAL 且不是 CARRIED_FORWARD;结转补零为 null,不能冒充实测零。offline 仅在主记录可用且 online 已知时取反,无记录或失败为 null。reportTime 直接使用实时主记录 event_time;没有真实源时点时为 null,不使用投影 updated_at、接收时间或请求时间代替。SQL 总里程为空时为 null,不沿用旧接口的补零,也不使用日统计兜底。

SIMPLE summary 按所选展示字段可用性统计(实时5项、日里程1项、日用氢1项):全有值为成功、部分为部分成功、全空为失败。车牌、未选类型及预留百公里氢耗不参与;null 和失败计数不等于查询执行失败,不用于自动重试。FULL 仍按子查询执行结果统计。

hydrogenConsumptionPer100KmKg 当前固定 null:独立日投影不提供完整可比覆盖证明,本次不新增算法或同日相除。日内用氢可能为 PRELIMINARY;正式对账使用 FULL 核查阶段、质量与来源。授权、批量上限、查询次数、超时和限流均沿用原实现。

单日用氢量

POST /api/v1/vehicles/hydrogen-consumption/query
{
  "plateNumbers": ["粤A12345", "粤B67890"],
  "date": "2026-07-01"
}

plateNumbers 可选。省略或传空数组时,返回应用在该自然日有效授权的全部车辆;传入时最多 200 个,不允许空字符串或重复值。date 固定为 yyyy-MM-dd。

查询全部授权车辆:

{
  "date": "2026-07-01"
}
{
  "code": "SUCCESS",
  "message": "success",
  "data": [
    {
      "vin": "LNB00000000000001",
      "plateNumber": "粤A12345",
      "date": "2026-07-01",
      "hydrogenConsumptionKg": 12.315,
	  "calculationPhase": "FINAL",
	  "algorithmVersion": "PRESSURE_NIST_VALID_BOUNDARY_CHARGE_CYCLE_V3_5",
      "status": "NORMAL"
    },
    {
      "plateNumber": "粤B67890",
      "date": "2026-07-01",
      "hydrogenConsumptionKg": null,
      "status": "NO_DATA"
    }
  ],
  "traceId": "4ccf63c4e51d4d4ab9107d931783a53e"
}

当天查询可能返回 calculationPhase: PRELIMINARY,表示结果由 V3.5 流式状态机生成,后续帧可能使结果回修;完成日终全量重算后变为 FINAL。

单日里程

POST /api/v1/vehicles/mileage/query

除 date 和可选 plateNumbers 外,里程接口支持可选的 protocolPriority:

{
  "plateNumbers": ["粤A12345"],
  "date": "2026-07-01",
  "protocolPriority": ["GB32960", "MQTT", "JT808"]
}

外部协议值只允许 GB32960、MQTT、JT808。传入时数组必须非空、不能重复;每辆车独立按数组顺序选择第一个有效协议,未列出的协议被完全禁用,不能兜底。不传该字段时保持现网默认选源行为。

支持的典型模式包括:["GB32960","MQTT","JT808"]、["JT808","GB32960","MQTT"]、["GB32960","MQTT"]、["JT808"]。

{
  "code": "SUCCESS",
  "message": "success",
  "data": [
    {
      "plateNumber": "粤A12345",
      "date": "2026-07-01",
      "dailyMileageKm": 182.437,
      "totalMileageKm": 12345.679,
      "dataTime": "2026-07-01T23:58:45+08:00",
      "updatedAt": "2026-07-02T05:10:00+08:00",
      "sourceProtocol": "GB32960",
      "status": "NORMAL"
    }
  ],
  "traceId": "b7ff5582ab1a4e13bfb4f10943685599"
}

dailyMileageKm 表示该自然日行驶里程;totalMileageKm 表示同一协议统计记录在该日最后有效时刻的累计总里程;sourceProtocol 是该行实际选中的协议;dataTime 是统计实际采用的最后一条车辆源数据时间;updatedAt 是日统计投影最后更新时间。时间均为带 +08:00 的 ISO 8601。

status=NORMAL 时数据字段均有值;任一关键字段缺失时整车返回 NO_DATA,相关数据字段和 sourceProtocol 均为 null。真实零日里程返回 NORMAL 和 dailyMileageKm: 0。

里程与时间字段始终取自 sourceProtocol 指定的同一协议统计记录,不跨协议拼接。内部数据源 YUTONG_MQTT 对外统一返回 MQTT。

当查询日没有任何有效日里程和累计总里程,但查询日前存在有效累计里程时,接口执行前向填充:

  • dailyMileageKm 返回 0;
  • totalMileageKm 沿用此前最近的有效累计总里程;
  • sourceProtocol、dataTime 沿用该历史统计记录;
  • updatedAt 返回上一个有效统计周期的计算时间,不伪装成查询日计算时间;
  • 只有查询日前也不存在有效累计里程时才返回 NO_DATA。

区间日里程

POST /api/v1/vehicles/mileage/range/query

最长支持 366 天。plateNumbers 可省略,省略时查询整个区间均有效授权的全部车辆;指定时最多 5,000 个。pageSize 为 1–5,000,默认 5,000。

首次请求:

{
  "startDate": "2026-07-01",
  "endDate": "2026-07-23",
  "protocolPriority": ["JT808", "GB32960", "MQTT"],
  "pageSize": 5000
}

响应:

{
  "code": "SUCCESS",
  "message": "success",
  "data": [
    {
      "vin": "LNB00000000000001",
      "plateNumber": "粤A12345",
      "date": "2026-07-01",
      "dailyMileageKm": 182.437,
      "totalMileageKm": 12345.679,
      "dataTime": "2026-07-01T23:58:45+08:00",
      "updatedAt": "2026-07-02T05:10:00+08:00",
      "sourceProtocol": "JT808",
      "status": "NORMAL"
    }
  ],
  "snapshotId": "9f8a74efbf9846349ae5676f3a5c0de8",
  "nextCursor": null,
  "traceId": "4ccf63c4e51d4d4ab9107d931783a53e"
}

protocolPriority 的校验、选源和缺日前向填充规则与单日接口完全一致,并对区间内每辆车、每个自然日独立执行。有下一页时,保持 startDate、endDate、plateNumbers、protocolPriority 和 pageSize 不变,把上一页 nextCursor 放入下一次请求。同一次查询的 snapshotId 保持不变。快照有效期为 2 小时。

接口只固化授权车辆清单,不预生成“车辆数 × 天数”的明细;每页读取带复合索引的日统计投影,因此大范围查询不会扫描原始时序明细。

指定时刻总里程

POST /api/v1/vehicles/total-mileage/query

接口返回“不晚于请求时间的最近一条有效总里程记录”。time 使用北京时间,格式必须为 yyyy-MM-dd HH:mm:ss。

{
  "vin": "LA9GG68L2PBAF4790",
  "time": "2026-07-21 09:30:00",
  "protocol": "GB32960"
}

protocol 可选,只接受车辆数据中台、接收、统计和开放平台共同使用的唯一规范值:GB32960、YUTONG_MQTT、JT808。不再接受 32960、mqtt、808 等别名。未指定时严格按以下优先级查找首个有数据的协议:

GB32960 > YUTONG_MQTT > JT808

协议总里程含义:

协议 含义
GB32960 车辆仪表盘累计总里程,对应 GB/T 32960 整车数据累计里程
YUTONG_MQTT 车辆仪表盘或车端控制器累计总里程,由 MQTT 平台上报
JT808 定位终端累计里程,由 GPS/终端侧计算,不等同于车辆仪表盘里程

三种协议可能使用不同里程源、标定值和重置机制,不能直接拼接成同一条连续里程曲线。响应会返回实际命中的协议和记录时间,调用方应结合时间差判断记录是否足够新。

{
  "code": "SUCCESS",
  "message": "success",
  "data": {
    "vin": "LA9GG68L2PBAF4790",
    "queryTime": "2026-07-21 09:30:00",
    "totalMileageKm": 12345.678,
    "protocol": "GB32960",
    "protocolInput": "GB32960",
    "mileageMeaning": "车辆仪表盘累计总里程(GB/T 32960整车数据累计里程)",
    "recordTime": "2026-07-21 09:29:45",
    "timeDifferenceSeconds": 15,
    "selectionPolicy": "GB32960 > YUTONG_MQTT > JT808",
    "status": "NORMAL"
  },
  "traceId": "95bddca78133474fa2bf56ecdf758e22"
}

字段说明:

  • recordTime:实际命中的采集记录时间;
  • timeDifferenceSeconds:queryTime - recordTime,单位秒,正常为非负数;
  • protocol:实际采用的采集协议,而不是默认优先级描述;
  • NO_DATA:授权通过,但所有候选协议在请求时间之前都没有有效总里程。

错误码

HTTP code 说明
400 INVALID_REQUEST 请求结构、车牌或数量不正确
400 INVALID_DATE_FORMAT 日期不是 yyyy-MM-dd
400 INVALID_DATETIME_FORMAT 时间不是 yyyy-MM-dd HH:mm:ss
401 UNAUTHORIZED appKey 不存在、停用或过期
403 FORBIDDEN Key 或车辆授权未覆盖查询自然日
500 INTERNAL_ERROR 服务内部异常

管理 API

管理 API 使用车辆数据平台后台 Bearer Token,仅 admin 可调用。

POST /api/v2/open-platform/apps
GET  /api/v2/open-platform/apps
PUT  /api/v2/open-platform/apps/{id}
POST /api/v2/open-platform/apps/{id}/rotate-key
GET  /api/v2/open-platform/apps/{id}/vehicles
PUT  /api/v2/open-platform/apps/{id}/vehicles
GET  /api/v2/open-platform/users
POST /api/v2/open-platform/users
PUT  /api/v2/open-platform/users/{id}
GET  /api/v2/open-platform/users/{id}/apps
PUT  /api/v2/open-platform/users/{id}/apps

创建应用:

{
  "name": "示例合作方",
  "status": "enabled",
  "validFrom": "2026-07-01T00:00:00+08:00",
  "validTo": "2027-07-01T00:00:00+08:00"
}

创建和轮换响应中的 data.appKey 是唯一一次明文交付机会。轮换后旧 Key 立即失效。

车辆授权 PUT 使用完整替换语义,单次最多 5000 辆:

{
  "vehicles": [
    {
      "vin": "LTEST32960VIN0001",
      "validFrom": "2026-07-01T00:00:00+08:00",
      "validTo": "2027-07-01T00:00:00+08:00"
    }
  ]
}

VIN 必须已存在于平台身份绑定表。

管理员可以从车辆目录按 VIN、车牌或品牌搜索并勾选,也可以批量粘贴 VIN 或车牌自动匹配,或者一次选择当前车辆主数据中的全部车辆。批量匹配会报告 未识别项,不会静默忽略。“全部车辆”是保存时点的车辆快照,不会自动包含 以后新增的车辆。

管理端可通过下列接口读取可授权车辆目录:

GET /portal-api/admin/vehicles
Authorization: Bearer <admin-token>

合作伙伴用户可配置账号有效期,并通过 owner、developer、viewer 角色绑定一个或多个应用。密码至少 12 位且必须同时包含大小写字母和数字。 连续 5 次登录失败后账号锁定 15 分钟。

内部车辆平台中启用的本地 admin 账号可使用同一用户名和密码登录开放平台。 开放平台校验内部密码哈希后签发独立会话,不复制密码,也不共享两个域名的 浏览器 Token。管理员可在“平台管理”中创建开放应用、合作伙伴账号并配置 车辆与授权有效期。

合作伙伴门户

独立开放平台默认监听 20310,不再与内部平台 20300 共用外部 HTTP 入口。门户 API 使用短期会话 Token,数据 API 仍使用应用的 32 位 AppKey。

POST /portal-api/auth/login
POST /portal-api/auth/logout
GET  /portal-api/session
GET  /portal-api/catalog
GET  /portal-api/apps
GET  /portal-api/apps/{id}/vehicles
GET  /portal-api/apps/{id}/audit
POST /portal-api/apps/{id}/rotate-key
PUT  /portal-api/account/password

只有应用 owner 可以在门户轮换 AppKey;新 Key 仍只显示一次。门户登录、 密码修改、用户配置和密钥操作均写入审计表。

用氢量统计

用氢量只接收 GB32960 帧,不读取 JT808、宇通 MQTT 或其他协议。统一采用压力法:

剩余氢量 kg = NIST氢气密度(最高氢压 MPa, 最高氢温 ℃) × 车型氢瓶容量 L / 1000
  • 不使用广东扩展直接上报的剩余氢量、飞驰剩余百分比、宇通 MQTT 剩余氢量或百公里氢耗推算质量;
  • vehicle-stat-writer 启动时及每六小时从 ln_asset_management.vehicle_info → vehicle_model 同步车型参数:tank_capacity 按 VIN 投影到 vehicle_hydrogen_tank_capacity,battery_capacity 按 VIN 投影到 vehicle_hydrogen_energy_parameter;
  • 额定电量按车辆关联的具体车型读取,不按吨位写死;后续生效日期更晚的人工业务参数仍可覆盖自动同步基线;
  • 储氢容积同步后一次性加载到统计进程内存,高频帧计算不访问资产库、Redis或MySQL;额定电量由日氢耗任务按VIN和统计日期读取;缺少VIN容积时拒绝生成氢量;
  • 压力法使用 NIST 实氢气密度方程,允许 0–70 MPa、220–1000 K 的输入范围;
  • GB32960 上报的是“最高氢压”和“最高氢温”,两者不保证来自同一探头,也不保证代表静置平衡后的储氢瓶状态。因此压力法结果是车载信号估算值,不能用加氢站计量值直接反标公式系数。

当天准实时数据由 vehicle-stat-writer 消费 GB32960 fields Kafka 流。每个有效样本在同一个 MySQL 事务中更新 vehicle_open_hydrogen_segment_stream_state,并投影到 vehicle_open_daily_energy。Kafka offset 只在事务提交成功后提交,因此服务重启或消息重放不会重复累计。状态按 VIN + 日期 保存并合并全部连接端点;每条消息只做常数次数据库操作,不扫描当天历史数据。

统计规则:

  • 已结束日期先按 VIN 合并全部 source_endpoint,同一事件时间只保留一条可信样本;连接端口仅用于追踪,不再作为互斥统计来源;
  • 只有相邻两条样本都确认燃料电池处于工作状态时,才纳入工作段;优先采用广东扩展 engine_work_state=2,缺失时以燃料电池电流大于 1 A 作为回退;完全没有工作区间的日期标记为 NO_DATA;
  • 加氢、超过 5 分钟的数据断点、燃料电池停机和异常跳变会切分工作段;每段开始与结束各取 5 条样本的氢质量中位数做差,再累加为当日用氢量;
  • 每个可计算工作段至少需要 10 条有效样本;段首尾差值未超过动态噪声阈值时不累计;
  • 燃料电池未工作时,下降的压力质量只更新防重复计算的低水位,不降低对外剩余氢量;压力或质量恢复上升后再更新剩余量;
  • 抖动阈值按该VIN容积对应的 0.2 MPa 质量变化动态计算,最小 0.05 kg、最大 1 kg;
  • 质量相对本轮低水位上升超过 max(1 kg, 当前质量×5%) 时识别为加氢并重置低水位;
  • 相邻样本在 60 秒内无工作耗氢却恢复至少 8 MPa 时,按压力信号恢复处理,重置低水位但不增加加氢次数;
  • 单次下降超过 OPEN_STAT_HYDROGEN_MAX_DROP_KG 时过滤并标记 SUSPECT,默认 20 kg;
  • 对外只返回 quality_status=OK 的结果。

分段计算由流式状态机直接执行:每车仅保留当前段的首 5 帧、尾 5 帧、低水位、上一帧和事件时间水位,状态不会随当天帧数增长。水位只覆盖当前自然日 00:00:00–24:00:00,不向前后日期扩窗,也不借用次日帧;早于或等于当前事件时间水位的延迟帧直接忽略。流式结果采用与日终相同的工作段、中位数、加氢和异常跳变规则;已结束日期仍由 open-platform-stat 重算,作为最终对账结果。

历史重放只接收事件时间和接收时间同时落在统计日内的 GB32960 帧。每个统计日先读取有数据且配置了氢瓶容积的 VIN,再在同一进程内按单车单日并行查询;默认 4 个工作线程,全部车辆成功后才一次性事务替换当天结果。单车帧按 event_time 排序并单遍去重;JSON 只读取压力、温度和工作状态字段,不展开整帧,从而减少全车排序与反序列化开销。可通过 -vin-workers 或 OPEN_STAT_VIN_WORKERS 调整工作线程数。

/opt/lingniu-vehicle-platform/current/open-platform-stat \
  -date 2026-07-01 \
  -lookback-days 1

systemctl enable --now lingniu-vehicle-open-stat.timer

流式算法首次切换时,先停止 lingniu-go-stat-writer.service,再对当天执行一次 -seed-stream-state。该参数会在同一个 MySQL 事务中替换当天结果并写入每车最后事件时间水位;恢复 Kafka 消费后,已经包含在重算结果中的 backlog 不会再次累计。正常日终任务不使用此参数。