Files
junhong_cmp_fiber/docs/7月迭代/独立方案/新增需求/04-全局多视角审计方案.md
2026-07-17 16:39:41 +08:00

29 KiB
Raw Blame History

新增需求 04全局多视角审计方案

状态:已合并至标准评审稿,本文保留为实施明细。 评审主文档:../../7月迭代技术方案-标准评审稿.md 范围:全系统业务写操作、安全操作、外部集成和审计查询前端。

一、目标

审计系统必须能够从不同视角回答:

谁做了什么?
某个资源经历了什么?
一次请求引发了哪些跨模块变化?
哪些操作失败、被拒绝或存在风险?
外部 Gateway、运营商和企业微信交互发生了什么
资金变化对应哪个业务动作和审批结果?

不是把所有日志塞进一张表,而是建立统一审计事件,并通过 Query 组合访问日志、领域流水和外部集成记录。

二、当前代码问题

现有实现已经有账号审计和资产审计,但没有形成全系统能力:

  • tb_account_operation_logtb_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 主表

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 无法完整查询。

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

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 表示入口,不表示操作者:

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 结果与风险

resultsuccess / failed / denied / partial

risk_levelnormal / warning / high / critical

默认风险示例:

操作 风险等级
普通字段修改成功 normal
批量部分失败 warning
权限拒绝、重复回调冲突 warning
钱包余额、退款、角色权限、支付配置修改 high
企微通过后撤销但资金已执行、审计写入失败 critical

五、领域模型和代码结构

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 中自由拼字符串:

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 在事务内调用:

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

表示跨请求、跨任务的完整业务链路:

退款申请创建
→ 企微审批提交
→ 企微回调
→ 人工退款结果入账
→ 代理钱包退款流水(仅代理钱包支付订单)
→ 佣金回扣
→ 套餐失效和停机

以上事件共享退款申请创建时生成的 correlation_id。Asynq、Outbox、企微实例和同步任务载荷都必须传递该值。

8.3 parent_event_id

表示直接因果关系。例如“套餐失效”事件的父事件是“退款终态处理成功”,用于前端画出树状链路。

九、数据脱敏

9.1 永不进入审计的数据

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_dataafter_datametadata 分别限制 16KB。超过后保存

{
  "_truncated": true,
  "_original_bytes": 38210,
  "_summary": "批量修改500条资产",
  "_artifact_ref": "batch_task:123"
}

批量明细保存在对应任务结果表或对象存储,不塞入审计 JSON。

十、外部集成日志

企业微信、运营商回调、Gateway 请求和事件同步尝试使用通用 Integration Log。同步尝试即使在真正发送 HTTP 前因合并、限流或预期状态已满足而结束,也写一条结果记录,保证能够解释“为什么没有请求上游”。

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);

directioninbound / outbound

result 至少支持:

success
failed
not_found
invalid_payload
ignored
merged
rate_limited
completed
cancelled

数据同步字段约定:

  • provider=gateway:实名、流量、卡状态和设备信息查询。
  • provider=cmcc/cucc/ctccdirection=inbound:运营商实名或解除实名回调。
  • trigger_sourcepolling/business_event/open_api/manual_refresh/carrier_callback
  • trigger_scene:触发埋点场景,例如 client_asset_infocard_resumedevice_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 全局事件视角

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 事件详情

GET /api/admin/audit/events/{event_id}

返回:

  • 操作者快照。
  • 入口、租户和请求信息。
  • 主要资源、影响资源和引用资源。
  • 前后数据差异。
  • 错误信息和风险等级。
  • request/correlation/parent 关联。
  • 可访问的领域流水和 Integration Log 链接。

12.3 操作者视角

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 资源视角

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 请求视角

GET /api/admin/audit/requests/{request_id}/timeline

按时间展示一次 HTTP 请求产生的 Access Log 摘要、Audit Event、Outbox 和 Integration Log。

12.6 业务链路视角

GET /api/admin/audit/correlations/{correlation_id}/timeline

用于查看退款、企微审批、钱包和资产处理等跨请求链路。返回节点包含 parent_event_id,前端可展示因果树。

12.7 风险视角

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 外部集成视角

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 资金视角

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 导出

POST /api/admin/audit/exports

复用现有导出任务系统。导出字段按角色配置,默认不允许导出完整 IP、手机号、ICCID、前后 JSON 和外部响应摘要。

十三、权限

建议权限码:

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 全局事件表

字段:

时间 | 风险 | 操作者 | 操作 | 主要资源 | 来源 | 结果 | request_id
  • 支持服务端分页和列筛选。
  • 点击行打开右侧详情抽屉。
  • 风险仅用图标和有限颜色表达,普通成功事件保持中性色。
  • request_id、资源编号可点击进入对应视角。

14.3 事件详情抽屉

分区:

事件摘要
操作者与入口
关联资源
字段变更对比
请求与业务链路
错误和外部交互

字段变更使用结构化键值对比,不直接把整段 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 修正

当前访问日志的响应体需要使用与请求体相同的递归脱敏逻辑:

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_eventtb_integration_log
  • 账号、角色、权限、资产、套餐、订单、退款、充值、钱包、系统配置、导入导出和登录安全等现有敏感操作同时切换到统一 Audit Writer。
  • 卡同步不创建 tb_card_sync_execution;轮询、手动刷新、事件阶梯尝试和运营商回调统一写 Integration Log。
  • 发布后旧 tb_account_operation_logtb_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.LogOperationasset_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. 发布后旧账号、资产和轮询日志表不再新增记录,新旧历史仍能在同一审计界面查询。