备份: 完整开发状态(含反混淆脚本和临时文件)
This commit is contained in:
@@ -3,16 +3,18 @@
|
||||
"""
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query, Header
|
||||
from sqlalchemy.orm import Session
|
||||
from app.database import get_db
|
||||
from app.config import settings
|
||||
from app.services import AccountService, KeyService, LogService, GlobalSettingsService, BatchService, authenticate_admin, create_access_token, get_current_user
|
||||
from app.schemas import (
|
||||
AccountCreate, AccountUpdate, AccountResponse, AccountImport,
|
||||
KeyCreate, KeyUpdate, KeyResponse,
|
||||
DashboardStats, Token, LoginRequest,
|
||||
GlobalSettingsResponse, GlobalSettingsUpdate,
|
||||
BatchExtendRequest, BatchExtendResponse
|
||||
BatchExtendRequest, BatchExtendResponse,
|
||||
ExternalBatchUpload, ExternalBatchResponse
|
||||
)
|
||||
from app.models import MembershipType, KeyDevice, UsageLog, ActivationKey
|
||||
|
||||
@@ -33,6 +35,138 @@ async def login(request: LoginRequest):
|
||||
return Token(access_token=access_token)
|
||||
|
||||
|
||||
# ========== 外部系统API (Token认证) ==========
|
||||
|
||||
def verify_api_token(x_api_token: str = Header(..., alias="X-API-Token")):
|
||||
"""验证外部系统API Token"""
|
||||
if x_api_token != settings.API_TOKEN:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid API Token"
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
@router.post("/external/accounts/batch", response_model=ExternalBatchResponse)
|
||||
async def external_batch_upload(
|
||||
data: ExternalBatchUpload,
|
||||
db: Session = Depends(get_db),
|
||||
_: bool = Depends(verify_api_token)
|
||||
):
|
||||
"""外部系统批量上传账号
|
||||
|
||||
使用方法:
|
||||
POST /admin/external/accounts/batch
|
||||
Headers: X-API-Token: your-api-token
|
||||
Body: {
|
||||
"accounts": [
|
||||
{
|
||||
"email": "user@example.com",
|
||||
"access_token": "xxx",
|
||||
"refresh_token": "xxx",
|
||||
"workos_session_token": "xxx",
|
||||
"membership_type": "free", // free=auto账号, pro=高级账号
|
||||
"remark": "备注"
|
||||
}
|
||||
],
|
||||
"update_existing": true // 是否更新已存在的账号
|
||||
}
|
||||
"""
|
||||
created = 0
|
||||
updated = 0
|
||||
failed = 0
|
||||
errors = []
|
||||
|
||||
for item in data.accounts:
|
||||
try:
|
||||
# 转换membership_type
|
||||
mt = MembershipType.FREE if item.membership_type == "free" else MembershipType.PRO
|
||||
|
||||
existing = AccountService.get_by_email(db, item.email)
|
||||
if existing:
|
||||
if data.update_existing:
|
||||
# 更新已存在的账号
|
||||
AccountService.update(
|
||||
db, existing.id,
|
||||
access_token=item.access_token,
|
||||
refresh_token=item.refresh_token,
|
||||
workos_session_token=item.workos_session_token,
|
||||
membership_type=mt,
|
||||
remark=item.remark or existing.remark
|
||||
)
|
||||
updated += 1
|
||||
else:
|
||||
failed += 1
|
||||
errors.append(f"{item.email}: 账号已存在")
|
||||
else:
|
||||
# 创建新账号
|
||||
account_data = AccountCreate(
|
||||
email=item.email,
|
||||
access_token=item.access_token,
|
||||
refresh_token=item.refresh_token,
|
||||
workos_session_token=item.workos_session_token,
|
||||
membership_type=mt,
|
||||
remark=item.remark
|
||||
)
|
||||
AccountService.create(db, account_data)
|
||||
created += 1
|
||||
except Exception as e:
|
||||
failed += 1
|
||||
errors.append(f"{item.email}: {str(e)}")
|
||||
|
||||
return ExternalBatchResponse(
|
||||
success=failed == 0,
|
||||
total=len(data.accounts),
|
||||
created=created,
|
||||
updated=updated,
|
||||
failed=failed,
|
||||
errors=errors[:20] # 只返回前20个错误
|
||||
)
|
||||
|
||||
|
||||
@router.get("/external/accounts/stats")
|
||||
async def external_account_stats(
|
||||
db: Session = Depends(get_db),
|
||||
_: bool = Depends(verify_api_token)
|
||||
):
|
||||
"""外部系统获取账号统计"""
|
||||
stats = AccountService.count(db)
|
||||
return {
|
||||
"total": stats["total"],
|
||||
"active": stats["active"],
|
||||
"pro": stats["pro"],
|
||||
"free": stats["total"] - stats["pro"]
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/external/accounts/batch")
|
||||
async def external_batch_delete(
|
||||
emails: List[str],
|
||||
db: Session = Depends(get_db),
|
||||
_: bool = Depends(verify_api_token)
|
||||
):
|
||||
"""外部系统批量删除账号"""
|
||||
deleted = 0
|
||||
failed = 0
|
||||
|
||||
for email in emails:
|
||||
try:
|
||||
account = AccountService.get_by_email(db, email)
|
||||
if account:
|
||||
AccountService.delete(db, account.id)
|
||||
deleted += 1
|
||||
else:
|
||||
failed += 1
|
||||
except Exception:
|
||||
failed += 1
|
||||
|
||||
return {
|
||||
"success": failed == 0,
|
||||
"deleted": deleted,
|
||||
"failed": failed
|
||||
}
|
||||
|
||||
|
||||
# ========== 仪表盘 ==========
|
||||
|
||||
@router.get("/dashboard", response_model=DashboardStats)
|
||||
@@ -118,6 +252,16 @@ async def import_accounts(
|
||||
}
|
||||
|
||||
|
||||
@router.get("/accounts/count")
|
||||
async def get_accounts_count(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""获取账号总数"""
|
||||
stats = AccountService.count(db)
|
||||
return {"total": stats["total"]}
|
||||
|
||||
|
||||
@router.get("/accounts/{account_id}", response_model=AccountResponse)
|
||||
async def get_account(
|
||||
account_id: int,
|
||||
@@ -157,6 +301,166 @@ async def delete_account(
|
||||
return {"message": "删除成功"}
|
||||
|
||||
|
||||
@router.post("/accounts/{account_id}/toggle-status")
|
||||
async def toggle_account_status(
|
||||
account_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""快捷切换账号状态
|
||||
|
||||
切换逻辑:
|
||||
- 使用中(in_use) -> 可用(active) 释放账号
|
||||
- 可用(active) -> 禁用(disabled)
|
||||
- 禁用(disabled) -> 可用(active)
|
||||
- 过期(expired) -> 可用(active)
|
||||
"""
|
||||
from app.models import AccountStatus, Account
|
||||
account = db.query(Account).filter(Account.id == account_id).first()
|
||||
if not account:
|
||||
raise HTTPException(status_code=404, detail="账号不存在")
|
||||
|
||||
old_status = account.status
|
||||
|
||||
# 根据当前状态切换
|
||||
if account.status == AccountStatus.IN_USE:
|
||||
account.status = AccountStatus.ACTIVE
|
||||
account.current_key_id = None # 释放绑定
|
||||
elif account.status == AccountStatus.ACTIVE:
|
||||
account.status = AccountStatus.DISABLED
|
||||
elif account.status == AccountStatus.DISABLED:
|
||||
account.status = AccountStatus.ACTIVE
|
||||
elif account.status == AccountStatus.EXPIRED:
|
||||
account.status = AccountStatus.ACTIVE
|
||||
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"old_status": old_status.value,
|
||||
"new_status": account.status.value,
|
||||
"message": f"状态已从 {old_status.value} 切换为 {account.status.value}"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/accounts/{account_id}/release")
|
||||
async def release_account(
|
||||
account_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""释放账号(从使用中变为可用)"""
|
||||
from app.models import AccountStatus, Account
|
||||
account = db.query(Account).filter(Account.id == account_id).first()
|
||||
if not account:
|
||||
raise HTTPException(status_code=404, detail="账号不存在")
|
||||
|
||||
if account.status != AccountStatus.IN_USE:
|
||||
return {"success": False, "message": "账号不在使用中状态"}
|
||||
|
||||
account.status = AccountStatus.ACTIVE
|
||||
account.current_key_id = None
|
||||
db.commit()
|
||||
|
||||
return {"success": True, "message": "账号已释放"}
|
||||
|
||||
|
||||
@router.post("/accounts/batch-enable")
|
||||
async def batch_enable_accounts(
|
||||
account_ids: List[int],
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""批量启用账号"""
|
||||
from app.models import AccountStatus, Account
|
||||
success = 0
|
||||
failed = 0
|
||||
|
||||
for account_id in account_ids:
|
||||
try:
|
||||
account = db.query(Account).filter(Account.id == account_id).first()
|
||||
if account:
|
||||
account.status = AccountStatus.ACTIVE
|
||||
success += 1
|
||||
else:
|
||||
failed += 1
|
||||
except Exception:
|
||||
failed += 1
|
||||
|
||||
db.commit()
|
||||
return {
|
||||
"success": success,
|
||||
"failed": failed,
|
||||
"message": f"成功启用 {success} 个账号"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/accounts/batch-disable")
|
||||
async def batch_disable_accounts(
|
||||
account_ids: List[int],
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""批量禁用账号"""
|
||||
from app.models import AccountStatus, Account
|
||||
success = 0
|
||||
failed = 0
|
||||
|
||||
for account_id in account_ids:
|
||||
try:
|
||||
account = db.query(Account).filter(Account.id == account_id).first()
|
||||
if account:
|
||||
account.status = AccountStatus.DISABLED
|
||||
success += 1
|
||||
else:
|
||||
failed += 1
|
||||
except Exception:
|
||||
failed += 1
|
||||
|
||||
db.commit()
|
||||
return {
|
||||
"success": success,
|
||||
"failed": failed,
|
||||
"message": f"成功禁用 {success} 个账号"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/accounts/batch-delete")
|
||||
async def batch_delete_accounts(
|
||||
account_ids: List[int],
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""批量删除账号"""
|
||||
success = 0
|
||||
failed = 0
|
||||
|
||||
for account_id in account_ids:
|
||||
try:
|
||||
if AccountService.delete(db, account_id):
|
||||
success += 1
|
||||
else:
|
||||
failed += 1
|
||||
except Exception:
|
||||
failed += 1
|
||||
|
||||
return {
|
||||
"success": success,
|
||||
"failed": failed,
|
||||
"message": f"成功删除 {success} 个账号"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/accounts/count")
|
||||
async def get_accounts_count(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""获取账号总数"""
|
||||
stats = AccountService.count(db)
|
||||
return {"total": stats["total"]}
|
||||
|
||||
|
||||
# ========== 激活码管理 ==========
|
||||
|
||||
@router.get("/keys", response_model=List[KeyResponse])
|
||||
@@ -165,7 +469,7 @@ async def list_keys(
|
||||
limit: int = 100,
|
||||
search: Optional[str] = Query(None, description="搜索激活码"),
|
||||
status: Optional[str] = Query(None, description="状态筛选: active/disabled"),
|
||||
activated: Optional[bool] = Query(None, description="是否已激活"),
|
||||
activated: Optional[str] = Query(None, description="是否已激活: true/false"),
|
||||
membership_type: Optional[str] = Query(None, description="套餐类型: pro/free"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
@@ -180,12 +484,13 @@ async def list_keys(
|
||||
|
||||
# 状态筛选
|
||||
if status:
|
||||
query = query.filter(ActivationKey.status == status)
|
||||
status_enum = KeyStatus.ACTIVE if status == "active" else KeyStatus.DISABLED
|
||||
query = query.filter(ActivationKey.status == status_enum)
|
||||
|
||||
# 是否已激活
|
||||
if activated is True:
|
||||
if activated and activated == "true":
|
||||
query = query.filter(ActivationKey.first_activated_at != None)
|
||||
elif activated is False:
|
||||
elif activated and activated == "false":
|
||||
query = query.filter(ActivationKey.first_activated_at == None)
|
||||
|
||||
# 套餐类型筛选
|
||||
@@ -196,6 +501,43 @@ async def list_keys(
|
||||
return query.offset(skip).limit(limit).all()
|
||||
|
||||
|
||||
@router.get("/keys/count")
|
||||
async def get_keys_count(
|
||||
search: Optional[str] = Query(None, description="搜索激活码"),
|
||||
status: Optional[str] = Query(None, description="状态筛选: active/disabled"),
|
||||
activated: Optional[str] = Query(None, description="是否已激活: true/false"),
|
||||
membership_type: Optional[str] = Query(None, description="套餐类型: pro/free"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""获取激活码总数(支持筛选)"""
|
||||
from app.models import KeyStatus
|
||||
query = db.query(ActivationKey)
|
||||
|
||||
# 搜索激活码
|
||||
if search:
|
||||
query = query.filter(ActivationKey.key.contains(search))
|
||||
|
||||
# 状态筛选
|
||||
if status:
|
||||
status_enum = KeyStatus.ACTIVE if status == "active" else KeyStatus.DISABLED
|
||||
query = query.filter(ActivationKey.status == status_enum)
|
||||
|
||||
# 是否已激活
|
||||
if activated and activated == "true":
|
||||
query = query.filter(ActivationKey.first_activated_at != None)
|
||||
elif activated and activated == "false":
|
||||
query = query.filter(ActivationKey.first_activated_at == None)
|
||||
|
||||
# 套餐类型筛选
|
||||
if membership_type:
|
||||
mt = MembershipType.PRO if membership_type.lower() == "pro" else MembershipType.FREE
|
||||
query = query.filter(ActivationKey.membership_type == mt)
|
||||
|
||||
total = query.count()
|
||||
return {"total": total}
|
||||
|
||||
|
||||
@router.post("/keys", response_model=List[KeyResponse])
|
||||
async def create_keys(
|
||||
key_data: KeyCreate,
|
||||
@@ -344,6 +686,129 @@ async def enable_key(
|
||||
return {"message": "激活码已启用"}
|
||||
|
||||
|
||||
@router.post("/keys/batch-enable")
|
||||
async def batch_enable_keys(
|
||||
key_ids: List[int],
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""批量启用激活码"""
|
||||
from app.models import KeyStatus
|
||||
success = 0
|
||||
failed = 0
|
||||
|
||||
for key_id in key_ids:
|
||||
try:
|
||||
key = KeyService.get_by_id(db, key_id)
|
||||
if key:
|
||||
key.status = KeyStatus.ACTIVE
|
||||
success += 1
|
||||
else:
|
||||
failed += 1
|
||||
except Exception:
|
||||
failed += 1
|
||||
|
||||
db.commit()
|
||||
return {
|
||||
"success": success,
|
||||
"failed": failed,
|
||||
"message": f"成功启用 {success} 个激活码"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/keys/batch-disable")
|
||||
async def batch_disable_keys(
|
||||
key_ids: List[int],
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""批量禁用激活码"""
|
||||
from app.models import KeyStatus
|
||||
success = 0
|
||||
failed = 0
|
||||
|
||||
for key_id in key_ids:
|
||||
try:
|
||||
key = KeyService.get_by_id(db, key_id)
|
||||
if key:
|
||||
key.status = KeyStatus.DISABLED
|
||||
success += 1
|
||||
else:
|
||||
failed += 1
|
||||
except Exception:
|
||||
failed += 1
|
||||
|
||||
db.commit()
|
||||
return {
|
||||
"success": success,
|
||||
"failed": failed,
|
||||
"message": f"成功禁用 {success} 个激活码"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/keys/batch-delete")
|
||||
async def batch_delete_keys(
|
||||
key_ids: List[int],
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""批量删除激活码"""
|
||||
success = 0
|
||||
failed = 0
|
||||
|
||||
for key_id in key_ids:
|
||||
try:
|
||||
if KeyService.delete(db, key_id):
|
||||
success += 1
|
||||
else:
|
||||
failed += 1
|
||||
except Exception:
|
||||
failed += 1
|
||||
|
||||
return {
|
||||
"success": success,
|
||||
"failed": failed,
|
||||
"message": f"成功删除 {success} 个激活码"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/keys/count")
|
||||
async def get_keys_count(
|
||||
search: Optional[str] = Query(None, description="搜索激活码"),
|
||||
status: Optional[str] = Query(None, description="状态筛选: active/disabled"),
|
||||
activated: Optional[str] = Query(None, description="是否已激活: true/false"),
|
||||
membership_type: Optional[str] = Query(None, description="套餐类型: pro/free"),
|
||||
db: Session = Depends(get_db),
|
||||
current_user: dict = Depends(get_current_user)
|
||||
):
|
||||
"""获取激活码总数(支持筛选)"""
|
||||
from app.models import KeyStatus
|
||||
query = db.query(ActivationKey)
|
||||
|
||||
# 搜索激活码
|
||||
if search:
|
||||
query = query.filter(ActivationKey.key.contains(search))
|
||||
|
||||
# 状态筛选
|
||||
if status:
|
||||
status_enum = KeyStatus.ACTIVE if status == "active" else KeyStatus.DISABLED
|
||||
query = query.filter(ActivationKey.status == status_enum)
|
||||
|
||||
# 是否已激活
|
||||
if activated and activated == "true":
|
||||
query = query.filter(ActivationKey.first_activated_at != None)
|
||||
elif activated and activated == "false":
|
||||
query = query.filter(ActivationKey.first_activated_at == None)
|
||||
|
||||
# 套餐类型筛选
|
||||
if membership_type:
|
||||
mt = MembershipType.PRO if membership_type.lower() == "pro" else MembershipType.FREE
|
||||
query = query.filter(ActivationKey.membership_type == mt)
|
||||
|
||||
total = query.count()
|
||||
return {"total": total}
|
||||
|
||||
|
||||
@router.post("/keys/{key_id}/add-quota", response_model=KeyResponse)
|
||||
async def add_key_quota(
|
||||
key_id: int,
|
||||
|
||||
@@ -3,20 +3,87 @@
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional, List
|
||||
from pydantic import BaseModel
|
||||
from app.database import get_db
|
||||
from app.services import AccountService, KeyService, LogService, GlobalSettingsService
|
||||
from app.schemas import VerifyKeyRequest, VerifyKeyResponse, SwitchAccountRequest, SwitchAccountResponse
|
||||
from app.models import AccountStatus, MembershipType
|
||||
|
||||
router = APIRouter(prefix="/api", tags=["Client API"])
|
||||
|
||||
|
||||
@router.post("/verify-key", response_model=VerifyKeyResponse)
|
||||
# ========== 账号数据响应模型 ==========
|
||||
|
||||
class AccountData(BaseModel):
|
||||
"""账号数据 - 写入本地Cursor的数据"""
|
||||
accessToken: str
|
||||
refreshToken: Optional[str] = None
|
||||
workosSessionToken: Optional[str] = None
|
||||
email: str
|
||||
membership_type: str
|
||||
usage_type: Optional[str] = "default"
|
||||
# 机器ID相关 (可选)
|
||||
serviceMachineId: Optional[str] = None
|
||||
machineId: Optional[str] = None
|
||||
macMachineId: Optional[str] = None
|
||||
devDeviceId: Optional[str] = None
|
||||
sqmId: Optional[str] = None
|
||||
machineIdFile: Optional[str] = None
|
||||
# 使用统计
|
||||
requestCount: Optional[int] = 0
|
||||
usageAmount: Optional[float] = 0.0
|
||||
# 额度信息
|
||||
quota: Optional[int] = None
|
||||
quotaUsed: Optional[int] = None
|
||||
quotaRemaining: Optional[int] = None
|
||||
expireDate: Optional[str] = None
|
||||
|
||||
|
||||
class ApiResponse(BaseModel):
|
||||
"""通用API响应"""
|
||||
success: bool
|
||||
message: Optional[str] = None
|
||||
error: Optional[str] = None
|
||||
data: Optional[AccountData] = None
|
||||
|
||||
|
||||
# ========== 验证和切换 API ==========
|
||||
|
||||
def build_account_data(account, key) -> AccountData:
|
||||
"""构建账号数据对象"""
|
||||
expire_date = key.expire_at.strftime("%Y-%m-%d %H:%M:%S") if key.expire_at else None
|
||||
return AccountData(
|
||||
accessToken=account.access_token,
|
||||
refreshToken=account.refresh_token,
|
||||
workosSessionToken=account.workos_session_token,
|
||||
email=account.email,
|
||||
membership_type=account.membership_type.value,
|
||||
quota=key.quota,
|
||||
quotaUsed=key.quota_used,
|
||||
quotaRemaining=key.quota - key.quota_used,
|
||||
expireDate=expire_date
|
||||
)
|
||||
|
||||
|
||||
@router.post("/verify")
|
||||
async def verify(request: VerifyKeyRequest, req: Request, db: Session = Depends(get_db)):
|
||||
"""验证激活码 (前端调用的路径)"""
|
||||
return await verify_key_impl(request, req, db)
|
||||
|
||||
|
||||
@router.post("/verify-key")
|
||||
async def verify_key(request: VerifyKeyRequest, req: Request, db: Session = Depends(get_db)):
|
||||
"""验证激活码"""
|
||||
"""验证激活码 (兼容路径)"""
|
||||
return await verify_key_impl(request, req, db)
|
||||
|
||||
|
||||
async def verify_key_impl(request: VerifyKeyRequest, req: Request, db: Session):
|
||||
"""验证激活码实现"""
|
||||
key = KeyService.get_by_key(db, request.key)
|
||||
|
||||
if not key:
|
||||
return VerifyKeyResponse(success=False, error="激活码不存在")
|
||||
return {"success": False, "valid": False, "error": "激活码不存在"}
|
||||
|
||||
# 首次激活:设置激活时间和过期时间
|
||||
KeyService.activate(db, key)
|
||||
@@ -26,75 +93,81 @@ async def verify_key(request: VerifyKeyRequest, req: Request, db: Session = Depe
|
||||
device_ok, device_msg = KeyService.check_device(db, key, request.device_id)
|
||||
if not device_ok:
|
||||
LogService.log(db, key.id, "verify", ip_address=req.client.host, success=False, message=device_msg)
|
||||
return VerifyKeyResponse(success=False, error=device_msg)
|
||||
return {"success": False, "valid": False, "error": device_msg}
|
||||
|
||||
# 检查激活码是否有效
|
||||
is_valid, message = KeyService.is_valid(key, db)
|
||||
if not is_valid:
|
||||
LogService.log(db, key.id, "verify", ip_address=req.client.host, success=False, message=message)
|
||||
return VerifyKeyResponse(success=False, error=message)
|
||||
return {"success": False, "valid": False, "error": message}
|
||||
|
||||
# 获取当前绑定的账号,或分配新账号
|
||||
account = None
|
||||
if key.current_account_id:
|
||||
account = AccountService.get_by_id(db, key.current_account_id)
|
||||
|
||||
if not account or account.status != "active":
|
||||
# 只有账号不存在或被禁用/过期才分配新的(IN_USE 状态的账号继续使用)
|
||||
if not account or account.status in (AccountStatus.DISABLED, AccountStatus.EXPIRED):
|
||||
# 分配新账号
|
||||
account = AccountService.get_available(db, key.membership_type)
|
||||
if not account:
|
||||
LogService.log(db, key.id, "verify", ip_address=req.client.host, success=False, message="无可用账号")
|
||||
return VerifyKeyResponse(success=False, error="暂无可用账号,请稍后重试")
|
||||
return {"success": False, "valid": False, "error": "暂无可用账号,请稍后重试"}
|
||||
|
||||
KeyService.bind_account(db, key, account)
|
||||
AccountService.mark_used(db, account, key.id)
|
||||
|
||||
LogService.log(db, key.id, "verify", account.id, ip_address=req.client.host, success=True)
|
||||
|
||||
# 返回格式匹配原版插件期望
|
||||
expire_date = key.expire_at.strftime("%Y-%m-%d %H:%M:%S") if key.expire_at else None
|
||||
quota_cost = KeyService.get_quota_cost(db, key.membership_type)
|
||||
|
||||
return VerifyKeyResponse(
|
||||
success=True,
|
||||
email=account.email,
|
||||
accessToken=account.access_token,
|
||||
refreshToken=account.refresh_token,
|
||||
WorkosCursorSessionToken=account.workos_session_token,
|
||||
membership_type=account.membership_type.value,
|
||||
quota=key.quota,
|
||||
quotaUsed=key.quota_used,
|
||||
quotaRemaining=key.quota - key.quota_used,
|
||||
quotaCost=quota_cost,
|
||||
expireDate=expire_date
|
||||
)
|
||||
return {
|
||||
"success": True,
|
||||
"valid": True,
|
||||
"expire_date": expire_date,
|
||||
"switch_remaining": key.quota - key.quota_used,
|
||||
"switch_limit": key.quota,
|
||||
"data": build_account_data(account, key)
|
||||
}
|
||||
|
||||
|
||||
@router.post("/switch-account", response_model=SwitchAccountResponse)
|
||||
@router.post("/switch")
|
||||
async def switch(request: SwitchAccountRequest, req: Request, db: Session = Depends(get_db)):
|
||||
"""切换账号 (前端调用的路径)"""
|
||||
return await switch_account_impl(request, req, db)
|
||||
|
||||
|
||||
@router.post("/switch-account")
|
||||
async def switch_account(request: SwitchAccountRequest, req: Request, db: Session = Depends(get_db)):
|
||||
"""切换账号"""
|
||||
"""切换账号 (兼容路径)"""
|
||||
return await switch_account_impl(request, req, db)
|
||||
|
||||
|
||||
async def switch_account_impl(request: SwitchAccountRequest, req: Request, db: Session):
|
||||
"""切换账号实现"""
|
||||
key = KeyService.get_by_key(db, request.key)
|
||||
|
||||
if not key:
|
||||
return SwitchAccountResponse(success=False, error="激活码不存在")
|
||||
return ApiResponse(success=False, error="激活码不存在")
|
||||
|
||||
# 检查设备限制
|
||||
if request.device_id:
|
||||
device_ok, device_msg = KeyService.check_device(db, key, request.device_id)
|
||||
if not device_ok:
|
||||
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message=device_msg)
|
||||
return SwitchAccountResponse(success=False, error=device_msg)
|
||||
return ApiResponse(success=False, error=device_msg)
|
||||
|
||||
# 检查激活码是否有效
|
||||
is_valid, message = KeyService.is_valid(key, db)
|
||||
if not is_valid:
|
||||
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message=message)
|
||||
return SwitchAccountResponse(success=False, error=message)
|
||||
return ApiResponse(success=False, error=message)
|
||||
|
||||
# 检查换号频率限制
|
||||
can_switch, switch_msg = KeyService.can_switch(db, key)
|
||||
if not can_switch:
|
||||
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message=switch_msg)
|
||||
return SwitchAccountResponse(success=False, error=switch_msg)
|
||||
return ApiResponse(success=False, error=switch_msg)
|
||||
|
||||
# 释放当前账号
|
||||
if key.current_account_id:
|
||||
@@ -106,7 +179,7 @@ async def switch_account(request: SwitchAccountRequest, req: Request, db: Sessio
|
||||
account = AccountService.get_available(db, key.membership_type)
|
||||
if not account:
|
||||
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message="无可用账号")
|
||||
return SwitchAccountResponse(success=False, error="暂无可用账号,请稍后重试")
|
||||
return ApiResponse(success=False, error="暂无可用账号,请稍后重试")
|
||||
|
||||
# 绑定新账号并扣除额度
|
||||
KeyService.bind_account(db, key, account)
|
||||
@@ -115,34 +188,581 @@ async def switch_account(request: SwitchAccountRequest, req: Request, db: Sessio
|
||||
|
||||
LogService.log(db, key.id, "switch", account.id, ip_address=req.client.host, success=True)
|
||||
|
||||
return SwitchAccountResponse(
|
||||
return ApiResponse(
|
||||
success=True,
|
||||
message="切换成功",
|
||||
email=account.email,
|
||||
accessToken=account.access_token,
|
||||
refreshToken=account.refresh_token,
|
||||
WorkosCursorSessionToken=account.workos_session_token,
|
||||
membership_type=account.membership_type.value,
|
||||
quotaUsed=key.quota_used,
|
||||
quotaRemaining=key.quota - key.quota_used
|
||||
data=build_account_data(account, key)
|
||||
)
|
||||
|
||||
|
||||
# ========== 版本 API ==========
|
||||
|
||||
@router.get("/version")
|
||||
async def get_version():
|
||||
"""获取版本信息"""
|
||||
return {
|
||||
"success": True,
|
||||
"version": "1.0.0",
|
||||
"update_url": None,
|
||||
"message": None
|
||||
"latestVersion": "1.0.0",
|
||||
"updateUrl": None,
|
||||
"message": None,
|
||||
"forceUpdate": False
|
||||
}
|
||||
|
||||
|
||||
# ========== 代理配置 API ==========
|
||||
|
||||
# 内存存储代理配置 (生产环境应存数据库)
|
||||
proxy_config = {
|
||||
"is_enabled": False,
|
||||
"proxy_url": ""
|
||||
}
|
||||
|
||||
@router.get("/proxy-config")
|
||||
async def get_proxy_config():
|
||||
"""获取代理配置"""
|
||||
return {
|
||||
"success": True,
|
||||
"data": proxy_config
|
||||
}
|
||||
|
||||
|
||||
@router.post("/proxy-config")
|
||||
async def update_proxy_config(config: dict):
|
||||
"""更新代理配置"""
|
||||
global proxy_config
|
||||
if "is_enabled" in config:
|
||||
proxy_config["is_enabled"] = config["is_enabled"]
|
||||
if "proxy_url" in config:
|
||||
proxy_config["proxy_url"] = config["proxy_url"]
|
||||
return {
|
||||
"success": True,
|
||||
"message": "代理配置已更新",
|
||||
"data": proxy_config
|
||||
}
|
||||
|
||||
|
||||
# ========== 无感换号 API ==========
|
||||
|
||||
# 内存存储无感配置 (生产环境应存数据库)
|
||||
seamless_config = {
|
||||
"enabled": False,
|
||||
"mode": "auto",
|
||||
"switchThreshold": 10,
|
||||
"accountPool": [],
|
||||
"currentIndex": 0
|
||||
}
|
||||
|
||||
@router.get("/seamless/status")
|
||||
async def seamless_status():
|
||||
"""无感换号状态(简化实现)"""
|
||||
async def seamless_status(db: Session = Depends(get_db)):
|
||||
"""获取无感换号状态"""
|
||||
return {
|
||||
"success": True,
|
||||
"enabled": seamless_config["enabled"],
|
||||
"message": "无感换号功能已就绪" if seamless_config["enabled"] else "无感换号功能未启用"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/seamless/user-status")
|
||||
async def seamless_user_status(key: str = None, userKey: str = None, db: Session = Depends(get_db)):
|
||||
"""获取用户切换状态"""
|
||||
# 兼容两种参数名
|
||||
actual_key = key or userKey
|
||||
if not actual_key:
|
||||
return {"success": False, "valid": False, "error": "缺少激活码参数"}
|
||||
|
||||
activation_key = KeyService.get_by_key(db, actual_key)
|
||||
if not activation_key:
|
||||
return {"success": False, "valid": False, "error": "激活码不存在"}
|
||||
|
||||
# 检查是否有效
|
||||
is_valid, message = KeyService.is_valid(activation_key, db)
|
||||
if not is_valid:
|
||||
return {"success": False, "valid": False, "error": message}
|
||||
|
||||
# 获取当前绑定的账号
|
||||
locked_account = None
|
||||
if activation_key.current_account_id:
|
||||
account = AccountService.get_by_id(db, activation_key.current_account_id)
|
||||
if account and account.status not in (AccountStatus.DISABLED, AccountStatus.EXPIRED):
|
||||
locked_account = {
|
||||
"email": account.email,
|
||||
"membership_type": account.membership_type.value
|
||||
}
|
||||
|
||||
# Pro密钥:使用 quota (积分制)
|
||||
# Auto密钥:使用换号次数限制
|
||||
is_pro = activation_key.membership_type == MembershipType.PRO
|
||||
switch_remaining = activation_key.quota - activation_key.quota_used if is_pro else 999 # Auto不限积分
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"valid": True,
|
||||
"switchRemaining": switch_remaining,
|
||||
"canSwitch": switch_remaining > 0,
|
||||
"lockedAccount": locked_account,
|
||||
"membershipType": activation_key.membership_type.value,
|
||||
"data": {
|
||||
"canSwitch": True,
|
||||
"quotaRemaining": activation_key.quota - activation_key.quota_used,
|
||||
"switchCount": activation_key.switch_count
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.get("/seamless/config")
|
||||
async def get_seamless_config():
|
||||
"""获取无感配置"""
|
||||
return {
|
||||
"success": True,
|
||||
"data": seamless_config
|
||||
}
|
||||
|
||||
|
||||
@router.post("/seamless/config")
|
||||
async def update_seamless_config(config: dict):
|
||||
"""更新无感配置"""
|
||||
global seamless_config
|
||||
for key in ["enabled", "mode", "switchThreshold", "accountPool", "currentIndex"]:
|
||||
if key in config:
|
||||
seamless_config[key] = config[key]
|
||||
return {
|
||||
"success": True,
|
||||
"message": "配置已更新",
|
||||
"data": seamless_config
|
||||
}
|
||||
|
||||
|
||||
@router.post("/seamless/inject")
|
||||
async def inject_seamless(data: dict, req: Request, db: Session = Depends(get_db)):
|
||||
"""注入无感模式 - 返回账号数据"""
|
||||
user_key = data.get("user_key")
|
||||
if not user_key:
|
||||
return {"success": False, "error": "缺少user_key参数"}
|
||||
|
||||
key = KeyService.get_by_key(db, user_key)
|
||||
if not key:
|
||||
return {"success": False, "error": "激活码不存在"}
|
||||
|
||||
# 检查有效性
|
||||
is_valid, message = KeyService.is_valid(key, db)
|
||||
if not is_valid:
|
||||
return {"success": False, "error": message}
|
||||
|
||||
# 获取账号
|
||||
account = None
|
||||
if key.current_account_id:
|
||||
account = AccountService.get_by_id(db, key.current_account_id)
|
||||
|
||||
# 只有账号不存在或被禁用/过期才分配新的
|
||||
if not account or account.status in (AccountStatus.DISABLED, AccountStatus.EXPIRED):
|
||||
account = AccountService.get_available(db, key.membership_type)
|
||||
if not account:
|
||||
return {"success": False, "error": "暂无可用账号"}
|
||||
KeyService.bind_account(db, key, account)
|
||||
AccountService.mark_used(db, account, key.id)
|
||||
|
||||
LogService.log(db, key.id, "seamless_inject", account.id, ip_address=req.client.host, success=True)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"message": "无感模式已注入",
|
||||
"data": build_account_data(account, key).model_dump()
|
||||
}
|
||||
|
||||
|
||||
@router.post("/seamless/restore")
|
||||
async def restore_seamless():
|
||||
"""恢复无感模式设置"""
|
||||
global seamless_config
|
||||
seamless_config["enabled"] = False
|
||||
return {
|
||||
"success": True,
|
||||
"message": "已恢复默认设置"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/seamless/accounts")
|
||||
async def get_seamless_accounts(db: Session = Depends(get_db)):
|
||||
"""获取账号池列表"""
|
||||
# 返回可用账号列表 (脱敏)
|
||||
accounts = AccountService.get_all(db, limit=100)
|
||||
account_list = []
|
||||
for acc in accounts:
|
||||
if acc.status == AccountStatus.ACTIVE:
|
||||
account_list.append({
|
||||
"id": acc.id,
|
||||
"email": acc.email[:3] + "***" + acc.email[acc.email.index("@"):],
|
||||
"membership_type": acc.membership_type.value,
|
||||
"status": acc.status.value
|
||||
})
|
||||
return {
|
||||
"success": True,
|
||||
"data": {
|
||||
"accounts": account_list,
|
||||
"total": len(account_list)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.post("/seamless/accounts")
|
||||
async def sync_seamless_accounts(data: dict):
|
||||
"""同步账号池"""
|
||||
# 这个接口在我们的架构中不需要实际功能
|
||||
# 账号由管理后台统一管理
|
||||
return {
|
||||
"success": True,
|
||||
"message": "账号由管理后台统一管理"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/seamless/token")
|
||||
async def get_seamless_token(key: str, db: Session = Depends(get_db)):
|
||||
"""获取无感Token"""
|
||||
activation_key = KeyService.get_by_key(db, key)
|
||||
if not activation_key:
|
||||
return {"success": False, "error": "激活码不存在"}
|
||||
|
||||
account = None
|
||||
if activation_key.current_account_id:
|
||||
account = AccountService.get_by_id(db, activation_key.current_account_id)
|
||||
|
||||
if not account:
|
||||
return {"success": False, "error": "未绑定账号"}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": build_account_data(account, activation_key).model_dump()
|
||||
}
|
||||
|
||||
|
||||
@router.get("/seamless/get-token")
|
||||
async def get_seamless_token_v2(userKey: str = None, key: str = None, req: Request = None, db: Session = Depends(get_db)):
|
||||
"""获取无感Token (注入代码调用的路径,兼容 userKey 和 key 两种参数名)
|
||||
|
||||
返回格式需要直接包含 accessToken、email 等字段,供注入代码使用
|
||||
"""
|
||||
actual_key = userKey or key
|
||||
if not actual_key:
|
||||
return {"success": False, "error": "缺少激活码参数"}
|
||||
|
||||
activation_key = KeyService.get_by_key(db, actual_key)
|
||||
if not activation_key:
|
||||
return {"success": False, "error": "激活码不存在"}
|
||||
|
||||
# 检查有效性
|
||||
is_valid, message = KeyService.is_valid(activation_key, db)
|
||||
if not is_valid:
|
||||
return {"success": False, "error": message}
|
||||
|
||||
# 获取或分配账号
|
||||
account = None
|
||||
is_new = False
|
||||
if activation_key.current_account_id:
|
||||
account = AccountService.get_by_id(db, activation_key.current_account_id)
|
||||
|
||||
# 只有账号不存在或被禁用/过期才分配新的
|
||||
if not account or account.status in (AccountStatus.DISABLED, AccountStatus.EXPIRED):
|
||||
# Auto密钥:检查是否允许获取新账号(频率限制)
|
||||
if activation_key.membership_type == MembershipType.FREE:
|
||||
can_switch, switch_msg = KeyService.can_switch(db, activation_key)
|
||||
if not can_switch:
|
||||
return {"success": False, "error": switch_msg}
|
||||
|
||||
# 分配新账号
|
||||
account = AccountService.get_available(db, activation_key.membership_type)
|
||||
if not account:
|
||||
return {"success": False, "error": "暂无可用账号"}
|
||||
KeyService.bind_account(db, activation_key, account)
|
||||
AccountService.mark_used(db, account, activation_key.id)
|
||||
# 记录换号(用于频率限制)
|
||||
KeyService.use_switch(db, activation_key)
|
||||
is_new = True
|
||||
|
||||
# 记录日志
|
||||
if req:
|
||||
LogService.log(db, activation_key.id, "seamless_get_token", account.id, ip_address=req.client.host, success=True)
|
||||
|
||||
# 返回格式需要直接包含字段,供注入代码使用
|
||||
# 注入代码检查: if(d && d.accessToken) { ... }
|
||||
return {
|
||||
"success": True,
|
||||
"accessToken": account.access_token,
|
||||
"refreshToken": account.refresh_token or "",
|
||||
"email": account.email,
|
||||
"membership_type": account.membership_type.value,
|
||||
"switchVersion": activation_key.switch_count, # 用于检测是否需要更新
|
||||
"switchRemaining": activation_key.quota - activation_key.quota_used,
|
||||
"is_new": is_new,
|
||||
"machineIds": None # 机器ID由客户端本地管理
|
||||
}
|
||||
|
||||
|
||||
@router.post("/seamless/switch")
|
||||
async def switch_seamless_token(data: dict, req: Request, db: Session = Depends(get_db)):
|
||||
"""切换无感Token"""
|
||||
user_key = data.get("userKey")
|
||||
if not user_key:
|
||||
return {"switched": False, "message": "缺少userKey参数"}
|
||||
|
||||
# 复用切换逻辑
|
||||
request = SwitchAccountRequest(key=user_key)
|
||||
return await switch_token_impl(request, req, db)
|
||||
|
||||
|
||||
@router.post("/seamless/switch-token")
|
||||
async def switch_seamless_token_v2(data: dict, req: Request, db: Session = Depends(get_db)):
|
||||
"""切换无感Token (client.js 调用的路径)"""
|
||||
user_key = data.get("userKey")
|
||||
if not user_key:
|
||||
return {"switched": False, "message": "缺少userKey参数"}
|
||||
|
||||
request = SwitchAccountRequest(key=user_key)
|
||||
return await switch_token_impl(request, req, db)
|
||||
|
||||
|
||||
async def switch_token_impl(request: SwitchAccountRequest, req: Request, db: Session):
|
||||
"""切换Token实现 - 返回插件期望的格式"""
|
||||
key = KeyService.get_by_key(db, request.key)
|
||||
|
||||
if not key:
|
||||
return {"switched": False, "message": "激活码不存在"}
|
||||
|
||||
# 检查设备限制
|
||||
if request.device_id:
|
||||
device_ok, device_msg = KeyService.check_device(db, key, request.device_id)
|
||||
if not device_ok:
|
||||
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message=device_msg)
|
||||
return {"switched": False, "message": device_msg}
|
||||
|
||||
# 检查激活码是否有效
|
||||
is_valid, message = KeyService.is_valid(key, db)
|
||||
if not is_valid:
|
||||
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message=message)
|
||||
return {"switched": False, "message": message}
|
||||
|
||||
# 检查换号频率限制
|
||||
can_switch, switch_msg = KeyService.can_switch(db, key)
|
||||
if not can_switch:
|
||||
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message=switch_msg)
|
||||
return {"switched": False, "message": switch_msg}
|
||||
|
||||
# 释放当前账号
|
||||
if key.current_account_id:
|
||||
old_account = AccountService.get_by_id(db, key.current_account_id)
|
||||
if old_account:
|
||||
AccountService.release(db, old_account)
|
||||
|
||||
# 获取新账号
|
||||
account = AccountService.get_available(db, key.membership_type)
|
||||
if not account:
|
||||
LogService.log(db, key.id, "switch", ip_address=req.client.host, success=False, message="无可用账号")
|
||||
return {"switched": False, "message": "暂无可用账号,请稍后重试"}
|
||||
|
||||
# 绑定新账号并扣除额度
|
||||
KeyService.bind_account(db, key, account)
|
||||
KeyService.use_switch(db, key)
|
||||
AccountService.mark_used(db, account, key.id)
|
||||
|
||||
LogService.log(db, key.id, "switch", account.id, ip_address=req.client.host, success=True)
|
||||
|
||||
return {
|
||||
"switched": True,
|
||||
"switchRemaining": key.quota - key.quota_used,
|
||||
"email": account.email,
|
||||
"data": build_account_data(account, key)
|
||||
}
|
||||
|
||||
|
||||
# ========== 代理配置 API ==========
|
||||
|
||||
@router.get("/proxy-config")
|
||||
async def get_proxy_config(db: Session = Depends(get_db)):
|
||||
"""获取代理配置"""
|
||||
return {
|
||||
"success": True,
|
||||
"enabled": False,
|
||||
"message": "无感换号功能暂未开放"
|
||||
"host": "",
|
||||
"port": 0,
|
||||
"username": "",
|
||||
"password": ""
|
||||
}
|
||||
|
||||
|
||||
@router.put("/proxy-config")
|
||||
async def update_proxy_config(config: dict, db: Session = Depends(get_db)):
|
||||
"""更新代理配置 (客户端本地使用,后端仅返回成功)"""
|
||||
return {"success": True, "message": "代理配置已保存"}
|
||||
|
||||
|
||||
# ========== 无感换号配置 API ==========
|
||||
|
||||
@router.get("/seamless/status")
|
||||
async def get_seamless_status(db: Session = Depends(get_db)):
|
||||
"""获取无感换号全局状态"""
|
||||
settings = GlobalSettingsService.get_all(db)
|
||||
account_stats = AccountService.count(db)
|
||||
return {
|
||||
"success": True,
|
||||
"enabled": True,
|
||||
"available_accounts": account_stats.get("active", 0),
|
||||
"total_accounts": account_stats.get("total", 0),
|
||||
"switch_interval_minutes": settings.auto_switch_interval_minutes,
|
||||
"max_switches_per_day": settings.auto_max_switches_per_day
|
||||
}
|
||||
|
||||
|
||||
@router.get("/seamless/config")
|
||||
async def get_seamless_config(db: Session = Depends(get_db)):
|
||||
"""获取无感换号配置"""
|
||||
settings = GlobalSettingsService.get_all(db)
|
||||
return {
|
||||
"success": True,
|
||||
"auto_switch": True,
|
||||
"switch_interval_minutes": settings.auto_switch_interval_minutes,
|
||||
"max_switches_per_day": settings.auto_max_switches_per_day,
|
||||
"pro_quota_cost": settings.pro_quota_cost
|
||||
}
|
||||
|
||||
|
||||
@router.post("/seamless/config")
|
||||
async def update_seamless_config(config: dict, db: Session = Depends(get_db)):
|
||||
"""更新无感换号配置"""
|
||||
# 从请求中提取配置
|
||||
updates = {}
|
||||
if "switch_interval_minutes" in config:
|
||||
updates["auto_switch_interval_minutes"] = config["switch_interval_minutes"]
|
||||
if "max_switches_per_day" in config:
|
||||
updates["auto_max_switches_per_day"] = config["max_switches_per_day"]
|
||||
if "pro_quota_cost" in config:
|
||||
updates["pro_quota_cost"] = config["pro_quota_cost"]
|
||||
|
||||
if updates:
|
||||
GlobalSettingsService.update_all(db, **updates)
|
||||
|
||||
return {"success": True, "message": "配置已更新"}
|
||||
|
||||
|
||||
@router.post("/seamless/inject")
|
||||
async def seamless_inject(data: dict, db: Session = Depends(get_db)):
|
||||
"""注入无感换号代码 (客户端本地操作,后端仅记录)"""
|
||||
user_key = data.get("userKey")
|
||||
if user_key:
|
||||
key = KeyService.get_by_key(db, user_key)
|
||||
if key:
|
||||
LogService.log(db, key.id, "seamless_inject", success=True, message="启用无感换号")
|
||||
return {"success": True, "message": "无感换号已启用"}
|
||||
|
||||
|
||||
@router.post("/seamless/restore")
|
||||
async def seamless_restore(data: dict = None, db: Session = Depends(get_db)):
|
||||
"""恢复原始代码 (客户端本地操作,后端仅记录)"""
|
||||
if data and data.get("userKey"):
|
||||
key = KeyService.get_by_key(db, data["userKey"])
|
||||
if key:
|
||||
LogService.log(db, key.id, "seamless_restore", success=True, message="禁用无感换号")
|
||||
return {"success": True, "message": "已恢复原始状态"}
|
||||
|
||||
|
||||
@router.get("/seamless/accounts")
|
||||
async def get_seamless_accounts(userKey: str = None, db: Session = Depends(get_db)):
|
||||
"""获取可用账号列表 (供管理使用)"""
|
||||
if not userKey:
|
||||
return {"success": False, "error": "缺少 userKey 参数"}
|
||||
|
||||
key = KeyService.get_by_key(db, userKey)
|
||||
if not key:
|
||||
return {"success": False, "error": "激活码不存在"}
|
||||
|
||||
# 获取该激活码类型的可用账号数量
|
||||
accounts = AccountService.get_all(db, limit=100)
|
||||
available_count = sum(1 for a in accounts if a.status == AccountStatus.ACTIVE and a.membership_type == key.membership_type)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"available_count": available_count,
|
||||
"membership_type": key.membership_type.value
|
||||
}
|
||||
|
||||
|
||||
@router.post("/seamless/sync-accounts")
|
||||
async def sync_seamless_accounts(data: dict, db: Session = Depends(get_db)):
|
||||
"""同步账号 (客户端上报账号信息)"""
|
||||
# 客户端可能上报当前使用的账号状态
|
||||
return {"success": True, "message": "同步成功"}
|
||||
|
||||
|
||||
# ========== 版本检查 API ==========
|
||||
|
||||
@router.get("/version")
|
||||
async def get_version():
|
||||
"""获取最新版本信息"""
|
||||
return {
|
||||
"success": True,
|
||||
"current_version": "0.4.5",
|
||||
"latest_version": "0.4.5",
|
||||
"has_update": False,
|
||||
"download_url": "",
|
||||
"changelog": ""
|
||||
}
|
||||
|
||||
|
||||
# ========== 公告 API ==========
|
||||
|
||||
@router.get("/announcement")
|
||||
async def get_announcement(db: Session = Depends(get_db)):
|
||||
"""获取公告信息"""
|
||||
return {
|
||||
"success": True,
|
||||
"has_announcement": True,
|
||||
"data": {
|
||||
"is_active": True,
|
||||
"title": "欢迎使用蜂鸟Pro",
|
||||
"content": "感谢使用蜂鸟Pro!\n\n如有问题请联系客服。",
|
||||
"type": "info",
|
||||
"created_at": "2024-12-17 00:00:00"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@router.get("/announcements/latest")
|
||||
async def get_announcements_latest(db: Session = Depends(get_db)):
|
||||
"""获取最新公告 (前端调用的路径)"""
|
||||
return {
|
||||
"success": True,
|
||||
"data": {
|
||||
"is_active": True,
|
||||
"title": "欢迎使用蜂鸟Pro",
|
||||
"content": "感谢使用蜂鸟Pro!\n\n如有问题请联系客服。",
|
||||
"type": "info",
|
||||
"created_at": "2024-12-17 00:00:00"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# ========== 用量查询 API ==========
|
||||
|
||||
@router.get("/usage")
|
||||
async def get_usage(userKey: str = None, db: Session = Depends(get_db)):
|
||||
"""获取用量信息"""
|
||||
if not userKey:
|
||||
return {"success": False, "error": "缺少 userKey 参数"}
|
||||
|
||||
key = KeyService.get_by_key(db, userKey)
|
||||
if not key:
|
||||
return {"success": False, "error": "激活码不存在"}
|
||||
|
||||
account = None
|
||||
if key.current_account_id:
|
||||
account = AccountService.get_by_id(db, key.current_account_id)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"membership_type": key.membership_type.value,
|
||||
"quota": key.quota,
|
||||
"quota_used": key.quota_used,
|
||||
"quota_remaining": key.quota - key.quota_used,
|
||||
"switch_count": key.switch_count,
|
||||
"expire_at": key.expire_at.strftime("%Y-%m-%d %H:%M:%S") if key.expire_at else None,
|
||||
"current_email": account.email if account else None
|
||||
}
|
||||
|
||||
@@ -12,13 +12,16 @@ class Settings(BaseSettings):
|
||||
DB_NAME: str = "cursorpro"
|
||||
|
||||
# JWT配置
|
||||
SECRET_KEY: str = "your-secret-key-change-in-production"
|
||||
SECRET_KEY: str = "hb8x2kF9mNpQ3rT7vY1zA4cE6gJ0lO5sU8wB2dH4"
|
||||
ALGORITHM: str = "HS256"
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 7 # 7天
|
||||
|
||||
# 管理员账号
|
||||
ADMIN_USERNAME: str = "admin"
|
||||
ADMIN_PASSWORD: str = "admin123"
|
||||
ADMIN_PASSWORD: str = "Hb@2024Pro!"
|
||||
|
||||
# 外部系统API Token (用于批量上传账号等)
|
||||
API_TOKEN: str = "hb-ext-9kX2mP5nQ8rT1vY4zA7c"
|
||||
|
||||
@property
|
||||
def DATABASE_URL(self) -> str:
|
||||
|
||||
@@ -43,6 +43,32 @@ 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" # free/pro, 默认free(auto账号)
|
||||
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):
|
||||
|
||||
Reference in New Issue
Block a user