You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

12 KiB

V37 防刷风险概览与运营规则配置

状态:已完成并通过真实 MySQL/Docker 黑盒验收(PASS=76 / FAIL=0 / SKIP=0)。 功能版本:V37;Flyway:V36__risk_governance.sql。 基线:OpenAPI 148 paths / 182 operations;V35、V36、V37 待建立联合 Git 检查点。

1. 目标与边界

本模块为 ADMIN 提供确定性、可解释的防刷风险概览与规则配置能力。它将既有拒绝型安全审计事件转为可聚合的风险事件,供运营人员查看趋势、分布、明细及进行人工标记。

本版只做以下内容:

  • 基于既有 audit_logs 的规则型风险检测;
  • ADMIN 风险概览、趋势、分布、事件列表;
  • ADMIN 启停或调整阈值、窗口、等级;
  • ADMIN 对风险事件做无自由文本的人工标记;
  • 全链路审计、脱敏、幂等扫描与并发保护。

明确不做:

  • 自动封禁、自动警告、自动限制投递/消息/评分;
  • AI 风控、设备指纹、地理位置、IP 归因、手机号或邮箱展示;
  • 修改 V33 举报限流、屏蔽语义,或修改 V35/V36 测评和评分引擎;
  • 企业端、学生端风险事件查询;
  • 对历史 audit_logs.details 的回填、修复或删除。

2. 现有能力与输入范围

现有 SecurityAuditService 已以独立事务写入拒绝型安全审计,当前可安全复用且可关联用户的输入只有:

审计 action 来源 初始规则用途
REPORT_RATE_LIMITED V33 举报第 11 次及以后被拒绝 一次命中即 HIGH 风险
BLOCKED_SEND_PROHIBITED V33 屏蔽关系下的新建私聊或发消息被拒绝 窗口内多次命中为 MEDIUM 风险

SMS_RATE_LIMITED 当前不具备安全审计中的用户主体,且原始手机号不能进入风险库;本版不纳入。后续如需纳入,必须单独设计不可逆主体哈希和密钥治理。

风险计算只读取 audit_logs 的 id/user_id/action/entity_type/entity_id/request_id/created_at,不复制 details/ip_address/user_agent。

3. 风险规则

规则是确定性阈值条件:在 windowMinutes 滑动窗口内,某用户同类安全审计数量达到 thresholdCount 后,生成一条风险事件。事件是提醒,不改变业务权限。

初始种子规则:

ruleCode sourceAction thresholdCount windowMinutes severity 默认启用
REPORT_RATE_LIMIT_BURST REPORT_RATE_LIMITED 1 60 HIGH true
BLOCKED_MESSAGE_BURST BLOCKED_SEND_PROHIBITED 3 15 MEDIUM true

规则更新只影响后续扫描;不回溯重算、不会删除既有风险事件。若启用后需重新观察历史,应在未来另行设计“回放”操作,避免误产生大量旧告警。

4. 数据模型与 Flyway

迁移文件为 V36__risk_governance.sql。V36 是功能版本号,Flyway 当前最新迁移为 V35,因此使用严格连续的 V36。

4.1 scoring_rule_configs

字段 约束/用途
id PK
rule_code UNIQUE,稳定规则标识
source_action 审计 action 白名单
threshold_count 1–100
window_minutes 1–1440
severity LOW/MEDIUM/HIGH
enabled 是否参与后续扫描
version @Version 乐观锁
created_at/updated_at 审计时间

4.2 scoring_risk_events

字段 约束/用途
id PK;对外只返回此事件 ID
rule_config_id FK → scoring_rule_configs
subject_user_id 内部关联用户,所有 API 响应均不返回
source_audit_log_id 触发本事件的审计记录;与 rule_config_id 联合唯一,保证扫描幂等
source_action 冗余快照,支持稳定统计
severity 生成时规则等级快照
window_started_at/window_ended_at 命中统计窗口
occurrence_count 命中数量
status OPEN/ACKNOWLEDGED/DISMISSED
detected_at 检测时间
version 乐观锁

索引:(detected_at,status)、(severity,detected_at)、(rule_config_id,detected_at)、(subject_user_id,detected_at)。

4.3 scoring_risk_actions

风险事件人工操作历史。为防止运营备注成为新的个人信息载体,本版不提供自由文本 note。

字段 约束/用途
id PK
risk_event_id FK → scoring_risk_events
operator_user_id ADMIN 操作人,响应不返回
action ACKNOWLEDGE/DISMISS
reason_code MONITORING/CONFIRMED_FALSE_POSITIVE/OUT_OF_BAND_ESCALATED
created_at 操作时间

4.4 scoring_risk_scan_checkpoints

持久化扫描游标,解决应用重启、暂停或扫描耗时导致的审计事件遗漏问题。该表只保留一条 checkpoint_key=RISK_EVENT_DETECTOR 的记录。

字段 约束/用途
id PK
checkpoint_key UNIQUE,固定为 RISK_EVENT_DETECTOR
last_audit_log_id 已成功处理的最大 audit_logs.id,默认 0
version 乐观锁
updated_at 最近成功推进时间

迁移同时为 audit_logs 新增 (action, user_id, created_at, id) 复合索引,仅优化风险扫描,不改变任何既有审计语义。

5. 检测与状态机

5.1 扫描器

RiskEventDetector 使用 @Scheduled 固定延迟扫描(默认 60 秒,可配置 app.risk.scan.fixed-delay-ms),其核心 scanOnce() 必须可被测试直接调用。每轮:

  1. 在一个事务中以悲观锁或等价机制锁定 RISK_EVENT_DETECTOR checkpoint;
  2. 按 audit_logs.id > last_audit_log_id、升序、每批最多 500 条读取新增审计;仅处理 user_id 非空且 action 在规则白名单内的记录;
  3. 读取全部 enabled=true 的规则。已禁用规则对应的新增审计会随游标跳过;重新启用不回放历史;
  4. 以“当前审计记录时间 - windowMinutes”为窗口,统计同一用户、同一 action 的命中数;
  5. 仅当命中数恰好等于 thresholdCount 时生成事件,避免阈值之后的每次操作都刷出一条风险事件;
  6. 按 (rule_config_id, source_audit_log_id) 原子插入事件;唯一键冲突视为已处理,不报错;
  7. 成功处理整批后,在同一事务推进 last_audit_log_id;任何处理失败必须回滚,游标不得前进;
  8. 写入 RISK_EVENT_DETECTED 审计,但 details 仅包含 riskEventId/ruleCode/severity/occurrenceCount/requestId。

扫描器不处理无用户主体的审计,也不会发送通知、修改用户状态或调用评分引擎。首版允许最长约 60 秒可见延迟。

5.2 事件状态机

OPEN ──ACKNOWLEDGE──> ACKNOWLEDGED
OPEN ──DISMISS──────> DISMISSED
ACKNOWLEDGED ──DISMISS──> DISMISSED

DISMISSED 为终态;不能重新打开。每次合法状态变更新增一条 scoring_risk_actions 记录,并写 RISK_EVENT_ACTIONED 审计。

6. 接口契约(仅 ADMIN)

除 401 外,所有非 ADMIN 情况统一 403/2002。Controller 从 JWT SecurityContext 取身份,禁止接受 adminUserId/operatorUserId/userId 参数。

方法 路径 说明
GET /admin/scoring/risk/overview 时间范围内 OPEN 总数、等级分布、规则分布、最近检测时间
GET /admin/scoring/risk/trend 按 HOUR/DAY 聚合的事件趋势
GET /admin/scoring/risk/distribution 按规则和风险等级聚合的事件分布
GET /admin/scoring/risk/events 分页事件列表;可按 status/severity/ruleCode/from/to 筛选
GET /admin/scoring/risk/rules 规则列表与当前版本
PUT /admin/scoring/risk/rules/{ruleCode} 更新规则,须提供 expectedVersion
POST /admin/scoring/risk/events/{eventId}/actions ACKNOWLEDGE/DISMISS 人工操作

预期 OpenAPI:141 → 148 paths,175 → 182 operations。

6.1 规则更新请求

{
  "enabled": true,
  "thresholdCount": 3,
  "windowMinutes": 15,
  "severity": "MEDIUM",
  "expectedVersion": 2
}

不允许修改 ruleCode 或 sourceAction,避免配置被用于监听未批准的审计类型。

6.2 事件操作请求

{
  "action": "ACKNOWLEDGE",
  "reasonCode": "MONITORING",
  "expectedVersion": 0
}

6.3 事件响应白名单

