Files
lingniu-vehicle-ingest/vehicle-data-platform/docs/open-platform-api.md
T

395 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 车辆数据开放平台 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 位展示前缀。
## 授权规则
```http
Authorization: Bearer <32位appKey>
Content-Type: application/json
```
请求需要同时满足:
1. appKey 状态为 `enabled`,当前调用时间在 Key 有效期内;
2. 查询自然日完整落在 Key 有效期内;
3. 每辆车的授权完整覆盖查询自然日;
4. 车牌能映射到授权 VIN。
任一车辆未授权时整批返回 `403 FORBIDDEN`,不会返回部分结果。单日聚合不开放授权首日或结束日的半日数据。
## 单日用氢量
```http
POST /api/v1/vehicles/hydrogen-consumption/query
```
```json
{
"plateNumbers": ["粤A12345", "粤B67890"],
"date": "2026-07-01"
}
```
`plateNumbers` 可选。省略或传空数组时,返回应用在该自然日有效授权的全部车辆;传入时最多 200 个,不允许空字符串或重复值。`date` 固定为 `yyyy-MM-dd`
查询全部授权车辆:
```json
{
"date": "2026-07-01"
}
```
```json
{
"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`
## 单日里程
```http
POST /api/v1/vehicles/mileage/query
```
`date` 和可选 `plateNumbers` 外,里程接口支持可选的 `protocolPriority`
```json
{
"plateNumbers": ["粤A12345"],
"date": "2026-07-01",
"protocolPriority": ["GB32960", "MQTT", "JT808"]
}
```
外部协议值只允许 `GB32960``MQTT``JT808`。传入时数组必须非空、不能重复;每辆车独立按数组顺序选择第一个有效协议,未列出的协议被完全禁用,不能兜底。不传该字段时保持现网默认选源行为。
支持的典型模式包括:`["GB32960","MQTT","JT808"]``["JT808","GB32960","MQTT"]``["GB32960","MQTT"]``["JT808"]`
```json
{
"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`
## 区间日里程
```http
POST /api/v1/vehicles/mileage/range/query
```
最长支持 366 天。`plateNumbers` 可省略,省略时查询整个区间均有效授权的全部车辆;指定时最多 5,000 个。`pageSize` 为 15,000,默认 5,000。
首次请求:
```json
{
"startDate": "2026-07-01",
"endDate": "2026-07-23",
"protocolPriority": ["JT808", "GB32960", "MQTT"],
"pageSize": 5000
}
```
响应:
```json
{
"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 小时。
接口只固化授权车辆清单,不预生成“车辆数 × 天数”的明细;每页读取带复合索引的日统计投影,因此大范围查询不会扫描原始时序明细。
## 指定时刻总里程
```http
POST /api/v1/vehicles/total-mileage/query
```
接口返回“不晚于请求时间的最近一条有效总里程记录”。`time` 使用北京时间,格式必须为 `yyyy-MM-dd HH:mm:ss`
```json
{
"vin": "LA9GG68L2PBAF4790",
"time": "2026-07-21 09:30:00",
"protocol": "GB32960"
}
```
`protocol` 可选,只接受车辆数据中台、接收、统计和开放平台共同使用的唯一规范值:`GB32960``YUTONG_MQTT``JT808`。不再接受 `32960``mqtt``808` 等别名。未指定时严格按以下优先级查找首个有数据的协议:
```text
GB32960 > YUTONG_MQTT > JT808
```
协议总里程含义:
| 协议 | 含义 |
|---|---|
| `GB32960` | 车辆仪表盘累计总里程,对应 GB/T 32960 整车数据累计里程 |
| `YUTONG_MQTT` | 车辆仪表盘或车端控制器累计总里程,由 MQTT 平台上报 |
| `JT808` | 定位终端累计里程,由 GPS/终端侧计算,不等同于车辆仪表盘里程 |
三种协议可能使用不同里程源、标定值和重置机制,不能直接拼接成同一条连续里程曲线。响应会返回实际命中的协议和记录时间,调用方应结合时间差判断记录是否足够新。
```json
{
"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` 可调用。
```http
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
```
创建应用:
```json
{
"name": "示例合作方",
"status": "enabled",
"validFrom": "2026-07-01T00:00:00+08:00",
"validTo": "2027-07-01T00:00:00+08:00"
}
```
创建和轮换响应中的 `data.appKey` 是唯一一次明文交付机会。轮换后旧 Key 立即失效。
车辆授权 `PUT` 使用完整替换语义,单次最多 5000 辆:
```json
{
"vehicles": [
{
"vin": "LTEST32960VIN0001",
"validFrom": "2026-07-01T00:00:00+08:00",
"validTo": "2027-07-01T00:00:00+08:00"
}
]
}
```
VIN 必须已存在于平台身份绑定表。
管理员可以从车辆目录按 VIN、车牌或品牌搜索并勾选,也可以批量粘贴 VIN
或车牌自动匹配,或者一次选择当前车辆主数据中的全部车辆。批量匹配会报告
未识别项,不会静默忽略。“全部车辆”是保存时点的车辆快照,不会自动包含
以后新增的车辆。
管理端可通过下列接口读取可授权车辆目录:
```http
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。
```http
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 或其他协议。统一采用压力法:
```text
剩余氢量 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 实氢气密度方程,允许 070 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:0024:00:00`,不向前后日期扩窗,也不借用次日帧;早于或等于当前事件时间水位的延迟帧直接忽略。流式结果采用与日终相同的工作段、中位数、加氢和异常跳变规则;已结束日期仍由 `open-platform-stat` 重算,作为最终对账结果。
历史重放只接收事件时间和接收时间同时落在统计日内的 GB32960 帧。每个统计日先读取有数据且配置了氢瓶容积的 VIN,再在同一进程内按单车单日并行查询;默认 4 个工作线程,全部车辆成功后才一次性事务替换当天结果。单车帧按 `event_time` 排序并单遍去重;JSON 只读取压力、温度和工作状态字段,不展开整帧,从而减少全车排序与反序列化开销。可通过 `-vin-workers``OPEN_STAT_VIN_WORKERS` 调整工作线程数。
```bash
/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 不会再次累计。正常日终任务不使用此参数。