# OneOS 车辆业务与车辆数据中台集成分析

完整系统、模块、接口、安全、数据库和运维分析见 `docs/oneos-ln-asset-management-system-analysis.md`。本文件聚焦车辆数据中台集成边界。

更新时间：2026-07-14

生产数据库聚合审计结果见 `oneos-production-audit-report.md`。
涉及 OneOS 的精确修改文件、批次、测试和回滚边界见 `oneos-change-approval-proposal.md`；该提案尚未执行。
车辆数据中台全部 API 的客户 Scope、IDOR、导出和地图服务覆盖矩阵见 `customer-scope-api-enforcement-matrix.md`。

## 1. 分析范围与约束

- OneOS 根目录：`/Users/lingniu/project/ai-coding/oneos`
- 核心业务服务：`ln-asset-management`，分支 `dev`
- 相关身份服务：`ln-cloud/ruoyi-auth`、`ln-cloud/ruoyi-system`
- 本轮仅进行只读分析，没有修改 OneOS 文件、数据库或运行配置。
- 车辆锁车、控车不在本方案范围内。

## 2. 系统职责边界

详细业务影响与遥测迁移边界见 `oneos-business-impact-matrix.md`。

### OneOS 应作为权威来源

- 客户档案：`customer_info`
- 客户账号及启停：`ry-cloud.sys_user`
- 车辆基础信息：`vehicle_info`、`vehicle_model`、`vehicle_status`
- 合同客户：`vehicle_lease_contract_info`
- 当前车辆交付/归还状态：`vehicle_lease_order_record`，必要时结合交还车任务表复核
- 业务筛选维度：运营状态、车型、品牌、运营公司、业务部门、合同和项目

### 车辆数据中台应作为权威来源

- 多协议接入状态及最后上报时间
- 实时位置、车速、SOC 和车辆告警
- 历史轨迹和原始数据证据
- 按 VIN 归并后的每日里程、统计期里程和最新总里程
- 协议数据质量与异常诊断

OneOS 当前仍通过 HASP/旧车联网接口拉取位置和总里程，其中 `/vehicle-info/page?withTotalMileage=true` 会按 VIN 逐车调用外部 HTTP 接口，存在 N+1 请求。新集成不应让车辆中台反向依赖这条旧链路。

## 3. 已确认的数据关系

### 权威表与用途

| 数据域 | 表 | 主键/关联 | 集成用途 | 注意事项 |
| --- | --- | --- | --- | --- |
| 客户档案 | `customer_info` | `id` | 客户名称、编码、合作状态 | 含联系人、银行等敏感字段，范围接口只取最小必要字段 |
| 客户账号 | `ry-cloud.sys_user` | `user_id = customer_info.id` | 登录、启停、用户类型 | 账号可能缺失；当前 `user_type=customer` 未被权限枚举支持 |
| 账号角色 | `ry-cloud.sys_user_role`、`sys_role` | `user_id`、`role_id` | 客户门户功能权限 | 新客户当前不自动分配角色 |
| 车辆档案 | `vehicle_info` | `id`、`vin` | VIN、车牌、车型、运营组织 | 实体已不再保存客户归属；VIN 必须核验空值和重复值 |
| 车辆状态 | `vehicle_status` | `vehicle_id` | 运营、车辆、证照、保险状态 | `online_status`、里程仍混有旧链路数据，不作为中台遥测权威值 |
| 合同 | `vehicle_lease_contract_info` | `id`、`order_id` | 主客户、丙方客户、合同和项目 | 有效客户为 `COALESCE(other_customer_id, customer_id)` |
| 当前租赁聚合 | `vehicle_lease_order_record` | 设计上一车一行 | 当前客户、合同、最近交还车事实 | Mapper 不完整、写入有清空风险，必须审计后使用 |
| 租赁历史 | `vehicle_lease_order_record_log` | `contract_id + vehicle_id` | 归属和交还车复核 | 是可追溯证据之一，但当前按组合更新，并非不可变事件日志 |
| 交车事实 | `delivery_vehicle` | `vehicle_id`、`order_detail_id` | 核验交车时间、里程和单据 | 完成状态枚举需与生产字典确认 |
| 还车事实 | `return_vehicle_task` | `vehicle_id`、`contract_id` | 核验还车时间、里程和完成状态 | 多条完成路径会更新聚合表，部分异常不中断主流程 |

车辆数据中台不应复制 `contact_mobile`、身份证/信用代码、银行账号、密码等字段。客户投影只需要客户 ID、展示名称、账号状态、业务版本和同步时间。

### 客户与账号

新增客户时，`customer_info.id` 使用雪花 ID，并尝试以同一个 ID 创建 `sys_user.user_id`：

```text
customer_info.id = sys_user.user_id
sys_user.user_type = customer
```

但账号创建失败只记录日志，不阻断客户保存，因此该关系不是天然完整的，必须进行覆盖率核验和补偿。

### 车辆与客户

2026-03-27 的 `9a8e2a5f` 提交将以下业务归属字段从 `VehicleInfo` 实体中移除：

- `customerId`
- `contractCode`
- `businessId`
- `businessDeptId`
- `contractType`

代码注释明确这些字段改由 `vehicle_lease_order_record` 提供。因此不能再把 `vehicle_info.customer_id` 当作唯一权威口径，即使旧数据库或旧文档仍保留该列。

合同存在甲方客户和丙方客户，现有业务代码的有效客户规则为：

```text
effectiveCustomerId = other_customer_id != null ? other_customer_id : customer_id
```

客户车辆范围还必须考虑交还车边界：只有已经交车且尚未还车的车辆，才能视为客户当前可见车辆。仅按 `vehicle_lease_order_record.customer_id` 查询会把已经归还的车辆继续授权给客户。

建议的当前客户车辆口径已调整为事务事实优先：

```text
vehicle_info.del_flag = 0
AND vehicle_lease_order_record.del_flag = 0
AND effective_customer_id = 当前客户
AND 最新有效 delivery_vehicle.delivery_status IN (2,3)
AND 不存在该交车单对应的 return_vehicle_task.status IN (2,3,5)
```

`vehicle_lease_order_record` 只用于客户归属交叉校验，不使用 `last_return_time` 判断生命周期，因为还车草稿会提前写入该字段。生产影子查询已验证该条件，仍需由业务抽样确认换车、合同变更、丙方客户、重复交还车和历史补录场景。

### 订单记录的可信度限制

`vehicle_lease_order_record` 的设计目标是“每辆车一条当前记录”，但当前实现还不能未经校验就作为唯一授权依据：

- `VehicleLeaseOrderRecordMapper.xml` 的 `BaseResultMap` 和 `Base_Column_List` 没有 `customer_id`、`project_name`、交还车单 ID、交还车人等实体字段。调用 `selectByVehicleId`、`selectByVehicleIds` 等自定义查询时，这些字段会丢失。
- `saveOrUpdateForContract()` 在 `deliveryVehicleId == null` 时会清空已有交车和还车字段。合同提交、重新提交、审批通过等多条路径都会调用该方法，若车辆此前已经交付，可能暂时或永久破坏当前租赁状态。
- 还车提交路径先写入还车时间，完成路径又可能清空当前合同和客户字段；不同查询不能仅以 `customer_id IS NOT NULL` 或仅以时间字段判断。
- 交还车更新失败只记录日志、不中断主流程，业务任务成功不代表聚合记录一定成功同步。

因此阶段 A 需要同时以当前聚合表、历史表、交车单、还车任务和合同表交叉核对。正式的范围快照接口应在一个受测试的查询服务中封装口径，并对异常记录失败关闭，不能让各调用方直接读取聚合表自行解释。

## 4. 已发现的高风险缺口

### P0：现有客户车辆远程接口会返回全量车辆

`RemoteVehicleService.listByCustomerId(customerId)` 的客户过滤已被注释，当前实现实际查询全部未删除车辆；`RemoteVehicleVo.customerId` 和 `contractCode` 的回填也被注释。

该接口在修复前不得用于任何外部客户权限，否则存在严重跨客户数据泄露风险。

### P0：客户账号体系尚不能直接用于车辆数据中台

- `customer_info` 创建成功不代表 `sys_user` 一定创建成功。
- 新客户账号没有分配角色或菜单权限。
- `sys_user.user_type=customer`，但 `UserType` 枚举只识别 `sys_user` 和 `app_user`。
- Sa-Token 权限解析会调用 `UserType.getUserType()`，客户账号触发权限判断时可能抛出类型不存在异常。
- 车辆数据中台当前只支持静态 Bearer Token 和 `viewer/operator/admin`，Principal 中没有 `customerId`、`tenantId` 或 VIN 范围。
- 客户账号创建复用了公开注册 RPC；该 RPC受 `sys.account.registerUser` 开关控制。生产若关闭自助注册，客户档案仍会成功但账号创建必然失败。
- 所有业务账号使用相同初始密码 `ln123456`，没有发现首次登录强制改密或一次性激活机制。外部客户上线前必须废弃该做法。
- 通用密码登录按 `tenantId + username` 查询，不按 `user_type` 区分；客户编码必须在租户内与其他类型账号全局唯一，并需要防止账号类型混淆。

推荐新增“业务账号开通”内部能力，不再复用公开注册开关：

