""" 客户端 API - 兼容原 CursorPro 插件 """ 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"]) # ========== 账号数据响应模型 ========== 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 {"success": False, "valid": False, "error": "激活码不存在"} # 激活密钥(支持合并) activate_ok, activate_msg, master_key = KeyService.activate(db, key, request.device_id) if not activate_ok: LogService.log(db, key.id, "verify", ip_address=req.client.host, success=False, message=activate_msg) return {"success": False, "valid": False, "error": activate_msg} # 使用主密钥进行后续操作 active_key = master_key if master_key else key # 检查主密钥是否有效 is_valid, message = KeyService.is_valid(active_key, db) if not is_valid: LogService.log(db, active_key.id, "verify", ip_address=req.client.host, success=False, message=message) return {"success": False, "valid": False, "error": message} # 获取当前绑定的账号,或分配新账号 account = None if active_key.current_account_id: account = AccountService.get_by_id(db, active_key.current_account_id) # 只有账号不存在或被禁用/过期才分配新的 if not account or account.status in (AccountStatus.DISABLED, AccountStatus.EXPIRED): account = AccountService.get_available(db, active_key.membership_type) if not account: LogService.log(db, active_key.id, "verify", ip_address=req.client.host, success=False, message="无可用账号") return {"success": False, "valid": False, "error": "暂无可用账号,请稍后重试"} KeyService.bind_account(db, active_key, account) AccountService.mark_used(db, account, active_key.id) # 只记录首次激活,不记录每次验证(减少日志量) if "激活成功" in activate_msg or "合并" in activate_msg: LogService.log(db, active_key.id, "activate", account.id, ip_address=req.client.host, success=True, message=activate_msg) # 返回格式 expire_date = active_key.expire_at.strftime("%Y-%m-%d %H:%M:%S") if active_key.expire_at else None is_pro = active_key.membership_type == MembershipType.PRO return { "success": True, "valid": True, "message": activate_msg, "membership_type": active_key.membership_type.value, "expire_date": expire_date, "switch_remaining": active_key.quota - active_key.quota_used if is_pro else 999, "switch_limit": active_key.quota if is_pro else 999, "quota": active_key.quota if is_pro else None, "quota_used": active_key.quota_used if is_pro else None, "merged_count": active_key.merged_count, "master_key": active_key.key[:8] + "****", # 隐藏部分密钥 "data": build_account_data(account, active_key) } @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 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 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 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 ApiResponse(success=False, error=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 ApiResponse(success=False, error="暂无可用账号,请稍后重试") # 绑定新账号并扣除额度 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 ApiResponse( success=True, message="切换成功", data=build_account_data(account, key) ) # ========== 设备密钥信息 API ========== @router.get("/device-keys") async def get_device_keys(device_id: str = None, db: Session = Depends(get_db)): """获取设备的所有密钥信息(Auto和Pro)""" if not device_id: return {"success": False, "error": "缺少设备ID"} keys_info = KeyService.get_device_keys(db, device_id) result = { "success": True, "device_id": device_id, "auto": None, "pro": None } # Auto 密钥信息 if keys_info["auto"]: auto_data = keys_info["auto"] master = auto_data["master"] result["auto"] = { "has_key": True, "master_key": master.key[:8] + "****", "expire_at": master.expire_at.strftime("%Y/%m/%d %H:%M:%S") if master.expire_at else None, "merged_count": auto_data["total_keys"], "current_account": master.current_account.email if master.current_account else None, "status": master.status.value } else: result["auto"] = {"has_key": False} # Pro 密钥信息 if keys_info["pro"]: pro_data = keys_info["pro"] master = pro_data["master"] result["pro"] = { "has_key": True, "master_key": master.key[:8] + "****", "quota": pro_data["quota"], "quota_used": pro_data["quota_used"], "quota_remaining": pro_data["quota_remaining"], "merged_count": pro_data["total_keys"], "expire_at": master.expire_at.strftime("%Y/%m/%d %H:%M:%S") if master.expire_at else None, "current_account": master.current_account.email if master.current_account else None, "status": master.status.value } else: result["pro"] = {"has_key": False} return result @router.get("/device-keys/detail") async def get_device_keys_detail(device_id: str = None, membership_type: str = None, db: Session = Depends(get_db)): """获取设备某类型密钥的详细信息(包括所有合并的密钥)""" if not device_id: return {"success": False, "error": "缺少设备ID"} if membership_type not in ["auto", "pro", "free"]: return {"success": False, "error": "无效的密钥类型"} # 映射类型 mem_type = MembershipType.FREE if membership_type in ["auto", "free"] else MembershipType.PRO # 获取主密钥 master = KeyService.get_master_key(db, device_id, mem_type) if not master: return {"success": True, "has_key": False, "keys": []} # 获取所有合并的密钥 from app.models import ActivationKey merged_keys = db.query(ActivationKey).filter( ActivationKey.master_key_id == master.id ).order_by(ActivationKey.merged_at.desc()).all() keys_list = [] # 主密钥 keys_list.append({ "id": master.id, "key": master.key[:8] + "****", "is_master": True, "status": master.status.value, "contribution": master.quota_contribution if mem_type == MembershipType.PRO else master.duration_days, "contribution_type": "积分" if mem_type == MembershipType.PRO else "天", "activated_at": master.first_activated_at.strftime("%Y/%m/%d %H:%M") if master.first_activated_at else None }) # 合并的密钥 for k in merged_keys: keys_list.append({ "id": k.id, "key": k.key[:8] + "****", "is_master": False, "status": k.status.value, "contribution": k.quota_contribution if mem_type == MembershipType.PRO else k.duration_days, "contribution_type": "积分" if mem_type == MembershipType.PRO else "天", "merged_at": k.merged_at.strftime("%Y/%m/%d %H:%M") if k.merged_at else None }) return { "success": True, "has_key": True, "membership_type": membership_type, "total_keys": len(keys_list), "keys": keys_list } # ========== 版本 API ========== @router.get("/version") async def get_version(): """获取版本信息""" return { "success": True, "version": "1.0.0", "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(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 # 只记录获取新账号的情况,不记录每次token验证 if req and is_new: LogService.log(db, activation_key.id, "seamless_get_token", account.id, ip_address=req.client.host, success=True, message="分配新账号") # 返回格式需要直接包含字段,供注入代码使用 # 注入代码检查: 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, "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 }