# OneOS 客户车辆门户变更审批提案

更新时间：2026-07-14

状态：已停止。2026-07-14 用户明确要求不修改 `ln-asset-management` 任何逻辑；此前本地试验改动已全部撤销，OneOS 仓库保持干净。本文件仅保留为风险证据，不构成待执行审批项。

## 1. 审批目标

本提案只解决客户只读访问车辆数据所必需的三件事：

1. OneOS 能稳定识别客户账号并输出不可伪造的客户身份；
2. OneOS 的“当前客户车辆”事实不会因合同保存被破坏；
3. 车辆数据中台收到客户身份后，只能访问该客户的车辆 Scope。

车辆锁车、控车以及保险、结算、事故、违章等客户功能不在本批次范围内。

## 2. 当前源码证据

### 2.1 客户车辆 RPC 当前会泄露全量车辆

`ln-asset-management/src/main/java/com/ln/asset/api/impl/RemoteVehicleServiceImpl.java:83`
的 `listByCustomerId` 忽略 `customerId`，实际只按 `del_flag=0` 查询。其 VO 转换也没有回填
`customerId` 和 `contractCode`。

该接口在修复前必须保持内部禁用，不能作为车辆数据中台的授权来源。

### 2.2 当前租赁 Mapper 会丢字段

`ln-asset-management/src/main/resources/mapper/VehicleLeaseOrderRecordMapper.xml:6`
的 `BaseResultMap` 与 `Base_Column_List` 缺少以下实体字段：

- `customer_id`、`project_name`；
- `delivery_vehicle_id`、`last_delivery_name`；
- `return_vehicle_task_id`、`last_return_name`；
- `update_by`。

使用这些自定义查询取得记录后，上述字段会变成空值。生产审计同时发现聚合记录与最新历史记录有
325 条关键字段差异，不能把空值当成“无业务关系”。

### 2.3 合同保存会清空交还车事实

`VehicleLeaseOrderRecordServiceImpl.java:262` 在 `deliveryVehicleId == null` 时，同时清空交车和还车的
单据 ID、时间、里程与操作人。合同提交/更新并不代表交车事实撤销，因此这里违反业务事实的单一写入原则。

交车字段只能由交车完成/撤销流程修改；还车字段只能由还车完成/撤销流程修改。合同更新只能更新客户、合同、
项目和业务组织字段。

### 2.4 客户账号当前不能安全登录

- `CustomerInfoServiceImpl.java:188` 先尝试创建 `sys_user`，之后才保存客户档案；远程调用失败被吞掉，两个库之间不存在事务一致性。
- `SysUserRegistrar.java:27` 给所有业务账号使用共享初始密码 `ln123456`。
- `RemoteUserServiceImpl.java:222` 把业务账号开通绑定到公开注册开关 `sys.account.registerUser`。
- `UserType.java:14` 只支持 `sys_user` 和 `app_user`；`customer` 会在 Sa-Token 权限解析时抛异常。
- 密码登录按 `tenantId + username` 查询，短信登录默认取同手机号的第一条记录，均不能可靠区分客户账号。
- 更新客户档案不会同步账号；删除客户时账号删除异常同样只记日志。

生产审计结果是 335 个有效客户中，当前客户账号数量为 0，因此不能直接批量启用现有逻辑。

## 3. 推荐变更批次

### B1：修复客户车辆业务事实（`ln-asset-management`）

预计修改文件：

- `src/main/resources/mapper/VehicleLeaseOrderRecordMapper.xml`
- `src/main/java/com/ln/asset/modules/contract/mapper/VehicleLeaseOrderRecordMapper.java`
- `src/main/java/com/ln/asset/modules/contract/service/impl/VehicleLeaseOrderRecordServiceImpl.java`
- `src/main/java/com/ln/asset/api/impl/RemoteVehicleServiceImpl.java`
- `ln-asset-api/src/main/java/com/ln/asset/api/RemoteVehicleService.java`
- `ln-asset-api/src/main/java/com/ln/asset/api/domain/vo/RemoteVehicleVo.java`
- 对应单元/集成测试文件

变更内容：

1. 补齐 Mapper 的所有实体字段映射与列清单；禁止“读出空值再全实体更新”覆盖已有事实。
2. 把合同字段更新与交车/还车生命周期更新拆开：合同保存不再修改任何交还车字段。
3. 增加显式的当前 Scope 查询，条件必须同时包含有效车辆、有效客户、有效合同、已交车未还车、
   `record.customer_id = COALESCE(contract.other_customer_id, contract.customer_id)`。
4. 异常关系返回隔离原因，不为其生成授权记录。
5. 修复 `listByCustomerId`，但将其标注为兼容接口；车辆中台继续使用已实现的最小权限 RDS 快照同步，
   不依赖一次 RPC 返回无限列表。
6. 交车、还车聚合更新失败必须让业务事务失败或写入可重试 Outbox，不能仅记录日志后继续成功。

明确不做：

- 不把遥测位置、在线状态或总里程回写为交还车业务事实；
- 不恢复 `vehicle_info.customer_id` 作为客户归属；
- 不修改锁车、控车业务。

验收测试：

- 客户 A 查询结果不含客户 B 车辆；空客户 ID 返回空集合；
- 丙方客户存在时只归属 `other_customer_id`；
- 未交车、已还车、客户/合同不一致、缺 VIN 的车辆均不授权；
- 合同重复保存前后，交还车单 ID、时间、里程完全不变；
- 同一车辆存在多个当前关系时整体隔离，而不是任选一条；
- 本段验收基线已被事务事实影子查询取代；当前只读中台口径为 639 个候选、613 个可发布、26 个隔离。

回滚：

