backend v2.1: 公告管理功能 + 系统重构
- 新增 Announcement 数据模型,支持公告的增删改查 - 后台管理新增"公告管理"Tab(创建/编辑/删除/启用禁用) - 客户端 /api/announcement 改为从数据库读取 - 账号服务重构,新增无感换号、自动分析等功能 - 新增后台任务调度器、数据库迁移脚本 - Schema/Service/Config 全面升级至 v2.1 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,37 +1,67 @@
|
||||
from pydantic import BaseModel, EmailStr
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel, EmailStr, Field, model_validator
|
||||
from typing import Optional, List, Any
|
||||
from datetime import datetime
|
||||
from app.models.models import MembershipType, AccountStatus, KeyStatus
|
||||
from app.models.models import KeyMembershipType, AccountStatus, KeyStatus
|
||||
|
||||
|
||||
# ========== 账号相关 ==========
|
||||
|
||||
class AccountBase(BaseModel):
|
||||
"""账号基础信息 (用于创建/更新)"""
|
||||
email: str
|
||||
access_token: str
|
||||
refresh_token: Optional[str] = None
|
||||
workos_session_token: Optional[str] = None
|
||||
membership_type: MembershipType = MembershipType.PRO
|
||||
token: Optional[str] = Field(None, description="兼容旧字段: user_id::jwt")
|
||||
access_token: Optional[str] = Field(None, description="Access Token")
|
||||
refresh_token: Optional[str] = Field(None, description="Refresh Token")
|
||||
workos_session_token: Optional[str] = Field(None, description="WorkosCursorSessionToken")
|
||||
password: Optional[str] = None
|
||||
remark: Optional[str] = None
|
||||
|
||||
class AccountCreate(AccountBase):
|
||||
pass
|
||||
"""创建账号 (兼容旧字段)"""
|
||||
|
||||
@model_validator(mode='before')
|
||||
@classmethod
|
||||
def ensure_token(cls, data: Any) -> Any:
|
||||
"""确保至少提供一个 Token"""
|
||||
if isinstance(data, dict):
|
||||
if not data.get('token'):
|
||||
for field in ("workos_session_token", "access_token"):
|
||||
if data.get(field):
|
||||
data['token'] = data[field]
|
||||
break
|
||||
return data
|
||||
|
||||
class AccountUpdate(BaseModel):
|
||||
email: Optional[str] = None
|
||||
token: Optional[str] = None
|
||||
access_token: Optional[str] = None
|
||||
refresh_token: Optional[str] = None
|
||||
workos_session_token: Optional[str] = None
|
||||
membership_type: Optional[MembershipType] = None
|
||||
password: Optional[str] = None
|
||||
status: Optional[AccountStatus] = None
|
||||
remark: Optional[str] = None
|
||||
|
||||
class AccountResponse(AccountBase):
|
||||
class AccountResponse(BaseModel):
|
||||
"""账号响应 (匹配 CursorAccount 模型)"""
|
||||
id: int
|
||||
email: str
|
||||
token: str
|
||||
access_token: Optional[str] = None
|
||||
refresh_token: Optional[str] = None
|
||||
workos_session_token: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
status: AccountStatus
|
||||
usage_count: int
|
||||
last_used_at: Optional[datetime] = None
|
||||
current_key_id: Optional[int] = None
|
||||
account_type: Optional[str] = None
|
||||
membership_type: Optional[str] = None
|
||||
trial_days_remaining: int = 0
|
||||
usage_limit: int = 0
|
||||
usage_used: int = 0
|
||||
usage_remaining: int = 0
|
||||
usage_percent: float = 0
|
||||
total_requests: int = 0
|
||||
locked_by_key_id: Optional[int] = None
|
||||
last_analyzed_at: Optional[datetime] = None
|
||||
remark: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
@@ -51,7 +81,7 @@ class ExternalAccountItem(BaseModel):
|
||||
access_token: str
|
||||
refresh_token: Optional[str] = None
|
||||
workos_session_token: Optional[str] = None
|
||||
membership_type: Optional[str] = "free" # free/pro, 默认free(auto账号)
|
||||
membership_type: Optional[str] = "free" # Cursor账号类型: free/free_trial/pro/business
|
||||
remark: Optional[str] = None
|
||||
|
||||
class ExternalBatchUpload(BaseModel):
|
||||
@@ -72,7 +102,7 @@ class ExternalBatchResponse(BaseModel):
|
||||
# ========== 激活码相关 ==========
|
||||
|
||||
class KeyBase(BaseModel):
|
||||
membership_type: MembershipType = MembershipType.PRO # pro=高级模型, free=无限auto
|
||||
membership_type: KeyMembershipType = KeyMembershipType.PRO # pro=高级模型, auto=无限换号
|
||||
quota: int = 500 # 总额度 (仅Pro有效)
|
||||
valid_days: int = 30 # 有效天数,0表示永久 (仅Auto有效)
|
||||
max_devices: int = 2 # 最大设备数
|
||||
@@ -83,7 +113,7 @@ class KeyCreate(KeyBase):
|
||||
count: int = 1 # 批量生成数量
|
||||
|
||||
class KeyUpdate(BaseModel):
|
||||
membership_type: Optional[MembershipType] = None
|
||||
membership_type: Optional[KeyMembershipType] = None
|
||||
quota: Optional[int] = None
|
||||
valid_days: Optional[int] = None
|
||||
max_devices: Optional[int] = None
|
||||
@@ -98,15 +128,15 @@ class KeyResponse(BaseModel):
|
||||
id: int
|
||||
key: str
|
||||
status: KeyStatus
|
||||
membership_type: MembershipType
|
||||
membership_type: KeyMembershipType
|
||||
quota: int
|
||||
quota_used: int
|
||||
quota_remaining: Optional[int] = None # 剩余额度(计算字段)
|
||||
valid_days: int
|
||||
valid_days: int = 30 # 有效天数 (映射自 duration_days)
|
||||
first_activated_at: Optional[datetime] = None
|
||||
expire_at: Optional[datetime] = None
|
||||
max_devices: int
|
||||
switch_count: int
|
||||
switch_count: int = 0
|
||||
last_switch_at: Optional[datetime] = None
|
||||
current_account_id: Optional[int] = None
|
||||
remark: Optional[str] = None
|
||||
@@ -184,17 +214,43 @@ class LoginRequest(BaseModel):
|
||||
|
||||
class GlobalSettingsResponse(BaseModel):
|
||||
"""全局设置响应"""
|
||||
# Auto密钥设置
|
||||
auto_switch_interval_minutes: int = 20 # 换号最小间隔(分钟)
|
||||
auto_max_switches_per_day: int = 50 # 每天最大换号次数
|
||||
# Pro密钥设置
|
||||
pro_quota_cost: int = 50 # 每次换号扣除额度
|
||||
# ===== 密钥策略 =====
|
||||
key_max_devices: int = 2 # 主密钥最大设备数
|
||||
auto_merge_enabled: bool = True # 是否启用同类型密钥自动合并
|
||||
# ===== 自动检测开关 =====
|
||||
auto_analyze_enabled: bool = False # 是否启用自动账号分析
|
||||
auto_switch_enabled: bool = True # 是否启用自动换号
|
||||
# ===== 账号分析设置 =====
|
||||
account_analyze_interval: int = 300 # 账号分析间隔(秒)
|
||||
account_analyze_batch_size: int = 10 # 每批分析账号数量
|
||||
# ===== 换号阈值 =====
|
||||
auto_switch_threshold: int = 98 # Auto池自动换号阈值(用量百分比)
|
||||
pro_switch_threshold: int = 98 # Pro池自动换号阈值(用量百分比)
|
||||
# ===== 换号限制 =====
|
||||
max_switch_per_day: int = 50 # 每日最大换号次数
|
||||
auto_daily_switches: int = 999 # Auto密钥每日换号次数限制
|
||||
auto_switch_interval: int = 0 # Auto密钥换号冷却时间(分钟), 0表示无限制
|
||||
pro_quota_per_switch: int = 1 # Pro密钥每次换号消耗积分
|
||||
|
||||
class GlobalSettingsUpdate(BaseModel):
|
||||
"""更新全局设置"""
|
||||
auto_switch_interval_minutes: Optional[int] = None
|
||||
auto_max_switches_per_day: Optional[int] = None
|
||||
pro_quota_cost: Optional[int] = None
|
||||
# ===== 密钥策略 =====
|
||||
key_max_devices: Optional[int] = None
|
||||
auto_merge_enabled: Optional[bool] = None
|
||||
# ===== 自动检测开关 =====
|
||||
auto_analyze_enabled: Optional[bool] = None
|
||||
auto_switch_enabled: Optional[bool] = None
|
||||
# ===== 账号分析设置 =====
|
||||
account_analyze_interval: Optional[int] = None
|
||||
account_analyze_batch_size: Optional[int] = None
|
||||
# ===== 换号阈值 =====
|
||||
auto_switch_threshold: Optional[int] = None
|
||||
pro_switch_threshold: Optional[int] = None
|
||||
# ===== 换号限制 =====
|
||||
max_switch_per_day: Optional[int] = None
|
||||
auto_daily_switches: Optional[int] = None
|
||||
auto_switch_interval: Optional[int] = None
|
||||
pro_quota_per_switch: Optional[int] = None
|
||||
|
||||
|
||||
# ========== 批量操作相关 ==========
|
||||
@@ -210,3 +266,20 @@ class BatchExtendResponse(BaseModel):
|
||||
success: int
|
||||
failed: int
|
||||
errors: List[str] = []
|
||||
|
||||
|
||||
# ========== 公告相关 ==========
|
||||
|
||||
class AnnouncementCreate(BaseModel):
|
||||
"""创建公告"""
|
||||
title: str
|
||||
content: str
|
||||
type: str = "info" # info/warning/error/success
|
||||
is_active: bool = True
|
||||
|
||||
class AnnouncementUpdate(BaseModel):
|
||||
"""更新公告"""
|
||||
title: Optional[str] = None
|
||||
content: Optional[str] = None
|
||||
type: Optional[str] = None
|
||||
is_active: Optional[bool] = None
|
||||
|
||||
Reference in New Issue
Block a user