Files
junhong_cmp_fiber/docs/feature-504-multi-view-audit-center/跨视角调查与前端导航契约.md
break c64f3d8b80
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m31s
全局审计完成
2026-08-07 11:02:52 +08:00

468 lines
42 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,作为审计链路和前端接入的单一说明入口。它回答以下问题:链路 ID 从哪里产生、存到哪里、不同入口为什么有的没有 `request_id`、15 条审计接口如何调用、每个入参与返回字段表示什么,以及前端应在哪些现有业务页面增加入口。
所有接口都使用统一响应 `{code,msg,data,timestamp}`;本文中的返回字段默认位于 `response.data`,列表项默认位于 `response.data.items[]`。本文定义调用契约,不包含前端页面实现;机器可读类型、枚举和约束以 `docs/admin-openapi.yaml` 为准。
## 一、先理解五个稳定 ID
| 字段 | 表示什么 | 谁产生 | 前端能否产生 | 主要用途 |
|---|---|---|---|---|
| `request_id` | 一次 HTTP 请求 | 请求携带 `X-Request-ID` 时沿用;否则 Fiber 中间件生成 UUID | 业务页面不应为了审计查询临时生成HTTP 客户端可统一传入自己的全局唯一 ID | 把 Access Log、同一次 HTTP 内的 Audit Event、Integration Log 和 Outbox 串起来 |
| `correlation_id` | 一条可跨 HTTP、Outbox、Asynq、Callback 和外部交互的业务链路 | HTTP 起点默认等于 `request_id`进入异步或具体业务后可改为订单号、支付号、Integration ID、任务 ID 等稳定链路值 | 不得根据时间、资源或中文描述推断 | 查询完整业务链路;它不表示技术重试 |
| `parent_event_id` | 一个审计事件的直接父事件 | 批量根事件、异步派生或消费者在创建子事件时传播 | 不生成 | 表示直接因果和批量根子关系,不替代 correlation |
| `integration_id` | 一次外部交互尝试的稳定 ID | Integration Log Writer 生成或调用方提供 | 不生成 | 查询单次外部交互详情,也是轮询链路的重要入口 |
| `trigger_series` + `attempt` | 同一外部操作的显式技术尝试序列和序号 | 可重试的 Integration 调用方 | 不生成 | 判断第几次重试;没有 series 时只展示单次记录,禁止猜测重试 |
### `request_id` 的实际生命周期
1. 浏览器、开放接口调用方或外部回调发起 HTTP 请求。调用方如已提供 `X-Request-ID`,服务端沿用;否则服务端生成 UUID。
2. 服务端把同一个值写入响应头 `X-Request-ID`,浏览器可读取该响应头用于问题反馈。
3. Access Log 把它记录为 `request_id`,同时审计上下文在 HTTP 起点设置 `request_id`,并默认设置 `correlation_id=request_id`
4. 当前请求内发生业务变更时Audit Writer 将其写入 `tb_audit_event.request_id`;发生外部交互时可写入 `tb_integration_log.request_id`;创建可靠事件时可写入 `tb_outbox_event.request_id`
5. 前端只有在事件或 Integration 返回了该值,或运维人员从响应头/Access Log 得到该值时,才调用请求时间线。普通资源详情页不需要先取得 `request_id`
不是每个 HTTP 请求都会产生 Audit Event。例如普通列表查询只有 Access Log没有业务审计事件此时即使响应头有 `X-Request-ID`,请求时间线也可能没有业务节点。
### `request_id` 在哪里落库
| 事实 | 存储位置 | `request_id` 字段 | `correlation_id` 字段 | 说明 |
|---|---|---|---|---|
| HTTP 调试事实 | Access Log 文件 | JSON 字段 `request_id` | 当前访问日志不承担业务 correlation 查询 | 用于按请求排查原始 HTTP审计时间线不会扫描日志文件 |
| Audit Event | `tb_audit_event` | `request_id`,非空列但允许保存空字符串 | `correlation_id`,非空列但允许保存空字符串 | 解释谁对什么资源做了什么 |
| Integration Log | `tb_integration_log` | `request_id`,可空 | `correlation_id`,可空 | 解释调用了哪个外部系统、结果和尝试序列 |
| Outbox | `tb_outbox_event` | `request_id`,允许空字符串 | `correlation_id`,允许空字符串 | 可靠投递事实;投递成功不等于业务成功 |
| Asynq | 任务 payload 或 Audit Event 的任务资源引用 | 按任务类型选择性携带 | 按任务类型选择性携带 | Redis 队列不是审计查询数据源;查询只展示已持久化引用,不扫描历史队列 |
| 手动轮询运行记录 | `tb_polling_manual_trigger_log` | 没有该字段 | 没有该字段 | 只承担进度、结果、触发人和卡列表;人工触发命令另写 Audit Event |
## 二、轮询为什么通常没有 `request_id`
普通自动轮询由 Scheduler/Asynq Worker 触发,不是 HTTP 请求,因此没有 `request_id` 是正确语义:
1. Worker 调用 Gateway 前创建 Integration Log生成 `integration_id`
2. 普通轮询的 `tb_integration_log.request_id` 保持空;`trigger_series``correlation_id` 通常使用该 `integration_id`
3. Gateway 返回后如果观测结果实际改变卡、设备、套餐或网络状态Worker 才写 Audit Event。
4. 该 Audit Event 的 `request_id` 仍为空,`correlation_id` 使用 Integration ID、Asynq Task ID 或稳定任务标识,从而与外部尝试和资源时间线关联。
5. 前端看到 `request_id` 为空时隐藏“请求链路”;`correlation_id` 非空时显示“业务链路”;无论两者是否存在,都可以继续使用 `integration_id` 或资源引用。
人工轮询需要区分两个事实:
- 管理员点击“手动触发”的 HTTP 命令会产生 Audit Event因此该“谁发起了轮询”事件有 `request_id`
- 随后真正执行轮询的是 Worker。当前手动队列只携带卡 ID执行阶段没有原 HTTP `request_id`Gateway 尝试和实际观测变化按 Worker 自身的 Integration ID/correlation 串联。
- `tb_polling_manual_trigger_log` 继续显示进度和结果Audit Event 解释谁触发/取消Integration Log 解释 Gateway 是否被调用以及调用结果。三类事实不能合并成一张记录。
## 三、前端选择入口的顺序
前端不应把 `request_id` 当作所有审计入口的必填条件。统一按以下顺序选择:
1. 业务页面已有内部 ID平台直接调用资源时间线。
2. 代理或企业页面已有 ICCID、VirtualNo、分配单号、换货单号等业务 identifier调用主体资源活动接口。
3. 订单、退款、充值、钱包或店铺页面:直接以任一稳定业务 ID 调用资金时间线,关联 ID 由服务端补全。
4. 审计节点返回 `investigation_refs`按引用字段跳转事件、操作者、资源、请求、correlation 或 Integration 视角。
5. 只有 `resource_key` 没有内部 ID先调用资源精确搜索零命中或多命中时让用户选择不自动猜测。
6. 必需字段不存在:隐藏入口;不得用名称、中文说明、相近时间、编号前缀或第三方流水猜关系。
## 四、15 条接口总览
| 接口 | 何时调用 | 必填入参 | 主要返回 |
|---|---|---|---|
| `GET /api/admin/audit/events` | 平台审计中心全局列表或其他节点带筛选跳转 | 无 | `EventPage` |
| `GET /api/admin/audit/events/{event_id}` | 查看一个稳定审计事件 | `event_id` | `EventDetail` |
| `GET /api/admin/audit/actors/{kind}/{id}/events` | 查看某个操作者行为 | `kind``id` | `EventPage` |
| `GET /api/admin/audit/resources/search` | 只有业务 Key、没有内部资源 ID | `resource_type``keyword` | `ResourceSearchPage` |
| `GET /api/admin/audit/resources/{resource_type}/{resource_id}/timeline` | 平台业务页面或资源引用进入审计 | `resource_type``resource_id` | `EventPage` |
| `GET /api/admin/audit/requests/{request_id}/timeline` | 已有真实 request ID 时调查一次 HTTP | `request_id` | `LinkTimeline` |
| `GET /api/admin/audit/correlations/{correlation_id}/timeline` | 调查跨请求、异步和外部业务链路 | `correlation_id` | `LinkTimeline` |
| `GET /api/admin/audit/finance/timeline` | 从订单、退款、充值、钱包、店铺等进入资金调查 | 至少一个稳定资金条件 | `FinanceTimelinePage` |
| `GET /api/admin/audit/risks/overview` | 平台风险中心总览 | 无;时间不传时使用在线窗口 | `RiskOverview` |
| `GET /api/admin/audit/risks/events` | 从风险分桶查看明细 | 无;分页和筛选可选 | `RiskEventPage` |
| `GET /api/admin/audit/integrations/overview` | 平台 Integration 调查总览 | 无;时间不传时使用在线窗口 | `IntegrationOverview` |
| `GET /api/admin/audit/integrations` | 外部交互列表和组合筛选 | 无 | `IntegrationListPage` |
| `GET /api/admin/audit/integrations/{integration_id}` | 查看单次外部交互和尝试序列 | `integration_id` | `IntegrationDetailResponse` |
| `GET /api/admin/agent/resource-activities/{resource_type}/{identifier}` | 代理查看授权范围内资源活动 | `resource_type``identifier` | `SubjectActivityPage` |
| `GET /api/admin/enterprise/resource-activities/{resource_type}/{identifier}` | 企业查看当前有效授权卡或设备活动 | `resource_type``identifier` | `SubjectActivityPage` |
平台 `/audit/*` 接口仅允许超级管理员和平台账号访问;代理、企业必须使用各自的主体活动接口。全部为 GET不提供修改、删除、导出、恢复、重试、补偿或风险处置。
## 五、请求参数字典
### 通用时间与分页
| 字段 | 类型 | 是否必填 | 含义 |
|---|---|---|---|
| `created_from` | RFC3339 字符串 | 否 | 起始时间,包含该时刻;未传时从在线留存窗口开始 |
| `created_to` | RFC3339 字符串 | 否 | 结束时间,不包含该时刻;未传时为当前时间 |
| `page` | int | 否 | 页码,默认 1 |
| `page_size` | int | 否 | 每页数量,默认 20最大 100 |
显式时间早于 `retention.online_from` 或跨越在线边界时返回“数据已归档”稳定错误不会从对象存储读取部分结果。Integration 和风险查询最长连续 31 天。
### 全局事件 `GET /audit/events`
| 字段 | 类型/枚举 | 来源与用途 |
|---|---|---|
| `action` | string | 稳定动作编码,使用返回的 `action_code` |
| `category` | `configuration/reliability/asset/security/identity/business` | 动作类别 |
| `actor_kind` | `account/personal_customer/openapi/system_task/scheduled_job/external_system` | 操作者类型 |
| `actor_id` | string | 与 actor_kind 共同定位操作者 |
| `source` | `admin_api/personal_api/openapi/worker/scheduler/callback` | 操作入口 |
| `result` | `success/failed/denied/partial/unknown` | 业务结果 |
| `risk` | `low/normal/high/critical` | 风险等级 |
| `scope_type` | `platform/shop/personal_customer` | 业务范围类型 |
| `scope_id` | string | 业务范围稳定 ID |
| `resource_type` | Registry 类型 | 资源类型 |
| `resource_id` | string | 资源内部稳定 ID |
| `resource_key` | string | 资源业务稳定 Key |
| `request_id` | string | 可选精确筛选;来自真实 HTTP 请求,不是必填入口 |
| `correlation_id` | string | 可选精确筛选业务链路 |
| `created_from/to/page/page_size` | 通用字段 | 时间和分页 |
### 事件详情、操作者和资源
| 接口 | 参数 | 说明 |
|---|---|---|
| `/audit/events/{event_id}` | path `event_id` | 来自事件列表或 `investigation_refs.event_id` |
| `/audit/actors/{kind}/{id}/events` | path `kind/id`query `action/result/risk/resource_type/resource_id/created_from/created_to/page/page_size` | kind/id 来自 `actor_ref`action 使用返回的稳定编码 |
| `/audit/resources/search` | query `resource_type/keyword/page/page_size` | resource_type 仅 `iot_card/device/shop/order/refund`keyword 为精确业务标识 |
| `/audit/resources/{resource_type}/{resource_id}/timeline` | path `resource_type/resource_id`query `created_from/created_to/action/result/page/page_size` | path 来自业务页面内部 ID、搜索结果或 resource_refs |
资源搜索 keyword 规则:卡支持 ICCID/VirtualNo设备支持 VirtualNo/IMEI/SN店铺使用店铺编号订单使用订单号退款使用退款单号。
### request 与 correlation 时间线
| 接口 | 参数 | 说明 |
|---|---|---|
| `/audit/requests/{request_id}/timeline` | path `request_id` | 来自 Event/Integration 返回字段、响应头或 Access Log普通轮询为空时不调用 |
| `/audit/correlations/{correlation_id}/timeline` | path `correlation_id` | 来自 Event、Integration、Outbox/任务或业务详情稳定字段 |
这两个接口不接受分页返回在线窗口内全部已持久化关联节点。request 查询会同时返回 `access_log_lookup_request_id`,供运维复制到 Access Log 检索;接口自身不扫描 Access Log。
### 资金时间线 `GET /audit/finance/timeline`
| 字段 | 类型 | 前端来源 |
|---|---|---|
| `shop_id` | uint | 店铺详情或资金概况 |
| `wallet_id` | uint | 代理主钱包或资产钱包详情 |
| `order_id/order_no` | uint/string | 订单列表或详情 |
| `payment_id/payment_no` | uint/string | 支付记录或已明确返回的支付单号 |
| `refund_id/refund_no` | uint/string | 退款列表或详情 |
| `recharge_id/recharge_no` | uint/string | 代理充值或个人资产充值 |
| `approval_instance_id` | uint | 审批实例 |
| `third_party_trade_no` | string | 已明确返回的第三方交易号 |
| `actor_kind/actor_id` | enum/string | 调查某操作者涉及的资金事实,必须成对提供 |
| `correlation_id` | string | 已有稳定业务链路时使用 |
| `created_from/to/page/page_size` | 通用字段 | 时间和分页 |
调用方至少提供一个稳定资金条件;同一业务的其他 ID 由服务端解析,不要求前端补齐。
### 风险接口
| 接口 | 参数 | 映射 |
|---|---|---|
| `/audit/risks/overview` | `created_from/created_to/risk/result/action/source` | 独立风险中心筛选 |
| `/audit/risks/events` | 上述字段 + `page/page_size` | `risks[].code→risk``results[].code→result``actions[].code→action``sources[].code→source` |
### Integration overview/list
overview 和 list 共用以下筛选overview 另有 `bucket=hour/day`list 另有 `page/page_size`
| 字段 | 类型/枚举 | 来源与用途 |
|---|---|---|
| `integration_id` | string | 列表、通知 target 或 investigation_refs 返回的稳定 ID |
| `provider` | `ctcc/cmcc/cucc/wechat_pay/alipay/fuiou/wecom/gateway` | 外部服务提供方 |
| `direction` | `inbound/outbound` | 入站或出站 |
| `operation` | OpenAPI enum | 外部操作稳定编码,使用列表/详情返回的 operation |
| `result` | `pending/success/failed/unknown/not_found/invalid_payload/conflict/ignored/merged/rate_limited/completed/cancelled` | 原始结果 |
| `result_category` | `processing/succeeded/indeterminate/failed/not_sent` | 服务端从 result 派生的固定类别 |
| `external_id` | string | 外部系统业务或请求标识 |
| `resource_type/resource_id/resource_key` | string | 本地主要资源稳定引用 |
| `trigger_source/trigger_scene/trigger_series` | string | 触发来源、业务场景和显式尝试序列 |
| `state_changed` | bool | 是否改变本地业务状态 |
| `http_status` | 100599 | 外部 HTTP 状态码 |
| `provider_code` | string | 外部服务稳定结果码 |
| `request_id/correlation_id` | string | 已有稳定链路字段时筛选;自动轮询 request_id 为空 |
| `created_from/to` | 通用字段 | 时间范围,最长 31 天 |
当前 operation 枚举为:`realname_callback``realname_removal_callback``payment_precreate``payment_query``payment_callback``get_access_token``list_visible_members``list_visible_departments``get_template_detail``upload_approval_attachment``submit_approval``approval_callback``get_approval_detail``get_approval_info``query_realname_status``query_flow``query_card_status``query_device_info``set_speed_tier``stop_card``start_card``set_device_wifi``set_device_switch_mode``switch_device_card``reboot_device``reset_device`。前端使用 OpenAPI 或接口返回值,不维护另一份中文到编码映射。
### 代理与企业主体活动
| 接口 | `resource_type` | `identifier` | 其他参数 |
|---|---|---|---|
| `/agent/resource-activities/{resource_type}/{identifier}` | `iot_card/device/asset_allocation_record/exchange_order/shop/enterprise` | 依次使用 ICCID、VirtualNo、分配单号、换货单号、店铺编号、企业编号 | `created_from/created_to/page/page_size` |
| `/enterprise/resource-activities/{resource_type}/{identifier}` | 仅 `iot_card/device` | 卡用 ICCID设备用 VirtualNo | `created_from/created_to/page/page_size` |
代理店铺范围和企业 ID 只来自认证上下文,前端不能传 shop_id/enterprise_id 证明权限。
## 六、返回参数字典
### 统一响应与留存信息
| 字段 | 含义 |
|---|---|
| `code` | 统一响应码,成功为 0 |
| `msg` | 中文响应消息 |
| `data` | 本接口业务数据 |
| `timestamp` | 服务端响应时间 |
| `retention.online_from` | PostgreSQL 当前可在线查询的最早时间 |
| `retention.archived_before` | 早于该时间的数据已归档,不可通过这些接口读取 |
| `retention.timezone` | 留存边界时区,当前为 `Asia/Shanghai` |
### EventPage、EventDetail 与 EventView
`EventPage` 返回 `total/page/page_size/items[]/retention``EventDetail` 返回单个 EventView 并附带 retention。
| EventView 字段 | 含义/前端用法 |
|---|---|
| `event_id` | 稳定审计事件 ID可打开事件详情 |
| `occurred_at/created_at` | 业务事实发生时间/审计记录写入时间 |
| `category` | 动作类别稳定编码 |
| `action_code/action_name` | 稳定动作编码/中文展示名;筛选使用 code |
| `summary` | 中文事件摘要 |
| `actor_kind/actor_id/actor_name` | 操作者类型、稳定 ID、事件发生时名称快照 |
| `actor_shop_id/name` | 操作者所属店铺快照 |
| `actor_enterprise_id/name` | 操作者所属企业快照 |
| `source` | admin_api、worker、scheduler、callback 等入口来源 |
| `request_path/request_method/ip_address/user_agent` | HTTP 来源信息Worker/Scheduler 可为空 |
| `scope_type/scope_id/scope_name` | 业务范围类型、ID 和名称快照 |
| `result/risk_level` | 结果和风险等级稳定编码 |
| `error_code/error_summary` | 失败或拒绝时的稳定错误码和脱敏摘要 |
| `request_id` | 有值才显示“请求链路”;自动轮询通常为空 |
| `correlation_id` | 有值显示“业务链路” |
| `parent_event_id` | 直接父事件 ID |
| `batch_total/success_count/fail_count` | 批量根事件计数;非批量为 0 |
| `metadata` | 已脱敏动作扩展字段,具体含义由 action_code 定义 |
| `content_hash` | 事件不可变内容摘要 |
| `resources[]` | 事件涉及的全部资源及各自快照 |
| `investigation_refs` | 前端跨视角跳转的唯一稳定来源 |
`resources[]` 字段:`resource_type/resource_id/resource_key/display_name` 定位资源;`relation``primary/affected/reference``role` 为资源业务角色;`identity_snapshot` 为事件发生时身份;`before_data/after_data` 为该资源变更前后数据;`subject_visibility/subject_summary/subject_data` 为代理或企业安全投影;`sort_order/created_at` 为展示顺序和资源关联记录写入时间。
`investigation_refs` 字段:`event_id``actor_ref{kind,id}``resource_refs[]{resource_type,resource_id,resource_key,display_name}``request_id``correlation_id``integration_refs[]{integration_id}`。任一目标所需字段不完整时隐藏该入口。
### ResourceSearchPage
返回 `total/page/page_size/items[]/retention`。候选项包含 `resource_type/resource_id/resource_key/display_name/identity_snapshot/historical``historical=true` 表示当前业务表未命中,只从在线历史事件快照解析,不代表资源当前仍存在。
### LinkTimeline
| 字段 | 含义 |
|---|---|
| `request_id/correlation_id` | 本次查询使用的链路值,二者只会对应当前查询视角 |
| `access_log_lookup_request_id` | 可复制到 Access Log 的 request ID |
| `nodes[]` | 按发生时间稳定升序的跨事实节点 |
| `retention` | Audit 与 Integration 的共同在线边界 |
`nodes[]` 包含:`record_source``node_id``occurred_at``code/title``result/result_name``summary``reference_only``request_id/correlation_id/parent_event_id``resources[]``investigation_refs``fidelity`
`record_source` 可能是 `audit_event``integration_log``outbox_event`;由事件资源派生的只读引用还可能是 `asynq_task``domain_ledger_ref``reference_only=true` 只表示引用,不代表该节点独立改变了业务状态。`fidelity` 包含 `request_available``correlation_available``parent_event_available``direct_audit_link_available``stable_resource_available`false 时禁止猜测补齐。
### FinanceTimelinePage
返回 `total/page/page_size/items[]/retention`。每个资金节点包含:
- `record_source/node_id/occurred_at`:事实来源、来源内稳定 ID、发生时间。
- `code/title/result/result_name`:来源内稳定编码和中文名称。
- `amount/balance_before/balance_after/currency`:金额和余额,单位分,人民币为 CNY为空表示该节点不承载该金额。
- `shop_id/wallet{resource_type,wallet_id}`:店铺和钱包引用。
- `amount_authority{authoritative,table,field,conflict_rule}`:是否为权威金额及其数据库表字段;冲突时按 conflict_rule 展示。
- `facts`:该事实来源的安全结构化业务字段。
- `investigation_refs`继续进入事件、资源、操作者、request、correlation 或 Integration。
### RiskOverview 与 RiskEventPage
RiskOverview 返回:`total`、服务端选择的 `bucket=hour/day``signals/risks/results/actions/sources` 分布、`trend[]` 和 retention。所有分布项统一为 `{code,name,count}`,筛选使用 code、展示使用 name。
- `signals[].code``high_risk/finance/security/failed/denied/partial/unknown`
- `risks[].code``low/normal/high/critical`
- `results[].code``success/failed/denied/partial/unknown`
- `actions[].code`:稳定 action_code。
- `sources[].code``admin_api/personal_api/openapi/worker/scheduler/callback`
- `trend[]``bucket_at/total/high_risk/finance/security/failed/denied/partial/unknown`
RiskEventPage 与 EventPage 结构相同items[] 只包含固定风险集合内的 EventView。
### IntegrationOverview、ListPage 和 Detail
IntegrationOverview 返回 `total/anomaly_count/unknown_count/stale_pending_count/state_changed_count/average_duration_ms/p95_duration_ms/results/providers/directions/trend/retention``results[]``{code,name,category,count}`providers/directions 为 `{code,name,count}``trend[]``{bucket_at,total,succeeded,processing,indeterminate,failed,not_sent}`。分布 code 可原样回填 list 筛选name 只用于展示。
Integration ListPage 返回 `total/page/page_size/items[]/retention`。每项包含:
- `integration_id`:详情入口。
- `provider/provider_name``direction/direction_name``operation/operation_name`:稳定编码和中文名。
- `resource{type,id,key}`type/id 均存在时可进入资源时间线。
- `result/result_name/result_category`:原始结果、中文名和派生类别。
- `duration_ms/state_changed/created_at`:耗时、本地状态是否变化和创建时间。
- `request_id/correlation_id`:分别控制请求链路和业务链路入口;自动轮询 request_id 可为空。
Integration Detail 按分组返回:
| 分组 | 字段与用途 |
|---|---|
| `identity` | `integration_id/provider/provider_name/direction/direction_name/operation/operation_name/external_id` |
| `resource` | `type/id/key`type/id 齐全时进入资源时间线 |
| `trigger` | `source/scene/series/attempt`series 为空时不拼接其他重试 |
| `result` | `code/name/category/http_status/provider_code/provider_message/duration_ms/state_changed/recovery_strategy`;本接口只展示,不执行恢复 |
| `content` | 已脱敏 `request_summary/response_summary/metadata/content_hash` |
| `linkage` | `request_id/correlation_id/audit_event_id`;稳定事件跳转优先使用 investigation_refs 中的 event_id |
| `timestamps` | `scheduled_at/started_at/created_at/updated_at` |
| `attempts[]` | 同 series 的 `integration_id/attempt/operation/operation_name/sent/result/result_name/result_category/duration_ms/state_changed/created_at` |
| `fidelity` | `trigger_series_available/attempt_sequence_reliable/correlation_available/resource_id_available/provider_message_fidelity` |
| `retention` | 在线留存边界 |
### SubjectActivityPage
代理与企业安全活动返回 `resource/total/page/page_size/items[]/retention`
- `resource``related_resources[]``resource_type/resource_id/resource_key/display_name` 安全摘要;主体前端不得据此调用平台 `/audit/*`
- `items[].action_code/action_name`:安全动作编码和中文名。
- `items[].subject_summary/subject_data`:写入时生成的主体安全摘要和白名单业务字段,不是平台 before/after 删除字段后的结果。
- `items[].result/occurred_at`:结果和发生时间。
- 响应不包含平台 actor、risk、内部原因、before/after、Audit Event ID、request_id、correlation_id、Integration 内容或 investigation_refs。
## 七、跨视角只读接口
| 视角 | 接口 | 入参来源 | 响应重点 |
|---|---|---|---|
| 请求链路 | `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/personal_customer/openapi/system_task/scheduled_job/external_system`。代理和企业活动响应不得包含 `investigation_refs`
## 八、完整调用链
### HTTP 同步业务操作
1. 前端调用现有业务写接口;可不传 `X-Request-ID`,服务端会生成并通过响应头返回。
2. HTTP 中间件建立 `request_id`,审计上下文初始设置 `correlation_id=request_id`
3. 业务变更与 Audit Event 在同一事务提交,事件写入 `request_id/correlation_id` 和资源快照。
4. 前端从业务资源页用资源 ID 查看时间线,或从 EventView 的 `request_id` 查看该请求链路。
5. 请求时间线只返回已经落入 Audit、Integration、Outbox 的节点Access Log 需另用 `access_log_lookup_request_id` 检索。
### HTTP → Outbox → Asynq/Worker
1. HTTP 操作产生 `request_id`,业务确定或沿用 `correlation_id`
2. 创建 Outbox 时,未显式传入的 request/correlation/parent 从审计上下文继承并写入 `tb_outbox_event`
3. Relay 将三个字段随 envelope 交给消费者;需要继续入 Asynq 的任务在 payload 中显式携带。
4. Worker 恢复系统操作者并写子 Audit Event有外部调用时另写 Integration Log。
5. 前端用 correlation 时间线查看跨进程链路;`record_source` 区分 Audit、Outbox、Integration 和任务引用,不能把 Outbox delivered 当作业务成功。
### 外部 Callback
1. Callback 同样经过 HTTP RequestID 中间件,因此会有 `request_id`
2. Integration Log 先记录入站外部交互correlation 可等于 request ID也可使用 payment_no、审批 SPNo 等更稳定业务键。
3. Callback 实际改变本地业务事实时,以 `external_system` actor 写 Audit Event没有状态变化时只保留 Integration Log。
4. 前端从 Integration detail 的 request/correlation/resource 引用继续调查。
### 自动轮询
1. Scheduler/Asynq 选择卡并执行 Worker没有 HTTP request因此不生成 `request_id`
2. Worker 创建 Gateway Integration Log`integration_id=trigger_series=correlation_id``request_id=null`
3. Gateway 结果若改变内部事实Audit Event 使用同一 correlationactor 为 `system_task`source 为 `worker`
4. 前端从 Integration 调查中心、资源时间线或 correlation 时间线进入;隐藏“请求链路”。
### 人工即时刷新与手动轮询入队
- “人工即时刷新卡数据”在一个 HTTP 用例内直接执行 Gateway 查询:有 request_id外部尝试可继承该 request/correlation能从请求时间线串起。
- “手动触发轮询”先记录 HTTP 触发 Audit Event因此触发命令有 request_id但当前 Redis 手动队列只保存 card ID后续 Worker 没有原 request/correlation执行阶段按新的 Integration ID 建链。
- 因此当前不能仅凭 request_id 从“手动触发事件”自动跳到后续 Gateway 轮询尝试。前端分别展示手动任务进度、触发审计和资源/Integration 活动,不按时间拼接。若产品要求强关联,需要单独实施稳定 trigger/correlation 传播,不属于本文前端接入范围。
### 资产详情
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/*`