Skip to content

feat: 新增风控中心模块#553

Open
zhiqicloud wants to merge 3 commits into
fawney19:mainfrom
zhiqicloud:feat/one-click-update
Open

feat: 新增风控中心模块#553
zhiqicloud wants to merge 3 commits into
fawney19:mainfrom
zhiqicloud:feat/one-click-update

Conversation

@zhiqicloud

@zhiqicloud zhiqicloud commented May 23, 2026

Copy link
Copy Markdown
Contributor

摘要

  • 新增风控中心模块,支持风控配置、规则测试、日志查看、命中详情、通知重试、retention 状态和自动处置恢复。
  • 接入后端能力:admin API、数据仓储、请求链路拦截、模块健康检查、observe 有界队列 worker、retention worker 和通知 outbox worker。
  • 完成前端接入:模块管理入口、风控中心页面、配置弹窗、日志详情、通知重试入口、响应式布局和移动端适配。
  • 强化安全边界:Regex 防滥用、命中 excerpt 默认隐藏、策略粒度、Provider SSRF 深层防护、自动处置审计。
  • 新增 risk_control_user_action_notice 通知项,管理员可单独开关,通知渠道沿用通知中心已启用渠道。

Commit 结构

  • feat(data): 增加风控持久化和通知 outbox
  • feat(risk-control): 新增风控中心模块
  • fix(risk-control): 修复风控中心 CI 检查

数据库变更摘要

  • 新增 risk_control_logs:记录风控命中、observe 结果、阻断结果、Provider 异常、自动处置和通知聚合状态,不保存完整请求 body。
  • 新增 risk_control_flagged_hashes:持久化已命中的输入 hash,用于后续快速预检和命中复用。
  • 扩展 risk_control_logs:增加 notification_attemptsnotification_last_errornotification_last_attempt_at,用于通知失败原因和重试可观测。
  • 新增 risk_control_notification_outbox:支持可靠通知投递、租约领取、退避重试、dead 状态和手动重新入队。
  • 同步 MySQL / PostgreSQL / SQLite migrations、generated baseline、logical schema 和 repository 实现。
  • 同步 PostgreSQL empty database snapshot:当前 snapshot cutoff 会标记 cutoff 前 migration 已执行,因此已把风控初始表、索引、默认配置和通知聚合字段补入 snapshot,避免 fresh bootstrap 漏 schema。

详细数据库影响说明见评论:
#553 (comment)

主要功能变更

  • 通知失败重试:durable outbox、失败原因落库、退避重试、死信状态和手动重新入队。
  • retention 可观测性:持久化并展示上次开始/完成时间、删除数量、错误和下次运行时间。
  • observe 后台审核:从无界 tokio::spawn 改为有界队列 + worker。
  • Regex 防滥用:增加数量、长度、复杂度、编译校验、空串匹配拒绝和运行时扫描预算。
  • 命中日志隐私:日志和 hash excerpt 默认隐藏,只有满足权限并显式请求才返回。
  • 自动处置审计:禁用用户 / 锁 API Key 写入明确审计日志,并提供恢复用户 / 解锁 Key 入口。
  • 策略粒度:支持模型、用户、用户组、API Key、route family、route kind、endpoint 的 all/include/exclude。
  • Provider SSRF:校验 HTTPS/public host,拒绝 unsafe DNS 目标,禁用 redirect/proxy,并固定连接到已校验地址。

验证

  • cargo fmt --check
  • cargo clippy -p aether-gateway --all-targets -- -D warnings
  • cargo clippy --workspace --exclude aether-gateway --exclude aether-data --all-targets -- -D warnings
  • cargo nextest run -p aether-gateway important_notification::tests::parse_notification_items_reads_channel_and_user_email_flag
  • cargo nextest run -p aether-data-schema workspace_logical_schema_generated_artifacts_are_current
  • cargo test -p aether-data migrate -- --nocapture --test-threads=1
  • cargo test -p aether-data risk_control -- --nocapture --test-threads=1
  • cargo test -p aether-gateway risk_control -- --nocapture --test-threads=1
  • cargo check -p aether-gateway
  • npm run type-check,在 frontend/ 目录执行
  • npm run build,在 frontend/ 目录执行

