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:
2026-03-09 19:58:05 +08:00
parent 73a71f198f
commit ac19d029da
20 changed files with 3341 additions and 1440 deletions

View File

@@ -1,5 +1,5 @@
"""
Cursor 官方用量 API 服务
Cursor 官方用量 API 服务 v2.1
用于验证账号有效性和查询用量信息
"""
import httpx
@@ -7,6 +7,7 @@ import asyncio
from typing import Optional, Dict, Any, Tuple, List
from dataclasses import dataclass
from datetime import datetime
from decimal import Decimal
@dataclass
@@ -289,27 +290,30 @@ async def get_account_usage(token: str) -> Dict[str, Any]:
}
async def batch_check_accounts(tokens: List[str]) -> List[Dict[str, Any]]:
async def batch_check_accounts(tokens: List[str], max_concurrency: int = 5) -> List[Dict[str, Any]]:
"""
批量检查多个账号
批量检查多个账号(并发,带限流)
"""
results = []
for token in tokens:
info = await cursor_usage_service.validate_and_get_usage(token)
results.append({
"token": token[:20] + "...", # 脱敏
"is_valid": info.is_valid,
"is_usable": info.is_usable,
"pool_type": info.pool_type, # pro/auto
"membership_type": info.membership_type if info.is_valid else None,
"days_remaining_on_trial": info.days_remaining_on_trial,
"plan_used": info.plan_used if info.is_valid else 0,
"plan_limit": info.plan_limit if info.is_valid else 0,
"plan_remaining": info.plan_remaining if info.is_valid else 0,
"total_requests": info.total_requests if info.is_valid else 0,
"error": info.error_message
})
return results
semaphore = asyncio.Semaphore(max_concurrency)
async def _check_one(token: str) -> Dict[str, Any]:
async with semaphore:
info = await cursor_usage_service.validate_and_get_usage(token)
return {
"token": token[:20] + "...",
"is_valid": info.is_valid,
"is_usable": info.is_usable,
"pool_type": info.pool_type,
"membership_type": info.membership_type if info.is_valid else None,
"days_remaining_on_trial": info.days_remaining_on_trial,
"plan_used": info.plan_used if info.is_valid else 0,
"plan_limit": info.plan_limit if info.is_valid else 0,
"plan_remaining": info.plan_remaining if info.is_valid else 0,
"total_requests": info.total_requests if info.is_valid else 0,
"error": info.error_message
}
return await asyncio.gather(*[_check_one(t) for t in tokens])
async def check_and_classify_account(token: str) -> Dict[str, Any]:
@@ -335,3 +339,95 @@ async def check_and_classify_account(token: str) -> Dict[str, Any]:
"total_requests": info.total_requests,
"recommendation": f"建议放入 {'Pro' if info.pool_type == 'pro' else 'Auto'} 号池"
}
# ============ 数据库集成函数 ============
def map_membership_to_account_type(membership_type: str) -> str:
"""
将 Cursor API 的 membershipType 映射到 AccountType
"""
mapping = {
"free_trial": "free_trial",
"pro": "pro",
"free": "free",
"business": "business",
"enterprise": "business",
}
return mapping.get(membership_type, "unknown")
def calculate_usage_percent(used: int, limit: int) -> Decimal:
"""计算用量百分比"""
if limit <= 0:
return Decimal("0")
return Decimal(str(round(used / limit * 100, 2)))
async def analyze_account_from_token(token: str) -> Dict[str, Any]:
"""
分析账号Token返回所有需要更新到数据库的字段
用于后台分析任务
"""
info = await cursor_usage_service.validate_and_get_usage(token)
if not info.is_valid:
return {
"success": False,
"error": info.error_message,
"status": "invalid"
}
# 计算用量百分比
usage_percent = calculate_usage_percent(info.plan_used, info.plan_limit)
# 确定账号状态
if usage_percent >= Decimal("95"):
status = "exhausted"
else:
status = "available"
# 解析计费周期时间
billing_start = None
billing_end = None
try:
if info.billing_cycle_start:
billing_start = datetime.fromisoformat(info.billing_cycle_start.replace('Z', '+00:00'))
if info.billing_cycle_end:
billing_end = datetime.fromisoformat(info.billing_cycle_end.replace('Z', '+00:00'))
except:
pass
return {
"success": True,
"status": status,
"account_type": map_membership_to_account_type(info.membership_type),
"membership_type": info.membership_type,
"billing_cycle_start": billing_start,
"billing_cycle_end": billing_end,
"trial_days_remaining": info.days_remaining_on_trial or 0,
"usage_limit": info.plan_limit,
"usage_used": info.plan_used,
"usage_remaining": info.plan_remaining,
"usage_percent": usage_percent,
"total_requests": info.total_requests,
"total_input_tokens": info.total_input_tokens,
"total_output_tokens": info.total_output_tokens,
"total_cost_cents": Decimal(str(info.total_cost_cents)),
"last_analyzed_at": datetime.now(),
"analyze_error": None
}
async def quick_validate_token(token: str) -> Tuple[bool, Optional[str]]:
"""
快速验证Token有效性仅调用usage-summary
用于激活时的快速检查
"""
try:
resp = await cursor_usage_service.get_usage_summary(token)
if resp["success"]:
return True, None
return False, resp.get("error", "验证失败")
except Exception as e:
return False, str(e)