Files
cursornew2026/backend/app/schemas/schemas.py
huangzhenpc ac19d029da backend v2.1: 公告管理功能 + 系统重构
- 新增 Announcement 数据模型,支持公告的增删改查
- 后台管理新增"公告管理"Tab(创建/编辑/删除/启用禁用)
- 客户端 /api/announcement 改为从数据库读取
- 账号服务重构,新增无感换号、自动分析等功能
- 新增后台任务调度器、数据库迁移脚本
- Schema/Service/Config 全面升级至 v2.1

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

286 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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