15fb73791f
## 需求背景 将比价、短信与登录、广告、引导与账号、风控免告警等限制统一配置,并支持按手机号或设备设置有有效期的临时白名单。 ## 主要改动 - 新增统一限制策略注册表、全局 JSON 配置与白名单覆盖表 - 新增白名单管理、设备检索、批量追加与主体统一编辑接口 - 接入比价、短信登录、广告奖励、引导视频、账号换绑及风险告警调用链 - 保留旧配置接口兼容,并同步统一策略全局值 - 增加单主体唯一有效期、恢复全局、审计日志和风险事件自动处理 - 增加数据库迁移及完整回归测试 ## 验证 - 白名单、权限、配置及风控测试 50 项通过 - 短信、登录、比价、广告关联测试 98 项通过 - Ruff 与 Python 编译检查通过 - Alembic 保持单一 head - 已同步最新 main --------- Co-authored-by: unknown <798648091@qq.com> Reviewed-on: #207 Co-authored-by: linkeyu <linkeyu@wonderable.ai> Co-committed-by: linkeyu <linkeyu@wonderable.ai>
105 lines
2.4 KiB
Python
105 lines
2.4 KiB
Python
"""风控监控后台接口 schema。"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
class RiskSummaryCard(BaseModel):
|
|
kind: str
|
|
alert_subject_count: int
|
|
today_total: int
|
|
threshold: int
|
|
rule_text: str
|
|
|
|
|
|
class RiskMonitorSummary(BaseModel):
|
|
date: str
|
|
updated_at: datetime
|
|
cards: list[RiskSummaryCard]
|
|
|
|
|
|
class RiskRuleConfig(BaseModel):
|
|
sms_hourly_threshold: int = Field(ge=1, le=100_000)
|
|
oneclick_daily_threshold: int = Field(ge=1, le=100_000)
|
|
compare_daily_threshold: int = Field(ge=1, le=100_000)
|
|
|
|
|
|
class RiskIncidentItem(BaseModel):
|
|
incident_id: int
|
|
kind: str
|
|
subject_type: str
|
|
subject_id: str
|
|
device_model: str | None = None
|
|
first_used_at: datetime | None = None
|
|
phone: str | None = None
|
|
user_id: int | None = None
|
|
registered_at: datetime | None = None
|
|
common_device_id: str | None = None
|
|
window_start: datetime
|
|
window_end: datetime
|
|
first_event_at: datetime
|
|
triggered_at: datetime
|
|
last_event_at: datetime
|
|
event_count: int
|
|
status: str
|
|
restricted: bool = False
|
|
restriction_id: int | None = None
|
|
|
|
|
|
class RiskIncidentPage(BaseModel):
|
|
items: list[RiskIncidentItem]
|
|
next_cursor: int | None = None
|
|
total: int
|
|
|
|
|
|
class RiskDetailItem(BaseModel):
|
|
id: int
|
|
occurred_at: datetime
|
|
outcome: str
|
|
phone: str | None = None
|
|
user_id: int | None = None
|
|
username: str | None = None
|
|
account_registered_at: datetime | None = None
|
|
verified: bool | None = None
|
|
reason: str | None = None
|
|
store_name: str | None = None
|
|
product_names: str | None = None
|
|
prices: dict[str, float | None] | None = None
|
|
saved_amount_yuan: float | None = None
|
|
status: str | None = None
|
|
|
|
|
|
class RiskDetailPage(BaseModel):
|
|
kind: str
|
|
incident_id: int
|
|
event_count: int
|
|
distinct_accounts: int | None = None
|
|
items: list[RiskDetailItem]
|
|
next_cursor: int | None = None
|
|
total: int
|
|
|
|
|
|
class RiskActionRequest(BaseModel):
|
|
reason: str = Field("", max_length=256)
|
|
|
|
|
|
class RiskActionResponse(BaseModel):
|
|
ok: bool = True
|
|
incident_id: int
|
|
status: str
|
|
restriction_id: int | None = None
|
|
|
|
|
|
class RiskResetResponse(BaseModel):
|
|
ok: bool = True
|
|
reset_at: datetime
|
|
reset_incident_count: int
|
|
reset_counts: dict[str, int]
|
|
|
|
|
|
class RestrictionRevokeResponse(BaseModel):
|
|
ok: bool = True
|
|
restriction_id: int
|