- 新增 Announcement 数据模型,支持公告的增删改查 - 后台管理新增"公告管理"Tab(创建/编辑/删除/启用禁用) - 客户端 /api/announcement 改为从数据库读取 - 账号服务重构,新增无感换号、自动分析等功能 - 新增后台任务调度器、数据库迁移脚本 - Schema/Service/Config 全面升级至 v2.1 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
286 lines
9.1 KiB
Python
286 lines
9.1 KiB
Python
from pydantic import BaseModel, EmailStr, Field, model_validator
|
||
from typing import Optional, List, Any
|
||
from datetime import datetime
|
||
from app.models.models import KeyMembershipType, AccountStatus, KeyStatus
|
||
|
||
|
||
# ========== 账号相关 ==========
|
||
|
||
class AccountBase(BaseModel):
|
||
"""账号基础信息 (用于创建/更新)"""
|
||
email: str
|
||
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):
|
||
"""创建账号 (兼容旧字段)"""
|
||
|
||
@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
|
||
password: Optional[str] = None
|
||
status: Optional[AccountStatus] = None
|
||
remark: Optional[str] = None
|
||
|
||
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
|
||
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
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
class AccountImport(BaseModel):
|
||
"""批量导入账号"""
|
||
accounts: List[AccountCreate]
|
||
|
||
|
||
# ========== 外部系统批量上传 ==========
|
||
|
||
class ExternalAccountItem(BaseModel):
|
||
"""外部系统上传的账号项"""
|
||
email: str
|
||
access_token: str
|
||
refresh_token: Optional[str] = None
|
||
workos_session_token: Optional[str] = None
|
||
membership_type: Optional[str] = "free" # Cursor账号类型: free/free_trial/pro/business
|
||
remark: Optional[str] = None
|
||
|
||
class ExternalBatchUpload(BaseModel):
|
||
"""外部系统批量上传请求"""
|
||
accounts: List[ExternalAccountItem]
|
||
update_existing: bool = True # 是否更新已存在的账号
|
||
|
||
class ExternalBatchResponse(BaseModel):
|
||
"""外部系统批量上传响应"""
|
||
success: bool
|
||
total: int
|
||
created: int
|
||
updated: int
|
||
failed: int
|
||
errors: List[str] = []
|
||
|
||
|
||
# ========== 激活码相关 ==========
|
||
|
||
class KeyBase(BaseModel):
|
||
membership_type: KeyMembershipType = KeyMembershipType.PRO # pro=高级模型, auto=无限换号
|
||
quota: int = 500 # 总额度 (仅Pro有效)
|
||
valid_days: int = 30 # 有效天数,0表示永久 (仅Auto有效)
|
||
max_devices: int = 2 # 最大设备数
|
||
remark: Optional[str] = None
|
||
|
||
class KeyCreate(KeyBase):
|
||
key: Optional[str] = None # 不提供则自动生成
|
||
count: int = 1 # 批量生成数量
|
||
|
||
class KeyUpdate(BaseModel):
|
||
membership_type: Optional[KeyMembershipType] = None
|
||
quota: Optional[int] = None
|
||
valid_days: Optional[int] = None
|
||
max_devices: Optional[int] = None
|
||
status: Optional[KeyStatus] = None
|
||
remark: Optional[str] = None
|
||
|
||
class KeyAddQuota(BaseModel):
|
||
"""叠加额度(只加额度不加时间)"""
|
||
add_quota: int
|
||
|
||
class KeyResponse(BaseModel):
|
||
id: int
|
||
key: str
|
||
status: KeyStatus
|
||
membership_type: KeyMembershipType
|
||
quota: int
|
||
quota_used: int
|
||
quota_remaining: Optional[int] = None # 剩余额度(计算字段)
|
||
valid_days: int = 30 # 有效天数 (映射自 duration_days)
|
||
first_activated_at: Optional[datetime] = None
|
||
expire_at: Optional[datetime] = None
|
||
max_devices: int
|
||
switch_count: int = 0
|
||
last_switch_at: Optional[datetime] = None
|
||
current_account_id: Optional[int] = None
|
||
remark: Optional[str] = None
|
||
created_at: datetime
|
||
updated_at: datetime
|
||
|
||
class Config:
|
||
from_attributes = True
|
||
|
||
|
||
# ========== 客户端 API 相关 ==========
|
||
|
||
class VerifyKeyRequest(BaseModel):
|
||
key: str
|
||
device_id: Optional[str] = None # 设备标识
|
||
|
||
class VerifyKeyResponse(BaseModel):
|
||
success: bool
|
||
message: Optional[str] = None
|
||
error: Optional[str] = None
|
||
# 成功时返回
|
||
email: Optional[str] = None
|
||
accessToken: Optional[str] = None
|
||
refreshToken: Optional[str] = None
|
||
WorkosCursorSessionToken: Optional[str] = None
|
||
membership_type: Optional[str] = None
|
||
# 额度信息
|
||
quota: Optional[int] = None # 总额度
|
||
quotaUsed: Optional[int] = None # 已用额度
|
||
quotaRemaining: Optional[int] = None # 剩余额度
|
||
quotaCost: Optional[int] = None # 每次换号消耗
|
||
expireDate: Optional[str] = None
|
||
|
||
class SwitchAccountRequest(BaseModel):
|
||
key: str
|
||
device_id: Optional[str] = None # 设备标识
|
||
|
||
class SwitchAccountResponse(BaseModel):
|
||
success: bool
|
||
message: Optional[str] = None
|
||
error: Optional[str] = None
|
||
email: Optional[str] = None
|
||
accessToken: Optional[str] = None
|
||
refreshToken: Optional[str] = None
|
||
WorkosCursorSessionToken: Optional[str] = None
|
||
membership_type: Optional[str] = None
|
||
# 额度信息
|
||
quotaUsed: Optional[int] = None
|
||
quotaRemaining: Optional[int] = None
|
||
|
||
|
||
# ========== 统计相关 ==========
|
||
|
||
class DashboardStats(BaseModel):
|
||
total_accounts: int
|
||
active_accounts: int
|
||
pro_accounts: int
|
||
total_keys: int
|
||
active_keys: int
|
||
today_usage: int
|
||
|
||
|
||
# ========== 认证相关 ==========
|
||
|
||
class Token(BaseModel):
|
||
access_token: str
|
||
token_type: str = "bearer"
|
||
|
||
class LoginRequest(BaseModel):
|
||
username: str
|
||
password: str
|
||
|
||
|
||
# ========== 全局设置相关 ==========
|
||
|
||
class GlobalSettingsResponse(BaseModel):
|
||
"""全局设置响应"""
|
||
# ===== 密钥策略 =====
|
||
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):
|
||
"""更新全局设置"""
|
||
# ===== 密钥策略 =====
|
||
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
|
||
|
||
|
||
# ========== 批量操作相关 ==========
|
||
|
||
class BatchExtendRequest(BaseModel):
|
||
"""批量延长请求"""
|
||
key_ids: List[int] # 激活码ID列表
|
||
extend_days: int = 0 # 延长天数(Auto和Pro都可用)
|
||
add_quota: int = 0 # 增加额度(仅Pro有效)
|
||
|
||
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
|