feat(platform): consolidate production vehicle data workflows

This commit is contained in:
lingniu
2026-07-15 23:26:29 +08:00
parent 6cddc0a43d
commit 3fabcf181a
59 changed files with 6849 additions and 3532 deletions

View File

@@ -308,6 +308,8 @@ PUT /api/v2/access/thresholds
`summary` and `vehicles` consume the same filter shape, so KPI, protocol/OEM distribution, and rows share one scope. Besides keyword, protocol, OEM and status, both accept substring filters `model` and `provider`, plus paired `firstSeenFrom`/`firstSeenTo` and `latestSeenFrom`/`latestSeenTo` bounds. A supplied pair must be ordered; malformed or reversed timestamps return `ACCESS_TIME_INVALID` or `ACCESS_TIME_RANGE_INVALID`. `onlineState` values are `online`, `offline`, `never_reported`, and `unknown`. Online state is calculated on every request from latest receive/update freshness and the current global/protocol threshold version; the stored snapshot boolean is not reused as the answer.
The authoritative access-management population is the distinct VIN set in `vehicle_identity_binding`; realtime rows without a bound VIN stay in `unresolved-identities` and never inflate the main-vehicle count. Each main vehicle is returned once with the standard expected source slots `GB32960`, `JT808`, and `YUTONG_MQTT`. `expectedProtocols`, `actualProtocols`, `missingProtocols`, and `protocolStatuses` make the expected-versus-actual difference explicit. Every protocol status independently carries provider, first-seen evidence, latest event/receive time, report interval, freshness, delay, and online state. `connectionState` is one of `healthy`, `incomplete`, `degraded`, `offline`, or `not_connected`; the additional query value `attention` returns every non-healthy vehicle for the difference-first operations list.
`unresolved-identities` returns terminals that have real access evidence but no authoritative VIN binding. The current implementation reads JT808 registration evidence and excludes terminals already present in `vehicle_identity_binding`. It exposes only a stable hashed ID and masked terminal identifier, never the raw phone number. Resolve `missing_vin_jt808` by verifying registration evidence and maintaining an authoritative `phone -> VIN` binding; do not infer or fabricate a VIN.
Access rows expose both event and receive time, `dataDelaySec`, `freshnessSec`, the applied `thresholdSec`, latest realtime message semantics, event ID, source table, model/company master data, and evidence notes. The gateway snapshot upsert maintains `access_first_seen_at`, `access_previous_received_at`, `access_latest_received_at`, `access_report_interval_ms`, `access_sample_count`, and the latest received event ID independently from device event-time ordering. A strictly newer receipt with a different event ID advances the projection atomically; duplicates, equal timestamps, replay and older receipts do not change the interval or sample count. The API rounds milliseconds to `reportIntervalSec` and returns `reportSampleCount`, `firstSeenSource`, `firstSeenEvidence`, and `reportIntervalEvidence`. `live_writer` means first observation after the writer began maintaining the row. `snapshot_backfill` is only a deployment baseline reconstructed from the previous current-state row and must not be described as the vehicle's historical first-ever access.

View File

@@ -0,0 +1,194 @@
# 车辆数据中台客户 Scope API 覆盖矩阵
更新时间2026-07-14
状态:安全审计与实施设计;尚未开放客户主体。
## 1. 当前结论
车辆中台当前的 Bearer Token 只表达 `viewer/operator/admin` 功能角色,不表达客户或车辆数据范围。
因此,即使 OneOS 已能登录客户,也不能直接把客户 token 映射成 `viewer`:这会让客户读取全量车辆、轨迹、
告警、统计及其他人的导出文件。
2026-07-14 已只读核验 ECS 当前运行状态:
- `AUTH_MODE=enforce`
- 使用 `AUTH_TOKENS_JSON`,未使用单一 `AUTH_TOKEN`
- `/opt/lingniu-vehicle-platform/env/platform.env` 权限为 `0600 root:root`
- `lingniu-vehicle-platform` 服务已启用且正在运行。
该配置能阻止匿名访问普通平台 API但不能提供客户级隔离。
## 2. 独立发现:高德服务端接口绕过平台鉴权
`app.NewServer` 当前先把平台 API 包在 `withAPIAuth` 中,之后又在外层注册
`withAMapReverseGeocodeAPI`。所以 `/api/map/reverse-geocode` 在进入鉴权中间件前就会被处理;只要配置了
`AMAP_API_KEY`,匿名请求即可消耗服务端高德额度。
整改要求:
- 将逆地理编码路由放回鉴权之后;
- 对客户和内部主体分别限流,并限制经纬度格式和并发;
- 不记录完整 access token
- `app-config.js` 可继续公开,但只能暴露高德 Web JS 公钥和代理路径,不暴露服务端 API Key
- 高德安全代理因浏览器 SDK 需要可访问,应在边缘层配置域名来源、速率和响应缓存,不能把它视为客户身份接口。
## 3. 主体与权限模型
`Principal` 至少扩展为:
```text
name
role # 仅内部功能角色
subject_type # employee | service | customer
subject_id # OneOS sys_user.user_id
customer_id # 仅 customer 必填
tenant_id
identity_version
capabilities # vehicle:read, history:read, export:create ...
```
约束:
- 内部静态 token 只能生成 `employee/service` 主体;
- 客户主体只能来自 OneOS 签名、短时且 `aud=vehicle-data-platform` 的声明;
- 浏览器 Query、Body、Cookie 或普通转发头中的 `customerId` 均无效;
- 客户权限不能通过 `roleRank(viewer)` 复用内部 viewer 权限,需要独立 capability 判断;
- 未知 `subject_type`、缺失 customer ID、签名异常或身份版本失效全部拒绝。
## 4. QueryScope 机制
所有 Store 查询显式接收 `QueryScope`,避免新增方法时忘记从 `context.Context` 读取 Scope
```text
mode: unrestricted | customer_current | customer_period
customer_id
active_version
valid_from / valid_to # 历史持有期查询
```
`unrestricted` 只能由已验证的内部 employee/service 主体创建。空客户 Scope 表示“零车辆”,绝不能解释为不限制。
MySQL 当前数据查询应 JOIN
```text
business_scope_state(id=1)
business_customer_vehicle_scope(
source_version = active_version,
customer_id = principal.customer_id,
vin = 业务表.vin
)
```
同时要求:
- `last_success_at` 距当前时间不超过 10 分钟;
- active version 存在且快照计数完整;
- 快照缺失或过期返回 `503 BUSINESS_SCOPE_UNAVAILABLE`,而不是空列表或全量数据;
- 快照新鲜但该客户确实没有车辆时返回空列表;
- 单车越权统一返回 404避免泄露 VIN 是否存在。
TDengine 无法直接 JOIN MySQL Scope单车查询先在 MySQL 做授权判断;多车查询从同一 active version 读取授权
VIN 集合,设置数量上限后再生成参数化过滤。不能先从 TDengine 取全量再由前端过滤。
## 5. API 覆盖矩阵
### 5.1 客户首期允许,但必须按当前 Scope 过滤
| API 组 | 代表路由 | 必须实施的控制 |
| --- | --- | --- |
| 全局监控 | `/api/v2/monitor/summary``/map` | 车辆、点位、聚合、告警数和所有汇总均从同一客户 Scope 计算;禁止混用全局 DashboardSummary |
| 车辆列表与解析 | `/api/vehicles``/resolve``/detail` | VIN、车牌、终端号解析必须先限定 Scope越权关键字表现为不存在 |
| 最新状态 | `/api/realtime/vehicles``/locations` | 列表、total、分页和筛选均在 SQL 层过滤 |
| 车辆只读档案 | `GET /api/v2/vehicles/{vin}/profile` | 先校验 VIN只返回门户需要的非敏感字段 |
| 最新遥测 | `/api/v2/vehicles/{vin}/telemetry/latest` | 路径 VIN 校验 Scope原始协议帧字段按白名单输出 |
| 轨迹 | `/api/v2/tracks` | 车牌/VIN/终端解析、轨迹点、停留点和统计都使用同一授权 VIN |
| 历史查询 | `/api/history/locations``/api/v2/history/query``/series` | 首期只允许当前车辆且 eventAt 不早于 `scope_start_at`;不能读取交付前数据 |
| 里程 | `/api/mileage/summary``/daily``/api/v2/statistics/mileage` | 列表与汇总按 VIN 和授权时间过滤total 不能泄露全局数量 |
| 在线统计 | `/api/statistics/online-summary``/online-vehicles` | 分母、在线数、离线数、趋势和明细均限定客户车辆 |
| 告警只读 | `/api/v2/alerts/summary``/events``/events/{id}` | 列表 JOIN event.vin Scope详情按 ID+Scope 查询,越权返回 404 |
| 指标元数据 | `/api/v2/metrics``/api/v2/history/metrics` | 可返回全局字段定义,但过滤内部专用/敏感指标 |
| 逆地理编码 | `/api/map/reverse-geocode` | 必须鉴权和限流;不依赖客户参数,不返回其他车辆信息 |
### 5.2 客户首期明确禁止
| API 组 | 路由 | 原因 |
| --- | --- | --- |
| 平台总览 | `/api/dashboard/summary` | 包含全局车辆、帧量、Kafka/服务健康信息 |
| 接入诊断 | `/api/vehicles/coverage*``/api/vehicle-service*` | 包含协议覆盖、终端、原始帧和数据质量诊断 |
| 原始帧 | `/api/history/raw-frames*` | 包含协议证据和内部字段;首期使用白名单遥测/历史 API 代替 |
| 质量中心 | `/api/quality/*`、旧 `/api/alert-events/*` 别名 | 平台级质量问题,不是客户业务告警 |
| 运维 | `/api/ops/*` | 数据源、容量、Kafka、Redis 和服务健康仅内部可见 |
| 接入管理 | `/api/v2/access/*` | 身份绑定、阈值和未解析终端均为内部运维能力 |
| 车辆档案写入 | `PUT /api/v2/vehicles/{vin}/profile``/vehicle-profiles/sync` | 只允许内部管理员/同步服务 |
| 告警规则/处置 | `/api/v2/alerts/rules*``events/{id}/actions` | 首期客户只读,不能修改平台规则或全局事件状态 |
| 平台通知 | `/api/v2/alerts/notifications*` | 当前通知没有客户 owner读取与已读操作均可能跨客户 |
禁止应在服务端 capability 层返回 403不能只隐藏前端菜单。
### 5.3 导出必须单独重构后才能开放
当前导出实现有四个 P0 问题:
1. `CreateHistoryExport` 没有 `context`,创建时无法固化主体;
2. `HistoryExportJob` 没有 owner/customer/scope version
3. `ListHistoryExports` 返回全局任务;
4. `HistoryExportFile(id)` 只校验 ID 和完成状态,知道 ID 即可下载。
整改要求:
- `Create/List/Download` 全部接收 context 和 Principal
- Job 固化 `owner_subject_id``customer_id``scope_version`、已解析 VIN、时间范围和审计 request ID
- 创建任务时校验每个 VIN异步执行前再次校验
- 列表只返回当前 owner下载同时校验 owner 和当前授权,越权返回 404
- 客户停用、车辆授权撤销或 Scope 失效时停止未完成任务并拒绝下载;
- 文件名和 CSV 元数据不得包含其他客户信息;文件按 owner 隔离目录并设置 `0640`
- 定期清理过期文件与 jobs 记录。
在上述改造完成前,客户主体不授予 `export:create``export:download`
## 6. 当前 Scope 与历史 Scope 的边界
已实现的 `business_customer_vehicle_scope` 只保存当前有效车辆。它足以保护实时监控,但不能完整实现“客户还车后
仍可查看持有期间历史”。
首期安全策略:
- 只允许查询当前仍在 Scope 中的车辆;
- 历史起点不得早于当前记录的 `scope_start_at`
- 车辆还车并从当前 Scope 移除后,历史访问也随即关闭。
若产品确认需要持有期历史,再新增版本化区间投影:
```text
customer_id, vin, contract_id, valid_from, valid_to, source_version
```
历史查询必须满足 VIN 和 `valid_from <= event_at < valid_to`。不能仅凭“客户曾经拥有过这辆车”开放该 VIN 的全部历史。
## 7. 必须覆盖的越权测试
准备客户 A、客户 B、内部 viewer、内部 operator、内部 admin 和 service 六类主体,至少覆盖:
1. A 在列表、关键字解析、路径 VIN、POST body、分页游标中请求 B 的车辆;
2. A 直接访问 B 的告警事件 ID、导出 ID 和下载 URL
3. A 使用车牌或终端号绕过 VIN 校验;
4. A 的空 Scope 不返回全量Scope 缺失/过期返回 503
5. 快照切换期间所有查询只使用一个 active version不混合新旧结果
6. total、summary、地图聚合数量和在线率不泄露全局或 B 的数量;
7. A 只能读取交付后的历史点,不能读取交付前记录;
8. 车辆从 A 还车并交付给 B 后A 的实时访问立即 404B 只能读取自身授权时间段;
9. 客户主体访问运维、接入、质量、写档案、规则和处置接口均为 403
10. 匿名逆地理编码失败;认证请求达到限额后返回 429
11. 客户 token 的 customer ID 不能被 Query/Header/Body 覆盖;
12. 服务重启后导出 owner、Scope 和下载授权仍保持,不能退化成全局任务。
## 8. 推荐实施顺序
1. 扩展 Principal 和 OneOS JWT 校验,但暂不开放客户路由;
2. 引入强类型 QueryScope先改车辆解析与单车详情
3. 改所有列表、汇总、地图、统计和 TDengine 查询;
4. 改告警详情 IDOR、逆地理编码鉴权限流
5. 重构导出 owner 与异步 Scope
6. 完成双客户自动化测试和生产影子查询;
7. 最后为测试客户启用 capabilities默认保持客户入口关闭。