1. 以 `(tenant_id, user_type, business_id)` 幂等开通客户账号；`business_id` 对客户即 `customer_info.id`。
2. 同一事务或 Outbox 中完成账号、客户角色和业务绑定；失败可重试并可见，不再吞异常。
3. 使用一次性激活链接或随机临时口令，首次登录强制设置密码；禁止共享默认密码。
4. 登录成功后校验账号主体类型为 `customer`，并从服务端绑定关系取得 `customerId`。
5. 客户停用、合作终止、账号删除时撤销会话并让车辆中台短时声明快速失效。
6. 存量补偿任务只处理缺失/异常账号，输出成功、冲突、跳过清单，不覆盖现有密码。

### P0：OneOS 地图和车辆查询没有客户级数据隔离

- `MapTruckController` 的列表、搜索、详情和信息树均未按客户过滤。
- 地图 SQL 默认返回所有符合运营状态的车辆。
- `VehicleInfoMapper` 仅声明业务部门数据权限；业务专员/主管的默认过滤逻辑当前被整体注释。
- 客户账号既没有部门数据范围，也没有独立客户数据范围。

### P1：代码、文档和映射存在漂移

- `VehicleInfo` 实体已移除客户和合同字段。
- `VehicleInfoMapper.xml` 的基础 ResultMap 和字段清单仍包含旧字段。
- `docs/VehicleInfo.md` 仍描述 `vehicle_info.customer_id` 并给出过时接口参数。
- 部分详情服务明确把客户和合同字段设置为 `null`。
- `RemoteContractMatchService.matchByVehicleAtTime` 已处理交还车时间边界，但合同命中时直接返回 `contract.customerId`，没有采用丙方优先的有效客户规则。
- `VehicleLeaseOrderRecordMapper.xml` 未完整映射实体字段，自定义查询取得的 `customerId` 等值可能为 `null`。
- 合同保存会重置交还车字段，而交还车聚合更新异常又不会阻断主流程，存在聚合状态与真实业务任务不一致的窗口。

### P0：车辆中台现有授权是“功能角色”，不是“数据范围”

当前 `Principal` 只有 `Name` 和 `Role`，鉴权中间件仅按 `viewer/operator/admin` 判断 HTTP 方法与功能权限。Store 接口和 SQL 构造器没有客户或 VIN Scope，`PrincipalFromContext` 也只被用于写操作的审计 Actor。

这会产生以下具体越权面：

| API 组 | 当前行为 | 客户开放前要求 |
| --- | --- | --- |
| 监控、车辆、实时位置 | 全量查询，可按 VIN/车牌筛选 | 所有列表、汇总、点位、详情统一限定授权 VIN |
| 轨迹、历史、原始帧、里程 | 接受调用方传入 VIN/关键字 | 先校验 VIN Scope，再访问 MySQL/TDengine；空范围返回空，不返回全量 |
| 告警 | 告警列表只按业务筛选；详情直接按事件 ID 查询 | 列表和详情都通过事件 VIN 校验，越权统一返回 404 |
| 导出 | 任务没有 owner/customerId，列表展示全局任务，知道 ID 即可下载 | 任务固化创建者、客户、VIN Scope 和快照版本；列表/下载校验所有权 |
| 运维质量、接入管理、规则配置 | 只按 viewer/operator/admin 控制 | 外部客户主体默认禁止，不能因拥有 viewer 角色而开放 |
| 车辆档案写入、同步 | 管理员可写 | 仅内部管理员或受信同步服务可用，客户主体永远禁止 |

尤其是导出任务当前保存在全局 `exports` Map 和 `jobs.json`，`ListHistoryExports()` 不区分创建者，`HistoryExportFile(id)` 也不校验主体；告警详情同样存在按 ID 直接读取的问题。这两处在客户入口上线前必须按 P0 修复。

## 5. 推荐集成架构

### 身份链路

外部客户登录仍由 OneOS 管理，OneOS 负责账号启停和密码策略。车辆数据中台不复制客户密码。

推荐两种方案，优先级如下：

1. OneOS Gateway 验证 Sa-Token 后，向车辆数据中台转发由共享密钥签名的身份声明，包含 `userId`、`customerId`、`tenantId`、`role`、过期时间和请求 ID。
2. 车辆数据中台收到 OneOS Access Token 后，调用 OneOS 内部会话校验接口获得同样的身份声明。

禁止信任浏览器直接传入的 `customerId`、客户名或 VIN 列表。

### 车辆范围同步

OneOS 对外提供内部服务接口，返回客户当前授权车辆快照及版本：

```json
{
  "customerId": 1001,
  "version": "2026-07-14T12:00:00.000Z",
  "vehicles": [
    {
      "vehicleId": 2001,
      "vin": "...",
      "plateNumber": "...",
      "operationStatus": "...",
      "modelName": "...",
      "brandName": "...",
      "contractId": 3001,
      "contractCode": "...",
      "projectName": "...",
      "scopeStartAt": "..."
    }
  ]
}
```

车辆中台保存只读投影，例如：

- `business_customer`
- `business_customer_vehicle_scope`
- `business_scope_sync_run`
- `business_scope_audit`

所有车辆 API 在 Store/SQL 层统一追加 VIN Scope，不能逐个页面自行过滤，也不能先查询全量后在前端过滤。

推荐把范围变成显式的 `QueryScope` 参数，而不是只放在 `context.Context` 中：Store 接口若没有 Scope 参数就无法编译，从机制上减少新增查询忘记加权限的概率。MySQL 查询优先 JOIN 本地范围投影表；TDengine 单车查询先做 VIN 授权检查，多车查询使用已授权 VIN 集合并设置数量上限。空 Scope 对外部客户必须恒等于“无数据”，绝不能解释为“不限制”。

### 数据请求原则

- 身份声明决定客户 ID。
- 客户 ID 决定 VIN Scope。
- VIN Scope 再约束监控、车辆、轨迹、历史、里程、告警、导出和地图聚合查询。
- 内部管理员可显式选择客户；外部客户不能覆盖身份中的客户 ID。
- 当 Scope 服务不可用或同步数据过期时，外部访问应失败关闭，不得降级为全量数据。

## 6. 分阶段实施

### 阶段 A：只读数据审计

- 执行 `oneos-readonly-audit.sql`。
- 确认生产表结构是否仍有 `vehicle_info.customer_id`。
- 统计客户账号覆盖率、启用率、角色覆盖率。
- 统计当前交付车辆的 VIN 完整率、客户完整率和客户档案匹配率。
- 抽样验证丙方客户、换车、已还车和多合同场景。

验收：形成数据质量基线，业务负责人确认“当前客户车辆”的唯一口径。

### 阶段 B：修复 OneOS 权威接口和账号能力

该阶段已停止：用户明确要求不修改 `ln-asset-management` 任何逻辑。以下内容仅保留为已知风险清单，不执行。

- 修复 `listByCustomerId`，按有效客户和交还车边界查询。
- 增加批量客户车辆快照接口，返回 VIN 和业务版本。
- 统一有效客户规则，覆盖丙方客户。
- 修复订单记录 Mapper 字段遗漏，并禁止合同更新覆盖已经发生的交还车事实。
- 对范围异常记录给出隔离清单，不将不确定归属车辆授权给任何外部客户。
- 补充客户账号类型、角色、启停和存量账号补偿。
- 新增专用业务账号开通能力，移除共享默认密码和对公开注册开关的依赖。
- 增加双客户越权测试。

验收：客户 A、B 的车辆集合互斥；已还车辆立即退出范围；接口默认失败关闭。

### 阶段 C：车辆数据中台身份和 Scope 投影

- 扩展 Principal，加入主体类型、外部用户 ID、客户 ID 和租户 ID。
- 增加 OneOS 身份校验或可信代理声明校验。
- 建立客户与车辆范围投影、版本和审计表。
- 在底层查询构建器统一注入 VIN Scope。
- 覆盖监控、车辆、轨迹、历史、统计、告警、导出、地图聚合和反向地理编码相关入口。

验收：任何 URL 参数、VIN 搜索、批量接口和导出均不能越权；Scope 过期时外部请求拒绝访问。

### 阶段 D：客户门户和入口

- 外部子域名提供独立登录页。
- 内部 OneOS 菜单以新标签页进入车辆数据中台。
- 外部页面隐藏接入配置、运维质量和管理型功能。
- 客户页默认展示授权车辆数量、实时地图、轨迹和里程统计。

验收：内部入口统一，外部入口独立；账号停用后现有会话及时失效。

### 阶段 E：历史里程补录

- 定义补录文件格式和来源平台。
- 以 VIN、自然日、来源和导入批次保证幂等。
- 导入约 20 天缺失里程并输出成功、冲突、缺 VIN 和异常跳变报告。
- 不伪造无法取得的历史轨迹，页面明确轨迹数据起始时间。

验收：补录前后总量可对账，重复导入不重复累计，每条补录记录可追溯。

## 7. 需要修改 OneOS 前必须同步的范围

预计至少涉及以下模块，尚未进行任何修改：

- `ln-asset-management/ln-asset-api`
- `ln-asset-management/src/main/java/com/ln/asset/api/impl/RemoteVehicleServiceImpl.java`
- `ln-asset-management` 的车辆/合同 Mapper 与 Service
- `ln-cloud/ruoyi-common-core` 的用户类型定义
- `ln-cloud/ruoyi-auth` 的客户登录策略
- `ln-cloud/ruoyi-system` 的客户账号角色和状态管理
- 可能新增 OneOS 内部 HTTP API、数据库索引和菜单脚本

修改前需要先同步：具体文件、接口契约、SQL 变更、兼容方案、回滚方案和测试范围。