- B1 不删除字段、不改变 Dubbo 方法签名；可按服务版本回滚；
- 新查询与旧接口并存一个版本，车辆中台只读同步可独立停用；
- 数据修复 SQL 与代码发布分开审批，禁止发布脚本自动改写异常关系。

### B2：建立客户账号一致性（`ln-cloud` + `ln-asset-management`）

预计修改文件/模块：

- `ln-cloud/ruoyi-common/ruoyi-common-core/.../UserType.java`
- `ln-cloud/ruoyi-common/ruoyi-common-satoken/.../SaPermissionImpl.java`
- `ln-cloud/ruoyi-api/ruoyi-api-system/.../RemoteUserService.java`
- `ln-cloud/ruoyi-modules/ruoyi-system/.../RemoteUserServiceImpl.java`
- `ln-cloud/ruoyi-modules/ruoyi-system/.../ISysUserService.java`
- `ln-cloud/ruoyi-modules/ruoyi-system/.../SysUserServiceImpl.java`
- `ln-cloud/ruoyi-auth` 新增独立客户认证策略
- `ln-asset-management` 客户账号开通、更新、停用 Outbox/补偿任务
- 新增账号绑定/激活所需数据库迁移与测试

变更内容：

1. 增加明确的 `CUSTOMER("customer")` 类型，并为客户主体返回固定、最小的门户权限，不继承后台菜单权限。
2. 新增内部“业务账号开通”接口，不再复用公开自助注册开关；接口以
   `(tenantId, userType, businessId)` 幂等，并校验用户名、手机号冲突。
3. 增加显式业务绑定，不能仅依赖 `sys_user.user_id == customer_info.id` 的偶然约定。
4. 客户档案事务写入 Outbox；账号、角色、绑定创建失败可重试、可告警、可人工补偿。前端明确展示“账号待开通”，
   不再静默成功。
5. 删除共享默认密码。采用一次性随机激活凭证，短时有效、单次使用；激活后立即失效。
6. 新增独立 `customer_password` / `customer_sms` 登录策略。客户类型由受信客户端配置或策略固定，不能由浏览器
   任意传入；后台密码登录仍只允许后台账号。
7. 客户停用、删除或合作终止时撤销其会话，并产生身份版本变更事件。
8. 存量补偿先输出冲突清单；335 个客户不得按手机号自动合并，审计发现的 4 个手机号匹配必须人工确认。

验收测试：

- 同用户名/手机号存在不同主体类型时，客户登录只能命中 customer；
- 客户账号不能使用后台登录策略，后台账号不能使用客户策略；
- 公开注册关闭时，内部业务账号开通仍可按授权调用；
- 重复 Outbox 消息不会创建重复账号、绑定或角色；
- 激活凭证不能复用，日志中不出现明文凭证或密码；
- 删除/停用客户后现有 token 立即失效；
- 客户权限解析不抛 `UserType not found`，且没有后台菜单权限。

回滚：

- 新客户认证使用独立 grant type 和独立客户端，关闭该客户端即可停止入口；
- 绑定表和 Outbox 只新增，不破坏现有后台账号；
- 回滚应用版本前先停止补偿消费者，不删除已经创建的客户账号，只统一停用，避免 ID 被复用。

### B3：向车辆中台签发短时身份（`ln-cloud/ruoyi-auth`）

推荐新增内部/登录后交换接口：

```http
POST /inner/v1/vehicle-platform/token
Authorization: Bearer <oneos-customer-token>
```

服务端从 Sa-Token 会话和业务绑定取得 `userId/customerId/tenantId/userType`，签发最长 5 分钟、
`aud=vehicle-data-platform` 的短时 JWT。浏览器不能提交或覆盖 `customerId`。

最低声明：

```json
{
  "iss": "oneos-auth",
  "aud": "vehicle-data-platform",
  "sub": "<sys_user.user_id>",
  "subjectType": "customer",
  "customerId": "<customer_info.id>",
  "tenantId": "<tenant_id>",
  "scope": ["vehicle:read"],
  "identityVersion": 1,
  "iat": 0,
  "exp": 0,
  "jti": "<random>"
}
```

安全要求：

- 使用非对称签名，车辆中台只持有公钥；密钥 ID 支持轮换；
- 严格校验发行方、受众、算法、`exp/nbf/iat`、主体类型和客户 ID；
- 网关删除外部传入的同名身份头；不信任 `X-Customer-Id`；
- Scope 投影过期、身份绑定缺失或账号停用时，车辆中台失败关闭；
- 任何越权详情统一返回 404，避免泄露 VIN 或告警 ID 是否存在。

回滚：停止 token 交换接口或撤销对应客户端；车辆中台客户入口关闭，内部管理员入口不受影响。

## 4. 数据库变更必须单独审批

在修复 1 组重复聚合记录并确认软删除语义前，不直接添加唯一约束。建议顺序：

1. 输出重复记录和 26 条隔离关系的业务核对清单；
2. 由合同/交还车业务负责人确认每条修复方式；
3. 在预发布库回放合同保存、交车、还车、再次交车和丙方客户用例；
4. 再评估 `vehicle_lease_order_record(vehicle_id)` 唯一约束及 Scope 查询索引；
5. DDL 使用在线方式并准备反向 DDL，不与应用代码在同一步骤自动执行。

## 5. 推荐审批顺序

1. B1/B2 已停止，不修改 OneOS 业务逻辑；
2. 先完成车辆中台只读 Scope 投影与异常隔离；
3. 客户身份入口需另行确定可信身份来源并通过双客户越权测试；
4. 最后才进行外部客户灰度。

每个批次独立提交、独立发布、独立回滚。未经用户明确同意，不修改上述 OneOS 文件。