View File

@@ -13,6 +13,7 @@ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/alert-evaluator ./cmd/alert-evaluator
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/alert-stream-evaluator ./cmd/alert-stream-evaluator
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/platform-migrate ./cmd/platform-migrate
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/oneos-scope-sync ./cmd/oneos-scope-sync
# Optional release/performance gate; this binary is run on demand, not installed as a service.
GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags='-s -w' -o ../../dist/alert-benchmark ./cmd/alert-benchmark
```
@@ -44,6 +45,10 @@ EXPORT_DIR=/opt/lingniu-vehicle-platform/data/exports
AUTH_MODE=enforce
# JSON array with viewer/operator/admin principals. Keep this file mode 0600.
AUTH_TOKENS_JSON=[{"token":"<at-least-16-random-characters>","name":"ecs-admin","role":"admin"}]
ONEOS_MYSQL_DSN=vehicle_scope_reader:***@tcp(rm-bp179zbv481rnw3e2.mysql.rds.aliyuncs.com:3306)/ln_asset_management?parseTime=true&loc=Asia%2FShanghai
ONEOS_SCOPE_SYNC_TIMEOUT_SEC=60
ONEOS_SCOPE_MAX_REJECTED=100
ONEOS_SCOPE_MAX_REJECT_RATIO=0.10
REQUEST_TIMEOUT_MS=5000
AMAP_WEB_JS_KEY=***
AMAP_SECURITY_JS_CODE=***
@@ -91,6 +96,14 @@ Production smoke must create an export for an active VIN, wait for `completed`,
After editing the environment file, run `chmod 600 /opt/lingniu-vehicle-platform/env/platform.env`. Never put a real token in Git, static JavaScript, shell history or deployment logs.
`ONEOS_MYSQL_DSN` must use a dedicated account with direct table-level `SELECT` grants only. The sync binary runs `SHOW GRANTS FOR CURRENT_USER` before every read and accepts only global `USAGE` plus `SELECT` on the seven tables used by its query. It refuses database/global reads, `ALL PRIVILEGES`, DML, DDL, PROCESS, replication, roles, `SHOW VIEW`, or any other privilege. It then opens a repeatable-read, read-only transaction, applies a 10-second statement timeout, classifies invalid customer/contract relationships, and atomically publishes a content-addressed local snapshot. Never point it at the existing `ln-bi` account: production audit showed that account still has broad write and replication privileges.
Have an RDS administrator review and run `docs/oneos-scope-reader-provision.sql` separately. It restricts the login source to the verified ECS private address and grants `SELECT` on only the seven source tables used by the query. Do not include that DDL in application deployment or migration automation. Store `platform.env` as root-owned mode `0600`, and verify `SHOW GRANTS` before enabling the timer.
The active lifecycle comes from OneOS delivery and return task facts: the newest active delivery must have `delivery_status IN (2,3)`, and it must not have an active return task with `status IN (2,3,5)`. The sync intentionally does not use `vehicle_lease_order_record.last_return_time`, because OneOS currently writes that aggregate field while a return form is still a draft. The aggregate record remains a separate customer-ownership cross-check; missing, duplicate, or conflicting ownership fails closed.
The default two-minute timer reads one bounded current-scope result set (639 rows at the 2026-07-14 transactional-fact baseline), so it avoids per-request access to OneOS and adds negligible RDS load. A run is rejected before publication if it has no accepted rows, more than 100 rejected rows, or a rejected ratio above 10%. Current baseline is 613 accepted and 26 quarantined. An unchanged content hash records a successful run without duplicating the snapshot.
## Forward Migration
Before switching the API symlink, apply the idempotent access-threshold migration with a MySQL account allowed to create/alter platform-owned tables:
@@ -109,7 +122,8 @@ test -n "$MYSQL_DSN"
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/008_access_projection.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/009_alert_stream_checkpoint.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/010_alert_stream_metric_mapping.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/011_alert_stream_invalid_evidence.sql
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/011_alert_stream_invalid_evidence.sql \
/opt/lingniu-vehicle-platform/releases/$PLATFORM_RELEASE/deploy/migrations/012_business_scope_projection.sql
```
The API guards the access-threshold tables for compatibility, while alert APIs deliberately require the alert migrations to exist. Run every numbered migration explicitly before switching traffic so DDL permission and index creation failures are caught early. The migration journal records filename and SHA-256 and refuses a changed file; duplicate forward `ADD COLUMN` and `CREATE INDEX` statements are tolerated only when resuming partially executed MySQL DDL. Full-line SQL comments are removed before statement splitting, so punctuation in a comment cannot become executable SQL. Migration `008` adds forward-compatible access evidence columns and an index to the gateway-owned realtime snapshot table without changing its `(protocol, vin)` primary key. Migration `009` creates the per-group/topic/partition event-time checkpoint used to make MySQL effects authoritative before Kafka offsets are committed.
@@ -119,12 +133,16 @@ Install and start the evaluator as a separate unit only after API smoke checks a
```bash
sudo cp deploy/systemd/lingniu-vehicle-alert-evaluator.service /etc/systemd/system/
sudo cp deploy/systemd/lingniu-vehicle-alert-stream-evaluator.service /etc/systemd/system/
sudo cp deploy/systemd/lingniu-vehicle-oneos-scope-sync.service /etc/systemd/system/
sudo cp deploy/systemd/lingniu-vehicle-oneos-scope-sync.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now lingniu-vehicle-platform lingniu-vehicle-alert-evaluator lingniu-vehicle-alert-stream-evaluator
sudo systemctl enable --now lingniu-vehicle-platform lingniu-vehicle-alert-evaluator lingniu-vehicle-alert-stream-evaluator lingniu-vehicle-oneos-scope-sync.timer
sudo systemctl status --no-pager lingniu-vehicle-alert-evaluator
sudo systemctl status --no-pager lingniu-vehicle-alert-stream-evaluator
sudo journalctl -u lingniu-vehicle-alert-evaluator -n 100 --no-pager
sudo journalctl -u lingniu-vehicle-alert-stream-evaluator -n 100 --no-pager
sudo systemctl status --no-pager lingniu-vehicle-oneos-scope-sync.timer
sudo journalctl -u lingniu-vehicle-oneos-scope-sync.service -n 100 --no-pager
```
## Health
@@ -162,7 +180,7 @@ curl -fsS "http://127.0.0.1:20300$MAIN_ASSET" >/dev/null
unset PLATFORM_TOKEN AUTH_HEADER
```
The release gate must include both the root document and its hashed main asset. API-only smoke checks are insufficient because an incomplete archive can leave the service healthy while the browser returns 404. Before upload, verify the archive contains `web/index.html`, `platform-api`, both evaluator binaries, `platform-migrate`, every numbered migration and all three platform systemd units.
The release gate must include both the root document and its hashed main asset. API-only smoke checks are insufficient because an incomplete archive can leave the service healthy while the browser returns 404. Before upload, verify the archive contains `web/index.html`, `platform-api`, both evaluator binaries, `platform-migrate`, `oneos-scope-sync`, every numbered migration and all platform systemd units.
When `alertStream.lastInvalidCode` reports a recent `missing_vin_jt808`, query `/api/v2/access/unresolved-identities` as a viewer and hand the masked evidence to the identity owner. The response must not contain raw `phone` or unmasked identifier fields. Confirm the evidence against the GPS provider or vehicle owner, then maintain the authoritative `vehicle_identity_binding`; never derive VIN from the terminal number. The queue excludes a terminal after a valid binding exists. Retain the invalid counter as audit evidence and verify the recent warning clears after five minutes without another invalid frame.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -0,0 +1,97 @@
# OneOS 车辆业务影响矩阵
更新时间2026-07-14
## 1. 结论
车辆数据中台接入 OneOS 不能等同于“把所有里程和位置字段同步回资产库”。OneOS 同时存在实时遥测、人工单据、合同聚合和结算证据;它们含义不同,必须按数据域迁移。
- 实时位置、在线状态、SOC、速度、当日里程、最新总里程以车辆数据中台为权威来源。
- 交车里程、还车里程、异动起止里程和结算里程:保留 OneOS 单据事实,不允许遥测自动覆盖。
- 客户、合同、项目、当前租赁归属、车型和运营状态:以 OneOS 为权威来源。
- 车辆中台只消费最小业务投影,不复制合同金额、联系人、银行账号等敏感字段。
## 2. 当前遥测链路
| 链路 | 当前用途 | 已发现问题 | 建议 |
| --- | --- | --- | --- |
| `LocationUtil``47.99.166.38:20000` | 交车、还车、迷你交车按 VIN 取坐标和总里程 | 地址硬编码;每次新建 HTTP Client失败返回固定坐标而非“无数据”会把故障伪装成真实位置 | P0 禁止固定坐标降级;后续改为调用车辆中台单车快照 API |
| `TruckSyncService` → HASP | 每 10 分钟拉位置、在线、SOC、氢耗、里程 | 全车辆串行请求;依赖车牌/VIN 双接口;状态可能滞后;同时写 `vehicle_status.online_status` | 数据中台稳定后停用拉取任务,改为受控兼容投影或 OneOS 只读调用 |
| `tab_truck_remote_sync_realtime_info` | OneOS 地图、车辆列表位置和里程 | 老表字段多为字符串;总里程单位是米、日里程按 km坐标已转 GCJ-02客户名可能来自 HASP 而非 OneOS | 迁移期只作兼容缓存,不作为客户归属或历史权威来源 |
| `vehicle_realtime_location` | CRUD 型实时位置表 | 当前更像普通业务表,没有持续同步和客户范围 | 不新增依赖;确认无调用方后归档或明确用途 |
| `vehicle_status` | 运营/车辆/证照/保险/在线/里程混合状态 | 前四项是业务状态,后两项是遥测状态,来源混杂 | 保留业务状态;在线和遥测里程逐步改为中台只读展示,不反向覆盖业务状态 |
## 3. 业务域影响
| 业务域 | 主要模块/表 | 与客户车辆范围关系 | 中台接入策略 | 风险级别 |
| --- | --- | --- | --- | --- |
| 客户与账号 | `customer_info``sys_user` | 决定客户主体和启停 | OneOS 认证,向中台传递签名客户身份 | P0 |
| 合同 | `vehicle_lease_contract_info` | 主客户/丙方客户决定有效客户 | OneOS 输出有效客户,不由中台推断合同 | P0 |
| 当前租赁 | `vehicle_lease_order_record` | 决定当前可见车辆 | 修复聚合一致性后输出版本化 Scope | P0 |
| 交车 | `delivery_vehicle`、交车任务 | 形成 Scope 开始时间;保存签署里程和地点 | Scope 只使用已确认交车事实;人工里程不覆盖 | P0 |
| 还车 | `return_vehicle_task` | 形成 Scope 结束;从客户范围移除 | 完成还车后发布新 Scope异常时失败关闭 | P0 |
| 换车/转三方 | `VehicleReplaceRecordService`、合同变更 | 同时改变旧车、新车和有效客户 | 必须做原子或同版本范围切换 | P0 |
| 地图 | `MapTruckController`、旧 realtime 表 | 当前返回全量车辆 | 客户门户使用中台地图OneOS 内部地图后续再迁移 | P0 |
| 车辆列表/详情 | `VehicleInfoMapper` | 混入客户、合同、位置、总里程 | 业务档案继续 OneOS遥测展示改为批量调用禁止逐 VIN N+1 | P1 |
| 事故、违章 | `AccidentInfo``TrafficViolation` | 通过车辆或历史租赁记录关联客户 | 若未来开放客户查看,必须按“事件发生时的客户”而非当前客户授权 | P1 |
| 保险、年检、年审、证照 | 对应 vehicle 业务模块 | 当前以 vehicleId 关联 | 默认仍为内部业务;若开放客户须额外产品授权,不自动随实时车辆 Scope 开放 | P1 |
| 加氢台账 | `HydrogenFuelLedger` | 使用客户、车辆和里程字段 | 台账里程是业务记录;中台遥测只用于对账,不自动改账 | P1 |
| 结算 | `ReturnSettlement` | 使用交还车里程计算费用 | 中台可提供差异证据,不能成为自动覆盖结算的来源 | P0 |
| 异动、调拨、故障 | 多个车辆业务模块 | 保存计划/起止/故障时里程 | 保留业务单据值;按需要显示中台采集值作为旁证 | P1 |
| 运维和接入质量 | 车辆数据中台 | 不属于外部客户默认权限 | 仅内部运维/管理员开放 | P0 |
## 4. 时间语义
必须区分两类车辆权限:
### 当前车辆权限
用于 `/monitor`、当前车辆列表、最新遥测和当前告警。依据当前有效交付关系,车辆还车后立即移除。
### 历史事件权限
轨迹、历史数据和事故/违章存在两种产品口径,不能默认选择:
1. **持有期口径(推荐)**:客户只能查看 `scopeStartAt <= eventAt < scopeEndAt` 的数据。车辆还车后仍可查看租赁期间历史,但不能查看还车后的数据。
2. **仅当前口径**:还车后所有历史立即不可见,实现简单但不利于客户对账。
建议范围投影保留历史有效期,而不是只保存当前 VIN 集合:
```text
customer_id, vin, valid_from, valid_to, contract_id, scope_version
```
任何历史查询都同时校验 VIN 和事件时间。当前快照接口用于实时查询,历史范围接口或增量事件用于回填 `valid_to`
## 5. 坐标与单位
| 字段 | OneOS 当前情况 | 数据中台约定 | 展示规则 |
| --- | --- | --- | --- |
| 原始坐标 | 上游多为 WGS-84 | 中台保存 WGS-84 原始值并标注坐标系 | 高德前端展示前统一转 GCJ-02只转换一次 |
| 旧地图坐标 | `TruckSyncService` 已转 GCJ-02 | 不回写为“原始坐标” | 迁移时避免再次转换造成漂移 |
| `total_mileage` | HASP 老表单位为 m、类型为字符串 | 中台规范为 km、数值类型 | API 字段明确命名 `totalMileageKm` |
| `day_mileage` | OneOS 按 km 使用 | 中台按自然日、时区和来源生成 | 明确 `Asia/Shanghai` 日界线和统计版本 |
| 交还车里程 | 人工/单据 km | 不覆盖 | 可增加 `telemetryMileageKm` 作对照证据 |
## 6. 推荐迁移顺序
1. 修复并审计客户账号、当前租赁聚合和 Scope 接口。
2. 车辆中台完成客户身份、当前/历史 Scope 投影和全 API 隔离。
3. 外部客户门户先只读上线,功能限定为当前监控、持有期轨迹、里程和授权告警。
4. 为 OneOS 提供批量最新遥测 API影子对比 HASP 与旧接口,不改业务单据。
5. 对比位置、在线、SOC、总里程、日里程的覆盖率与延迟达到验收标准后切换 OneOS 地图读取源。
6. 最后停用 `TruckSyncService``LocationUtil` 旧链路;保留可回滚开关,不删除历史表。
## 7. 遥测切换验收
- 连续 7 天 VIN 覆盖率不低于旧链路,关键车辆无遗漏。
- 最新位置 P95 延迟满足业务约定,离线判断与数据上报周期匹配。
- WGS-84 → GCJ-02 只执行一次,抽样点位无系统性漂移。
- 总里程单位和来源明确,无米/公里千倍错误,无异常倒退未告警。
- OneOS 地图使用批量接口,不出现逐车 N+1 调用。
- 数据中台不可用时显示“实时数据暂不可用”,不得展示固定默认坐标或过期数据为实时值。
- 交车、还车、结算、加氢台账等人工事实在切换前后完全不变。
## 8. 本轮不建议改动的业务
在客户车辆范围和遥测切换稳定前,不建议同时改造保险、年检、结算、违章、事故、加氢台账等业务流程。它们可以消费 Scope 或遥测证据,但不应与第一批权限修复绑在一次发布中。

View File

@@ -0,0 +1,206 @@
# 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 文件。

View File

@@ -0,0 +1,180 @@
# OneOS 客户车辆范围内部接口契约(草案)
更新时间2026-07-14
## 1. 目的与安全边界
该接口仅向车辆数据中台提供 OneOS 已确认的客户车辆授权范围,不提供密码,不允许浏览器直连,也不承担实时遥测查询。
- OneOS 是客户、合同、交还车事实和车辆档案的权威来源。
- 车辆数据中台只保存可重建的只读投影。
- 客户 ID 必须来自已验证的 OneOS 会话或内部同步任务,不能采用浏览器请求参数。
- 归属不确定、VIN 缺失、存在多客户冲突的车辆必须放入异常列表,不能进入授权列表。
- 内部请求需要服务身份认证、时间戳、防重放和请求 ID网关必须剥离外部同名身份头。
## 2. 推荐接口
### 查询客户车辆快照
```http
GET /inner/v1/customer-vehicle-scopes/{customerId}?cursor=&limit=500
Authorization: Service <service-token>
X-Request-Id: <uuid>
X-Request-Timestamp: <unix-seconds>
X-Request-Signature: <hmac-sha256>
```
响应:
```json
{
"code": 0,
"data": {
"customerId": "1001",
"scopeVersion": "2026-07-14T12:00:00.123Z/987654",
"generatedAt": "2026-07-14T12:00:00.123Z",
"complete": true,
"nextCursor": null,
"items": [
{
"vehicleId": "2001",
"vin": "LXXXXXXXXXXXXXXXX",
"plateNumber": "沪A00000",
"customerId": "1001",
"contractId": "3001",
"contractCode": "HT20260001",
"projectName": "示例项目",
"modelName": "示例车型",
"brandName": "示例品牌",
"operationStatus": "2",
"scopeStartAt": "2026-07-01T08:00:00+08:00",
"sourceUpdatedAt": "2026-07-14T11:59:58.456+08:00"
}
],
"rejected": [
{
"vehicleId": "2002",
"reason": "VIN_MISSING"
}
]
},
"requestId": "..."
}
```
约束:
- 雪花 ID 一律用 JSON 字符串传输,避免 JavaScript 数字精度丢失。
- `scopeVersion` 对同一业务快照稳定;分页中的每一页必须来自同一个快照。
- `complete=false` 表示快照不完整,车辆中台不得发布该版本。
- `items` 内 VIN 必须非空、标准化为大写并在当前快照内唯一。
- `rejected` 只返回机器可读原因和内部车辆 ID不返回敏感客户信息。
- 建议 `ETag=scopeVersion`,支持 `If-None-Match` 降低同步流量。
### 批量检查客户车辆归属
供登录后单车访问和安全审计使用,不代替快照同步:
```http
POST /inner/v1/customer-vehicle-scopes/check
Content-Type: application/json
{
"customerId": "1001",
"vins": ["LXXXXXXXXXXXXXXXX"]
}
```
响应必须对每个 VIN 返回 `allowed/denied/unknown``unknown` 与接口异常在外部访问场景中均按拒绝处理。
## 3. 当前车辆判定
候选记录至少满足:
```text
vehicle_info.del_flag = 0
vehicle_lease_order_record.del_flag = 0
最新有效 delivery_vehicle.delivery_status IN (2,3)
不存在该交车单对应的有效 return_vehicle_task.status IN (2,3,5)
vehicle_lease_order_record.customer_id = customerId仅作为独立归属交叉校验
VIN 非空且唯一
```
同时必须核验:
- 记录客户等于合同 `COALESCE(other_customer_id, customer_id)`
- 不使用 `vehicle_lease_order_record.last_return_time` 判定当前生命周期OneOS 还车草稿保存会提前写入该聚合字段;
- 聚合记录的合同、交车单和最近历史记录一致;
- 不存在同一 VIN/vehicleId 同时授权给多个客户;
- 合同保存没有覆盖已发生的交还车事实。
生产数据审计完成前,该判定仅为候选口径,不得直接开放外部客户。
## 4. 身份声明契约
客户请求由 OneOS Gateway 完成登录态验证后,可向车辆中台传递短时签名声明:
```json
{
"iss": "oneos-gateway",
"aud": "vehicle-data-platform",
"sub": "1001",
"subjectType": "customer",
"customerId": "1001",
"tenantId": "000000",
"roles": ["customer_vehicle_viewer"],
"iat": 1784001600,
"exp": 1784001900,
"jti": "..."
}
```
车辆中台必须校验发行方、受众、签名、有效期和 `jti`,并把 `customerId` 写入请求上下文。禁止从 Query、Body 或普通转发头覆盖它。
## 5. 车辆中台投影
建议表:
```text
business_customer
customer_id, status, source_version, synced_at
business_customer_vehicle_scope
customer_id, vin, vehicle_id, contract_id, scope_start_at,
source_version, source_updated_at, published_at
business_scope_sync_run
run_id, customer_id, source_version, status,
received_count, accepted_count, rejected_count, started_at, finished_at, error
business_scope_audit
request_id, principal_id, customer_id, action, vin, decision, reason, created_at
```
唯一约束至少包括 `(customer_id, vin)`;发布新版本时应在事务中整体切换,不能边分页边覆盖当前版本。
## 6. 失败策略
- 首次同步失败:客户门户不可用,不回退到全量车辆。
- 已有快照过期:达到配置的最大陈旧时间后拒绝访问,并告知“业务车辆范围同步异常”。
- 单车不在范围:统一返回 404避免泄露 VIN 是否存在。
- 多客户冲突或 VIN 缺失:隔离并告警,不授权。
- OneOS 账号停用:会话校验立即失败;车辆中台短时声明最长建议 5 分钟。
## 7. 验收用例
1. 客户 A 无法通过 VIN、车牌、导出、轨迹、告警 ID 或分页游标访问客户 B 的车辆。
2. 车辆还车后,新快照发布即从客户范围移除;旧 URL 也返回 404。
3. 车辆再次交付给另一客户后,仅新客户可见。
4. `other_customer_id` 存在时,只授权给有效丙方客户。
5. 缺 VIN、多客户冲突、聚合与合同不一致的车辆不进入任何外部范围。
6. OneOS 不可用、签名错误、Scope 过期或分页版本变化时全部失败关闭。
7. 1,000 台车辆的快照分页结果无重复、无遗漏,条件请求可返回 304。
8. 所有拒绝决策保留 request ID、主体、VIN 摘要和原因,且不记录访问令牌。
## 8. 兼容与回滚
- 保留现有 Dubbo `RemoteVehicleService`,但在修复前明确标记 `listByCustomerId` 不可用于外部授权。
- 新 HTTP 内部接口使用独立 `/inner/v1` 前缀,不改变现有页面接口。
- 先以影子模式计算快照并与人工样本对账,再允许车辆中台消费。
- 若上线后异常,关闭客户入口并回退到上一个已验证 Scope 版本;不得回退为“无 Scope 查询”。

View File

@@ -0,0 +1,301 @@
# 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. 存量补偿任务只处理缺失/异常账号,输出成功、冲突、跳过清单,不覆盖现有密码。
### P0OneOS 地图和车辆查询没有客户级数据隔离
- `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 变更、兼容方案、回滚方案和测试范围。