备注

  • 策略粒度等运行配置不新增独立策略表,先保存在现有 runtime JSON 中。
  • Provider SSRF 的最终防线在后端运行时 DNS 解析和连接前校验;前端只做早期 UX 提示。

@zhiqicloud zhiqicloud force-pushed the feat/one-click-update branch from 2610de1 to 867e3b8 Compare May 24, 2026 03:59
@zhiqicloud

Copy link
Copy Markdown
Contributor Author

给作者/维护者补一份 #553 的完整数据库变更说明,方便评审数据库影响面。

数据库变更总览

本 PR 的数据库变更集中在 crates/aether-data 的风控域,覆盖 MySQL / PostgreSQL / SQLite 三套迁移、generated baseline schema、logical schema,以及 memory/mysql/postgres/sqlite repository 实现。

涉及迁移文件:

  • crates/aether-data/migrations/mysql/20260523000000_add_risk_control.sql
  • crates/aether-data/migrations/postgres/20260523000000_add_risk_control.sql
  • crates/aether-data/migrations/sqlite/20260523000000_add_risk_control.sql
  • crates/aether-data/migrations/mysql/20260526010000_extend_risk_control_operability.sql
  • crates/aether-data/migrations/postgres/20260526010000_extend_risk_control_operability.sql
  • crates/aether-data/migrations/sqlite/20260526010000_extend_risk_control_operability.sql
  • crates/aether-data/migrations/mysql/20260527010000_add_risk_control_notification_outbox.sql
  • crates/aether-data/migrations/postgres/20260527010000_add_risk_control_notification_outbox.sql
  • crates/aether-data/migrations/sqlite/20260527010000_add_risk_control_notification_outbox.sql

