|
|
"""管理接口 - Token管理和系统配置""" |
|
|
|
|
|
import secrets |
|
|
from typing import Dict, Any, List, Optional |
|
|
from datetime import datetime, timedelta |
|
|
from pathlib import Path |
|
|
from fastapi import APIRouter, HTTPException, Depends, Header |
|
|
from fastapi.responses import HTMLResponse |
|
|
from pydantic import BaseModel |
|
|
|
|
|
from app.core.config import setting |
|
|
from app.core.logger import logger |
|
|
from app.services.grok.token import token_manager |
|
|
from app.models.grok_models import TokenType |
|
|
|
|
|
|
|
|
router = APIRouter(tags=["管理"]) |
|
|
|
|
|
|
|
|
STATIC_DIR = Path(__file__).parents[2] / "template" |
|
|
TEMP_DIR = Path(__file__).parents[3] / "data" / "temp" |
|
|
IMAGE_CACHE_DIR = TEMP_DIR / "image" |
|
|
VIDEO_CACHE_DIR = TEMP_DIR / "video" |
|
|
SESSION_EXPIRE_HOURS = 24 |
|
|
BYTES_PER_KB = 1024 |
|
|
BYTES_PER_MB = 1024 * 1024 |
|
|
|
|
|
|
|
|
_sessions: Dict[str, datetime] = {} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LoginRequest(BaseModel): |
|
|
username: str |
|
|
password: str |
|
|
|
|
|
|
|
|
class LoginResponse(BaseModel): |
|
|
success: bool |
|
|
token: Optional[str] = None |
|
|
message: str |
|
|
|
|
|
|
|
|
class AddTokensRequest(BaseModel): |
|
|
tokens: List[str] |
|
|
token_type: str |
|
|
|
|
|
|
|
|
class DeleteTokensRequest(BaseModel): |
|
|
tokens: List[str] |
|
|
token_type: str |
|
|
|
|
|
|
|
|
class TokenInfo(BaseModel): |
|
|
token: str |
|
|
token_type: str |
|
|
created_time: Optional[int] = None |
|
|
remaining_queries: int |
|
|
heavy_remaining_queries: int |
|
|
status: str |
|
|
tags: List[str] = [] |
|
|
note: str = "" |
|
|
|
|
|
|
|
|
class TokenListResponse(BaseModel): |
|
|
success: bool |
|
|
data: List[TokenInfo] |
|
|
total: int |
|
|
|
|
|
|
|
|
class UpdateSettingsRequest(BaseModel): |
|
|
global_config: Optional[Dict[str, Any]] = None |
|
|
grok_config: Optional[Dict[str, Any]] = None |
|
|
|
|
|
|
|
|
class UpdateTokenTagsRequest(BaseModel): |
|
|
token: str |
|
|
token_type: str |
|
|
tags: List[str] |
|
|
|
|
|
|
|
|
class UpdateTokenNoteRequest(BaseModel): |
|
|
token: str |
|
|
token_type: str |
|
|
note: str |
|
|
|
|
|
|
|
|
class TestTokenRequest(BaseModel): |
|
|
token: str |
|
|
token_type: str |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def validate_token_type(token_type_str: str) -> TokenType: |
|
|
"""验证Token类型""" |
|
|
if token_type_str not in ["sso", "ssoSuper"]: |
|
|
raise HTTPException( |
|
|
status_code=400, |
|
|
detail={"error": "无效的Token类型", "code": "INVALID_TYPE"} |
|
|
) |
|
|
return TokenType.NORMAL if token_type_str == "sso" else TokenType.SUPER |
|
|
|
|
|
|
|
|
def parse_created_time(created_time) -> Optional[int]: |
|
|
"""解析创建时间""" |
|
|
if isinstance(created_time, str): |
|
|
return int(created_time) if created_time else None |
|
|
elif isinstance(created_time, int): |
|
|
return created_time |
|
|
return None |
|
|
|
|
|
|
|
|
def calculate_token_stats(tokens: Dict[str, Any], token_type: str) -> Dict[str, int]: |
|
|
"""计算Token统计""" |
|
|
total = len(tokens) |
|
|
expired = sum(1 for t in tokens.values() if t.get("status") == "expired") |
|
|
|
|
|
if token_type == "normal": |
|
|
unused = sum(1 for t in tokens.values() |
|
|
if t.get("status") != "expired" and t.get("remainingQueries", -1) == -1) |
|
|
limited = sum(1 for t in tokens.values() |
|
|
if t.get("status") != "expired" and t.get("remainingQueries", -1) == 0) |
|
|
active = sum(1 for t in tokens.values() |
|
|
if t.get("status") != "expired" and t.get("remainingQueries", -1) > 0) |
|
|
else: |
|
|
unused = sum(1 for t in tokens.values() |
|
|
if t.get("status") != "expired" and |
|
|
t.get("remainingQueries", -1) == -1 and t.get("heavyremainingQueries", -1) == -1) |
|
|
limited = sum(1 for t in tokens.values() |
|
|
if t.get("status") != "expired" and |
|
|
(t.get("remainingQueries", -1) == 0 or t.get("heavyremainingQueries", -1) == 0)) |
|
|
active = sum(1 for t in tokens.values() |
|
|
if t.get("status") != "expired" and |
|
|
(t.get("remainingQueries", -1) > 0 or t.get("heavyremainingQueries", -1) > 0)) |
|
|
|
|
|
return {"total": total, "unused": unused, "limited": limited, "expired": expired, "active": active} |
|
|
|
|
|
|
|
|
def verify_admin_session(authorization: Optional[str] = Header(None)) -> bool: |
|
|
"""验证管理员会话""" |
|
|
if not authorization or not authorization.startswith("Bearer "): |
|
|
raise HTTPException(status_code=401, detail={"error": "未授权访问", "code": "UNAUTHORIZED"}) |
|
|
|
|
|
token = authorization[7:] |
|
|
|
|
|
if token not in _sessions: |
|
|
raise HTTPException(status_code=401, detail={"error": "会话无效", "code": "SESSION_INVALID"}) |
|
|
|
|
|
if datetime.now() > _sessions[token]: |
|
|
del _sessions[token] |
|
|
raise HTTPException(status_code=401, detail={"error": "会话已过期", "code": "SESSION_EXPIRED"}) |
|
|
|
|
|
return True |
|
|
|
|
|
|
|
|
def get_token_status(token_data: Dict[str, Any], token_type: str) -> str: |
|
|
"""获取Token状态""" |
|
|
if token_data.get("status") == "expired": |
|
|
return "失效" |
|
|
|
|
|
remaining = token_data.get("remainingQueries", -1) |
|
|
heavy_remaining = token_data.get("heavyremainingQueries", -1) |
|
|
|
|
|
relevant = max(remaining, heavy_remaining) if token_type == "ssoSuper" else remaining |
|
|
|
|
|
if relevant == -1: |
|
|
return "未使用" |
|
|
elif relevant == 0: |
|
|
return "限流中" |
|
|
else: |
|
|
return "正常" |
|
|
|
|
|
|
|
|
def _calculate_dir_size(directory: Path) -> int: |
|
|
"""计算目录大小""" |
|
|
total = 0 |
|
|
for file_path in directory.iterdir(): |
|
|
if file_path.is_file(): |
|
|
try: |
|
|
total += file_path.stat().st_size |
|
|
except Exception as e: |
|
|
logger.warning(f"[Admin] 无法获取文件大小: {file_path.name}, {e}") |
|
|
return total |
|
|
|
|
|
|
|
|
def _format_size(size_bytes: int) -> str: |
|
|
"""格式化文件大小""" |
|
|
size_mb = size_bytes / BYTES_PER_MB |
|
|
if size_mb < 1: |
|
|
return f"{size_bytes / BYTES_PER_KB:.1f} KB" |
|
|
return f"{size_mb:.1f} MB" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/login", response_class=HTMLResponse) |
|
|
async def login_page(): |
|
|
"""登录页面""" |
|
|
login_html = STATIC_DIR / "login.html" |
|
|
if login_html.exists(): |
|
|
return login_html.read_text(encoding="utf-8") |
|
|
raise HTTPException(status_code=404, detail="登录页面不存在") |
|
|
|
|
|
|
|
|
@router.get("/manage", response_class=HTMLResponse) |
|
|
async def manage_page(): |
|
|
"""管理页面""" |
|
|
admin_html = STATIC_DIR / "admin.html" |
|
|
if admin_html.exists(): |
|
|
return admin_html.read_text(encoding="utf-8") |
|
|
raise HTTPException(status_code=404, detail="管理页面不存在") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/api/login", response_model=LoginResponse) |
|
|
async def admin_login(request: LoginRequest) -> LoginResponse: |
|
|
"""管理员登录""" |
|
|
try: |
|
|
logger.debug(f"[Admin] 登录尝试: {request.username}") |
|
|
|
|
|
expected_user = setting.global_config.get("admin_username", "") |
|
|
expected_pass = setting.global_config.get("admin_password", "") |
|
|
|
|
|
if request.username != expected_user or request.password != expected_pass: |
|
|
logger.warning(f"[Admin] 登录失败: {request.username}") |
|
|
return LoginResponse(success=False, message="用户名或密码错误") |
|
|
|
|
|
session_token = secrets.token_urlsafe(32) |
|
|
_sessions[session_token] = datetime.now() + timedelta(hours=SESSION_EXPIRE_HOURS) |
|
|
|
|
|
logger.debug(f"[Admin] 登录成功: {request.username}") |
|
|
return LoginResponse(success=True, token=session_token, message="登录成功") |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 登录异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"登录失败: {e}", "code": "LOGIN_ERROR"}) |
|
|
|
|
|
|
|
|
@router.post("/api/logout") |
|
|
async def admin_logout(_: bool = Depends(verify_admin_session), authorization: Optional[str] = Header(None)) -> Dict[str, Any]: |
|
|
"""管理员登出""" |
|
|
try: |
|
|
if authorization and authorization.startswith("Bearer "): |
|
|
token = authorization[7:] |
|
|
if token in _sessions: |
|
|
del _sessions[token] |
|
|
logger.debug("[Admin] 登出成功") |
|
|
return {"success": True, "message": "登出成功"} |
|
|
|
|
|
logger.warning("[Admin] 登出失败: 无效会话") |
|
|
return {"success": False, "message": "无效的会话"} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 登出异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"登出失败: {e}", "code": "LOGOUT_ERROR"}) |
|
|
|
|
|
|
|
|
@router.get("/api/tokens", response_model=TokenListResponse) |
|
|
async def list_tokens(_: bool = Depends(verify_admin_session)) -> TokenListResponse: |
|
|
"""获取Token列表""" |
|
|
try: |
|
|
logger.debug("[Admin] 获取Token列表") |
|
|
|
|
|
all_tokens = token_manager.get_tokens() |
|
|
token_list: List[TokenInfo] = [] |
|
|
|
|
|
|
|
|
for token, data in all_tokens.get(TokenType.NORMAL.value, {}).items(): |
|
|
token_list.append(TokenInfo( |
|
|
token=token, |
|
|
token_type="sso", |
|
|
created_time=parse_created_time(data.get("createdTime")), |
|
|
remaining_queries=data.get("remainingQueries", -1), |
|
|
heavy_remaining_queries=data.get("heavyremainingQueries", -1), |
|
|
status=get_token_status(data, "sso"), |
|
|
tags=data.get("tags", []), |
|
|
note=data.get("note", "") |
|
|
)) |
|
|
|
|
|
|
|
|
for token, data in all_tokens.get(TokenType.SUPER.value, {}).items(): |
|
|
token_list.append(TokenInfo( |
|
|
token=token, |
|
|
token_type="ssoSuper", |
|
|
created_time=parse_created_time(data.get("createdTime")), |
|
|
remaining_queries=data.get("remainingQueries", -1), |
|
|
heavy_remaining_queries=data.get("heavyremainingQueries", -1), |
|
|
status=get_token_status(data, "ssoSuper"), |
|
|
tags=data.get("tags", []), |
|
|
note=data.get("note", "") |
|
|
)) |
|
|
|
|
|
logger.debug(f"[Admin] Token列表获取成功: {len(token_list)}个") |
|
|
return TokenListResponse(success=True, data=token_list, total=len(token_list)) |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 获取Token列表异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"获取失败: {e}", "code": "LIST_ERROR"}) |
|
|
|
|
|
|
|
|
@router.post("/api/tokens/add") |
|
|
async def add_tokens(request: AddTokensRequest, _: bool = Depends(verify_admin_session)) -> Dict[str, Any]: |
|
|
"""批量添加Token""" |
|
|
try: |
|
|
logger.debug(f"[Admin] 添加Token: {request.token_type}, {len(request.tokens)}个") |
|
|
|
|
|
token_type = validate_token_type(request.token_type) |
|
|
await token_manager.add_token(request.tokens, token_type) |
|
|
|
|
|
logger.debug(f"[Admin] Token添加成功: {len(request.tokens)}个") |
|
|
return {"success": True, "message": f"成功添加 {len(request.tokens)} 个Token", "count": len(request.tokens)} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] Token添加异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"添加失败: {e}", "code": "ADD_ERROR"}) |
|
|
|
|
|
|
|
|
@router.post("/api/tokens/delete") |
|
|
async def delete_tokens(request: DeleteTokensRequest, _: bool = Depends(verify_admin_session)) -> Dict[str, Any]: |
|
|
"""批量删除Token""" |
|
|
try: |
|
|
logger.debug(f"[Admin] 删除Token: {request.token_type}, {len(request.tokens)}个") |
|
|
|
|
|
token_type = validate_token_type(request.token_type) |
|
|
await token_manager.delete_token(request.tokens, token_type) |
|
|
|
|
|
logger.debug(f"[Admin] Token删除成功: {len(request.tokens)}个") |
|
|
return {"success": True, "message": f"成功删除 {len(request.tokens)} 个Token", "count": len(request.tokens)} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] Token删除异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"删除失败: {e}", "code": "DELETE_ERROR"}) |
|
|
|
|
|
|
|
|
@router.get("/api/settings") |
|
|
async def get_settings(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]: |
|
|
"""获取配置""" |
|
|
try: |
|
|
logger.debug("[Admin] 获取配置") |
|
|
return {"success": True, "data": {"global": setting.global_config, "grok": setting.grok_config}} |
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 获取配置失败: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"获取失败: {e}", "code": "GET_SETTINGS_ERROR"}) |
|
|
|
|
|
|
|
|
@router.post("/api/settings") |
|
|
async def update_settings(request: UpdateSettingsRequest, _: bool = Depends(verify_admin_session)) -> Dict[str, Any]: |
|
|
"""更新配置""" |
|
|
try: |
|
|
logger.debug("[Admin] 更新配置") |
|
|
await setting.save(global_config=request.global_config, grok_config=request.grok_config) |
|
|
logger.debug("[Admin] 配置更新成功") |
|
|
return {"success": True, "message": "配置更新成功"} |
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 更新配置失败: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"更新失败: {e}", "code": "UPDATE_SETTINGS_ERROR"}) |
|
|
|
|
|
|
|
|
@router.get("/api/cache/size") |
|
|
async def get_cache_size(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]: |
|
|
"""获取缓存大小""" |
|
|
try: |
|
|
logger.debug("[Admin] 获取缓存大小") |
|
|
|
|
|
image_size = _calculate_dir_size(IMAGE_CACHE_DIR) if IMAGE_CACHE_DIR.exists() else 0 |
|
|
video_size = _calculate_dir_size(VIDEO_CACHE_DIR) if VIDEO_CACHE_DIR.exists() else 0 |
|
|
total_size = image_size + video_size |
|
|
|
|
|
logger.debug(f"[Admin] 缓存大小: 图片{_format_size(image_size)}, 视频{_format_size(video_size)}") |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"data": { |
|
|
"image_size": _format_size(image_size), |
|
|
"video_size": _format_size(video_size), |
|
|
"total_size": _format_size(total_size), |
|
|
"image_size_bytes": image_size, |
|
|
"video_size_bytes": video_size, |
|
|
"total_size_bytes": total_size |
|
|
} |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 获取缓存大小异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"获取失败: {e}", "code": "CACHE_SIZE_ERROR"}) |
|
|
|
|
|
|
|
|
@router.post("/api/cache/clear") |
|
|
async def clear_cache(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]: |
|
|
"""清理所有缓存""" |
|
|
try: |
|
|
logger.debug("[Admin] 清理缓存") |
|
|
|
|
|
image_count = 0 |
|
|
video_count = 0 |
|
|
|
|
|
|
|
|
if IMAGE_CACHE_DIR.exists(): |
|
|
for file_path in IMAGE_CACHE_DIR.iterdir(): |
|
|
if file_path.is_file(): |
|
|
try: |
|
|
file_path.unlink() |
|
|
image_count += 1 |
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 删除失败: {file_path.name}, {e}") |
|
|
|
|
|
|
|
|
if VIDEO_CACHE_DIR.exists(): |
|
|
for file_path in VIDEO_CACHE_DIR.iterdir(): |
|
|
if file_path.is_file(): |
|
|
try: |
|
|
file_path.unlink() |
|
|
video_count += 1 |
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 删除失败: {file_path.name}, {e}") |
|
|
|
|
|
total = image_count + video_count |
|
|
logger.debug(f"[Admin] 缓存清理完成: 图片{image_count}, 视频{video_count}") |
|
|
|
|
|
return { |
|
|
"success": True, |
|
|
"message": f"成功清理缓存,删除图片 {image_count} 个,视频 {video_count} 个,共 {total} 个文件", |
|
|
"data": {"deleted_count": total, "image_count": image_count, "video_count": video_count} |
|
|
} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 清理缓存异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"清理失败: {e}", "code": "CACHE_CLEAR_ERROR"}) |
|
|
|
|
|
|
|
|
@router.post("/api/cache/clear/images") |
|
|
async def clear_image_cache(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]: |
|
|
"""清理图片缓存""" |
|
|
try: |
|
|
logger.debug("[Admin] 清理图片缓存") |
|
|
|
|
|
count = 0 |
|
|
if IMAGE_CACHE_DIR.exists(): |
|
|
for file_path in IMAGE_CACHE_DIR.iterdir(): |
|
|
if file_path.is_file(): |
|
|
try: |
|
|
file_path.unlink() |
|
|
count += 1 |
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 删除失败: {file_path.name}, {e}") |
|
|
|
|
|
logger.debug(f"[Admin] 图片缓存清理完成: {count}个") |
|
|
return {"success": True, "message": f"成功清理图片缓存,删除 {count} 个文件", "data": {"deleted_count": count, "type": "images"}} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 清理图片缓存异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"清理失败: {e}", "code": "IMAGE_CACHE_CLEAR_ERROR"}) |
|
|
|
|
|
|
|
|
@router.post("/api/cache/clear/videos") |
|
|
async def clear_video_cache(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]: |
|
|
"""清理视频缓存""" |
|
|
try: |
|
|
logger.debug("[Admin] 清理视频缓存") |
|
|
|
|
|
count = 0 |
|
|
if VIDEO_CACHE_DIR.exists(): |
|
|
for file_path in VIDEO_CACHE_DIR.iterdir(): |
|
|
if file_path.is_file(): |
|
|
try: |
|
|
file_path.unlink() |
|
|
count += 1 |
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 删除失败: {file_path.name}, {e}") |
|
|
|
|
|
logger.debug(f"[Admin] 视频缓存清理完成: {count}个") |
|
|
return {"success": True, "message": f"成功清理视频缓存,删除 {count} 个文件", "data": {"deleted_count": count, "type": "videos"}} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 清理视频缓存异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"清理失败: {e}", "code": "VIDEO_CACHE_CLEAR_ERROR"}) |
|
|
|
|
|
|
|
|
@router.get("/api/stats") |
|
|
async def get_stats(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]: |
|
|
"""获取统计信息""" |
|
|
try: |
|
|
logger.debug("[Admin] 开始获取统计信息") |
|
|
|
|
|
all_tokens = token_manager.get_tokens() |
|
|
normal_stats = calculate_token_stats(all_tokens.get(TokenType.NORMAL.value, {}), "normal") |
|
|
super_stats = calculate_token_stats(all_tokens.get(TokenType.SUPER.value, {}), "super") |
|
|
total = normal_stats["total"] + super_stats["total"] |
|
|
|
|
|
logger.debug(f"[Admin] 统计信息获取成功 - 普通Token: {normal_stats['total']}, Super Token: {super_stats['total']}, 总计: {total}") |
|
|
return {"success": True, "data": {"normal": normal_stats, "super": super_stats, "total": total}} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 获取统计信息异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"获取失败: {e}", "code": "STATS_ERROR"}) |
|
|
|
|
|
|
|
|
@router.get("/api/storage/mode") |
|
|
async def get_storage_mode(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]: |
|
|
"""获取存储模式""" |
|
|
try: |
|
|
logger.debug("[Admin] 获取存储模式") |
|
|
import os |
|
|
mode = os.getenv("STORAGE_MODE", "file").upper() |
|
|
return {"success": True, "data": {"mode": mode}} |
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 获取存储模式异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"获取失败: {e}", "code": "STORAGE_MODE_ERROR"}) |
|
|
|
|
|
|
|
|
@router.post("/api/tokens/tags") |
|
|
async def update_token_tags(request: UpdateTokenTagsRequest, _: bool = Depends(verify_admin_session)) -> Dict[str, Any]: |
|
|
"""更新Token标签""" |
|
|
try: |
|
|
logger.debug(f"[Admin] 更新Token标签: {request.token[:10]}..., {request.tags}") |
|
|
|
|
|
token_type = validate_token_type(request.token_type) |
|
|
await token_manager.update_token_tags(request.token, token_type, request.tags) |
|
|
|
|
|
logger.debug(f"[Admin] Token标签更新成功: {request.token[:10]}...") |
|
|
return {"success": True, "message": "标签更新成功", "tags": request.tags} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] Token标签更新异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"更新失败: {e}", "code": "UPDATE_TAGS_ERROR"}) |
|
|
|
|
|
|
|
|
@router.get("/api/tokens/tags/all") |
|
|
async def get_all_tags(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]: |
|
|
"""获取所有标签""" |
|
|
try: |
|
|
logger.debug("[Admin] 获取所有标签") |
|
|
|
|
|
all_tokens = token_manager.get_tokens() |
|
|
tags_set = set() |
|
|
|
|
|
for token_type_data in all_tokens.values(): |
|
|
for token_data in token_type_data.values(): |
|
|
tags = token_data.get("tags", []) |
|
|
if isinstance(tags, list): |
|
|
tags_set.update(tags) |
|
|
|
|
|
tags_list = sorted(list(tags_set)) |
|
|
logger.debug(f"[Admin] 标签获取成功: {len(tags_list)}个") |
|
|
return {"success": True, "data": tags_list} |
|
|
|
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] 获取标签异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"获取失败: {e}", "code": "GET_TAGS_ERROR"}) |
|
|
|
|
|
|
|
|
@router.post("/api/tokens/note") |
|
|
async def update_token_note(request: UpdateTokenNoteRequest, _: bool = Depends(verify_admin_session)) -> Dict[str, Any]: |
|
|
"""更新Token备注""" |
|
|
try: |
|
|
logger.debug(f"[Admin] 更新Token备注: {request.token[:10]}...") |
|
|
|
|
|
token_type = validate_token_type(request.token_type) |
|
|
await token_manager.update_token_note(request.token, token_type, request.note) |
|
|
|
|
|
logger.debug(f"[Admin] Token备注更新成功: {request.token[:10]}...") |
|
|
return {"success": True, "message": "备注更新成功", "note": request.note} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] Token备注更新异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"更新失败: {e}", "code": "UPDATE_NOTE_ERROR"}) |
|
|
|
|
|
|
|
|
@router.post("/api/tokens/test") |
|
|
async def test_token(request: TestTokenRequest, _: bool = Depends(verify_admin_session)) -> Dict[str, Any]: |
|
|
"""测试Token可用性""" |
|
|
try: |
|
|
logger.debug(f"[Admin] 测试Token: {request.token[:10]}...") |
|
|
|
|
|
token_type = validate_token_type(request.token_type) |
|
|
auth_token = f"sso-rw={request.token};sso={request.token}" |
|
|
|
|
|
result = await token_manager.check_limits(auth_token, "grok-4-fast") |
|
|
|
|
|
if result: |
|
|
logger.debug(f"[Admin] Token测试成功: {request.token[:10]}...") |
|
|
return { |
|
|
"success": True, |
|
|
"message": "Token有效", |
|
|
"data": { |
|
|
"valid": True, |
|
|
"remaining_queries": result.get("remainingTokens", -1), |
|
|
"limit": result.get("limit", -1) |
|
|
} |
|
|
} |
|
|
else: |
|
|
logger.warning(f"[Admin] Token测试失败: {request.token[:10]}...") |
|
|
|
|
|
all_tokens = token_manager.get_tokens() |
|
|
token_data = all_tokens.get(token_type.value, {}).get(request.token) |
|
|
|
|
|
if token_data: |
|
|
if token_data.get("status") == "expired": |
|
|
return {"success": False, "message": "Token已失效", "data": {"valid": False, "error_type": "expired", "error_code": 401}} |
|
|
elif token_data.get("remainingQueries") == 0: |
|
|
return {"success": False, "message": "Token已被限流", "data": {"valid": False, "error_type": "limited", "error_code": "other"}} |
|
|
else: |
|
|
return {"success": False, "message": "服务器被block或网络错误", "data": {"valid": False, "error_type": "blocked", "error_code": 403}} |
|
|
else: |
|
|
return {"success": False, "message": "Token数据异常", "data": {"valid": False, "error_type": "unknown", "error_code": "data_error"}} |
|
|
|
|
|
except HTTPException: |
|
|
raise |
|
|
except Exception as e: |
|
|
logger.error(f"[Admin] Token测试异常: {e}") |
|
|
raise HTTPException(status_code=500, detail={"error": f"测试失败: {e}", "code": "TEST_TOKEN_ERROR"}) |
|
|
|