View File

@@ -0,0 +1,398 @@
# OneOS 与 ln-asset-management 系统分析
更新时间2026-07-14
分析方式:源码静态分析、生产数据库只读元数据与聚合核验
修改边界:本报告未修改 `/Users/lingniu/project/ai-coding/oneos`。用户已明确要求不修改 `ln-asset-management` 任何逻辑;所有建议均需另行同步和批准。
## 1. 结论先行
`ln-asset-management` 不是单一车辆档案服务,而是 OneOS 的资产和车辆业务单体,覆盖车辆主档、租赁合同、交车、还车、换车、应收、结算、保险、证照、异常异动、维修故障、加氢、备件、培训、消息与工作台等领域。车辆数据中台不能把它当作实时遥测服务,也不能直接把它的现有 REST/Dubbo 接口公开给客户。
推荐的系统职责是:
- OneOS车辆业务身份、客户、合同、交还车、业务状态的权威来源
- 车辆数据中台TDengine/Redis/Kafka 中遥测、轨迹、在线状态、告警和分析的权威来源;
- 两者之间:使用最小权限只读快照,不在客户请求链路上实时查询 OneOS不信任客户端传入的客户 ID
- 当前客户车辆生命周期:以完成的 `delivery_vehicle``return_vehicle_task` 事务事实判断,`vehicle_lease_order_record` 仅用于客户归属交叉校验。
当前不能安全公开 OneOS 接口,主要原因包括:
1. `RemoteVehicleService.listByCustomerId` 当前忽略客户 ID存在返回全量车辆风险
2. 网关能证明“已登录”,但 93 个业务 Controller 中只有 1 个检出细粒度权限注解,不能证明接口级或客户级授权完整;
3. 全局 API 日志拦截器会记录请求头、请求体和响应体,且未像注解切面一样脱敏 Authorization、Token 和 Cookie
4. 注解接口同时被全局拦截器与 `@ApiLog` 切面记录,存在重复日志;两个月日志表约占生产库数据体积 83%
5. 租赁聚合表会被合同保存和还车草稿等非最终业务动作影响,不能单独作为授权真值;
6. 核心交车、还车和合同类达到 2,7006,000 行,自动化测试没有覆盖核心生命周期;
7. 24 个定时任务未发现分布式锁,服务多副本部署存在重复执行风险。
## 2. 仓库与运行架构
`/Users/lingniu/project/ai-coding/oneos` 下是四个独立 Git 仓库,而不是一个单仓:
| 仓库 | 主要职责 | 与车辆数据中台关系 |
| --- | --- | --- |
| `ln-cloud` | RuoYi Cloud 网关、认证、用户、组织、字典、资源、工作流、Nacos 等 | 身份和组织来源;外部客户身份尚未建立 |
| `ln-asset-management` | 车辆和资产全生命周期业务 | 车辆业务主档和客户 Scope 来源 |
| `ln-energy-v2` | 能源及财务类远程能力 | OneOS 内部依赖,不作为遥测主源 |
| `ln-ocr` | OCR 能力及共享 API | 保险、证照等业务依赖 |
`ln-asset-management` 技术基线:
- Java 21Spring Boot 3.5.10Spring Cloud 2025.0.0
- MyBatis-Plus 3.5.15MySQL Connector/J 9.5
- Nacos 配置与注册、Dubbo 服务、Sa-Token、RabbitMQ、异步和定时任务
- 主服务端口 8701网关路由 `/asset/**` 后去除一层前缀;
- Dockerfile 默认 `test` profile生产实际配置依赖 Nacos 覆盖;
- Maven 依赖公司私有 SNAPSHOT 包,本机缺少仓库凭证时构建会在依赖解析阶段收到 401
- 仓库没有发现 Maven Wrapper、CI 工作流、docker-compose 或完整生产运行手册。
源码规模基线:
| 指标 | 数量 |
| --- | ---: |
| Java 代码 | 约 181,425 行 |
| 业务 Controller | 93 个(按源码注解统计;命名扫描为 95 个) |
| 显式 HTTP 方法 | 约 815 个 |
| Mapper | 约 161 个 |
| `@TableName` 实体映射 | 158 个 |
| Dubbo 服务实现 | 20 个 |
| 含 Dubbo 引用的文件 | 85 个 |
| 定时任务文件 | 24 个 |
| 测试文件 | 30 个、约 4,372 行 |
## 3. 业务领域清单
### 3.1 车辆主档与运营状态
模块路径存在历史拼写 `modules/vihicle`,但它是车辆核心模块,共 11 个 Controller、约 107 个 HTTP 方法。
主要能力:
- `VehicleInfoController`:车辆档案、查询、导入导出;
- `VehicleModelController`:车型;
- `VehicleRealtimeLocationController`OneOS 内部实时位置投影;
- `PrepareCarController`:备车;
- `VehicleLicenseController`、批量上牌:证照和上牌;
- `VehicleTransferController`:调拨;
- `AftermarketDeviceController`:后装设备;
- `AccidentInfoController`:事故;
- `VehicleCheckItemController``VehicleMaintenanceItemController`:检查和维保配置。
主要表:`vehicle_info``vehicle_model``vehicle_status``vehicle_realtime_location``prepare_car``vehicle_transfer``vehicle_license``aftermarket_device``accident_info``tab_data_attachment` 等 19 张显式映射表。
权威性判断:
- 车辆身份以 `vehicle_info.id` 和标准化 VIN 为主;
- 车牌会变化,不能作为跨系统唯一键;
- `vehicle_status` 是 OneOS 业务运营状态,不等于车联网在线状态;
- `vehicle_realtime_location` 和地图同步表是 OneOS 的展示投影,实时遥测仍应以车辆数据中台 Redis/TDengine 为准;
- `vehicle_info.customer_id` 不是可靠客户归属来源,客户关系需通过合同和交还车事实确定。
### 3.2 租赁合同、交车、还车和换车
`contract` 是最大领域21 个 Controller、约 237 个 HTTP 方法、46 张显式映射表。
主要链路:
```text
客户/合同
-> vehicle_lease_contract_info
-> vehicle_lease_order / vehicle_lease_order_detail
-> delivery_task_subject / delivery_vehicle
-> 正式交车完成
-> 客户当前持有车辆
-> return_vehicle_task 或 vehicle_replacement
-> 正式还车/换车完成
-> 应收、还车付款、还车结算、发票
```
关键事实表:
- 合同:`vehicle_lease_contract_info``vehicle_lease_contract_log`
- 合同车辆:`vehicle_lease_order``vehicle_lease_order_detail`
- 交车:`delivery_task_subject``delivery_vehicle``delivery_vehicle_check_item`
- 还车:`return_vehicle_task``return_vehicle_check_item`
- 换车:`vehicle_replacement`
- 当前聚合与历史:`vehicle_lease_order_record``vehicle_lease_order_record_log`
- 财务:`receivable_*``return_payment*``return_settlement*``rental_invoice`
当前客户车辆的安全口径:
```text
最新有效 delivery_vehicle.delivery_status IN (2, 3)
AND delivery_time IS NOT NULL
AND 不存在该交车单对应的有效 return_vehicle_task.status IN (2, 3, 5)
AND vehicle_info 有效且 VIN 非空
AND customer_info 有效
AND vehicle_lease_contract_info 有效
AND vehicle_lease_order_record.customer_id
= COALESCE(contract.other_customer_id, contract.customer_id)
AND 同一车辆和标准化 VIN 不存在多个当前关系
```
不能仅使用 `vehicle_lease_order_record.last_delivery_time/last_return_time`:还车草稿保存会提前写 `last_return_time`;合同保存也可能覆盖交还车聚合字段;部分聚合更新失败只记录日志。用户已要求不修改这些 OneOS 逻辑,因此车辆数据中台必须在读取侧绕开并失败关闭。
### 3.3 客户、司机与身份
- `customer_info`:业务客户档案;
- `driver_info`:司机档案、黑名单、手机号和证件;
- `ln-cloud/ry-cloud.sys_user`:登录账号;
- 业务客户 ID 与 `sys_user.user_id` 没有稳定映射,生产只读审计未发现可直接使用的客户账号体系;
- 手机号只能作为人工核对线索,不能自动建立授权关系;
- 合同有甲方和丙方客户,当前有效客户规则为 `COALESCE(other_customer_id, customer_id)`
结论:车辆数据中台内部管理员可以继续使用独立平台令牌;外部客户入口在可信身份映射建立前不能开放。
### 3.4 地图与外部定位
`map` 模块有 5 个 Controller字典、组织、站点、车辆和地理编码。
数据和依赖:
- `tab_truck_remote_sync_realtime_info`:车辆地图投影;
- `tab_outside_hydrogen_site`:站点投影;
- Hasp、远程实际车辆接口和高德 REST API
- `MapSyncTask` 定期同步车辆与站点。
结论OneOS 地图适合内部业务展示,不适合作为车辆数据中台遥测源。两套地图需要统一 WGS-84 到 GCJ-02 的显示转换,但原始存储坐标必须保留来源和坐标系标识。
### 3.5 资产后市场与合规
覆盖领域包括:
- 保险采购、保单、OCR14 张表、约 69 个 HTTP 方法;
- 车辆异常异动:`vehicle_abnormal_move*`
- 故障:`vehicle_fault_manage`
- 年检、年审、车辆证书、交通违章;
- 维修站、救援队、检查站;
- 备件、出入库和仓库;
- 培训材料与培训记录。
这些业务可以消费车辆中台的里程、位置、在线、告警证据,但车辆中台不能反向改写合同、保险、证照、结算或事故事实。涉及锁车、控车的能力不在本分析和实施范围。
### 3.6 能源与工作流
- 加氢站、加氢台账、加氢场站、充电站;
- 消息、通知规则、待办任务和工作台;
- 通过 `RemoteWorkflowService``RemoteUserService``RemoteDictService``RemoteFileService` 等 Dubbo 服务依赖 `ln-cloud`
- 定时生成账单、合同到期提醒、证照到期提醒、台账状态同步等。
## 4. 接口与安全边界
### 4.1 已有安全机制
- 网关 `AuthFilter` 对非白名单请求执行 `StpUtil.checkLogin()`
- 微服务 `SecurityConfiguration` 可通过 Same-Token 验证请求是否由网关转发;
- actuator 使用 Basic Auth
- 网关把 `/asset/**` 转发到 `ln-asset-management`
这些机制只能证明请求已登录或来自网关,不能自动证明用户有某个业务动作权限,更不能证明客户只能访问自己的车辆。
### 4.2 接口级授权缺口
静态扫描 93 个业务 Controller只有 1 个文件出现 `@SaCheckPermission` 等细粒度权限注解;另有 3 个 Controller/回调路径使用 `@SaIgnore`。部分数据权限由 Mapper/DataPermission 处理,但没有形成覆盖所有车辆、合同、地图和导出入口的统一客户 Scope。
因此:
- OneOS 现有 REST API 只能作为内部业务接口;
- 不能直接公网开放 8701
- 不能把 `customerId` 查询参数或 `X-Customer-Id` 请求头当作授权身份;
- 车辆数据中台应在自身底层查询统一注入 VIN Scope并对详情返回 404避免泄漏对象存在性。
### 4.3 Dubbo 接口评估
`ln-asset-api` 暴露 20 个远程服务,包括车辆、客户、合同匹配、充电/加氢站、异常异动、保险、结算和任务等。
车辆数据中台当前不应直接调用 `RemoteVehicleService.listByCustomerId`:实现忽略客户 ID并按有效车辆返回全量结果。可复用的 Dubbo 接口必须逐个做参数、范围、分页、超时和失败策略审计RPC 不等于安全边界。
## 5. 数据库现状
生产只读元数据基线:
| 指标 | 结果 |
| --- | ---: |
| 基础表 | 448 |
| 数据体积 | 约 2.63 GB |
| 索引体积 | 约 381 MB |
| 有效车辆 | 1,209 |
| 当前事务事实候选车辆 | 639 |
| 可安全发布 Scope | 613 |
| 隔离关系 | 26 |
448 张表明显多于 158 个实体映射因为数据库还包含历史表、BI 表、手工迁移表、备份表和动态月表。迁移、备份和运行表混放在同一业务库,增加容量、权限和误操作风险。
### 5.1 API 请求日志容量与隐私风险
`api_request_log_202606``api_request_log_202607` 合计约 2.19 GB约占全库数据体积 83%。源码证据:
- `ApiLogWebConfig` 把全局拦截器挂到几乎所有业务路径;
- `ApiLogConfig` 默认记录参数、请求体、响应体和请求头;
- `ApiLogInterceptor.getRequestHeaders()` 不脱敏任何请求头;
- `ApiLogAspect.getRequestHeaders()` 会脱敏 Authorization/Token/Cookie但仅用于 `@ApiLog` 切面;
-`@ApiLog` 的接口同时被拦截器和切面保存,可能生成两条日志;
- 清理接口只删除“当前时间减保留天数所在月份”一张表内的旧行,并不会遍历或删除所有更早月表;异常被吞掉;
- `/api-request-log/clean` 没有细粒度权限注解。
这是 P0 安全和运维风险:日志可能包含登录凭证、身份证、手机号、合同、签章、附件地址和完整业务响应,并持续快速增长。
### 5.2 关键索引和约束
已确认:
- `vehicle_info` 有车牌索引,但 VIN 没有索引或唯一约束;
- `vehicle_lease_order_record.vehicle_id``vehicle_status.vehicle_id` 只有普通索引,没有唯一约束;
- `vehicle_lease_order_record.customer_id` 缺少 Scope 查询索引;
- `delivery_vehicle.vehicle_id``return_vehicle_task.delivery_vehicle_id` 已有普通/组合索引;
- 生产数据存在 1 组“一车多聚合记录”和大量聚合/历史差异,不能直接添加唯一约束。
任何清理、唯一约束或索引 DDL 都必须单独审批,不属于当前只读分析。
## 6. 可靠性与可维护性
### 6.1 超大类热点
| 文件 | 行数(约) | 风险 |
| --- | ---: | --- |
| `VehicleLeaseContractFullServiceImpl` | 6,082 | 合同、工作流、附件和聚合职责耦合 |
| `ReturnTaskServiceImpl` | 4,015 | 草稿、完成、签章、结算与聚合边界混合 |
| `ReturnSettlementServiceImpl` | 3,902 | 财务聚合复杂、回滚边界难验证 |
| `InsuranceProcurement2ServiceImpl` | 3,626 | OCR、任务、保单、附件和状态混合 |
| `HydrogenFuelLedgerServiceImpl` | 3,322 | 导入、匹配、财务状态耦合 |
| `VehicleTransferServiceImpl` | 3,268 | 调拨、车辆状态、工作流耦合 |
| `DeliveryTaskServiceImpl` | 2,715 | 交车事实和下游副作用耦合 |
测试集中在加氢、保险辅助逻辑、证照和少量 Mapper没有覆盖合同保存、交车完成、还车草稿、还车完成、再次交车、丙方客户、换车等 Scope 核心用例。
### 6.2 事务和错误处理
106 个 `service/impl` 文件中约 63 个文件出现 `@Transactional`,但“存在注解”不代表跨 Dubbo、工作流、签章、文件和本地数据库具备原子性。核心链路存在捕获异常后只记日志、继续返回成功的模式导致任务状态与聚合表可能不一致。
### 6.3 定时任务
源码有 24 个 `@Scheduled` 文件,应用主类全局启用调度,未发现 ShedLock 或等价分布式锁。若生产运行多个实例,账单生成、到期提醒、地图同步、证照状态同步等任务可能重复执行。部署前必须确认生产实例数和任务幂等性。
### 6.4 配置和交付
- `application.yml` 是 Git 跟踪文件,包含邮件、短信和第三方服务的凭证型配置项;部分值看起来不是纯占位符,必须在不打印原值的前提下轮换并迁到密钥管理/Nacos
- README 只给出 Swagger 地址,没有依赖、构建、配置、迁移、部署、回滚、健康检查和故障处置说明;
- Dockerfile 默认测试环境,若生产变量遗漏可能连入错误命名空间;
- 私有 Maven 仓库凭证缺失会阻断可重复构建;
- 未发现 CI 门禁,无法证明每次发布都执行测试、依赖扫描和镜像校验。
### 6.5 可从源码推导的运维顺序
仓库没有 OneOS 专用生产运维手册,`ln-cloud/README.md` 基本是上游 RuoYi 框架说明Nacos README 只有“复制配置”的一句提示。以下只能作为源码推导,不能替代生产值班手册:
1. 先启动 MySQL、Redis、Nacos以及文件存储/资源服务所需基础设施;
2. 在对应 namespace/group 中准备 `application-common.yml``datasource.yml``ln-asset-management.yaml`
3. 启动 `ruoyi-system``ruoyi-resource``ruoyi-workflow` 等被 Dubbo 引用的服务;
4. 启动 `ruoyi-auth``ruoyi-gateway`
5. 最后启动 `ln-asset-management`,核验 Nacos 注册、Dubbo 引用、数据库、Redis、RabbitMQ、文件服务和 actuator
6. 确认只有一个实例执行非幂等 `@Scheduled`,或先证明所有定时任务具备分布式幂等;
7. 再开放 `/asset/**` 流量并检查错误率、Dubbo 超时、数据库连接池、API 日志增速和任务重复执行。
构建时应显式选择 profile并避免 Maven package 阶段意外连接 Docker daemon
```text
mvn -Pprod -Ddocker.skip=true clean package
```
该命令当前仍依赖公司私有 Maven 仓库凭证。Dockerfile 自带的 `SPRING_PROFILE_ACTIVE=test``NAMESPACE=test` 必须由生产环境显式覆盖;否则不能视为生产可部署制品。
生产运维说明仍缺少真实部署拓扑、实例数、镜像仓库流程、Nacos 配置版本、SQL 迁移账本、备份恢复、发布回滚、健康检查阈值、告警接收人、日志保留和密钥轮换。未补齐前,不建议自动化改动或独立部署 OneOS。
## 7. 风险矩阵
| 等级 | 风险 | 当前处置 |
| --- | --- | --- |
| P0 | API 日志可能保存未脱敏认证头和大量敏感请求/响应 | 不改 OneOS立即限制日志查询/清理接口访问,制定脱敏、保留和轮换方案后另行审批 |
| P0 | `listByCustomerId` 返回全量车辆 | 数据中台禁用该接口,使用只读事务事实快照 |
| P0 | 登录校验不等于业务/客户数据权限 | OneOS 不公开;数据中台统一执行 VIN Scope |
| P0 | 客户账号与业务客户没有可信映射 | 外部客户入口保持关闭 |
| P1 | 聚合生命周期被草稿/合同保存影响 | 读取交还车事务事实,聚合仅作交叉校验 |
| P1 | 关键业务异常被吞掉,状态可能不一致 | 快照失败关闭并隔离;上游修复需重新同步批准 |
| P1 | 多副本定时任务可能重复 | 上线前确认单实例或任务幂等/分布式锁 |
| P1 | VIN、聚合和状态缺少唯一约束 | 不直接 DDL先清理异常并做影子验证 |
| P1 | 核心生命周期测试缺失 | 将合同/交还车/换车作为未来修改的强制回归门禁 |
| P1 | 凭证型配置进入源码 | 安全轮换和密钥迁移需单独紧急流程 |
| P2 | 超大类、命名和 URL 漂移 | 分领域抽取应用服务,保持兼容接口 |
| P2 | 运维文档与 CI 缺失 | 补齐构建、发布、回滚、监控和演练说明 |
## 8. 对车辆数据中台的可复用与禁止清单
### 可只读复用
- `vehicle_info`VIN、车牌、车型和主档
- `vehicle_model`:车型能力和显示配置;
- `vehicle_status`OneOS 业务运营状态;
- `customer_info``vehicle_lease_contract_info`:客户和合同;
- `delivery_vehicle``return_vehicle_task`:当前持有生命周期;
- 停车场、站点、司机等低频主数据;
- 所有读取都通过专用表级 SELECT 账号和周期快照。
### 不作为权威来源
- OneOS 地图投影中的在线状态、位置和里程;
- `vehicle_info.customer_id`
- 单独的 `vehicle_lease_order_record.last_return_time`
- `vehicle_lease_order_record_log` 最大 ID
- 客户手机号与系统用户手机号的自动匹配;
- 客户请求参数中的客户 ID。
### 禁止回写
- 合同、交车、还车、换车;
- 锁车、控车;
- 保险、事故、违章、证照、结算和发票;
- OneOS 用户和客户归属;
- 任何生产数据修复或 DDL除非用户另行明确批准。
## 9. 分阶段建议
### 阶段 A只读基线已完成
- 仓库和模块清单;
- 生产表规模、车辆/客户/Scope 数据质量;
- 生命周期和权限风险;
- OneOS 保持零修改。
### 阶段 B车辆数据中台只读投影代码已准备尚未部署
- 专用只读账号只授予 7 张必要表 SELECT
- 两分钟一次读取交还车事务事实;
- 613 条安全关系发布26 条隔离;
- 内容寻址版本、事务整体切换、异常比例门禁;
- ECS 当前仍缺 `ONEOS_MYSQL_DSN`、平台迁移 012 和同步 timer。
### 阶段 C客户身份与 API Scope未完成
- 明确可信客户身份来源;
- 平台 Principal 增加主体类型和客户 ID
- 对监控、车辆、轨迹、历史、统计、告警、导出和地图统一注入 Scope
- 完成双客户越权测试后才允许灰度。
### 阶段 DOneOS 风险治理(未获授权)
如用户未来批准,应优先按独立批次处理:
1. API 日志脱敏、去重、保留策略和访问权限;
2. 客户车辆 RPC 失败关闭;
3. 聚合写入的事务与生命周期边界;
4. 定时任务幂等或分布式锁;
5. 凭证轮换、构建和运维门禁。
每个批次都必须先同步文件、行为变化、数据库影响、测试和回滚方案,不与数据修复或 DDL 混合发布。
## 10. 证据索引
- 系统集成与 Scope`docs/oneos-integration-analysis.md`
- 生产只读审计:`docs/oneos-production-audit-report.md`
- 业务影响:`docs/oneos-business-impact-matrix.md`
- 客户 API 覆盖:`docs/customer-scope-api-enforcement-matrix.md`
- Scope 契约:`docs/oneos-customer-vehicle-scope-contract.md`
- OneOS 网关认证:`ln-cloud/ruoyi-gateway/.../AuthFilter.java`
- 微服务 Same-Token`ln-cloud/ruoyi-common/ruoyi-common-security/.../SecurityConfiguration.java`
- 全局 API 日志:`ln-asset-management/src/main/java/com/ln/asset/common/log/`
- 租赁生命周期:`ln-asset-management/src/main/java/com/ln/asset/modules/contract/`
- 车辆主档:`ln-asset-management/src/main/java/com/ln/asset/modules/vihicle/`
- 远程接口:`ln-asset-management/ln-asset-api/src/main/java/com/ln/asset/api/`

View File

@@ -0,0 +1,135 @@
# OneOS 生产数据库只读审计报告
审计时间2026-07-14
## 1. 执行边界
- 连接路径:本机 → SSH 加密隧道 → 当前车辆中台 ECS → 阿里云 RDS 内网地址。
- 所有查询会话均执行 `SET SESSION TRANSACTION READ ONLY`
- 单条查询设置 `MAX_EXECUTION_TIME=10000`10 秒)。
- 仅执行元数据和聚合查询,未读取密码、客户姓名、手机号、银行账号等明细。
- 未执行任何 INSERT、UPDATE、DELETE、DDL 或权限变更。
## 2. 账号权限核验
当前从 `ln-bi` 部署配置取得的 RDS 账号并不是账号级只读:
-`ln_asset_management``ry-cloud` 等多个业务库拥有 `ALL PRIVILEGES`
- 还拥有 `PROCESS``REPLICATION SLAVE``REPLICATION CLIENT``XA_RECOVER_ADMIN` 等全局权限。
本次审计依靠会话只读保护,没有使用这些写权限。但该账号不适合作为车辆数据中台的生产同步账号,也不应继续以“只读账号”管理。
建议另建独立账号,只授予必要表的 `SELECT`;如需最小到列级,优先建立脱敏只读视图并仅授权视图。权限调整属于数据库写操作,需另行审批。
## 3. 数据质量基线
### 车辆档案
| 指标 | 结果 |
| --- | ---: |
| 有效车辆 | 1,209 |
| VIN 缺失 | 0 |
| VIN 重复组 | 0 |
| 车牌缺失 | 0 |
车辆身份基础质量良好,当前 1,209 台有效车辆均有 VIN 和车牌,标准化 VIN 后无重复。
生产库尚未给 `vehicle_info.vin` 建立索引或唯一约束。当前数据满足增加唯一索引的前置条件,但正式 DDL 前仍要确认历史/逻辑删除记录及业务兼容性。
### 客户账号
| 指标 | 结果 |
| --- | ---: |
| 有效客户 | 335 |
| `customer_info.id = sys_user.user_id` 的账号 | 0 |
| `user_type=customer` 的账号 | 0 |
| 客户编码与系统用户名匹配 | 0 |
| 联系手机号与系统账号手机号匹配 | 4 |
| 客户编码缺失 | 7 |
结论客户账号体系尚未形成不能通过“补角色”直接开放。4 个手机号匹配仅能作为待人工核验线索,不能自动认定为客户账号或覆盖现有用户。
两个库的字符排序规则不同:
- `customer_info.customer_code/contact_mobile``utf8mb4_unicode_ci`
- `sys_user.user_name/phonenumber``utf8mb4_0900_ai_ci`
跨库字符串直接等值 JOIN 会报 collation 冲突。正式关联应优先使用稳定 ID补偿审计若按编码/手机号匹配,需要显式统一 collation。
### 当前客户车辆候选范围(事务事实口径)
候选条件:每车最新有效交车单已完成(`delivery_status IN (2,3)`),且该交车单不存在正式完成的还车任务(`status IN (2,3,5)`)。聚合表仅用于客户归属交叉校验,不使用可能被还车草稿提前写入的 `last_return_time` 判断生命周期。
| 指标 | 结果 |
| --- | ---: |
| 候选记录 | 639 |
| 候选车辆 | 639 |
| 可安全发布记录 | 613 |
| 应隔离记录 | 26 |
隔离原因(可能重叠):
| 原因 | 数量 |
| --- | ---: |
| 聚合记录或客户 ID 缺失 | 17 |
| 聚合客户与合同有效客户不一致 | 9 |
| 车辆档案不可用 | 0 |
| VIN 缺失 | 0 |
因此第一版客户车辆 Scope 最多只能发布 613 条已校验关系,其余 26 条必须隔离,不能为了数量完整而放宽权限。
与旧聚合时间口径相比,事务事实口径排除 16 台12 台已有正式还车事实、3 台最新交车尚未完成、1 台没有交车事实;另有 8 台进入事务候选但不在旧聚合候选,其中 4 台缺聚合记录并会继续 fail-closed 隔离。该差异证明 `last_delivery_time/last_return_time` 不适合作为外部授权生命周期真值。
### 租赁聚合与历史一致性
| 指标 | 结果 |
| --- | ---: |
| 有效当前聚合记录 | 831 |
| 无任何交还车时间 | 13 |
| 已交车、无还车时间 | 617 |
| 还车后再次交车 | 30 |
| 已还车 | 171 |
| 聚合表与最新历史关键字段不一致 | 325 |
| 违反“一车一条聚合记录”的车辆组 | 1 |
| 当前候选范围内重复车辆 | 0 |
325 条差异说明 `vehicle_lease_order_record_log` 不能简单取最大 ID 当作当前真值1 组重复聚合记录也意味着现有 `selectOne(vehicle_id)` 存在运行时异常风险。正式修复前需要对异常组做明细复核,但明细不写入本报告。
## 4. 索引结论
生产库已有车牌、vehicleId 和部分状态索引,但缺少:
- `vehicle_info.vin` 索引/唯一约束;
- `vehicle_lease_order_record.customer_id` 的客户范围查询索引;
- `vehicle_lease_order_record.vehicle_id` 唯一约束;
- `vehicle_status.vehicle_id` 唯一约束;
- 合同有效客户查询所需索引。
当前数据量不大。2026-07-14 事务事实影子查询耗时约 93 ms主交车表预估扫描 2,320 行,其余关联均使用 `eq_ref/ref`;两分钟一次的单次批量查询压力可控。后续数据增长后仍需监控执行时间,索引 DDL 必须单独审批并用 `EXPLAIN`、影子查询和回滚方案验证。
## 5. 当前可采用的权限口径
在业务确认和异常修复前,外部客户范围必须同时满足:
```text
车辆有效且 VIN 非空
聚合记录有效
最新有效交车单已完成
该交车单不存在正式完成的还车任务
客户档案有效
合同有效
聚合客户 ID 非空
合同有效客户非空
聚合客户 = COALESCE(合同丙方客户, 合同主客户)
同一车辆不存在多客户冲突
```
不满足任一条件的记录进入隔离清单,外部查询失败关闭。
## 6. 下一步
1. 创建真正的最小权限 RDS 只读账号,替换当前高权限账号。
2. 由业务人员复核 26 条隔离关系和 1 组重复聚合记录。
3. 不修改 `ln-asset-management`;将还车草稿提前写聚合字段记录为上游风险,数据中台只读取正式交还车任务状态。
4. 以 613 条可发布关系运行影子 Scope与合同和交还车样本对账。
5. 完成客户身份入口与所有客户 API 的 Scope 强制校验后,再审批上线外部客户入口。

View File

@@ -0,0 +1,252 @@
-- OneOS 客户账号与当前车辆范围只读审计
-- 仅允许在只读连接执行;不包含任何 INSERT/UPDATE/DELETE/DDL。
SET SESSION TRANSACTION READ ONLY;
SET SESSION MAX_EXECUTION_TIME = 10000;
-- 0. 核验实际授权。只读账号不应出现 ALL PRIVILEGES、PROCESS、REPLICATION 或 DDL/DML 权限。
SHOW GRANTS FOR CURRENT_USER;
-- 1. 核对关键表字段,识别代码、文档与生产结构漂移。
SELECT table_schema, table_name, column_name, data_type, is_nullable, column_key
FROM information_schema.columns
WHERE table_schema IN ('ln_asset_management', 'ry-cloud')
AND table_name IN (
'vehicle_info', 'vehicle_lease_order_record', 'vehicle_lease_order_record_log',
'vehicle_lease_contract_info', 'vehicle_lease_order_detail', 'delivery_vehicle',
'return_vehicle_task', 'customer_info', 'sys_user', 'sys_user_role'
)
AND column_name IN (
'id', 'user_id', 'vehicle_id', 'plate_number', 'vin', 'customer_id',
'other_customer_id', 'contract_id', 'contract_code', 'last_delivery_time',
'last_return_time', 'delivery_time', 'arrival_time', 'user_type', 'status', 'del_flag'
)
ORDER BY table_schema, table_name, ordinal_position;
-- 2. 车辆基础数据质量。
SELECT
COUNT(*) AS active_vehicles,
SUM(vin IS NULL OR TRIM(vin) = '') AS missing_vin,
COUNT(DISTINCT NULLIF(TRIM(vin), '')) AS distinct_vin,
COUNT(DISTINCT NULLIF(TRIM(plate_number), '')) AS distinct_plate
FROM ln_asset_management.vehicle_info
WHERE del_flag = '0';
-- 3. 客户档案与客户账号覆盖率,不输出手机号、密码等敏感字段。
SELECT
COUNT(*) AS active_customers,
SUM(u.user_id IS NOT NULL) AS customers_with_account,
SUM(u.status = '0' AND u.del_flag = '0') AS customers_with_enabled_account,
SUM(u.user_id IS NULL) AS customers_without_account
FROM ln_asset_management.customer_info c
LEFT JOIN `ry-cloud`.sys_user u
ON u.user_id = c.id
AND u.user_type = 'customer'
WHERE c.del_flag = '0';
-- 4. 客户账号角色覆盖率。
SELECT
COUNT(DISTINCT u.user_id) AS customer_accounts,
COUNT(DISTINCT ur.user_id) AS customer_accounts_with_role
FROM `ry-cloud`.sys_user u
LEFT JOIN `ry-cloud`.sys_user_role ur ON ur.user_id = u.user_id
WHERE u.user_type = 'customer'
AND u.del_flag = '0';
-- 5. 旧聚合时间口径,仅用于与事务事实口径对比,不能作为授权真值。
SELECT
COUNT(*) AS scope_rows,
COUNT(DISTINCT r.vehicle_id) AS scope_vehicles,
COUNT(DISTINCT r.customer_id) AS scope_customers,
SUM(r.customer_id IS NULL) AS missing_customer,
SUM(v.vin IS NULL OR TRIM(v.vin) = '') AS missing_vin,
SUM(c.id IS NULL AND r.customer_id IS NOT NULL) AS missing_customer_profile
FROM ln_asset_management.vehicle_lease_order_record r
LEFT JOIN ln_asset_management.vehicle_info v
ON v.id = r.vehicle_id AND v.del_flag = '0'
LEFT JOIN ln_asset_management.customer_info c
ON c.id = r.customer_id AND c.del_flag = '0'
WHERE r.del_flag = '0'
AND r.last_delivery_time IS NOT NULL
AND (r.last_return_time IS NULL OR r.last_return_time < r.last_delivery_time);
-- 5A. 当前交车/还车事务事实候选及归属质量。
SELECT
COUNT(*) AS candidate_rows,
COUNT(DISTINCT d.vehicle_id) AS candidate_vehicles,
SUM(v.id IS NULL OR v.vin IS NULL OR TRIM(v.vin) = '') AS invalid_vehicle,
SUM(r.id IS NULL OR r.customer_id IS NULL) AS missing_aggregate_customer,
SUM(ci.id IS NULL AND r.customer_id IS NOT NULL) AS invalid_customer_profile,
SUM(co.id IS NULL) AS invalid_contract,
SUM(co.id IS NOT NULL AND COALESCE(co.other_customer_id, co.customer_id) IS NULL) AS missing_contract_customer,
SUM(
r.customer_id IS NOT NULL
AND COALESCE(co.other_customer_id, co.customer_id) IS NOT NULL
AND r.customer_id <> COALESCE(co.other_customer_id, co.customer_id)
) AS customer_mismatch
FROM ln_asset_management.delivery_vehicle d
LEFT JOIN ln_asset_management.vehicle_info v
ON v.id = d.vehicle_id AND v.del_flag = '0'
LEFT JOIN ln_asset_management.vehicle_lease_order_record r
ON r.vehicle_id = d.vehicle_id AND r.del_flag = '0'
LEFT JOIN ln_asset_management.customer_info ci
ON ci.id = r.customer_id AND ci.del_flag = '0'
LEFT JOIN ln_asset_management.vehicle_lease_contract_info co
ON co.id = d.contract_id AND co.del_flag = '0'
WHERE d.del_flag = '0'
AND d.delivery_status IN (2, 3)
AND d.vehicle_id IS NOT NULL
AND d.delivery_time IS NOT NULL
AND NOT EXISTS (
SELECT 1
FROM ln_asset_management.delivery_vehicle newer
WHERE newer.del_flag = '0'
AND newer.vehicle_id = d.vehicle_id
AND (
COALESCE(newer.delivery_time, '1000-01-01') > COALESCE(d.delivery_time, '1000-01-01')
OR (
COALESCE(newer.delivery_time, '1000-01-01') = COALESCE(d.delivery_time, '1000-01-01')
AND newer.id > d.id
)
)
)
AND NOT EXISTS (
SELECT 1
FROM ln_asset_management.return_vehicle_task rt
WHERE rt.delivery_vehicle_id = d.id
AND rt.del_flag = '0'
AND rt.status IN (2, 3, 5)
);
-- 6. 检查同一车辆是否出现多个当前客户范围记录。
SELECT r.vehicle_id, COUNT(*) AS active_scope_rows, COUNT(DISTINCT r.customer_id) AS customer_count
FROM ln_asset_management.vehicle_lease_order_record r
WHERE r.del_flag = '0'
AND r.last_delivery_time IS NOT NULL
AND (r.last_return_time IS NULL OR r.last_return_time < r.last_delivery_time)
GROUP BY r.vehicle_id
HAVING COUNT(*) > 1 OR COUNT(DISTINCT r.customer_id) > 1
ORDER BY active_scope_rows DESC
LIMIT 100;
-- 7. 检查客户 ID 与客户档案不一致的记录。
SELECT COUNT(*) AS unmatched_customer_records
FROM ln_asset_management.vehicle_lease_order_record r
LEFT JOIN ln_asset_management.customer_info c
ON c.id = r.customer_id AND c.del_flag = '0'
WHERE r.del_flag = '0'
AND r.customer_id IS NOT NULL
AND c.id IS NULL;
-- 8. 聚合表客户与合同有效客户是否一致。
SELECT
COUNT(*) AS records_with_contract,
SUM(c.id IS NULL) AS missing_contract,
SUM(c.id IS NOT NULL AND r.customer_id IS NULL) AS missing_record_customer,
SUM(
c.id IS NOT NULL
AND r.customer_id IS NOT NULL
AND r.customer_id <> COALESCE(c.other_customer_id, c.customer_id)
) AS effective_customer_mismatch
FROM ln_asset_management.vehicle_lease_order_record r
LEFT JOIN ln_asset_management.vehicle_lease_contract_info c
ON c.id = r.contract_id AND c.del_flag = '0'
WHERE r.del_flag = '0'
AND r.contract_id IS NOT NULL;
-- 9. 已交车但聚合表被清空或缺少客户/合同的可疑记录。
-- delivery_vehicle 的正式完成状态字段需先通过第 1 项结构核对,再按生产枚举追加状态条件。
SELECT
COUNT(*) AS delivered_rows,
SUM(r.id IS NULL) AS missing_aggregate_record,
SUM(r.id IS NOT NULL AND r.customer_id IS NULL) AS aggregate_missing_customer,
SUM(r.id IS NOT NULL AND r.contract_id IS NULL) AS aggregate_missing_contract,
SUM(r.id IS NOT NULL AND r.last_delivery_time IS NULL) AS aggregate_missing_delivery_time
FROM ln_asset_management.delivery_vehicle d
LEFT JOIN ln_asset_management.vehicle_lease_order_record r
ON r.vehicle_id = d.vehicle_id AND r.del_flag = '0'
WHERE d.del_flag = '0'
AND d.delivery_time IS NOT NULL;
-- 10. 当前聚合表与最新历史记录的关键字段差异。
SELECT COUNT(*) AS aggregate_log_mismatch
FROM ln_asset_management.vehicle_lease_order_record r
JOIN ln_asset_management.vehicle_lease_order_record_log l
ON l.vehicle_id = r.vehicle_id
AND l.del_flag = '0'
AND l.id = (
SELECT MAX(l2.id)
FROM ln_asset_management.vehicle_lease_order_record_log l2
WHERE l2.vehicle_id = r.vehicle_id AND l2.del_flag = '0'
)
WHERE r.del_flag = '0'
AND NOT (
r.customer_id <=> l.customer_id
AND r.contract_id <=> l.contract_id
AND r.last_delivery_time <=> l.last_delivery_time
AND r.last_return_time <=> l.last_return_time
);
-- 11. 检查支撑客户车辆范围查询的索引。这里只读索引元数据,不执行 DDL。
SELECT table_name, index_name, non_unique,
GROUP_CONCAT(column_name ORDER BY seq_in_index) AS indexed_columns
FROM information_schema.statistics
WHERE table_schema = 'ln_asset_management'
AND table_name IN (
'vehicle_info', 'vehicle_lease_order_record', 'vehicle_lease_order_record_log',
'vehicle_lease_contract_info', 'delivery_vehicle', 'return_vehicle_task'
)
GROUP BY table_name, index_name, non_unique
ORDER BY table_name, index_name;
-- 12. 客户编码与系统用户名的冲突、错类型和错租户情况。
-- tenant_id 的目标值应由生产配置确认,这里先按客户 ID 关联并报告实际分布。
SELECT
COUNT(*) AS customer_profiles,
SUM(u.user_id IS NULL) AS no_same_id_account,
SUM(u.user_id IS NOT NULL AND u.user_type <> 'customer') AS same_id_wrong_type,
SUM(
u.user_id IS NOT NULL
AND (CONVERT(u.user_name USING utf8mb4) COLLATE utf8mb4_unicode_ci)
<> (CONVERT(c.customer_code USING utf8mb4) COLLATE utf8mb4_unicode_ci)
) AS username_code_mismatch,
COUNT(DISTINCT CASE WHEN u.user_id IS NOT NULL THEN u.tenant_id END) AS account_tenant_count
FROM ln_asset_management.customer_info c
LEFT JOIN `ry-cloud`.sys_user u ON u.user_id = c.id AND u.del_flag = '0'
WHERE c.del_flag = '0';
-- 13. 同一租户、用户名是否存在多账号或跨 user_type 冲突。
SELECT tenant_id, user_name,
COUNT(*) AS account_count,
COUNT(DISTINCT user_type) AS user_type_count
FROM `ry-cloud`.sys_user
WHERE del_flag = '0'
AND user_name IS NOT NULL
AND TRIM(user_name) <> ''
GROUP BY tenant_id, user_name
HAVING COUNT(*) > 1 OR COUNT(DISTINCT user_type) > 1
ORDER BY account_count DESC
LIMIT 100;
-- 14. 业务库整体体积与最大表。table_rows 为 InnoDB 估算值,不用于财务或业务计数。
SELECT
COUNT(*) AS base_tables,
SUM(data_length) AS data_bytes,
SUM(index_length) AS index_bytes
FROM information_schema.tables
WHERE table_schema = 'ln_asset_management'
AND table_type = 'BASE TABLE';
SELECT table_name, table_rows, data_length, index_length
FROM information_schema.tables
WHERE table_schema = 'ln_asset_management'
AND table_type = 'BASE TABLE'
ORDER BY data_length + index_length DESC
LIMIT 20;
-- 15. API 月日志体积,用于核对保留策略;不读取任何日志内容。
SELECT table_name, table_rows, data_length, index_length
FROM information_schema.tables
WHERE table_schema = 'ln_asset_management'
AND table_name LIKE 'api_request_log\\_%'
ORDER BY table_name;

