迭代计划准备
This commit is contained in:
809
docs/7月迭代/新增需求/04-全局多视角审计方案.md
Normal file
809
docs/7月迭代/新增需求/04-全局多视角审计方案.md
Normal file
@@ -0,0 +1,809 @@
|
||||
# 新增需求 04:全局多视角审计方案
|
||||
|
||||
> 状态:已合并至标准评审稿,本文保留为实施明细。
|
||||
> 评审主文档:`../7月迭代技术方案-标准评审稿.md`
|
||||
> 范围:全系统业务写操作、安全操作、外部集成和审计查询前端。
|
||||
|
||||
## 一、目标
|
||||
|
||||
审计系统必须能够从不同视角回答:
|
||||
|
||||
```text
|
||||
谁做了什么?
|
||||
某个资源经历了什么?
|
||||
一次请求引发了哪些跨模块变化?
|
||||
哪些操作失败、被拒绝或存在风险?
|
||||
外部 Gateway、运营商和企业微信交互发生了什么?
|
||||
资金变化对应哪个业务动作和审批结果?
|
||||
```
|
||||
|
||||
不是把所有日志塞进一张表,而是建立统一审计事件,并通过 Query 组合访问日志、领域流水和外部集成记录。
|
||||
|
||||
## 二、当前代码问题
|
||||
|
||||
现有实现已经有账号审计和资产审计,但没有形成全系统能力:
|
||||
|
||||
- `tb_account_operation_log` 和 `tb_asset_operation_log` 字段、操作类型和查询入口不一致。
|
||||
- 账号、资产审计通过 goroutine Best Effort 写入,进程退出或数据库短暂失败时会丢失。
|
||||
- 部分调用方又在审计服务外增加一层 goroutine,形成双重异步。
|
||||
- 退款审批等资金操作没有统一业务审计。
|
||||
- 线下充值借用账号操作日志,资源类型和关联关系不准确。
|
||||
- 访问日志只脱敏请求体,响应体仍按原文记录,可能泄露 Token、个人信息和敏感配置。
|
||||
- 现有审计表只能从单个账号或单个资产查看,无法按请求、关联业务、异常等级和外部交互串联。
|
||||
|
||||
## 三、四类记录边界
|
||||
|
||||
| 类型 | 权威性 | 存储 | 用途 |
|
||||
|---|---|---|---|
|
||||
| Access Log | 非业务权威 | 文件/日志平台 | HTTP 调试、性能和请求追踪 |
|
||||
| Audit Event | 业务审计权威 | PostgreSQL | 谁对什么做了什么、结果如何 |
|
||||
| Domain Ledger | 领域事实权威 | 现有业务表 | 钱包流水、退款单、审批实例、订单状态 |
|
||||
| Integration Log | 外部交互权威 | PostgreSQL | Gateway、运营商回调、企微 API、回调和同步尝试 |
|
||||
|
||||
规则:
|
||||
|
||||
- Audit Event 不替代钱包流水、退款记录和企微审批详情。
|
||||
- Access Log 不作为业务审计依据。
|
||||
- 数据同步的轮询、事件阶梯尝试、手动刷新和运营商回调统一进入 `tb_integration_log`,不再新建 `tb_card_sync_execution`。
|
||||
- 高频轮询只有在状态变化、人工强制触发、连续失败或高风险异常时生成 Audit Event。
|
||||
- Audit Query 可以把四类记录组合成时间线,但必须标明数据来源。
|
||||
|
||||
## 四、审计事件模型
|
||||
|
||||
### 4.1 主表
|
||||
|
||||
```sql
|
||||
CREATE TABLE tb_audit_event (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
event_id VARCHAR(64) NOT NULL UNIQUE,
|
||||
occurred_at TIMESTAMPTZ NOT NULL,
|
||||
category VARCHAR(32) NOT NULL,
|
||||
action VARCHAR(128) NOT NULL,
|
||||
action_name VARCHAR(255) NOT NULL,
|
||||
|
||||
actor_kind VARCHAR(32) NOT NULL,
|
||||
actor_id BIGINT,
|
||||
actor_name VARCHAR(255) NOT NULL DEFAULT '',
|
||||
actor_user_type INT,
|
||||
|
||||
source VARCHAR(64) NOT NULL,
|
||||
tenant_type VARCHAR(32),
|
||||
tenant_id BIGINT,
|
||||
shop_id BIGINT,
|
||||
enterprise_id BIGINT,
|
||||
|
||||
result VARCHAR(16) NOT NULL,
|
||||
risk_level VARCHAR(16) NOT NULL DEFAULT 'normal',
|
||||
summary TEXT NOT NULL,
|
||||
error_code VARCHAR(64),
|
||||
error_message TEXT,
|
||||
|
||||
before_data JSONB,
|
||||
after_data JSONB,
|
||||
metadata JSONB,
|
||||
|
||||
request_id VARCHAR(64),
|
||||
correlation_id VARCHAR(64),
|
||||
parent_event_id VARCHAR(64),
|
||||
|
||||
ip_address VARCHAR(64),
|
||||
user_agent TEXT,
|
||||
request_path VARCHAR(255),
|
||||
request_method VARCHAR(16),
|
||||
|
||||
content_hash VARCHAR(64) NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_audit_event_time
|
||||
ON tb_audit_event (occurred_at DESC, id DESC);
|
||||
CREATE INDEX idx_audit_event_actor
|
||||
ON tb_audit_event (actor_kind, actor_id, occurred_at DESC);
|
||||
CREATE INDEX idx_audit_event_action
|
||||
ON tb_audit_event (category, action, occurred_at DESC);
|
||||
CREATE INDEX idx_audit_event_result
|
||||
ON tb_audit_event (risk_level, result, occurred_at DESC);
|
||||
CREATE INDEX idx_audit_event_request
|
||||
ON tb_audit_event (request_id, occurred_at ASC);
|
||||
CREATE INDEX idx_audit_event_correlation
|
||||
ON tb_audit_event (correlation_id, occurred_at ASC);
|
||||
```
|
||||
|
||||
### 4.2 资源关系表
|
||||
|
||||
一次退款审批通过会同时影响退款单、订单、钱包、钱包流水、套餐和资产,只在主表保存一个 `resource_id` 无法完整查询。
|
||||
|
||||
```sql
|
||||
CREATE TABLE tb_audit_event_resource (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
audit_event_id BIGINT NOT NULL,
|
||||
resource_type VARCHAR(64) NOT NULL,
|
||||
resource_id BIGINT,
|
||||
resource_key VARCHAR(128) NOT NULL DEFAULT '',
|
||||
relation VARCHAR(20) NOT NULL DEFAULT 'affected',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX uq_audit_event_resource
|
||||
ON tb_audit_event_resource (
|
||||
audit_event_id,
|
||||
resource_type,
|
||||
COALESCE(resource_id, 0),
|
||||
resource_key,
|
||||
relation
|
||||
);
|
||||
|
||||
CREATE INDEX idx_audit_resource_timeline
|
||||
ON tb_audit_event_resource (resource_type, resource_id, audit_event_id DESC);
|
||||
CREATE INDEX idx_audit_resource_key
|
||||
ON tb_audit_event_resource (resource_type, resource_key, audit_event_id DESC);
|
||||
```
|
||||
|
||||
不建立数据库外键。`relation`:
|
||||
|
||||
| relation | 含义 |
|
||||
|---|---|
|
||||
| `primary` | 本次操作的主要对象 |
|
||||
| `affected` | 被本次操作修改的对象 |
|
||||
| `reference` | 只用于解释上下文的关联对象 |
|
||||
|
||||
### 4.3 操作者
|
||||
|
||||
`actor_kind`:
|
||||
|
||||
```text
|
||||
admin_user
|
||||
agent_user
|
||||
enterprise_user
|
||||
personal_customer
|
||||
open_api_account
|
||||
system_task
|
||||
carrier_callback
|
||||
wecom_callback
|
||||
```
|
||||
|
||||
系统和外部回调允许 `actor_id=NULL`,但必须保存清晰的 `actor_name`,例如“移动实名回调”“企微审批状态同步”“套餐到期任务”。
|
||||
|
||||
### 4.4 来源
|
||||
|
||||
`source` 表示入口,不表示操作者:
|
||||
|
||||
```text
|
||||
admin_api
|
||||
personal_api
|
||||
open_api
|
||||
asynq
|
||||
scheduled_job
|
||||
gateway
|
||||
carrier_callback.cmcc
|
||||
carrier_callback.cucc
|
||||
carrier_callback.ctcc
|
||||
wecom_callback
|
||||
wecom_polling
|
||||
data_migration
|
||||
```
|
||||
|
||||
### 4.5 结果与风险
|
||||
|
||||
`result`:`success / failed / denied / partial`。
|
||||
|
||||
`risk_level`:`normal / warning / high / critical`。
|
||||
|
||||
默认风险示例:
|
||||
|
||||
| 操作 | 风险等级 |
|
||||
|---|---|
|
||||
| 普通字段修改成功 | normal |
|
||||
| 批量部分失败 | warning |
|
||||
| 权限拒绝、重复回调冲突 | warning |
|
||||
| 钱包余额、退款、角色权限、支付配置修改 | high |
|
||||
| 企微通过后撤销但资金已执行、审计写入失败 | critical |
|
||||
|
||||
## 五、领域模型和代码结构
|
||||
|
||||
```text
|
||||
internal/
|
||||
├── domain/audit/
|
||||
│ ├── event.go 不可变 AuditEvent 实体及不变量
|
||||
│ ├── actor.go 操作者值对象
|
||||
│ ├── resource.go 多资源关联
|
||||
│ ├── sanitizer.go 审计数据脱敏规则
|
||||
│ ├── action_registry.go 操作编码、名称、分类和默认风险
|
||||
│ └── repository.go
|
||||
├── application/audit/
|
||||
│ ├── append_event.go 成功/失败审计写入
|
||||
│ ├── append_with_tx.go 关键业务事务内写入
|
||||
│ └── append_system_event.go
|
||||
├── query/audit/
|
||||
│ ├── event_list.go
|
||||
│ ├── actor_view.go
|
||||
│ ├── resource_view.go
|
||||
│ ├── request_view.go
|
||||
│ ├── correlation_view.go
|
||||
│ ├── risk_view.go
|
||||
│ ├── integration_view.go
|
||||
│ └── finance_view.go
|
||||
└── infrastructure/persistence/
|
||||
└── audit_repository.go
|
||||
```
|
||||
|
||||
Audit Event 具有独立 `event_id`,因此是不可变领域实体,不是值对象;Actor、Resource 和字段差异是值对象。事件创建后不提供 Update/Delete Repository 方法。
|
||||
|
||||
## 六、操作注册表
|
||||
|
||||
操作编码禁止在各 Service 中自由拼字符串:
|
||||
|
||||
```go
|
||||
type ActionDefinition struct {
|
||||
Code string
|
||||
Name string
|
||||
Category string
|
||||
DefaultRiskLevel string
|
||||
SensitiveFields []string
|
||||
}
|
||||
|
||||
var ActionRegistry = map[string]ActionDefinition{
|
||||
"account.role.assign": {
|
||||
Name: "分配账号角色", Category: "security", DefaultRiskLevel: "high",
|
||||
},
|
||||
"refund.wecom.approved": {
|
||||
Name: "企微审批通过并确认人工退款", Category: "finance", DefaultRiskLevel: "high",
|
||||
},
|
||||
"recharge.wecom.approved": {
|
||||
Name: "企微审批通过并完成线下充值", Category: "finance", DefaultRiskLevel: "high",
|
||||
},
|
||||
"iot_card.realname.callback": {
|
||||
Name: "运营商回调更新实名状态", Category: "asset", DefaultRiskLevel: "normal",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
DTO `description`、前端中文名称和筛选项都从同一注册表生成,避免操作编码与展示名称不一致。
|
||||
|
||||
## 七、写入可靠性
|
||||
|
||||
### 7.1 关键成功事件
|
||||
|
||||
以下事件必须与业务变更同事务写入:
|
||||
|
||||
- 钱包余额变化。
|
||||
- 人工退款结果确认和代理钱包回溯。
|
||||
- 线下充值入账。
|
||||
- 账号角色和权限变化。
|
||||
- 支付、企微和系统关键配置变化。
|
||||
- 卡实名、网络状态被人工修改。
|
||||
- 手工绑定企微 `sp_no`。
|
||||
|
||||
Application 在事务内调用:
|
||||
|
||||
```go
|
||||
auditWriter.AppendWithTx(ctx, tx, event)
|
||||
```
|
||||
|
||||
审计写入失败时事务回滚,禁止业务成功但关键审计缺失。
|
||||
|
||||
### 7.2 异步系统事件
|
||||
|
||||
轮询、事件阶梯同步、运营商回调和企微轮询等外部交互统一进入 Integration Log。只有以下情况同时写 Audit Event:
|
||||
|
||||
- 状态发生业务变化。
|
||||
- 连续失败达到阈值。
|
||||
- 人工强制触发。
|
||||
- 出现高风险异常。
|
||||
|
||||
系统事件通过同一业务事务或 Outbox 可靠写入,不启动裸 goroutine。
|
||||
|
||||
### 7.3 失败和拒绝事件
|
||||
|
||||
业务事务已经回滚时,使用独立短事务写 `failed/denied` 审计。审计写入失败不能把原业务错误改成成功,但必须记录 `critical` 应用日志和监控指标。
|
||||
|
||||
## 八、关联链路
|
||||
|
||||
### 8.1 request_id
|
||||
|
||||
表示一次 HTTP 请求,由现有 Request ID 中间件产生。一次请求触发的所有审计事件使用同一个 `request_id`。
|
||||
|
||||
### 8.2 correlation_id
|
||||
|
||||
表示跨请求、跨任务的完整业务链路:
|
||||
|
||||
```text
|
||||
退款申请创建
|
||||
→ 企微审批提交
|
||||
→ 企微回调
|
||||
→ 人工退款结果入账
|
||||
→ 代理钱包退款流水(仅代理钱包支付订单)
|
||||
→ 佣金回扣
|
||||
→ 套餐失效和停机
|
||||
```
|
||||
|
||||
以上事件共享退款申请创建时生成的 `correlation_id`。Asynq、Outbox、企微实例和同步任务载荷都必须传递该值。
|
||||
|
||||
### 8.3 parent_event_id
|
||||
|
||||
表示直接因果关系。例如“套餐失效”事件的父事件是“退款终态处理成功”,用于前端画出树状链路。
|
||||
|
||||
## 九、数据脱敏
|
||||
|
||||
### 9.1 永不进入审计的数据
|
||||
|
||||
```text
|
||||
password
|
||||
operation_password
|
||||
access_token / refresh_token
|
||||
agent_secret / app_secret
|
||||
callback_token / encoding_aes_key
|
||||
支付私钥、公钥原文
|
||||
短信验证码
|
||||
完整身份证号
|
||||
对象存储签名 URL
|
||||
企微 media_id
|
||||
```
|
||||
|
||||
这些字段直接删除,不保存为 `******`,避免字段存在本身造成误用。
|
||||
|
||||
### 9.2 按权限展示的数据
|
||||
|
||||
手机、IP、ICCID、钱包余额和第三方交易号可以存储受控快照,但 Query 根据权限返回:
|
||||
|
||||
- 普通审计权限:脱敏展示。
|
||||
- 敏感审计权限:展示完整值。
|
||||
- 导出权限单独控制,不能因为可查看页面就默认可导出完整值。
|
||||
|
||||
### 9.3 大字段
|
||||
|
||||
`before_data`、`after_data` 和 `metadata` 分别限制 16KB。超过后保存:
|
||||
|
||||
```json
|
||||
{
|
||||
"_truncated": true,
|
||||
"_original_bytes": 38210,
|
||||
"_summary": "批量修改500条资产",
|
||||
"_artifact_ref": "batch_task:123"
|
||||
}
|
||||
```
|
||||
|
||||
批量明细保存在对应任务结果表或对象存储,不塞入审计 JSON。
|
||||
|
||||
## 十、外部集成日志
|
||||
|
||||
企业微信、运营商回调、Gateway 请求和事件同步尝试使用通用 Integration Log。同步尝试即使在真正发送 HTTP 前因合并、限流或预期状态已满足而结束,也写一条结果记录,保证能够解释“为什么没有请求上游”。
|
||||
|
||||
```sql
|
||||
CREATE TABLE tb_integration_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
integration_id VARCHAR(64) NOT NULL UNIQUE,
|
||||
provider VARCHAR(32) NOT NULL,
|
||||
direction VARCHAR(16) NOT NULL,
|
||||
operation VARCHAR(64) NOT NULL,
|
||||
external_id VARCHAR(128),
|
||||
|
||||
resource_type VARCHAR(64),
|
||||
resource_id BIGINT,
|
||||
resource_key VARCHAR(128),
|
||||
|
||||
trigger_source VARCHAR(32),
|
||||
trigger_scene VARCHAR(128),
|
||||
trigger_series VARCHAR(64),
|
||||
scheduled_at TIMESTAMPTZ,
|
||||
started_at TIMESTAMPTZ,
|
||||
attempt INT NOT NULL DEFAULT 1,
|
||||
|
||||
result VARCHAR(20) NOT NULL,
|
||||
http_status INT,
|
||||
provider_code VARCHAR(64),
|
||||
provider_message TEXT,
|
||||
request_summary JSONB,
|
||||
response_summary JSONB,
|
||||
duration_ms BIGINT NOT NULL DEFAULT 0,
|
||||
state_changed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
metadata JSONB,
|
||||
|
||||
request_id VARCHAR(64),
|
||||
correlation_id VARCHAR(64),
|
||||
audit_event_id BIGINT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_integration_log_time
|
||||
ON tb_integration_log (created_at DESC, id DESC);
|
||||
CREATE INDEX idx_integration_log_provider
|
||||
ON tb_integration_log (provider, operation, result, created_at DESC);
|
||||
CREATE INDEX idx_integration_log_resource
|
||||
ON tb_integration_log (resource_type, resource_id, created_at DESC);
|
||||
CREATE INDEX idx_integration_log_resource_key
|
||||
ON tb_integration_log (resource_type, resource_key, created_at DESC);
|
||||
CREATE INDEX idx_integration_log_trigger
|
||||
ON tb_integration_log (trigger_series, attempt);
|
||||
CREATE INDEX idx_integration_log_correlation
|
||||
ON tb_integration_log (correlation_id, created_at ASC);
|
||||
```
|
||||
|
||||
`direction`:`inbound / outbound`。
|
||||
|
||||
`result` 至少支持:
|
||||
|
||||
```text
|
||||
success
|
||||
failed
|
||||
not_found
|
||||
invalid_payload
|
||||
ignored
|
||||
merged
|
||||
rate_limited
|
||||
completed
|
||||
cancelled
|
||||
```
|
||||
|
||||
数据同步字段约定:
|
||||
|
||||
- `provider=gateway`:实名、流量、卡状态和设备信息查询。
|
||||
- `provider=cmcc/cucc/ctcc` 且 `direction=inbound`:运营商实名或解除实名回调。
|
||||
- `trigger_source`:`polling/business_event/open_api/manual_refresh/carrier_callback`。
|
||||
- `trigger_scene`:触发埋点场景,例如 `client_asset_info`、`card_resume`、`device_switch_card`。
|
||||
- `trigger_series + attempt`:关联一次 `0/3/5` 阶梯同步。
|
||||
- `result=merged/rate_limited/completed` 时允许没有 HTTP 状态和响应摘要,因为没有真正请求上游。
|
||||
- `state_changed=true` 时通过 `audit_event_id` 关联对应业务状态变化事件。
|
||||
|
||||
普通高频轮询成功也写 Integration Log,但不生成 Audit Event。查询和清理必须使用时间索引及分批删除,不能在业务请求中同步聚合全量历史。
|
||||
|
||||
外部交互正文只保存脱敏摘要。企微加密报文、运营商原始回调和 Gateway 加密请求不进入普通审计详情。
|
||||
|
||||
## 十一、审计范围
|
||||
|
||||
### 11.1 必须审计的写操作
|
||||
|
||||
| 模块 | 操作 |
|
||||
|---|---|
|
||||
| 账号权限 | 创建、禁用、删除、角色分配、密码重置 |
|
||||
| 资产 | 分配、回收、绑定、解绑、停复机、实名策略、手工实名、限速 |
|
||||
| 套餐 | 创建、修改、上下架、分配、价格、已用量和到期时间调整 |
|
||||
| 钱包资金 | 充值、扣款、退款、信用变化、人工调整 |
|
||||
| 订单退款 | 创建退款、企微终态、人工退款确认、代理钱包回溯、资产后处理 |
|
||||
| 线下充值 | 创建、企微终态、入账、通过后撤销异常 |
|
||||
| 系统配置 | 支付配置、企微配置状态、模板版本发布、导出字段权限 |
|
||||
| 数据同步 | 人工强制同步、回调更新业务状态、连续失败告警 |
|
||||
| 导入导出 | 创建任务、下载敏感导出、批量任务结果 |
|
||||
| 登录安全 | 登录成功/失败、Token 撤销、开放接口签名失败和重放拒绝 |
|
||||
|
||||
### 11.2 不进入业务审计的普通读操作
|
||||
|
||||
- 普通列表、详情和未读数查询只进入 Access Log。
|
||||
- 下载敏感附件、导出、查看完整密钥状态、查看完整个人信息属于敏感读取,需要 Audit Event。
|
||||
|
||||
## 十二、多视角 API
|
||||
|
||||
### 12.1 全局事件视角
|
||||
|
||||
```http
|
||||
GET /api/admin/audit/events
|
||||
?category=finance
|
||||
&action=
|
||||
&result=success
|
||||
&risk_level=high
|
||||
&source=wecom_callback
|
||||
&start_at=
|
||||
&end_at=
|
||||
&keyword=
|
||||
&page=1&page_size=50
|
||||
```
|
||||
|
||||
`keyword` 只匹配事件摘要、资源编号、操作者名称和 request_id,不对 JSONB 做无索引模糊扫描。
|
||||
|
||||
### 12.2 事件详情
|
||||
|
||||
```http
|
||||
GET /api/admin/audit/events/{event_id}
|
||||
```
|
||||
|
||||
返回:
|
||||
|
||||
- 操作者快照。
|
||||
- 入口、租户和请求信息。
|
||||
- 主要资源、影响资源和引用资源。
|
||||
- 前后数据差异。
|
||||
- 错误信息和风险等级。
|
||||
- request/correlation/parent 关联。
|
||||
- 可访问的领域流水和 Integration Log 链接。
|
||||
|
||||
### 12.3 操作者视角
|
||||
|
||||
```http
|
||||
GET /api/admin/audit/actors
|
||||
?actor_kind=admin_user
|
||||
&keyword=
|
||||
&range=30d
|
||||
&page=1&page_size=20
|
||||
|
||||
GET /api/admin/audit/actors/{actor_kind}/{actor_id}/summary?range=30d
|
||||
GET /api/admin/audit/actors/{actor_kind}/{actor_id}/events?page=1&page_size=50
|
||||
```
|
||||
|
||||
人员列表返回操作者 ID、名称、类型、最近操作时间、操作量、失败量和最高风险等级;`keyword` 只做 ID、账号和名称的前缀匹配,不扫描审计 JSON。Summary 返回操作量、失败量、拒绝量、高风险量、常用操作、常操作资源和最近登录 IP。
|
||||
|
||||
### 12.4 资源视角
|
||||
|
||||
```http
|
||||
GET /api/admin/audit/resources/search
|
||||
?resource_type=
|
||||
&keyword=RF202607150001
|
||||
&page=1&page_size=20
|
||||
|
||||
GET /api/admin/audit/resources/{resource_type}/{resource_id}/timeline
|
||||
?include_related=true
|
||||
&page=1&page_size=50
|
||||
```
|
||||
|
||||
示例资源:`iot_card / device / refund / order / agent_wallet / wecom_approval / account / system_config`。
|
||||
|
||||
资源搜索属于 Query 模块,允许直接查询退款、订单、充值、账号和资产等读模型,不要求加载领域聚合。返回候选项的 `resource_type/resource_id/resource_key/display_name`,解决同一关键词命中多个资源的问题。静态 `resources/search` 路由必须先于动态资源路由注册。
|
||||
|
||||
`include_related=true` 时通过资源关系表返回影响该资源的跨模块事件,但不无限递归加载关联资源。
|
||||
|
||||
### 12.5 请求视角
|
||||
|
||||
```http
|
||||
GET /api/admin/audit/requests/{request_id}/timeline
|
||||
```
|
||||
|
||||
按时间展示一次 HTTP 请求产生的 Access Log 摘要、Audit Event、Outbox 和 Integration Log。
|
||||
|
||||
### 12.6 业务链路视角
|
||||
|
||||
```http
|
||||
GET /api/admin/audit/correlations/{correlation_id}/timeline
|
||||
```
|
||||
|
||||
用于查看退款、企微审批、钱包和资产处理等跨请求链路。返回节点包含 `parent_event_id`,前端可展示因果树。
|
||||
|
||||
### 12.7 风险视角
|
||||
|
||||
```http
|
||||
GET /api/admin/audit/risks/overview?range=24h
|
||||
GET /api/admin/audit/risks/events?risk_level=critical&page=1&page_size=50
|
||||
```
|
||||
|
||||
Overview:
|
||||
|
||||
- 高风险和严重事件数量。
|
||||
- 权限拒绝、开放接口重放、资金冲突和外部回调异常趋势。
|
||||
- 按操作人、来源和资源类型聚合。
|
||||
|
||||
### 12.8 外部集成视角
|
||||
|
||||
```http
|
||||
GET /api/admin/audit/integrations
|
||||
?provider=wecom
|
||||
&direction=inbound
|
||||
&operation=getapprovaldetail
|
||||
&result=failed
|
||||
&external_id=
|
||||
&resource_type=
|
||||
&resource_key=
|
||||
&trigger_source=
|
||||
&trigger_scene=
|
||||
&trigger_series=
|
||||
&page=1&page_size=50
|
||||
|
||||
GET /api/admin/audit/integrations/{integration_log_id}
|
||||
```
|
||||
|
||||
列表查询 `tb_integration_log`,并返回关联审计事件和业务链路 ID;详情返回脱敏后的请求摘要、响应摘要、耗时、错误、尝试次数、计划时间、触发场景和关联资源,不返回密钥、完整回调正文或附件内容。
|
||||
|
||||
当 `trigger_series` 有值时,详情同时返回该序列的全部尝试,前端按“立即、3 分钟、5 分钟”展示,不要求用户逐条搜索。同步记录不再通过独立 `/card-sync/executions` 接口查询。
|
||||
|
||||
### 12.9 资金视角
|
||||
|
||||
```http
|
||||
GET /api/admin/audit/finance/timeline
|
||||
?business_no=
|
||||
&wallet_id=
|
||||
&transaction_no=
|
||||
&page=1&page_size=50
|
||||
```
|
||||
|
||||
Finance Query 组合:
|
||||
|
||||
- Audit Event。
|
||||
- 代理钱包和资产钱包流水。
|
||||
- 退款单、充值单和订单。
|
||||
- 企微审批实例。
|
||||
|
||||
每条记录明确 `record_source`,钱包流水仍是金额变化的权威数据。
|
||||
|
||||
### 12.10 导出
|
||||
|
||||
```http
|
||||
POST /api/admin/audit/exports
|
||||
```
|
||||
|
||||
复用现有导出任务系统。导出字段按角色配置,默认不允许导出完整 IP、手机号、ICCID、前后 JSON 和外部响应摘要。
|
||||
|
||||
## 十三、权限
|
||||
|
||||
建议权限码:
|
||||
|
||||
```text
|
||||
audit:global:view
|
||||
audit:actor:view
|
||||
audit:resource:view
|
||||
audit:request:view
|
||||
audit:risk:view
|
||||
audit:integration:view
|
||||
audit:finance:view
|
||||
audit:sensitive:view
|
||||
audit:export
|
||||
```
|
||||
|
||||
第一版审计中心只开放给超级管理员和具有对应权限的平台角色。
|
||||
|
||||
代理、企业账号不进入全局审计中心;它们在资产、订单、充值等业务详情页只能查看自己有权限资源的资源时间线,且返回字段经过脱敏。
|
||||
|
||||
Service/Query 必须重新应用资源权限,前端隐藏 Tab 不能替代后端授权。
|
||||
|
||||
## 十四、前端多视角界面
|
||||
|
||||
### 14.1 审计中心
|
||||
|
||||
路由:`/operations/audit`
|
||||
|
||||
使用工作台式紧凑布局,不做营销式卡片页面。顶部为时间范围、关键词和常用过滤器,下方使用 Tab:
|
||||
|
||||
1. **全局事件**:按时间倒序的审计事件表。
|
||||
2. **人员行为**:操作者列表、风险统计和行为时间线。
|
||||
3. **资源轨迹**:输入资源类型和标识,展示资源生命周期。
|
||||
4. **请求链路**:按 request_id/correlation_id 展示跨模块时间线。
|
||||
5. **资金审计**:订单、审批、钱包流水和退款/充值组合视图。
|
||||
6. **风险事件**:失败、拒绝、高风险和严重事件。
|
||||
7. **外部集成**:Gateway、企微、运营商回调和事件同步阶梯尝试。
|
||||
|
||||
Tab 根据权限返回,前端不展示无权限入口。
|
||||
|
||||
外部集成 Tab 增加紧凑的来源切换:`全部 / Gateway 同步 / 运营商回调 / 企业微信`。选择 Gateway 同步后展示触发来源、触发场景、资源、序列、尝试、结果、耗时和状态是否变化;点击序列打开三次尝试时间线。
|
||||
|
||||
### 14.2 全局事件表
|
||||
|
||||
字段:
|
||||
|
||||
```text
|
||||
时间 | 风险 | 操作者 | 操作 | 主要资源 | 来源 | 结果 | request_id
|
||||
```
|
||||
|
||||
- 支持服务端分页和列筛选。
|
||||
- 点击行打开右侧详情抽屉。
|
||||
- 风险仅用图标和有限颜色表达,普通成功事件保持中性色。
|
||||
- `request_id`、资源编号可点击进入对应视角。
|
||||
|
||||
### 14.3 事件详情抽屉
|
||||
|
||||
分区:
|
||||
|
||||
```text
|
||||
事件摘要
|
||||
操作者与入口
|
||||
关联资源
|
||||
字段变更对比
|
||||
请求与业务链路
|
||||
错误和外部交互
|
||||
```
|
||||
|
||||
字段变更使用结构化键值对比,不直接把整段 JSON 原样堆在页面。未知字段可以折叠显示原始 JSON。
|
||||
|
||||
敏感字段默认脱敏;有 `audit:sensitive:view` 权限时提供“显示敏感数据”按钮,并对该次查看再写一条敏感读取审计。
|
||||
|
||||
### 14.4 人员行为视角
|
||||
|
||||
左侧为操作者搜索和筛选,右侧显示:
|
||||
|
||||
- 30 天操作量、失败率、高风险操作数。
|
||||
- 常用来源 IP。
|
||||
- 操作类型分布。
|
||||
- 最近行为时间线。
|
||||
- 涉及资源列表。
|
||||
|
||||
不做基于行为的自动封禁,只提供审计判断依据。
|
||||
左侧人员列表调用 `/api/admin/audit/actors`,选择人员后再并行加载 Summary 和事件列表,避免前端从全局事件自行聚合。
|
||||
|
||||
### 14.5 资源轨迹视角
|
||||
|
||||
支持输入 ICCID、设备虚拟号、退款单号、订单号、充值单号、账号名称等,先调用 `/api/admin/audit/resources/search` 返回候选资源;只有一个候选时直接打开时间线,多个候选时由用户选择,前端不自行猜测资源类型。
|
||||
|
||||
时间线同时展示:
|
||||
|
||||
- 业务状态变化。
|
||||
- 人工操作。
|
||||
- 企微审批结果。
|
||||
- 资金流水链接。
|
||||
- 重要 Gateway/回调事件。
|
||||
|
||||
普通高频轮询成功不进入资源审计时间线,避免有效信息被淹没。
|
||||
|
||||
### 14.6 请求和业务链路视角
|
||||
|
||||
使用纵向时间线展示节点,节点固定包含时间、来源、动作、结果和耗时。父子事件使用缩进和连接线表达,不使用复杂自由拖拽图编辑器。
|
||||
|
||||
### 14.7 风险视角
|
||||
|
||||
顶部显示 24 小时趋势和风险分类,下方是风险事件表。支持一键跳转操作者、资源和 correlation 链路,但第一版不建设风险处置工单。
|
||||
|
||||
## 十五、Access Log 修正
|
||||
|
||||
当前访问日志的响应体需要使用与请求体相同的递归脱敏逻辑:
|
||||
|
||||
```go
|
||||
responseBody := sanitizeBody(c.Response().Body())
|
||||
```
|
||||
|
||||
并增加路由级策略:
|
||||
|
||||
| 路由 | Body 记录策略 |
|
||||
|---|---|
|
||||
| 登录、Token、支付配置 | 只记录字段名和长度,不记录值 |
|
||||
| 企微/支付/运营商回调 | 记录摘要和哈希,不记录完整正文 |
|
||||
| 文件上传下载 | 不记录文件内容和签名 URL |
|
||||
| 普通 JSON API | 脱敏后最多 50KB |
|
||||
|
||||
Access Log 建议保留 30 天;不得因为 Access Log 已存在而省略关键业务 Audit Event。
|
||||
|
||||
## 十六、保留策略
|
||||
|
||||
| 数据 | 保留期限 |
|
||||
|---|---:|
|
||||
| 资金、权限、审批和关键配置 Audit Event | 5 年 |
|
||||
| 普通资产和业务操作 Audit Event | 2 年 |
|
||||
| Integration Log | 180 天 |
|
||||
| Gateway 高频同步 Integration Log | 正常 30 天,异常或状态变化 180 天 |
|
||||
| Access Log | 30 天 |
|
||||
|
||||
Audit Event 默认不在线删除。数据量达到单表维护阈值后按月分区,超期分区归档到低成本存储,再由专用维护流程删除;应用账号不具备 Update/Delete 审计表权限。
|
||||
|
||||
## 十七、一次性审计切换
|
||||
|
||||
本需求与普通 DDD 触碰式迁移不同:审计基础设施、写入入口、Query API 和前端多视角界面在同一次停机发布中全局切换,不能让新旧审计长期并行。
|
||||
|
||||
### 17.1 新写入
|
||||
|
||||
- 数据同步、企微审批、站内通知和后续新功能只写统一 `tb_audit_event` 与 `tb_integration_log`。
|
||||
- 账号、角色、权限、资产、套餐、订单、退款、充值、钱包、系统配置、导入导出和登录安全等现有敏感操作同时切换到统一 Audit Writer。
|
||||
- 卡同步不创建 `tb_card_sync_execution`;轮询、手动刷新、事件阶梯尝试和运营商回调统一写 Integration Log。
|
||||
- 发布后旧 `tb_account_operation_log`、`tb_asset_operation_log`、手动轮询触发日志等表不再产生新记录。
|
||||
- 禁止双写新旧审计表,避免同一操作产生两条不一致记录。
|
||||
|
||||
一次性切换审计不等于一次性把所有旧业务模块迁移为 DDD。未迁移的旧 Service 通过统一审计 Adapter 写入新模型;退款、资金、卡状态等复杂且本次触碰的用例按 DDD 规范迁入 Application/Domain。
|
||||
|
||||
### 17.2 历史数据
|
||||
|
||||
- 旧审计表保留只读,不在线回填到新表。
|
||||
- 审计 Query 使用 `UNION ALL` 把旧账号、资产和手动触发日志标准化为历史投影,返回 `record_source=legacy_account/legacy_asset/legacy_polling`。
|
||||
- 历史记录只用于查询,不允许更新或补写。
|
||||
- 后续达到归档条件后离线迁移或归档旧表,不影响本次新写入一次性切换。
|
||||
|
||||
## 十八、代码迁移重点
|
||||
|
||||
- 删除 `account_audit.Service.LogOperation` 和 `asset_audit.Service.LogOperation` 内部裸 goroutine。
|
||||
- 删除调用方外围 `go auditService.LogOperation(...)`。
|
||||
- 为尚未迁入 DDD 的旧 Service 提供统一 Audit Writer Adapter,并在本次发布中替换全部敏感操作旧写入口。
|
||||
- 退款、线下充值资金事务使用 `AppendWithTx`。
|
||||
- 卡实名领域用例在状态变化事务内写审计。
|
||||
- 轮询、事件同步、手动刷新和运营商回调统一写 Integration Log;只有状态变化、连续失败、手动操作和高风险异常追加 Audit Event。
|
||||
- 企微模板发布、手工绑定 sp_no、通过后撤销写高风险事件。
|
||||
- `pkg/logger/middleware.go` 对响应体和特殊路由实施脱敏策略。
|
||||
- 现有账号、资产和业务日志详情接口统一改为调用 `query/audit` 的资源时间线或历史投影。
|
||||
|
||||
## 十九、人工验证
|
||||
|
||||
1. 关键资金事务在审计写入失败时整体回滚。
|
||||
2. 同一事件不会因 Outbox/Asynq 重试生成重复审计。
|
||||
3. 一次退款能够从退款单、订单、钱包和资产四个资源视角查到同一事件。
|
||||
4. request_id 能串联一次 API 请求产生的多条审计记录。
|
||||
5. correlation_id 能串联退款申请、企微审批、退款、佣金和资产处理。
|
||||
6. 普通用户不能访问全局、人员、风险和集成视角。
|
||||
7. 敏感字段默认脱敏,查看完整敏感字段的动作本身再次被审计。
|
||||
8. Access Log 请求和响应均不泄露 Token、Secret、操作密码和签名 URL。
|
||||
9. 高频轮询成功只进入外部集成视角,不会淹没业务资源时间线,状态变化和连续失败仍可见。
|
||||
10. 企微通过后撤销且资金已执行时生成 critical 事件,不自动冲正。
|
||||
11. 历史账号/资产日志可以通过统一审计页面查询,发布后的新写入不再双写旧表。
|
||||
12. 审计导出字段受角色权限控制,页面可见不等于可导出完整敏感值。
|
||||
13. 人员、资源和集成视角都有独立查询接口,前端不通过下载全局事件后自行聚合。
|
||||
14. 同一次事件同步的立即、3 分钟、5 分钟尝试能通过 `trigger_series` 连续展示,合并、限流和提前完成都有可解释结果。
|
||||
15. 发布后旧账号、资产和轮询日志表不再新增记录,新旧历史仍能在同一审计界面查询。
|
||||
Reference in New Issue
Block a user