Files
junhong_cmp_fiber/docs/feature-504-multi-view-audit-center/跨视角调查与前端导航契约.md
break 88cc5e96ec
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m32s
暂存
2026-08-06 09:35:00 +08:00

114 lines
15 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.
# 跨视角调查与前端导航契约
本文对应 `build-multi-view-audit-center` 任务 9.5,冻结 request、correlation、资金和风险调查接口以及现有业务页面进入审计中心的第一跳。所有字段路径均位于统一响应的 `response.data` 下;列表字段位于 `items[]`。本文只定义前端调用契约,不包含前端页面实现。
## 跨视角只读接口
| 视角 | 接口 | 入参来源 | 响应重点 |
|---|---|---|---|
| 请求链路 | `GET /api/admin/audit/requests/{request_id}/timeline` | 审计或 Integration 节点的 `investigation_refs.request_id`,或开发人员从 Access Log 粘贴 | `request_id``access_log_lookup_request_id``nodes[]` |
| 业务链路 | `GET /api/admin/audit/correlations/{correlation_id}/timeline` | 审计、Integration、Outbox、任务或业务详情中的稳定 correlation | `correlation_id``nodes[]` |
| 资金时间线 | `GET /api/admin/audit/finance/timeline` | 业务页面稳定 ID、调查节点引用或调查人员输入 | 分页 `items[]`、事实来源、金额权威、`investigation_refs` |
| 风险总览 | `GET /api/admin/audit/risks/overview` | 调查人员选择的 RFC3339 时间范围及可选筛选,最长 31 天 | 信号、风险、结果、动作、来源和趋势 |
| 风险明细 | `GET /api/admin/audit/risks/events` | 风险总览分桶携带相同筛选,或调查人员输入 | 分页风险事件及 `investigation_refs` |
以上接口仅允许超级管理员和平台账号访问,全部为 GET。认证身份只来自认证上下文不提供导出、修改、删除、风险处置、自动封禁、重试、补偿或恢复能力。
## 资产和组织页面逐行导航
| 源页面 | 前置接口 | `response.data` 稳定字段 | 入口名称与可见条件 | 目标接口与参数映射 | 降级行为 |
|---|---|---|---|---|---|
| 卡列表 | `GET /api/admin/iot-cards/standalone` | `items[].id/iccid/virtual_no/shop_id/device_virtual_no/authorized_enterprise_id` | 平台显示“审计记录”,要求 `id` 非零;代理显示“活动记录”,要求 `iccid` 非空;企业不从此列表进入 | 平台:`/audit/resources/iot_card/{id}/timeline`;代理:`/agent/resource-activities/iot_card/{iccid}` | 缺少对应 ID/ICCID 时隐藏;企业改从企业卡列表进入 |
| 设备列表 | `GET /api/admin/devices` | `items[].id/virtual_no/imei/sn/shop_id/bound_card_count/authorized_enterprise_id` | 平台要求 `id`;代理要求 `virtual_no`;企业不从此列表进入 | 平台:`/audit/resources/device/{id}/timeline`;代理:`/agent/resource-activities/device/{virtual_no}` | 缺少字段时隐藏;企业改从企业设备列表进入 |
| 设备卡槽 | `GET /api/admin/devices/{virtual_no}/cards` | `bindings[].id/iot_card_id/iccid/slot_position/is_current` | 平台可分别查看卡和绑定审计,要求相应 ID代理可查看卡活动要求 ICCID企业仅在设备和卡均有效授权时显示卡活动 | 平台卡:`iot_card/{iot_card_id}`;平台绑定:`device_sim_binding/{bindings[].id}`;代理/企业卡:`iot_card/{iccid}` 的主体活动接口 | 不用绑定 ID 证明授权;设备时间线继续使用上层设备 ID/VirtualNo |
| 统一资产详情 | `GET /api/admin/assets/resolve/{identifier}` | `asset_type/asset_id/identifier/virtual_no/iccid/bound_device_id/cards[].card_id/exchange_trace[].asset_id/can_view` | 平台在 `asset_id` 存在时显示“审计记录”;代理在卡 ICCID 或设备 VirtualNo 存在时显示“活动记录”;企业不显示 | 平台:`card→iot_card/{asset_id}``device→device/{asset_id}`;代理:卡用 `iot_card/{iccid}`、设备用 `device/{virtual_no}` | 缺稳定字段时隐藏;换货轨迹仅 `can_view=true` 且资产 ID 存在时跳转;企业不得回退调用 resolve |
| 资产分配列表/详情 | `GET /api/admin/asset-allocation-records[/{id}]` | `items[].id/allocation_no/asset_type/asset_id/asset_identifier/from_owner_type/from_owner_id/to_owner_type/to_owner_id/related_device_id`;详情另有 `related_card_ids[]` | 平台要求记录或资产 ID代理要求 `allocation_no` 且当前关联资产或店铺仍在范围内;企业无独立入口 | 平台:`asset_allocation_record/{id}``{asset_type}/{asset_id}`;代理:`/agent/resource-activities/asset_allocation_record/{allocation_no}` | 后端独立复核分配归属;不能因可打开旧详情就视为有权;企业从已授权资产活动查看结论 |
| 换货列表/详情 | `GET /api/admin/exchanges[/{id}]` | `items[].id/exchange_no/old_asset_type/old_asset_id/new_asset_type/new_asset_id/shop_id/submitter_id` | 平台要求换货或资产 ID代理要求 `exchange_no` 且换货店铺仍在范围内;企业无独立入口 | 平台:`exchange_order/{id}`,旧新资产分别使用响应类型和 ID代理`/agent/resource-activities/exchange_order/{exchange_no}` | 旧新资产仅在各自仍可管理时开放;企业从有效授权资产活动查看结论 |
| 店铺列表/详情 | `GET /api/admin/shops[/{id}]` | `items[].id/shop_name/shop_code/parent_id/business_owner_account_id` 或详情同名字段 | 平台要求 `id`;代理要求 `shop_code` 且为自己或下级店铺;企业不显示 | 平台:`/audit/resources/shop/{id}/timeline`;代理:`/agent/resource-activities/shop/{shop_code}` | 缺字段或越权时隐藏/显示不可用,不搜索平台审计 |
| 企业列表 | `GET /api/admin/enterprises` | `items[].id/enterprise_name/enterprise_code/owner_shop_id` | 平台要求 `id`;代理要求 `enterprise_code` 且 owner shop 在范围内;企业自身不显示 | 平台:`enterprise/{id}`;代理:`/agent/resource-activities/enterprise/{enterprise_code}` | 当前没有企业详情接口,前端不得假设存在;企业 ID 不作为主体活动路径参数 |
| 企业卡列表 | `GET /api/admin/enterprises/{id}/cards` | `items[].id/iccid/virtual_no/device_id` | 平台要求 `id`;企业要求 `iccid` 且当前授权有效;代理不从此列表进入 | 平台:`iot_card/{id}`;企业:`/enterprise/resource-activities/iot_card/{iccid}` | 路由中的企业 ID 不作为授权证明;后端始终使用认证上下文复核 |
| 企业设备列表 | `GET /api/admin/enterprises/{id}/devices` | `items[].device_id/virtual_no` | 平台要求 `device_id`;企业要求 `virtual_no` 且当前授权有效;代理不从此列表进入 | 平台:`device/{device_id}`;企业:`/enterprise/resource-activities/device/{virtual_no}` | 字段为空或授权撤销时隐藏/显示活动不可用,不回退平台接口 |
## 账号、交易和资金页面逐行导航
| 源页面 | 前置接口 | `response.data` 稳定字段 | 入口名称与可见条件 | 目标接口与参数映射 | 降级行为 |
|---|---|---|---|---|---|
| 账号列表/详情 | `GET /api/admin/accounts[/{id}]` | 列表 `items[].id`;详情 `id` | 平台在 ID 非零时显示“审计记录” | `/audit/resources/account/{id}/timeline` | 缺 ID 时隐藏,不按用户名搜索 |
| 店铺列表/详情 | `GET /api/admin/shops[/{id}]` | 列表 `items[].id`;详情 `id` | ID 非零时显示“审计记录”和“资金链路” | 审计:`shop/{id}`;资金:`/audit/finance/timeline?shop_id={id}` | 缺 ID 时两个入口均隐藏 |
| 企业列表 | `GET /api/admin/enterprises` | `items[].id` | ID 非零时显示“审计记录” | `/audit/resources/enterprise/{id}/timeline` | 不假设存在企业详情接口 |
| 订单列表/详情 | `GET /api/admin/orders[/{id}]` | `items[].id/order_no` 或详情 `id/order_no` | ID 非零时显示“审计记录”和“资金链路” | 审计:`order/{id}`;资金:`/audit/finance/timeline?order_id={id}` | 不要求前端补 payment/refund ID缺 ID 时隐藏 |
| 退款列表/详情 | `GET /api/admin/refunds[/{id}]` | `items[].id/refund_no/order_id/approval_instance_id` 或详情同名字段 | ID 非零时显示审计和资金入口;审批 ID 非零时显示“审批审计” | 审计:`refund/{id}`;资金:`finance/timeline?refund_id={id}`;审批:`approval_instance/{approval_instance_id}` | 缺审批 ID 只隐藏审批入口,不解析退款编号猜测 |
| 代理充值列表/详情 | `GET /api/admin/agent-recharges[/{id}]` | `items[].id/recharge_no/shop_id/agent_wallet_id/approval_instance_id` 或详情同名字段 | ID 非零时显示审计和资金入口;审批 ID 非零时显示审批审计 | 审计:`agent_recharge/{id}`;资金:`finance/timeline?recharge_id={id}` | 缺 `payment_no` 由服务端关联,不要求前端补猜 |
| 代理在线充值结果 | `POST /api/admin/agent-recharges` | `recharge_id/recharge_no/payment_no` | `recharge_id` 非零时显示“资金链路” | `finance/timeline?recharge_id={recharge_id}`;可附加 `payment_no` 精确筛选 | `payment_no` 不直接构造 Integration 详情;缺 recharge ID 时隐藏 |
| 资产钱包 | `GET /api/admin/assets/{identifier}/wallet` | `wallet_id/resource_type/resource_id` | `wallet_id` 非零时显示“资金链路”;资源类型和 ID 齐全时显示“资产审计” | 资金:`finance/timeline?wallet_id={wallet_id}`;审计:`resources/{resource_type}/{resource_id}/timeline` | 两个入口独立判断;缺某组字段只隐藏对应入口 |
| 店铺资金概况 | `GET /api/admin/shops/fund-summary` | `items[].shop_id` | `shop_id` 非零时显示行内“资金链路” | `finance/timeline?shop_id={shop_id}` | 不要求该接口未返回的 agent wallet ID |
| 店铺主钱包流水 | `GET /api/admin/shops/{shop_id}/main-wallet/transactions` | path `shop_id``items[].id/asset_type/asset_id/asset_identifier` | 始终可按合法 path 显示资金入口;资产类型和 ID 齐全时显示资产审计 | 资金:`finance/timeline?shop_id={shop_id}`;审计:`resources/{asset_type}/{asset_id}/timeline` | 缺资产 ID 仍保留店铺资金入口,不按资产编号猜测 |
| 资产钱包流水 | `GET /api/admin/assets/{identifier}/wallet/transactions` | 上层钱包接口 `wallet_id``items[].id/reference_type/reference_no` | 上层 `wallet_id` 非零时显示“资金链路” | `finance/timeline?wallet_id={wallet_id}` | `reference_type/reference_no` 仅展示;需后端节点明确返回资源引用后才能继续跳转 |
## 调查节点逐行跳转
| `investigation_refs` 字段 | 入口名称 | 可见条件 | 目标接口与参数 | 降级行为 |
|---|---|---|---|---|
| `event_id` | “事件详情” | 非空 | `GET /api/admin/audit/events/{event_id}` | 空值隐藏 |
| `actor_ref.kind/id` | “查看操作者行为” | kind 和 id 均非空 | `GET /api/admin/audit/actors/{kind}/{id}/events` | 任一缺失即隐藏,不用当前账号资料补齐 |
| `resource_refs[]` | “查看资源审计” | `resource_type/resource_id` 均非空 | `GET /api/admin/audit/resources/{resource_type}/{resource_id}/timeline` | 只有 Key 时先精确搜索;零或多命中不自动选择 |
| `request_id` | “查看请求链路” | 非空 | `GET /api/admin/audit/requests/{request_id}/timeline` | 空值隐藏,不扫描 Access Log 猜测 |
| `correlation_id` | “查看业务链路” | 非空 | `GET /api/admin/audit/correlations/{correlation_id}/timeline` | 空值隐藏,不按相近时间拼链路 |
| `integration_refs[].integration_id` | “查看外部交互” | 非空 | `GET /api/admin/audit/integrations/{integration_id}` | 空值隐藏,不使用数据库主键或相似资源猜测 |
`actor_ref.kind` 第一阶段只使用 `account/openapi/system_task/scheduled_job/external_system`。代理和企业活动响应不得包含 `investigation_refs`
## 六条完整调用链
### 资产详情
1. 调用 `GET /api/admin/assets/resolve/{identifier}`
2. 平台读取 `data.asset_type/asset_id`:卡将 `card` 转为 `iot_card`,调用 `GET /api/admin/audit/resources/iot_card/{asset_id}/timeline?page=1&page_size=20`;设备调用 `.../device/{asset_id}/timeline`
3. 代理读取 `data.iccid``data.virtual_no`,调用对应 `/agent/resource-activities/...`
4. 字段缺失时隐藏入口;企业不调用 resolve也不回退平台接口。
### 订单
1. 调用 `GET /api/admin/orders/{id}`,读取 `data.id`
2. “审计记录”调用 `GET /api/admin/audit/resources/order/{id}/timeline`
3. “资金链路”调用 `GET /api/admin/audit/finance/timeline?order_id={id}&page=1&page_size=20`
4. Payment、Refund、钱包等关联由服务端解析前端不补猜。
### 退款
1. 调用 `GET /api/admin/refunds/{id}`,读取 `data.id` 和可选 `data.approval_instance_id`
2. 审计调用 `resources/refund/{id}/timeline`,资金调用 `finance/timeline?refund_id={id}`
3. 审批实例 ID 非零时再调用 `resources/approval_instance/{approval_instance_id}/timeline`
4. 审批字段缺失只隐藏审批入口,不影响退款审计与资金链路。
### 钱包
1. 调用 `GET /api/admin/assets/{identifier}/wallet`,读取 `data.wallet_id/resource_type/resource_id`
2. “资金链路”调用 `finance/timeline?wallet_id={wallet_id}`
3. “资产审计”调用 `resources/{resource_type}/{resource_id}/timeline`
4. 两组稳定字段分别判断,不使用交易备注或 reference 编号前缀推断资源。
### 通知
1. 点击通知后先调用 `GET /api/admin/notifications/{id}/target`
2. 仅当 `data.available=true` 时展示跳转。
3. `data.target_type=integration_log``data.target_key` 非空时,将 target key 原样作为 `integration_id` 调用 `GET /api/admin/audit/integrations/{target_key}`
4. 其他 target type 先进入对应业务详情,再按本文业务页面矩阵进入审计;不可用时只展示通知正文。
### 风险节点
1. 调用 `GET /api/admin/audit/risks/overview?created_from={from}&created_to={to}`
2. 点击风险、结果、动作或来源分桶时,将相同时间范围和对应稳定编码带入 `GET /api/admin/audit/risks/events`
3. 从明细 `items[].investigation_refs` 直接进入事件、操作者、资源、request、correlation 或 Integration 视角。
4. 缺少的引用入口隐藏;风险中心不提供处置、封禁或恢复按钮。
## 统一降级与事实边界
- 缺少目标接口必需的稳定 ID 或 identifier 时隐藏入口,不按名称、中文描述、时间或编号前缀猜测。
- 平台只有 Registry Key 时先调用精确资源搜索;零命中或多命中停留在搜索结果。
- 已删除资源只要调查节点仍有稳定资源类型和 ID就可查看事件快照时间线。
- 代理或企业遇到越权、授权撤销或资源不存在时统一显示“活动不可用”,不回退平台调查、资源搜索或旧 operation log。
- request/correlation 时间线的 `record_source` 保留 Audit Event、Integration Log、Outbox、Asynq 摘要和 Domain Ledger 引用的事实边界Outbox 投递成功不等于业务成功。
- 资金金额和余额以钱包流水及对应业务表为权威Audit Event 仅用于解释谁做了什么,不用于资金重算。
- 旧 operation log 仅由平台独立历史入口访问,不拼接到新 `/api/admin/audit/*`