View File

@@ -0,0 +1,37 @@
-- OneOS Scope 同步专用账号开通模板(由 RDS 管理员审核后执行)
--
-- 安全边界:
-- 1. 本文件包含 CREATE USER / GRANT不能使用现有业务账号执行自动部署。
-- 2. <GENERATE_AND_STORE_A_STRONG_PASSWORD> 必须替换为独立随机密码,并写入受限的 platform.env。
-- 3. 172.17.111.55 是 2026-07-14 核验到的当前 ECS 私网源地址;迁移 ECS 后必须同步调整。
-- 4. 不授予 *.*、库级 SELECT、PROCESS、REPLICATION、DML、DDL 或角色。
CREATE USER 'vehicle_scope_reader'@'172.17.111.55'
IDENTIFIED BY '<GENERATE_AND_STORE_A_STRONG_PASSWORD>';
GRANT SELECT ON ln_asset_management.vehicle_lease_order_record
TO 'vehicle_scope_reader'@'172.17.111.55';
GRANT SELECT ON ln_asset_management.delivery_vehicle
TO 'vehicle_scope_reader'@'172.17.111.55';
GRANT SELECT ON ln_asset_management.return_vehicle_task
TO 'vehicle_scope_reader'@'172.17.111.55';
GRANT SELECT ON ln_asset_management.vehicle_info
TO 'vehicle_scope_reader'@'172.17.111.55';
GRANT SELECT ON ln_asset_management.customer_info
TO 'vehicle_scope_reader'@'172.17.111.55';
GRANT SELECT ON ln_asset_management.vehicle_lease_contract_info
TO 'vehicle_scope_reader'@'172.17.111.55';
GRANT SELECT ON ln_asset_management.vehicle_status
TO 'vehicle_scope_reader'@'172.17.111.55';
SHOW GRANTS FOR 'vehicle_scope_reader'@'172.17.111.55';
-- 管理员验收:输出只能包含 USAGE 和以上 7 个表的 SELECT。
-- 回收(仅在确认同步服务已停用后由管理员执行):
-- DROP USER 'vehicle_scope_reader'@'172.17.111.55';