事件列表或详情只含:eventId/ruleCode/sourceAction/severity/status/occurrenceCount/windowStartedAt/windowEndedAt/detectedAt/version。

严禁含:subjectUserId/phone/email/ipAddress/userAgent/requestId/details/Token/password/设备指纹。

7. 错误码

新增 99xx 段:

code 名称 HTTP
9901 RISK_RULE_NOT_FOUND 404
9902 RISK_EVENT_NOT_FOUND 404
9903 RISK_RULE_CONCURRENT_UPDATE 409
9904 RISK_EVENT_CONCURRENT_UPDATE 409
9905 RISK_INVALID_ACTION 409
9906 RISK_INVALID_FILTER 400

复用:未登录 401/2001、无权限 403/2002、分页或时间参数错误 400/1001。

8. 查询、脱敏与性能

  • 全部 overview/trend/distribution 使用 SQL GROUP BY 投影,不在 Java 循环中逐事件查询规则或用户,避免 N+1;
  • 事件列表分页 page=0 起、size=1–50;空结果为 200 + items=[];
  • 默认时间范围为最近 7 天,最大查询范围 90 天;from > to 或超出 90 天返回 9906;
  • 对外 DTO 均使用字段白名单;不返回 subject_user_id;
  • 不复制 audit_logs.details/ip_address/user_agent 至风险事件;
  • 规则更新和事件操作均乐观锁;冲突不覆盖对方操作;
  • 定时扫描使用 checkpoint 断点续扫;事件唯一键保证重复扫描不会重复产生事件,也不会因服务停机而漏掉未处理审计。

9. 审计

action 触发点 details 白名单
RISK_EVENT_DETECTED 系统检测到阈值事件 riskEventId/ruleCode/severity/occurrenceCount
RISK_RULE_UPDATED ADMIN 更新规则 ruleCode/enabled/thresholdCount/windowMinutes/severity/version
RISK_EVENT_ACTIONED ADMIN 标记风险事件 riskEventId/action/reasonCode/version

审计 details 不允许写入用户身份、原始审计 details、IP、浏览器信息、请求正文或任何凭据。

10. 实现与测试计划

新增:4 Entity、4 Repository、RiskEventDetector、RiskGovernanceService、AdminRiskController、请求/响应 DTO、V36__risk_governance.sql、错误码与 OpenAPI 文档。

测试:

  • 单元:阈值、滑动窗口、规则禁用、仅阈值首次命中生成事件、checkpoint 断点续扫、扫描失败游标不前进、唯一键幂等、状态机、乐观锁、脱敏;
  • MockMvc:ADMIN 成功、STUDENT/COMPANY 403、匿名 401、分页/时间参数、响应不含敏感字段;
  • H2 持久化:Flyway、四表 FK/UNIQUE/CHECK、扫描重复执行只生成一条事件、checkpoint 并发锁、规则并发更新;
  • MySQL/Docker 黑盒:插入本轮独立的安全审计夹具 → 等待扫描 → 验证概览/趋势/分布/事件/规则/人工操作;验证无自动封禁、无 candidate_dimension_scores 写入、队列排空,并回归 V33/V35/V36。

11. 验收与冻结条件

  1. 专项与全量测试全绿;
  2. verify_openapi.py 静态路径/操作数校验通过;
  3. 真实 MySQL Flyway V36 成功,三表/索引/约束可查;
  4. Docker 黑盒 FAIL=0,关键权限、去重、脱敏、队列、回归项不得 SKIP;
  5. 风险事件不会改变用户状态、投递、消息、评分或候选人维度分;
  6. 经负责人审阅精确提交范围后,才与 V35/V36 一并建立 Git 检查点。

12. 待负责人确认

建议按本稿锁定以下选择后再编码:

  1. 规则输入首版仅限 REPORT_RATE_LIMITED 与 BLOCKED_SEND_PROHIBITED;
  2. 风险事件 API 不返回主体用户 ID,运营通过 eventId 处理,不做自动处置;
  3. 保留 POST /admin/scoring/risk/events/{eventId}/actions,因此接口增量为 +7/+7;
  4. 风险事件人工操作仅允许受控 reasonCode,不支持自由文本备注;
  5. 使用 checkpoint 驱动的定时扫描(默认 60 秒)而非把风险检测同步耦合到业务请求;
  6. 使用 V36__risk_governance.sql 与 9901–9906 错误码。