seaislee1209 be656900c0
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m13s
feat: v0.9.7 登录风控第二期 — IP归属地解析 + 异常检测(R1-R5) + 飞书告警 + 自动封禁
- IP138 在线 API + ip2region 离线库双通道归属地解析,60 秒熔断降级
- 5 条异常检测规则:地区不对/不可能旅行/频繁登录/团队遍地开花/海外IP太杂
- 飞书 interactive 卡片告警(红色严重/橙色警告),含辅助指标
- R2 自动封禁用户、R4 自动封禁团队,封禁即踢下线
- 系统设置页全局配置 + 团队详情页独立阈值覆盖
- 安全日志页面 + 管理员修改密码入口

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 00:02:56 +08:00

92 lines
4.9 KiB
Python

import uuid
from django.db import models
from django.conf import settings
class GenerationRecord(models.Model):
"""Video generation call record."""
MODE_CHOICES = [
('universal', '全能参考'),
('keyframe', '首尾帧'),
]
MODEL_CHOICES = [
('seedance_2.0', 'AirDrama'),
('seedance_2.0_fast', 'AirDrama Fast'),
]
STATUS_CHOICES = [
('queued', '排队中'),
('processing', '生成中'),
('completed', '已完成'),
('failed', '失败'),
]
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='generation_records',
verbose_name='用户',
)
task_id = models.UUIDField(default=uuid.uuid4, unique=True, verbose_name='任务ID')
ark_task_id = models.CharField(max_length=100, blank=True, default='', verbose_name='火山ARK任务ID')
prompt = models.TextField(blank=True, verbose_name='提示词')
mode = models.CharField(max_length=20, choices=MODE_CHOICES, verbose_name='创作模式')
model = models.CharField(max_length=30, choices=MODEL_CHOICES, verbose_name='模型')
aspect_ratio = models.CharField(max_length=10, verbose_name='宽高比')
duration = models.IntegerField(verbose_name='视频时长(秒)')
seconds_consumed = models.FloatField(default=0, verbose_name='消费秒数')
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='queued', verbose_name='状态')
result_url = models.CharField(max_length=1000, blank=True, default='', verbose_name='生成结果URL')
error_message = models.TextField(blank=True, default='', verbose_name='错误信息')
reference_urls = models.JSONField(default=list, blank=True, verbose_name='参考素材信息')
created_at = models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')
class Meta:
verbose_name = '生成记录'
verbose_name_plural = '生成记录'
ordering = ['-created_at']
indexes = [
models.Index(fields=['user', 'created_at']),
]
def __str__(self):
return f'{self.user.username} - {self.task_id}'
class QuotaConfig(models.Model):
"""Global quota configuration (singleton) — Phase 3: seconds + announcement + anomaly detection."""
default_daily_seconds_limit = models.IntegerField(default=600, verbose_name='默认每日秒数上限')
default_monthly_seconds_limit = models.IntegerField(default=6000, verbose_name='默认每月秒数上限')
announcement = models.TextField(blank=True, default='', verbose_name='系统公告')
announcement_enabled = models.BooleanField(default=False, verbose_name='启用公告')
max_desktop_sessions = models.IntegerField(default=1, verbose_name='每用户最大桌面端会话数')
max_mobile_sessions = models.IntegerField(default=0, verbose_name='每用户最大移动端会话数')
# ── 异常检测全局默认配置 ──
anomaly_detection_enabled = models.BooleanField(default=False, verbose_name='异常检测总开关')
r1_enabled_default = models.BooleanField(default=True, verbose_name='R1 默认开关')
r2_enabled_default = models.BooleanField(default=True, verbose_name='R2 默认开关')
r2_window_seconds = models.IntegerField(default=3600, verbose_name='R2 默认时间窗口(秒)')
r3_enabled_default = models.BooleanField(default=True, verbose_name='R3 默认开关')
r3_window_seconds = models.IntegerField(default=3600, verbose_name='R3 默认时间窗口(秒)')
r3_max_count = models.IntegerField(default=10, verbose_name='R3 默认最大登录次数')
r4_enabled_default = models.BooleanField(default=True, verbose_name='R4 默认开关')
r4_window_seconds = models.IntegerField(default=3600, verbose_name='R4 默认时间窗口(秒)')
r4_city_count = models.IntegerField(default=5, verbose_name='R4 默认预期外城市数')
r5_enabled_default = models.BooleanField(default=True, verbose_name='R5 默认开关')
r5_days = models.IntegerField(default=7, verbose_name='R5 默认统计天数')
r5_country_count = models.IntegerField(default=10, verbose_name='R5 默认海外国家数')
feishu_alert_mobiles = models.CharField(max_length=500, blank=True, default='', verbose_name='飞书告警接收人手机号')
sms_alert_mobiles = models.CharField(max_length=500, blank=True, default='', verbose_name='短信告警手机号(预留)')
alert_cooldown_seconds = models.IntegerField(default=1800, verbose_name='告警冷却时间(秒)')
updated_at = models.DateTimeField(auto_now=True)
class Meta:
verbose_name = '系统配置'
verbose_name_plural = '系统配置'
def save(self, *args, **kwargs):
self.pk = 1
super().save(*args, **kwargs)
def __str__(self):
return f'全局配额: {self.default_daily_seconds_limit}s/日, {self.default_monthly_seconds_limit}s/月'