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>
78 lines
2.6 KiB
Python
78 lines
2.6 KiB
Python
"""Per-subject limit policy overrides used by the admin whitelist page."""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
|
|
from sqlalchemy import (
|
|
Boolean,
|
|
DateTime,
|
|
Index,
|
|
Integer,
|
|
String,
|
|
UniqueConstraint,
|
|
func,
|
|
true,
|
|
)
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.db.base import Base
|
|
|
|
|
|
class LimitPolicyOverride(Base):
|
|
"""One rule override for one phone or device.
|
|
|
|
``reset_at`` is a non-destructive usage baseline. Business records and
|
|
security events remain intact; quota readers only count rows at or after
|
|
this timestamp.
|
|
"""
|
|
|
|
__tablename__ = "limit_policy_override"
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"subject_type",
|
|
"subject_value",
|
|
"rule_code",
|
|
name="uq_limit_policy_subject_rule",
|
|
),
|
|
Index(
|
|
"ix_limit_policy_lookup",
|
|
"subject_type",
|
|
"subject_value",
|
|
"rule_code",
|
|
"enabled",
|
|
),
|
|
Index("ix_limit_policy_expires", "expires_at"),
|
|
)
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
|
subject_type: Mapped[str] = mapped_column(String(16), nullable=False)
|
|
subject_value: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
rule_code: Mapped[str] = mapped_column(String(64), nullable=False)
|
|
# 产品只保留“临时不限/免告警”。即使有内部脚本绕过 API 直接建 ORM
|
|
# 对象,也不能再悄悄落成已经下线的 override 模式。
|
|
mode: Mapped[str] = mapped_column(String(24), nullable=False, default="unlimited")
|
|
limit_value: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
enabled: Mapped[bool] = mapped_column(
|
|
Boolean, nullable=False, default=True, server_default=true()
|
|
)
|
|
starts_at: Mapped[datetime | None] = mapped_column(
|
|
DateTime(timezone=True), nullable=True
|
|
)
|
|
expires_at: Mapped[datetime | None] = mapped_column(
|
|
DateTime(timezone=True), nullable=True
|
|
)
|
|
reset_at: Mapped[datetime | None] = mapped_column(
|
|
DateTime(timezone=True), nullable=True
|
|
)
|
|
reason: Mapped[str | None] = mapped_column(String(256), nullable=True)
|
|
created_by_admin_id: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), nullable=False
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True),
|
|
server_default=func.now(),
|
|
onupdate=func.now(),
|
|
nullable=False,
|
|
)
|