diff --git a/docs/oneos-mileage-api-requirements.md b/docs/oneos-mileage-api-requirements.md index 6fcbcfc..1561370 100644 --- a/docs/oneos-mileage-api-requirements.md +++ b/docs/oneos-mileage-api-requirements.md @@ -1,5 +1,8 @@ # OneOS 车辆里程接口需求 +当前协议选源联调阻塞项和 OneOS 修改清单见: +[OneOS 车辆里程数据源协议改造清单](./oneos-mileage-source-protocol-gaps.md)。 + ## 开发原则 1. 新增区间查询接口;车辆里程汇总能力在现有 @@ -39,6 +42,8 @@ POST /api/v1/vehicles/mileage/range/query - GPS 数据优先:`["JT808", "GB32960", "MQTT"]`; - 仅仪表数据:`["GB32960", "MQTT"]`; - 仅 GPS 数据:`["JT808"]`; +- BI 页面默认只启用“仪表数据”,即默认发送 + `["GB32960", "MQTT"]`;用户主动启用 GPS 后才发送包含 `JT808` 的组合; - 仪表数据内部优先级固定为 `GB32960 > MQTT`,不提供反向切换; - 数据量过大时使用 `cursor` 分页,同一次查询使用相同 `snapshotId`; - 每辆车每天返回一条记录; diff --git a/docs/oneos-mileage-source-protocol-gaps.md b/docs/oneos-mileage-source-protocol-gaps.md new file mode 100644 index 0000000..0b7c919 --- /dev/null +++ b/docs/oneos-mileage-source-protocol-gaps.md @@ -0,0 +1,183 @@ +# OneOS 车辆里程数据源协议改造清单 + +## 0. 发布后复测结论 + +复测日期:2026-07-23 + +开放平台发布后,BI 已重新连接 `https://open.d.lnoneos.com` 并完成复测: + +- 单日接口和区间接口均接受 `protocolPriority`,不再返回 + `INVALID_REQUEST`; +- 成功响应已返回实际使用的 `sourceProtocol`; +- 仪表优先、GPS 优先、仅仪表、仅 GPS 四种模式均返回 HTTP 200; +- “仅仪表数据”结果未出现 `JT808`; +- “仅 GPS 数据”结果未出现 `GB32960` 或 `MQTT`; +- 同一车辆四天区间查询能够随优先级在 `GB32960` 和 `JT808` + 之间正确切换; +- BI 页面已显示真实“仪表数据”或“GPS数据”,不再显示“来源待接口”。 + +复测时的单日协议分布如下,数据量会随当天车辆上报变化: + +| BI 模式 | GB32960 | MQTT | JT808 | 无数据 | 禁用协议混入 | +| --- | ---: | ---: | ---: | ---: | --- | +| 仪表数据优先 | 387 | 49 | 134 | 454 | 无 | +| GPS 数据优先 | 69 | 30 | 471 | 454 | 无 | +| 仅仪表数据 | 387 | 49 | 0 | 588 | 无 | +| 仅 GPS 数据 | 0 | 0 | 471 | 553 | 无 | + +结论:本清单中的协议选源阻塞项已关闭,当前接口满足 BI 对接要求。 +以下内容保留为接口契约和后续回归验收依据。 + +## 1. 历史联调结论(发布前) + +联调地址:`https://open.d.lnoneos.com` + +涉及接口: + +- `POST /api/v1/vehicles/mileage/query` +- `POST /api/v1/vehicles/mileage/range/query` + +发布前接口不满足 BI 对车辆里程数据源切换和来源标注的要求,阻塞项如下: + +| 序号 | 当前表现 | 期望表现 | 影响 | +| --- | --- | --- | --- | +| 1 | 请求增加 `protocolPriority` 后返回 HTTP 400,业务码为 `INVALID_REQUEST` | 两个里程接口均接受可选字段 `protocolPriority` | BI 无法切换仪表数据和 GPS 数据的优先级,也无法单独禁用某类数据 | +| 2 | 成功响应中没有 `sourceProtocol` | 每辆车、每天的结果返回实际采用的协议 | BI 无法准确标注“仪表数据”或“GPS数据” | +| 3 | 无法验证接口是否排除了已禁用协议 | 未列入 `protocolPriority` 的协议不得参与选源 | “仅仪表数据”和“仅 GPS 数据”无法生效 | +| 4 | 无法验证仪表数据内部的降级顺序 | 仪表数据固定按 `GB32960 > MQTT` 选源 | 32960 缺失时无法确认是否正确降级到 MQTT | + +BI 保留旧请求格式的兼容回退,用于开放平台异常或版本回滚时保证现有里程查询可用。回退结果不会伪造数据来源,页面会显示“来源待接口”。正常情况下新接口已不触发该回退。 + +## 2. OneOS 必须修改的请求字段 + +两个接口都需要增加以下可选字段: + +```json +{ + "protocolPriority": ["GB32960", "MQTT", "JT808"] +} +``` + +字段规则: + +1. 类型为非空字符串数组。 +2. 仅允许 `GB32960`、`MQTT`、`JT808`,协议名区分以此处定义为准。 +3. 数组顺序表示逐车、逐自然日的数据源优先级。 +4. 未出现在数组中的协议视为禁用,不得参与选源或作为兜底数据返回。 +5. `protocolPriority` 省略时保持现有接口的默认行为,确保旧调用方兼容。 +6. 非法枚举、空数组或重复协议应返回 HTTP 400,并在响应中给出明确的错误信息和 `traceId`。 + +BI 只会发送以下四种组合: + +| BI 模式 | `protocolPriority` | 选源规则 | +| --- | --- | --- | +| 仪表数据优先 | `["GB32960", "MQTT", "JT808"]` | 先查 32960,再查 MQTT,最后查 JT808 | +| GPS 数据优先 | `["JT808", "GB32960", "MQTT"]` | 先查 JT808,再查 32960,最后查 MQTT | +| 仅仪表数据 | `["GB32960", "MQTT"]` | 只允许 32960 和 MQTT,禁止使用 JT808 | +| 仅 GPS 数据 | `["JT808"]` | 只允许 JT808,禁止使用 32960 和 MQTT | + +BI 页面默认只启用“仪表数据”,即默认使用 +`["GB32960", "MQTT"]`;GPS 数据需要用户主动启用。 + +仪表数据内部优先级固定为 `GB32960 > MQTT`,BI 不会发送 `MQTT > GB32960`。 + +## 3. OneOS 必须增加的响应字段 + +两个接口的每条车辆日里程结果都需要增加: + +```json +{ + "sourceProtocol": "GB32960" +} +``` + +字段规则: + +1. 有有效里程数据时,只能返回 `GB32960`、`MQTT` 或 `JT808`。 +2. 返回值必须是本条结果实际采用的协议,不能返回车辆支持的协议列表,也不能返回请求中的第一项作为固定值。 +3. 无有效数据、`status="NO_DATA"` 时返回 `null`。 +4. `sourceProtocol` 对应关系: + - `GB32960`、`MQTT`:BI 显示为“仪表数据”; + - `JT808`:BI 显示为“GPS数据”。 +5. 如果 `sourceProtocol` 不在本次请求的 `protocolPriority` 中,应视为接口错误。 + +响应示例: + +```json +{ + "code": "SUCCESS", + "message": "success", + "data": [ + { + "vin": "LMRK...", + "plateNumber": "粤A00001F", + "date": "2026-07-23", + "dailyMileageKm": 182.437, + "totalMileageKm": 12345.678, + "sourceProtocol": "GB32960", + "dataTime": "2026-07-23T10:35:42+08:00", + "updatedAt": "2026-07-23T10:35:46+08:00", + "status": "NORMAL" + } + ], + "traceId": "..." +} +``` + +## 4. 逐车逐日选源规则 + +OneOS 需要针对每辆车、每个自然日独立执行以下逻辑: + +1. 按 `protocolPriority` 从前到后检查数据源。 +2. 找到第一份有效数据后立即使用,不再混用低优先级协议的数据。 +3. 有效数据必须满足现有里程质量规则,包括里程非空、非负且数据状态有效。 +4. 高优先级协议无有效数据时,才能降级到下一个已启用协议。 +5. 所有已启用协议均无有效数据时,返回: + +```json +{ + "dailyMileageKm": null, + "sourceProtocol": null, + "status": "NO_DATA" +} +``` + +6. 真实零里程必须返回: + +```json +{ + "dailyMileageKm": 0, + "sourceProtocol": "实际采用的协议", + "status": "NORMAL" +} +``` + +区间查询必须逐日选源,不能先按一个协议汇总整个区间后再切换协议。 + +## 5. 验收用例 + +OneOS 修改完成后,至少提供一辆在同一天同时存在 32960、MQTT、JT808 数据的测试车辆,以及一辆只有 MQTT 数据的测试车辆。 + +| 用例 | 请求优先级 | 必须满足的结果 | +| --- | --- | --- | +| 仪表优先 | `GB32960, MQTT, JT808` | 同时有三种数据时采用 GB32960,`sourceProtocol="GB32960"` | +| GPS 优先 | `JT808, GB32960, MQTT` | 同时有三种数据时采用 JT808,`sourceProtocol="JT808"` | +| 仅仪表 | `GB32960, MQTT` | 不得返回 JT808;32960 缺失时允许降级到 MQTT | +| 仅 GPS | `JT808` | 不得返回 GB32960 或 MQTT | +| 32960 降级 | `GB32960, MQTT` | 只有 MQTT 有效时返回 MQTT,`sourceProtocol="MQTT"` | +| 禁用验证 | `JT808` | 即使仪表数据存在,也不能回退到仪表数据 | +| 无数据 | 任一合法组合 | 返回 `dailyMileageKm=null`、`sourceProtocol=null`、`status="NO_DATA"` | +| 真实零里程 | 任一合法组合 | 返回 `dailyMileageKm=0`、实际 `sourceProtocol`、`status="NORMAL"` | +| 单日接口 | 四种组合分别测试 | 请求成功且选源、来源字段符合规则 | +| 区间接口 | 四种组合分别测试 | 每辆车每天独立选源,分页无重复、无漏行 | +| 兼容性 | 不传 `protocolPriority` | 原有调用方式和响应语义保持不变 | + +## 6. 交付和联调要求 + +1. 公网 `https://open.d.lnoneos.com` 和 ECS 内网地址同时发布相同能力。 +2. 发布后提供接口版本或发布日期、测试车辆、测试日期及对应协议数据情况。 +3. 提供四种合法组合的请求和响应样例。 +4. 提供一组禁用协议的验证结果,证明未启用协议不会被回退使用。 +5. 每个成功和失败响应均返回 `traceId`,便于双方定位问题。 + +完成标准:两个接口均支持 `protocolPriority`,每条结果准确返回 `sourceProtocol`,四种 BI 模式全部通过上述验收用例。 diff --git a/src/modules/mileage/MonitoringView.tsx b/src/modules/mileage/MonitoringView.tsx index b034ec2..7827f93 100644 --- a/src/modules/mileage/MonitoringView.tsx +++ b/src/modules/mileage/MonitoringView.tsx @@ -27,7 +27,7 @@ const HIGH_MILEAGE_ALERT_KM = 800; const ALL_MILEAGE_SOURCE_GROUPS: MileageSourceGroup[] = ['instrument', 'gps']; const MILEAGE_SOURCE_META = { - instrument: { label: '仪表数据', protocol: '32960 > MQTT' }, + instrument: { label: '仪表数据', protocol: null }, gps: { label: 'GPS数据', protocol: 'JT808' }, } as const; @@ -117,7 +117,9 @@ function SourcePriorityControl({ {meta.label} - {enabled ? meta.protocol : '已停用'} + + {enabled ? meta.protocol : '已停用'} + ); @@ -469,7 +471,7 @@ export default function MonitoringView() { const [detailVehicle, setDetailVehicle] = useState(null); const [rangeStart, setRangeStart] = useState(defaultMileageDate); const [rangeEnd, setRangeEnd] = useState(defaultMileageDate); - const [sourcePriority, setSourcePriority] = useState(['instrument', 'gps']); + const [sourcePriority, setSourcePriority] = useState(['instrument']); const [vehicles, setVehicles] = useState([]); const [stats, setStats] = useState({ totalToday: 0, totalAll: 0, vehicleCount: 0, yesterdayTotal: 0 }); @@ -978,16 +980,18 @@ export default function MonitoringView() {
-
{v.plate}
+
+
{v.plate}
+ + {sourceDisplay.label} + +
{statisticTime.label}
- - {sourceDisplay.label} - {v.customer || '-'} {v.brand || '-'} @@ -1495,21 +1499,21 @@ export default function MonitoringView() {
-
+
{v.plate} {v.isOnline ? '在线' : '离线'} + + {sourceDisplay.label} +
{statisticTime.label}
- - {sourceDisplay.label} -
{v.rentStatus || ''}{v.department ? ` · ${v.department.replace('业务', '')}` : ''} {v.customer || '-'} diff --git a/src/server/routes/mileage/source-policy.ts b/src/server/routes/mileage/source-policy.ts index 59318cb..8327740 100644 --- a/src/server/routes/mileage/source-policy.ts +++ b/src/server/routes/mileage/source-policy.ts @@ -2,8 +2,8 @@ export type MileageSourceGroup = 'instrument' | 'gps'; export type OneOsProtocol = 'GB32960' | 'MQTT' | 'JT808'; export type MileageSourceCategory = 'INSTRUMENT' | 'GPS'; -export const DEFAULT_MILEAGE_SOURCE_PRIORITY: MileageSourceGroup[] = ['instrument', 'gps']; -export const DEFAULT_ONEOS_PROTOCOL_PRIORITY: OneOsProtocol[] = ['GB32960', 'MQTT', 'JT808']; +export const DEFAULT_MILEAGE_SOURCE_PRIORITY: MileageSourceGroup[] = ['instrument']; +export const DEFAULT_ONEOS_PROTOCOL_PRIORITY: OneOsProtocol[] = ['GB32960', 'MQTT']; export function parseMileageSourcePriority(value?: string): MileageSourceGroup[] { if (!value) return [...DEFAULT_MILEAGE_SOURCE_PRIORITY];