同步更新:

  • crates/aether-data/schema/logical/009_risk_control.toml
  • crates/aether-data/schema/generated/mysql/baseline/009_risk_control.sql
  • crates/aether-data/schema/generated/postgres/baseline/009_risk_control.sql
  • crates/aether-data/schema/generated/sqlite/baseline/009_risk_control.sql
  • crates/aether-data/src/repository/risk_control/*

1. 新增 risk_control_logs

用途:持久化风控审计日志。该表记录每次风控命中、异常、observe 结果或配置要求的全量日志元数据,但不保存完整请求 body。

主要字段:

字段 说明
id 风控日志 ID。
trace_id 请求 trace ID。
request_id 请求 ID,来自请求头,做长度限制。
user_id / username / user_email 用户标识和展示信息。
api_key_id / api_key_name 命中的 API Key 信息。
route_family / route_kind / api_format 网关路由和协议分类。
endpoint 风控端点签名,例如 OpenAI Chat / Responses 等。
model 请求模型名。
mode 风控模式:observe / pre_block 等。
action 风控动作:allow / observe / block。
decision_source 判定来源:keyword / hash / api / api_error / regex_budget_limited 等。
flagged 是否命中风险。
highest_category / highest_score Provider 或规则判定的最高风险分类和分数。
category_scores Provider 分类分数 JSON。
thresholds 判定时使用的阈值 JSON 快照。
matched_keywords 命中的关键词 JSON。
input_hash 规范化输入 SHA-256,用于复用命中。
excerpt 有界摘要,不保存完整 body;管理端默认隐藏。
latency_ms 风控检查耗时。
queue_delay_ms observe 队列延迟。
violation_count 自动处置窗口内累计违规次数。
auto_action 自动处置动作,例如禁用用户 / 锁 Key。
notification_sent 兼容字段,标记通知是否最终发送成功。
notification_attempts 通知尝试次数。
notification_last_error 最近一次通知失败原因。
notification_last_attempt_at 最近一次通知尝试时间,Unix seconds。
error_message Provider 调用异常、配置异常等错误信息。
created_at 创建时间,Unix seconds。

索引:

索引 说明
idx_risk_control_logs_created_at 按时间分页/清理。
idx_risk_control_logs_flagged_created_at 按命中状态和时间查询。
idx_risk_control_logs_user_created_at 用户维度查询和自动处置窗口计数。
idx_risk_control_logs_api_key_created_at API Key 维度查询。
idx_risk_control_logs_endpoint_created_at endpoint 维度查询。
idx_risk_control_logs_input_hash 根据输入 hash 追溯日志。

2. 新增 risk_control_flagged_hashes

用途:持久化已命中的输入 hash,用于后续请求的快速 hash 预检,减少重复 Provider 调用和重复规则计算。

主要字段:

字段 说明
input_hash 主键,规范化输入 SHA-256。
source_log_id 首次来源风控日志 ID。
reason 命中原因,例如 keyword / api / hash_hit。
highest_category / highest_score 风险分类和分数快照。
excerpt 有界摘要,管理端默认隐藏。
first_seen_at 首次发现时间。
last_seen_at 最近命中时间。
hit_count 累计命中次数。

索引:

索引 说明
idx_risk_control_hashes_last_seen_at 支持按最近命中时间清理或展示。

3. 扩展 risk_control_logs 的通知可观测字段

20260526010000_extend_risk_control_operability.sqlrisk_control_logs 增加:

字段 说明
notification_attempts 通知投递尝试次数,默认 0
notification_last_error 最近一次通知失败原因。
notification_last_attempt_at 最近一次通知尝试时间,Unix seconds。

目的:之前通知失败只 warn,无法在 UI 或数据库中看到失败原因和尝试次数;扩展后日志详情能展示通知失败状态,也能和 outbox worker 的最终状态保持兼容。

4. 新增 risk_control_notification_outbox

20260527010000_add_risk_control_notification_outbox.sql 新增通知 outbox 表,用于可靠投递风控通知,避免请求链路直接发送通知导致失败丢失。

主要字段:

字段 说明
id outbox 任务 ID。
log_id 关联风控日志 ID。
item_key 通知项 key,例如风控命中、自动处置、用户处置通知。
title 通知标题快照。
markdown_body Markdown 通知正文快照。
text_body 纯文本通知正文快照。
variables_json 通知模板变量快照。PostgreSQL/MySQL 使用 JSON 类型,SQLite 使用 TEXT 存 JSON。
status 投递状态:pending / processing / sent / dead
attempt_count 已尝试次数。
max_attempts 最大尝试次数,默认 10
next_attempt_at 下次可投递时间。
lease_until worker 领取任务后的租约截止时间,用于处理 worker 崩溃/卡死。
last_error 最近一次失败原因。
created_at / updated_at / sent_at 生命周期时间。

约束:

约束 说明
Primary Key id 唯一标识 outbox 任务。
Unique log_id, item_key 同一条风控日志的同一通知项只允许一个任务,避免重复入队。

索引:

索引 说明
idx_risk_control_notification_outbox_due(status, next_attempt_at) worker 快速领取到期 pending 任务。
idx_risk_control_notification_outbox_lease(status, lease_until) processing 任务租约过期后可被重新领取。
idx_risk_control_notification_outbox_updated(updated_at) 支持状态统计、排查和后台管理展示。

行为说明:

  • 请求链路只写 outbox,不直接依赖通知发送成功。
  • worker 领取 due 任务后把状态改为 processing 并设置 lease_until
  • 通知中心未就绪时任务回到 pending 并延迟,不消耗发送重试预算。
  • 实际发送失败会增加 attempt_count,写入 last_error,并按退避策略更新 next_attempt_at
  • 超过最大尝试次数后进入 dead
  • 管理端日志详情可以把非 sent 的通知任务手动重新入队。

5. repository 层变更

新增/扩展 repository 能力:

  • 写入风控日志和 flagged hash。
  • 查询风控日志、flagged hash、outbox 状态。
  • 删除过期风控日志,用于 retention。
  • 插入 outbox 任务。
  • claim 到期 outbox 任务。
  • 记录 outbox 发送成功/失败。
  • readiness 未就绪时 defer outbox 任务。
  • 手动重置未发送成功的 outbox 任务。
  • 同步更新 risk_control_logs 上的通知聚合字段。

已覆盖:

  • memory.rs
  • mysql.rs
  • postgres.rs
  • sqlite.rs
  • types.rs
  • mod.rs

6. 没有新增独立表的配置

下面这些能力没有单独建表,仍复用现有 system config JSON:

  • 风控运行配置 module.risk_control.config
  • 风控总开关 module.risk_control.enabled
  • 策略粒度 scope:模型、用户、用户组、API Key、route family、route kind、endpoint。
  • observe queue capacity。
  • Regex 数量/长度/复杂度/运行时预算相关配置。
  • Provider SSRF 防护配置。
  • retention 运行状态写入 system config:module.risk_control.retention.status

7. 评审关注点

  • 这次数据库新增主要是为了可靠通知和可观测性,不改变用户、API Key、Provider 等既有表结构。
  • risk_control_logs.excerptrisk_control_flagged_hashes.excerpt 只保存有界摘要,不保存完整请求 body;管理端默认不返回 excerpt。
  • outbox 表保留通知内容和变量快照,便于重试时保持通知内容一致。
  • MySQL / PostgreSQL / SQLite 三套迁移和 baseline schema 已同步。
  • cargo test -p aether-data risk_control -- --nocapture --test-threads=1 已通过。

数据库变更:
- 新增 risk_control_logs,记录风控命中、observe 结果、自动处置、通知状态和排障字段,不保存完整请求 body。
- 新增 risk_control_flagged_hashes,用于命中输入 hash 复用、首次/最近命中时间和累计次数统计。
- 扩展 risk_control_logs,增加 notification_attempts、notification_last_error、notification_last_attempt_at,用于通知失败原因和重试可观测。
- 新增 risk_control_notification_outbox,用于 durable 通知投递,支持 pending/processing/sent/dead、租约领取、退避重试和手动重新入队。
- 同步 MySQL、PostgreSQL、SQLite migrations,logical schema,generated baseline schema,以及 memory/mysql/postgres/sqlite repository。
- 同步 PostgreSQL empty database snapshot:当前 snapshot cutoff 会把 20260523000000、20260526010000 及 20260527000000 标记已执行,因此快照中补入风控初始表、索引、默认配置和通知聚合字段,fresh bootstrap 只剩 20260527010000 outbox 迁移待跑。
- 新增风控配置、规则测试、命中日志、hash 复用、自动处置和恢复入口。
- 接入 gateway 请求链路、admin API、管理权限、模块健康检查、observe 有界队列 worker、retention worker 和通知 outbox worker。
- 强化 Regex 防滥用、命中 excerpt 默认隐藏、策略粒度和 Provider SSRF 连接前校验。
- 新增 risk_control_user_action_notice 通知项,沿用通知中心已启用渠道。
- 新增前端风控中心页面、配置弹窗、日志详情、通知重试入口、retention 状态展示和移动端适配。
@zhiqicloud zhiqicloud force-pushed the feat/one-click-update branch from bd2deeb to cb4f178 Compare May 27, 2026 07:16
修复 Gateway clippy 报告的多余借用和 bool::then 用法。

调整通知项解析测试,按 key 校验用户余额通知,兼容默认通知项扩展。

重新生成 risk_control schema baseline,确保 logical schema artifact 与生成器一致。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant