960 lines
32 KiB
Python
960 lines
32 KiB
Python
"""
|
|
管理接口模块
|
|
|
|
提供Token管理功能,包括登录验证、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):
|
|
"""批量添加Token请求"""
|
|
tokens: List[str]
|
|
token_type: str # "sso" 或 "ssoSuper"
|
|
|
|
|
|
class DeleteTokensRequest(BaseModel):
|
|
"""批量删除Token请求"""
|
|
tokens: List[str]
|
|
token_type: str # "sso" 或 "ssoSuper"
|
|
|
|
|
|
class TokenInfo(BaseModel):
|
|
"""Token信息"""
|
|
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):
|
|
"""Token列表响应"""
|
|
success: bool
|
|
data: List[TokenInfo]
|
|
total: int
|
|
|
|
|
|
# === 辅助函数 ===
|
|
|
|
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类型,必须是 'sso' 或 'ssoSuper'", "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: # super token
|
|
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:] # 移除 "Bearer " 前缀
|
|
|
|
# 检查token是否存在且未过期
|
|
if token not in _sessions:
|
|
raise HTTPException(
|
|
status_code=401,
|
|
detail={"error": "会话已过期或无效", "code": "SESSION_INVALID"}
|
|
)
|
|
|
|
# 检查会话是否过期(24小时)
|
|
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状态"""
|
|
# 首先检查是否失效(来自 token.json 的 status 字段)
|
|
if token_data.get("status") == "expired":
|
|
return "失效"
|
|
|
|
# 获取剩余次数
|
|
remaining_queries = token_data.get("remainingQueries", -1)
|
|
heavy_remaining = token_data.get("heavyremainingQueries", -1)
|
|
|
|
# 根据token类型选择正确的字段
|
|
if token_type == "ssoSuper":
|
|
# Super token 可能使用 heavy 模型
|
|
relevant_remaining = max(remaining_queries, heavy_remaining)
|
|
else:
|
|
# 普通token主要看 remaining_queries
|
|
relevant_remaining = remaining_queries
|
|
|
|
if relevant_remaining == -1:
|
|
return "未使用"
|
|
elif relevant_remaining == 0:
|
|
return "限流中"
|
|
else:
|
|
return "正常"
|
|
|
|
|
|
# === 页面路由 ===
|
|
|
|
@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="管理页面不存在")
|
|
|
|
|
|
# === API端点 ===
|
|
|
|
@router.post("/api/login", response_model=LoginResponse)
|
|
async def admin_login(request: LoginRequest) -> LoginResponse:
|
|
"""
|
|
管理员登录
|
|
|
|
验证用户名和密码,成功后返回会话token。
|
|
"""
|
|
try:
|
|
logger.debug(f"[Admin] 管理员登录尝试 - 用户名: {request.username}")
|
|
|
|
# 验证用户名和密码
|
|
expected_username = setting.global_config.get("admin_username", "")
|
|
expected_password = setting.global_config.get("admin_password", "")
|
|
|
|
if request.username != expected_username or request.password != expected_password:
|
|
logger.warning(f"[Admin] 登录失败: 用户名或密码错误 - 用户名: {request.username}")
|
|
return LoginResponse(
|
|
success=False,
|
|
message="用户名或密码错误"
|
|
)
|
|
|
|
# 生成会话token
|
|
session_token = secrets.token_urlsafe(32)
|
|
|
|
# 设置会话过期时间
|
|
expire_time = datetime.now() + timedelta(hours=SESSION_EXPIRE_HOURS)
|
|
_sessions[session_token] = expire_time
|
|
|
|
logger.debug(f"[Admin] 管理员登录成功 - 用户名: {request.username}")
|
|
|
|
return LoginResponse(
|
|
success=True,
|
|
token=session_token,
|
|
message="登录成功"
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"[Admin] 登录处理异常 - 用户名: {request.username}, 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"登录失败: {str(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]:
|
|
"""
|
|
管理员登出
|
|
|
|
清除会话token。
|
|
"""
|
|
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] 登出失败: 无效或缺失的会话Token")
|
|
return {"success": False, "message": "无效的会话"}
|
|
|
|
except Exception as e:
|
|
logger.error(f"[Admin] 登出处理异常 - 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"登出失败: {str(e)}", "code": "LOGOUT_ERROR"}
|
|
)
|
|
|
|
|
|
@router.get("/api/tokens", response_model=TokenListResponse)
|
|
async def list_tokens(_: bool = Depends(verify_admin_session)) -> TokenListResponse:
|
|
"""
|
|
获取所有Token列表
|
|
|
|
返回系统中所有Token及其状态信息。
|
|
"""
|
|
try:
|
|
logger.debug("[Admin] 开始获取Token列表")
|
|
|
|
all_tokens_data = token_manager.get_tokens()
|
|
token_list: List[TokenInfo] = []
|
|
|
|
# 处理普通Token
|
|
normal_tokens = all_tokens_data.get(TokenType.NORMAL.value, {})
|
|
for token, data in normal_tokens.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", []), # 向后兼容,如果没有tags字段则返回空列表
|
|
note=data.get("note", "") # 向后兼容,如果没有note字段则返回空字符串
|
|
))
|
|
|
|
# 处理Super Token
|
|
super_tokens = all_tokens_data.get(TokenType.SUPER.value, {})
|
|
for token, data in super_tokens.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", []), # 向后兼容,如果没有tags字段则返回空列表
|
|
note=data.get("note", "") # 向后兼容,如果没有note字段则返回空字符串
|
|
))
|
|
|
|
normal_count = len(normal_tokens)
|
|
super_count = len(super_tokens)
|
|
total_count = len(token_list)
|
|
|
|
logger.debug(f"[Admin] Token列表获取成功 - 普通Token: {normal_count}, Super Token: {super_count}, 总计: {total_count}")
|
|
|
|
return TokenListResponse(
|
|
success=True,
|
|
data=token_list,
|
|
total=total_count
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"[Admin] 获取Token列表异常 - 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"获取Token列表失败: {str(e)}", "code": "LIST_ERROR"}
|
|
)
|
|
|
|
|
|
@router.post("/api/tokens/add")
|
|
async def add_tokens(request: AddTokensRequest,
|
|
_: bool = Depends(verify_admin_session)) -> Dict[str, Any]:
|
|
"""
|
|
批量添加Token
|
|
|
|
支持添加普通Token(sso)和Super Token(ssoSuper)。
|
|
"""
|
|
try:
|
|
logger.debug(f"[Admin] 批量添加Token - 类型: {request.token_type}, 数量: {len(request.tokens)}")
|
|
|
|
# 验证并转换token类型
|
|
token_type = validate_token_type(request.token_type)
|
|
|
|
# 添加Token
|
|
await token_manager.add_token(request.tokens, token_type)
|
|
|
|
logger.debug(f"[Admin] Token添加成功 - 类型: {request.token_type}, 数量: {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添加异常 - 类型: {request.token_type}, 数量: {len(request.tokens)}, 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"添加Token失败: {str(e)}", "code": "ADD_ERROR"}
|
|
)
|
|
|
|
|
|
@router.post("/api/tokens/delete")
|
|
async def delete_tokens(request: DeleteTokensRequest,
|
|
_: bool = Depends(verify_admin_session)) -> Dict[str, Any]:
|
|
"""
|
|
批量删除Token
|
|
|
|
支持删除普通Token(sso)和Super Token(ssoSuper)。
|
|
"""
|
|
try:
|
|
logger.debug(f"[Admin] 批量删除Token - 类型: {request.token_type}, 数量: {len(request.tokens)}")
|
|
|
|
# 验证并转换token类型
|
|
token_type = validate_token_type(request.token_type)
|
|
|
|
# 删除Token
|
|
await token_manager.delete_token(request.tokens, token_type)
|
|
|
|
logger.debug(f"[Admin] Token删除成功 - 类型: {request.token_type}, 数量: {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删除异常 - 类型: {request.token_type}, 数量: {len(request.tokens)}, 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"删除Token失败: {str(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] 获取配置失败: {str(e)}")
|
|
raise HTTPException(status_code=500, detail={"error": f"获取配置失败: {str(e)}", "code": "GET_SETTINGS_ERROR"})
|
|
|
|
|
|
class UpdateSettingsRequest(BaseModel):
|
|
"""更新配置请求"""
|
|
global_config: Optional[Dict[str, Any]] = None
|
|
grok_config: Optional[Dict[str, Any]] = None
|
|
|
|
|
|
class StreamTimeoutSettings(BaseModel):
|
|
"""流式超时配置"""
|
|
stream_chunk_timeout: int = 120
|
|
stream_first_response_timeout: int = 30
|
|
stream_total_timeout: int = 600
|
|
|
|
|
|
@router.post("/api/settings")
|
|
async def update_settings(request: UpdateSettingsRequest, _: bool = Depends(verify_admin_session)) -> Dict[str, Any]:
|
|
"""更新全局配置"""
|
|
try:
|
|
logger.debug("[Admin] 更新全局配置")
|
|
|
|
# 使用ConfigManager的save方法(支持存储抽象层)
|
|
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] 更新配置失败: {str(e)}")
|
|
raise HTTPException(status_code=500, detail={"error": f"更新配置失败: {str(e)}", "code": "UPDATE_SETTINGS_ERROR"})
|
|
|
|
|
|
def _calculate_dir_size(directory: Path) -> int:
|
|
"""计算目录中所有文件的大小(字节)"""
|
|
total_size = 0
|
|
for file_path in directory.iterdir():
|
|
if file_path.is_file():
|
|
try:
|
|
total_size += file_path.stat().st_size
|
|
except Exception as e:
|
|
logger.warning(f"[Admin] 无法获取文件大小: {file_path.name}, 错误: {str(e)}")
|
|
return total_size
|
|
|
|
|
|
def _format_size(size_bytes: int) -> str:
|
|
"""格式化字节大小为可读字符串"""
|
|
size_mb = size_bytes / BYTES_PER_MB
|
|
if size_mb < 1:
|
|
size_kb = size_bytes / BYTES_PER_KB
|
|
return f"{size_kb:.1f} KB"
|
|
return f"{size_mb:.1f} MB"
|
|
|
|
|
|
@router.get("/api/cache/size")
|
|
async def get_cache_size(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]:
|
|
"""获取缓存大小"""
|
|
try:
|
|
logger.debug("[Admin] 开始获取缓存大小")
|
|
|
|
# 计算图片缓存大小
|
|
image_size = 0
|
|
if IMAGE_CACHE_DIR.exists():
|
|
image_size = _calculate_dir_size(IMAGE_CACHE_DIR)
|
|
|
|
# 计算视频缓存大小
|
|
video_size = 0
|
|
if VIDEO_CACHE_DIR.exists():
|
|
video_size = _calculate_dir_size(VIDEO_CACHE_DIR)
|
|
|
|
# 总大小
|
|
total_size = image_size + video_size
|
|
|
|
logger.debug(f"[Admin] 缓存大小获取完成 - 图片: {_format_size(image_size)}, 视频: {_format_size(video_size)}, 总计: {_format_size(total_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] 获取缓存大小异常 - 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"获取缓存大小失败: {str(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] 开始清理缓存")
|
|
|
|
deleted_count = 0
|
|
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
|
|
logger.debug(f"[Admin] 删除图片缓存: {file_path.name}")
|
|
except Exception as e:
|
|
logger.error(f"[Admin] 删除图片缓存失败: {file_path.name}, 错误: {str(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
|
|
logger.debug(f"[Admin] 删除视频缓存: {file_path.name}")
|
|
except Exception as e:
|
|
logger.error(f"[Admin] 删除视频缓存失败: {file_path.name}, 错误: {str(e)}")
|
|
|
|
deleted_count = image_count + video_count
|
|
logger.debug(f"[Admin] 缓存清理完成 - 图片: {image_count}, 视频: {video_count}, 总计: {deleted_count}")
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"成功清理缓存,删除图片 {image_count} 个,视频 {video_count} 个,共 {deleted_count} 个文件",
|
|
"data": {
|
|
"deleted_count": deleted_count,
|
|
"image_count": image_count,
|
|
"video_count": video_count
|
|
}
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"[Admin] 清理缓存异常 - 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"清理缓存失败: {str(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] 开始清理图片缓存")
|
|
|
|
deleted_count = 0
|
|
|
|
# 清理图片缓存
|
|
if IMAGE_CACHE_DIR.exists():
|
|
for file_path in IMAGE_CACHE_DIR.iterdir():
|
|
if file_path.is_file():
|
|
try:
|
|
file_path.unlink()
|
|
deleted_count += 1
|
|
logger.debug(f"[Admin] 删除图片缓存: {file_path.name}")
|
|
except Exception as e:
|
|
logger.error(f"[Admin] 删除图片缓存失败: {file_path.name}, 错误: {str(e)}")
|
|
|
|
logger.debug(f"[Admin] 图片缓存清理完成 - 删除 {deleted_count} 个文件")
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"成功清理图片缓存,删除 {deleted_count} 个文件",
|
|
"data": {
|
|
"deleted_count": deleted_count,
|
|
"type": "images"
|
|
}
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"[Admin] 清理图片缓存异常 - 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"清理图片缓存失败: {str(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] 开始清理视频缓存")
|
|
|
|
deleted_count = 0
|
|
|
|
# 清理视频缓存
|
|
if VIDEO_CACHE_DIR.exists():
|
|
for file_path in VIDEO_CACHE_DIR.iterdir():
|
|
if file_path.is_file():
|
|
try:
|
|
file_path.unlink()
|
|
deleted_count += 1
|
|
logger.debug(f"[Admin] 删除视频缓存: {file_path.name}")
|
|
except Exception as e:
|
|
logger.error(f"[Admin] 删除视频缓存失败: {file_path.name}, 错误: {str(e)}")
|
|
|
|
logger.debug(f"[Admin] 视频缓存清理完成 - 删除 {deleted_count} 个文件")
|
|
|
|
return {
|
|
"success": True,
|
|
"message": f"成功清理视频缓存,删除 {deleted_count} 个文件",
|
|
"data": {
|
|
"deleted_count": deleted_count,
|
|
"type": "videos"
|
|
}
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"[Admin] 清理视频缓存异常 - 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"清理视频缓存失败: {str(e)}", "code": "VIDEO_CACHE_CLEAR_ERROR"}
|
|
)
|
|
|
|
|
|
@router.get("/api/stats")
|
|
async def get_stats(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]:
|
|
"""
|
|
获取统计信息
|
|
|
|
返回Token的统计数据。
|
|
"""
|
|
try:
|
|
logger.debug("[Admin] 开始获取统计信息")
|
|
|
|
all_tokens_data = token_manager.get_tokens()
|
|
|
|
# 统计普通Token
|
|
normal_tokens = all_tokens_data.get(TokenType.NORMAL.value, {})
|
|
normal_stats = calculate_token_stats(normal_tokens, "normal")
|
|
|
|
# 统计Super Token
|
|
super_tokens = all_tokens_data.get(TokenType.SUPER.value, {})
|
|
super_stats = calculate_token_stats(super_tokens, "super")
|
|
|
|
total_count = normal_stats["total"] + super_stats["total"]
|
|
|
|
stats = {
|
|
"success": True,
|
|
"data": {
|
|
"normal": normal_stats,
|
|
"super": super_stats,
|
|
"total": total_count
|
|
}
|
|
}
|
|
|
|
logger.debug(f"[Admin] 统计信息获取成功 - 普通Token: {normal_stats['total']}, Super Token: {super_stats['total']}, 总计: {total_count}")
|
|
return stats
|
|
|
|
except Exception as e:
|
|
logger.error(f"[Admin] 获取统计信息异常 - 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"获取统计信息失败: {str(e)}", "code": "STATS_ERROR"}
|
|
)
|
|
|
|
|
|
@router.get("/api/storage/mode")
|
|
async def get_storage_mode(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]:
|
|
"""
|
|
获取当前存储模式
|
|
|
|
返回当前的存储模式(file/mysql/redis)。
|
|
"""
|
|
try:
|
|
logger.debug("[Admin] 获取存储模式")
|
|
|
|
import os
|
|
storage_mode = os.getenv("STORAGE_MODE", "file").upper()
|
|
|
|
return {
|
|
"success": True,
|
|
"data": {
|
|
"mode": storage_mode
|
|
}
|
|
}
|
|
|
|
except Exception as e:
|
|
logger.error(f"[Admin] 获取存储模式异常 - 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"获取存储模式失败: {str(e)}", "code": "STORAGE_MODE_ERROR"}
|
|
)
|
|
|
|
|
|
class UpdateTokenTagsRequest(BaseModel):
|
|
"""更新Token标签请求"""
|
|
token: str
|
|
token_type: str
|
|
tags: List[str]
|
|
|
|
|
|
@router.post("/api/tokens/tags")
|
|
async def update_token_tags(
|
|
request: UpdateTokenTagsRequest,
|
|
_: bool = Depends(verify_admin_session)
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
更新Token标签
|
|
|
|
为指定Token添加或修改标签。
|
|
"""
|
|
try:
|
|
logger.debug(f"[Admin] 更新Token标签 - Token: {request.token[:10]}..., Tags: {request.tags}")
|
|
|
|
# 验证并转换token类型
|
|
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标签更新成功 - Token: {request.token[:10]}..., Tags: {request.tags}")
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "标签更新成功",
|
|
"tags": request.tags
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"[Admin] Token标签更新异常 - Token: {request.token[:10]}..., 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"更新标签失败: {str(e)}", "code": "UPDATE_TAGS_ERROR"}
|
|
)
|
|
|
|
|
|
@router.get("/api/tokens/tags/all")
|
|
async def get_all_tags(_: bool = Depends(verify_admin_session)) -> Dict[str, Any]:
|
|
"""
|
|
获取所有标签
|
|
|
|
返回系统中所有Token使用的标签列表(去重)。
|
|
"""
|
|
try:
|
|
logger.debug("[Admin] 获取所有标签")
|
|
|
|
all_tokens_data = token_manager.get_tokens()
|
|
tags_set = set()
|
|
|
|
# 收集所有标签
|
|
for token_type_data in all_tokens_data.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] 获取标签异常 - 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"获取标签失败: {str(e)}", "code": "GET_TAGS_ERROR"}
|
|
)
|
|
|
|
|
|
class UpdateTokenNoteRequest(BaseModel):
|
|
"""更新Token备注请求"""
|
|
token: str
|
|
token_type: str
|
|
note: str
|
|
|
|
|
|
@router.post("/api/tokens/note")
|
|
async def update_token_note(
|
|
request: UpdateTokenNoteRequest,
|
|
_: bool = Depends(verify_admin_session)
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
更新Token备注
|
|
|
|
为指定Token添加或修改备注信息。
|
|
"""
|
|
try:
|
|
logger.debug(f"[Admin] 更新Token备注 - Token: {request.token[:10]}...")
|
|
|
|
# 验证并转换token类型
|
|
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备注更新成功 - Token: {request.token[:10]}...")
|
|
|
|
return {
|
|
"success": True,
|
|
"message": "备注更新成功",
|
|
"note": request.note
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"[Admin] Token备注更新异常 - Token: {request.token[:10]}..., 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"更新备注失败: {str(e)}", "code": "UPDATE_NOTE_ERROR"}
|
|
)
|
|
|
|
|
|
class TestTokenRequest(BaseModel):
|
|
"""测试Token请求"""
|
|
token: str
|
|
token_type: str
|
|
|
|
|
|
@router.post("/api/tokens/test")
|
|
async def test_token(
|
|
request: TestTokenRequest,
|
|
_: bool = Depends(verify_admin_session)
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
测试Token可用性
|
|
|
|
通过发送速率限制检查请求来验证Token是否有效。
|
|
根据不同的HTTP状态码进行相应的处理:
|
|
- 401: Token失效
|
|
- 403: 服务器被block,不改变Token状态
|
|
- 其他错误: 设置为限流状态
|
|
"""
|
|
try:
|
|
logger.debug(f"[Admin] 测试Token - Token: {request.token[:10]}...")
|
|
|
|
# 验证并转换token类型
|
|
token_type = validate_token_type(request.token_type)
|
|
|
|
# 构造完整的auth token
|
|
auth_token = f"sso-rw={request.token};sso={request.token}"
|
|
|
|
# 使用check_limits方法测试token
|
|
result = await token_manager.check_limits(auth_token, "grok-4-fast")
|
|
|
|
if result:
|
|
logger.debug(f"[Admin] Token测试成功 - Token: {request.token[:10]}...")
|
|
return {
|
|
"success": True,
|
|
"message": "Token有效",
|
|
"data": {
|
|
"valid": True,
|
|
"remaining_queries": result.get("remainingTokens", -1),
|
|
"limit": result.get("limit", -1)
|
|
}
|
|
}
|
|
else:
|
|
# 测试失败,check_limits方法已经调用了record_failure处理错误
|
|
# 现在检查token的状态来判断错误类型
|
|
logger.warning(f"[Admin] Token测试失败 - Token: {request.token[:10]}...")
|
|
|
|
# 检查token当前状态
|
|
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":
|
|
# Token被标记为失效(401错误)
|
|
return {
|
|
"success": False,
|
|
"message": "Token已失效",
|
|
"data": {
|
|
"valid": False,
|
|
"error_type": "expired",
|
|
"error_code": 401
|
|
}
|
|
}
|
|
elif token_data.get("remainingQueries") == 0:
|
|
# Token被设置为限流状态(其他错误)
|
|
return {
|
|
"success": False,
|
|
"message": "Token已被限流",
|
|
"data": {
|
|
"valid": False,
|
|
"error_type": "limited",
|
|
"error_code": "other"
|
|
}
|
|
}
|
|
else:
|
|
# 可能是403错误或其他网络问题,token状态未变
|
|
return {
|
|
"success": False,
|
|
"message": "服务器被block或网络错误",
|
|
"data": {
|
|
"valid": False,
|
|
"error_type": "blocked",
|
|
"error_code": 403
|
|
}
|
|
}
|
|
else:
|
|
# 找不到token数据
|
|
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测试异常 - Token: {request.token[:10]}..., 错误: {str(e)}")
|
|
raise HTTPException(
|
|
status_code=500,
|
|
detail={"error": f"测试Token失败: {str(e)}", "code": "TEST_TOKEN_ERROR"}
|
|
)
|