1420 lines
43 KiB
Python
1420 lines
43 KiB
Python
"""
|
||
Bot Manager API 路由
|
||
提供模型配置、Bot 管理、设置管理、MCP 服务器等功能的 API
|
||
"""
|
||
import logging
|
||
import uuid
|
||
import hashlib
|
||
import secrets
|
||
from datetime import datetime, timedelta
|
||
from typing import Optional, List
|
||
from fastapi import APIRouter, HTTPException, Header
|
||
from pydantic import BaseModel
|
||
|
||
from agent.db_pool_manager import get_db_pool_manager
|
||
from utils.fastapi_utils import extract_api_key_from_auth
|
||
|
||
logger = logging.getLogger('app')
|
||
|
||
router = APIRouter()
|
||
|
||
# ============== Admin 配置 ==============
|
||
ADMIN_USERNAME = "admin"
|
||
ADMIN_PASSWORD = "Admin123" # 生产环境应使用环境变量
|
||
TOKEN_EXPIRE_HOURS = 24
|
||
|
||
|
||
# ============== 认证函数 ==============
|
||
|
||
async def verify_admin_auth(authorization: Optional[str]) -> tuple[bool, Optional[str]]:
|
||
"""
|
||
验证管理员认证
|
||
|
||
Args:
|
||
authorization: Authorization header 值
|
||
|
||
Returns:
|
||
tuple[bool, Optional[str]]: (是否有效, 用户名)
|
||
"""
|
||
provided_token = extract_api_key_from_auth(authorization)
|
||
if not provided_token:
|
||
return False, None
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
# 检查 token 是否有效且未过期
|
||
await cursor.execute("""
|
||
SELECT username, expires_at
|
||
FROM agent_admin_tokens
|
||
WHERE token = %s
|
||
AND expires_at > NOW()
|
||
""", (provided_token,))
|
||
row = await cursor.fetchone()
|
||
|
||
if not row:
|
||
return False, None
|
||
|
||
return True, row[0]
|
||
|
||
|
||
def verify_auth(authorization: Optional[str]) -> None:
|
||
"""
|
||
验证请求认证
|
||
|
||
Args:
|
||
authorization: Authorization header 值
|
||
|
||
Raises:
|
||
HTTPException: 认证失败时抛出 401 错误
|
||
"""
|
||
provided_token = extract_api_key_from_auth(authorization)
|
||
if not provided_token:
|
||
raise HTTPException(
|
||
status_code=401,
|
||
detail="Authorization header is required"
|
||
)
|
||
|
||
|
||
# ============== Pydantic Models ==============
|
||
|
||
# --- Admin 登录相关 ---
|
||
class AdminLoginRequest(BaseModel):
|
||
"""管理员登录请求"""
|
||
username: str
|
||
password: str
|
||
|
||
|
||
class AdminLoginResponse(BaseModel):
|
||
"""管理员登录响应"""
|
||
token: str
|
||
username: str
|
||
expires_at: str
|
||
|
||
|
||
class AdminVerifyResponse(BaseModel):
|
||
"""管理员验证响应"""
|
||
valid: bool
|
||
username: Optional[str] = None
|
||
|
||
|
||
# --- 模型相关 ---
|
||
class ModelCreate(BaseModel):
|
||
"""创建模型请求"""
|
||
name: str
|
||
provider: str
|
||
model: str
|
||
server: Optional[str] = None
|
||
api_key: Optional[str] = None
|
||
is_default: bool = False
|
||
|
||
|
||
class ModelUpdate(BaseModel):
|
||
"""更新模型请求"""
|
||
name: Optional[str] = None
|
||
provider: Optional[str] = None
|
||
model: Optional[str] = None
|
||
server: Optional[str] = None
|
||
api_key: Optional[str] = None
|
||
is_default: Optional[bool] = None
|
||
|
||
|
||
class ModelResponse(BaseModel):
|
||
"""模型响应"""
|
||
id: str
|
||
name: str
|
||
provider: str
|
||
model: str
|
||
server: Optional[str]
|
||
api_key: Optional[str] # 掩码显示
|
||
is_default: bool
|
||
created_at: str
|
||
updated_at: str
|
||
|
||
|
||
# --- Bot 相关 ---
|
||
class BotCreate(BaseModel):
|
||
"""创建 Bot 请求"""
|
||
name: str
|
||
|
||
|
||
class BotUpdate(BaseModel):
|
||
"""更新 Bot 请求"""
|
||
name: Optional[str] = None
|
||
bot_id: Optional[str] = None
|
||
|
||
|
||
class BotResponse(BaseModel):
|
||
"""Bot 响应"""
|
||
id: str
|
||
name: str
|
||
bot_id: str
|
||
created_at: str
|
||
updated_at: str
|
||
|
||
|
||
# --- Bot 设置相关 ---
|
||
class BotSettingsUpdate(BaseModel):
|
||
"""更新 Bot 设置请求"""
|
||
model_id: Optional[str] = None
|
||
language: Optional[str] = None
|
||
robot_type: Optional[str] = None
|
||
dataset_ids: Optional[str] = None
|
||
system_prompt: Optional[str] = None
|
||
user_identifier: Optional[str] = None
|
||
enable_memori: Optional[bool] = None
|
||
tool_response: Optional[bool] = None
|
||
skills: Optional[str] = None
|
||
|
||
|
||
class ModelInfo(BaseModel):
|
||
"""模型信息"""
|
||
id: str
|
||
name: str
|
||
provider: str
|
||
model: str
|
||
server: Optional[str]
|
||
api_key: Optional[str] # 掩码显示
|
||
|
||
|
||
class BotSettingsResponse(BaseModel):
|
||
"""Bot 设置响应"""
|
||
bot_id: str
|
||
model_id: Optional[str]
|
||
model: Optional[ModelInfo] # 关联的模型信息
|
||
language: str
|
||
robot_type: Optional[str]
|
||
dataset_ids: Optional[str]
|
||
system_prompt: Optional[str]
|
||
user_identifier: Optional[str]
|
||
enable_memori: bool
|
||
tool_response: bool
|
||
skills: Optional[str]
|
||
updated_at: str
|
||
|
||
|
||
# --- 会话相关 ---
|
||
class SessionCreate(BaseModel):
|
||
"""创建会话请求"""
|
||
title: Optional[str] = None
|
||
|
||
|
||
class SessionResponse(BaseModel):
|
||
"""会话响应"""
|
||
id: str
|
||
bot_id: str
|
||
title: Optional[str]
|
||
created_at: str
|
||
updated_at: str
|
||
|
||
|
||
# --- MCP 相关 ---
|
||
class MCPServerCreate(BaseModel):
|
||
"""创建 MCP 服务器请求"""
|
||
name: str
|
||
type: str
|
||
config: dict
|
||
enabled: bool = True
|
||
|
||
|
||
class MCPServerUpdate(BaseModel):
|
||
"""更新 MCP 服务器请求"""
|
||
name: Optional[str] = None
|
||
type: Optional[str] = None
|
||
config: Optional[dict] = None
|
||
enabled: Optional[bool] = None
|
||
|
||
|
||
class MCPServerResponse(BaseModel):
|
||
"""MCP 服务器响应"""
|
||
id: str
|
||
bot_id: str
|
||
name: str
|
||
type: str
|
||
config: dict
|
||
enabled: bool
|
||
created_at: str
|
||
updated_at: str
|
||
|
||
|
||
# --- 通用响应 ---
|
||
class SuccessResponse(BaseModel):
|
||
"""通用成功响应"""
|
||
success: bool
|
||
message: str
|
||
|
||
|
||
# ============== 数据库表初始化 ==============
|
||
|
||
async def init_bot_manager_tables():
|
||
"""
|
||
初始化 Bot Manager 相关的所有数据库表
|
||
"""
|
||
pool = get_db_pool_manager().pool
|
||
|
||
# SQL 表创建语句
|
||
tables_sql = [
|
||
# admin_tokens 表(用于存储登录 token)
|
||
"""
|
||
CREATE TABLE IF NOT EXISTS agent_admin_tokens (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
username VARCHAR(255) NOT NULL,
|
||
token VARCHAR(255) NOT NULL UNIQUE,
|
||
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||
)
|
||
""",
|
||
# admin_tokens 索引
|
||
"CREATE INDEX IF NOT EXISTS idx_agent_admin_tokens_token ON agent_admin_tokens(token)",
|
||
"CREATE INDEX IF NOT EXISTS idx_agent_admin_tokens_expires ON agent_admin_tokens(expires_at)",
|
||
|
||
# models 表
|
||
"""
|
||
CREATE TABLE IF NOT EXISTS agent_models (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
name VARCHAR(255) NOT NULL,
|
||
provider VARCHAR(100) NOT NULL,
|
||
model VARCHAR(255) NOT NULL,
|
||
server VARCHAR(500),
|
||
api_key VARCHAR(500),
|
||
is_default BOOLEAN DEFAULT FALSE,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||
)
|
||
""",
|
||
# models 索引
|
||
"CREATE INDEX IF NOT EXISTS idx_agent_models_is_default ON agent_models(is_default)",
|
||
|
||
# bots 表
|
||
"""
|
||
CREATE TABLE IF NOT EXISTS agent_bots (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
name VARCHAR(255) NOT NULL,
|
||
bot_id VARCHAR(255) NOT NULL UNIQUE,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||
)
|
||
""",
|
||
# bots 索引
|
||
"CREATE INDEX IF NOT EXISTS idx_agent_bots_bot_id ON agent_bots(bot_id)",
|
||
|
||
# bot_settings 表
|
||
"""
|
||
CREATE TABLE IF NOT EXISTS agent_bot_settings (
|
||
bot_id UUID PRIMARY KEY REFERENCES agent_bots(id) ON DELETE CASCADE,
|
||
model_id UUID REFERENCES agent_models(id) ON DELETE SET NULL,
|
||
language VARCHAR(10) DEFAULT 'zh',
|
||
robot_type VARCHAR(50),
|
||
dataset_ids TEXT,
|
||
system_prompt TEXT,
|
||
user_identifier VARCHAR(255),
|
||
enable_memori BOOLEAN DEFAULT FALSE,
|
||
tool_response BOOLEAN DEFAULT FALSE,
|
||
skills TEXT,
|
||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||
)
|
||
""",
|
||
|
||
# mcp_servers 表
|
||
"""
|
||
CREATE TABLE IF NOT EXISTS agent_mcp_servers (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
bot_id UUID REFERENCES agent_bots(id) ON DELETE CASCADE,
|
||
name VARCHAR(255) NOT NULL,
|
||
type VARCHAR(50) NOT NULL,
|
||
config JSONB NOT NULL,
|
||
enabled BOOLEAN DEFAULT TRUE,
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||
)
|
||
""",
|
||
# mcp_servers 索引
|
||
"CREATE INDEX IF NOT EXISTS idx_agent_mcp_servers_bot_id ON agent_mcp_servers(bot_id)",
|
||
"CREATE INDEX IF NOT EXISTS idx_agent_mcp_servers_enabled ON agent_mcp_servers(enabled)",
|
||
|
||
# chat_sessions 表
|
||
"""
|
||
CREATE TABLE IF NOT EXISTS agent_chat_sessions (
|
||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||
bot_id UUID REFERENCES agent_bots(id) ON DELETE CASCADE,
|
||
title VARCHAR(500),
|
||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||
)
|
||
""",
|
||
# chat_sessions 索引
|
||
"CREATE INDEX IF NOT EXISTS idx_agent_chat_sessions_bot_id ON agent_chat_sessions(bot_id)",
|
||
"CREATE INDEX IF NOT EXISTS idx_agent_chat_sessions_created ON agent_chat_sessions(created_at DESC)",
|
||
]
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
for sql in tables_sql:
|
||
await cursor.execute(sql)
|
||
await conn.commit()
|
||
|
||
logger.info("Bot Manager tables initialized successfully")
|
||
|
||
|
||
# ============== 辅助函数 ==============
|
||
|
||
def mask_api_key(api_key: Optional[str]) -> Optional[str]:
|
||
"""对 API Key 进行掩码处理"""
|
||
if not api_key:
|
||
return None
|
||
if len(api_key) <= 8:
|
||
return "****"
|
||
return api_key[:4] + "****" + api_key[-4:]
|
||
|
||
|
||
def datetime_to_str(dt: datetime) -> str:
|
||
"""将 datetime 转换为 ISO 格式字符串"""
|
||
return dt.isoformat() if dt else ""
|
||
|
||
|
||
# ============== 模型管理 API ==============
|
||
|
||
@router.get("/api/v1/models", response_model=List[ModelResponse])
|
||
async def get_models(authorization: Optional[str] = Header(None)):
|
||
"""
|
||
获取所有模型配置
|
||
|
||
Args:
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
List[ModelResponse]: 模型列表
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("""
|
||
SELECT id, name, provider, model, server, api_key, is_default, created_at, updated_at
|
||
FROM agent_models
|
||
ORDER BY is_default DESC, created_at DESC
|
||
""")
|
||
rows = await cursor.fetchall()
|
||
|
||
return [
|
||
ModelResponse(
|
||
id=str(row[0]),
|
||
name=row[1],
|
||
provider=row[2],
|
||
model=row[3],
|
||
server=row[4],
|
||
api_key=mask_api_key(row[5]),
|
||
is_default=row[6],
|
||
created_at=datetime_to_str(row[7]),
|
||
updated_at=datetime_to_str(row[8])
|
||
)
|
||
for row in rows
|
||
]
|
||
|
||
|
||
@router.post("/api/v1/models", response_model=ModelResponse)
|
||
async def create_model(request: ModelCreate, authorization: Optional[str] = Header(None)):
|
||
"""
|
||
创建新模型
|
||
|
||
Args:
|
||
request: 模型创建请求
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
ModelResponse: 创建的模型信息
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
# 如果设置为默认,需要先取消其他默认模型
|
||
if request.is_default:
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("UPDATE agent_models SET is_default = FALSE WHERE is_default = TRUE")
|
||
await conn.commit()
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("""
|
||
INSERT INTO agent_models (name, provider, model, server, api_key, is_default)
|
||
VALUES (%s, %s, %s, %s, %s, %s)
|
||
RETURNING id, created_at, updated_at
|
||
""", (
|
||
request.name,
|
||
request.provider,
|
||
request.model,
|
||
request.server,
|
||
request.api_key,
|
||
request.is_default
|
||
))
|
||
row = await cursor.fetchone()
|
||
await conn.commit()
|
||
|
||
return ModelResponse(
|
||
id=str(row[0]),
|
||
name=request.name,
|
||
provider=request.provider,
|
||
model=request.model,
|
||
server=request.server,
|
||
api_key=mask_api_key(request.api_key),
|
||
is_default=request.is_default,
|
||
created_at=datetime_to_str(row[1]),
|
||
updated_at=datetime_to_str(row[2])
|
||
)
|
||
|
||
|
||
@router.put("/api/v1/models/{model_id}", response_model=ModelResponse)
|
||
async def update_model(
|
||
model_id: str,
|
||
request: ModelUpdate,
|
||
authorization: Optional[str] = Header(None)
|
||
):
|
||
"""
|
||
更新模型
|
||
|
||
Args:
|
||
model_id: 模型 ID
|
||
request: 模型更新请求
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
ModelResponse: 更新后的模型信息
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
# 构建更新字段
|
||
update_fields = []
|
||
values = []
|
||
|
||
if request.name is not None:
|
||
update_fields.append("name = %s")
|
||
values.append(request.name)
|
||
if request.provider is not None:
|
||
update_fields.append("provider = %s")
|
||
values.append(request.provider)
|
||
if request.model is not None:
|
||
update_fields.append("model = %s")
|
||
values.append(request.model)
|
||
if request.server is not None:
|
||
update_fields.append("server = %s")
|
||
values.append(request.server)
|
||
if request.api_key is not None:
|
||
update_fields.append("api_key = %s")
|
||
values.append(request.api_key)
|
||
if request.is_default is not None:
|
||
update_fields.append("is_default = %s")
|
||
values.append(request.is_default)
|
||
|
||
if not update_fields:
|
||
raise HTTPException(status_code=400, detail="No fields to update")
|
||
|
||
update_fields.append("updated_at = NOW()")
|
||
values.append(model_id)
|
||
|
||
# 如果设置为默认,需要先取消其他默认模型
|
||
if request.is_default is True:
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("UPDATE agent_models SET is_default = FALSE WHERE is_default = TRUE")
|
||
await conn.commit()
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute(f"""
|
||
UPDATE agent_models
|
||
SET {', '.join(update_fields)}
|
||
WHERE id = %s
|
||
RETURNING id, name, provider, model, server, api_key, is_default, created_at, updated_at
|
||
""", values)
|
||
row = await cursor.fetchone()
|
||
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="Model not found")
|
||
|
||
await conn.commit()
|
||
|
||
return ModelResponse(
|
||
id=str(row[0]),
|
||
name=row[1],
|
||
provider=row[2],
|
||
model=row[3],
|
||
server=row[4],
|
||
api_key=mask_api_key(row[5]),
|
||
is_default=row[6],
|
||
created_at=datetime_to_str(row[7]),
|
||
updated_at=datetime_to_str(row[8])
|
||
)
|
||
|
||
|
||
@router.delete("/api/v1/models/{model_id}", response_model=SuccessResponse)
|
||
async def delete_model(model_id: str, authorization: Optional[str] = Header(None)):
|
||
"""
|
||
删除模型
|
||
|
||
Args:
|
||
model_id: 模型 ID
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
SuccessResponse: 删除结果
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("DELETE FROM agent_models WHERE id = %s RETURNING id", (model_id,))
|
||
row = await cursor.fetchone()
|
||
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="Model not found")
|
||
|
||
await conn.commit()
|
||
|
||
return SuccessResponse(success=True, message="Model deleted successfully")
|
||
|
||
|
||
@router.patch("/api/v1/models/{model_id}/default", response_model=SuccessResponse)
|
||
async def set_default_model(model_id: str, authorization: Optional[str] = Header(None)):
|
||
"""
|
||
设置默认模型
|
||
|
||
Args:
|
||
model_id: 模型 ID
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
SuccessResponse: 设置结果
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
# 首先检查模型是否存在
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("SELECT id FROM agent_models WHERE id = %s", (model_id,))
|
||
row = await cursor.fetchone()
|
||
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="Model not found")
|
||
|
||
# 取消所有默认设置
|
||
await cursor.execute("UPDATE agent_models SET is_default = FALSE WHERE is_default = TRUE")
|
||
|
||
# 设置新的默认模型
|
||
await cursor.execute("UPDATE agent_models SET is_default = TRUE WHERE id = %s", (model_id,))
|
||
|
||
await conn.commit()
|
||
|
||
return SuccessResponse(success=True, message="Default model updated successfully")
|
||
|
||
|
||
# ============== Bot 管理 API ==============
|
||
|
||
@router.get("/api/v1/bots", response_model=List[BotResponse])
|
||
async def get_bots(authorization: Optional[str] = Header(None)):
|
||
"""
|
||
获取所有 Bot
|
||
|
||
Args:
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
List[BotResponse]: Bot 列表
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("""
|
||
SELECT id, name, bot_id, created_at, updated_at
|
||
FROM agent_bots
|
||
ORDER BY created_at DESC
|
||
""")
|
||
rows = await cursor.fetchall()
|
||
|
||
return [
|
||
BotResponse(
|
||
id=str(row[0]),
|
||
name=row[1],
|
||
bot_id=row[2],
|
||
created_at=datetime_to_str(row[3]),
|
||
updated_at=datetime_to_str(row[4])
|
||
)
|
||
for row in rows
|
||
]
|
||
|
||
|
||
@router.post("/api/v1/bots", response_model=BotResponse)
|
||
async def create_bot(request: BotCreate, authorization: Optional[str] = Header(None)):
|
||
"""
|
||
创建新 Bot
|
||
|
||
Args:
|
||
request: Bot 创建请求
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
BotResponse: 创建的 Bot 信息
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
# 自动生成 bot_id
|
||
bot_id = str(uuid.uuid4())
|
||
|
||
try:
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("""
|
||
INSERT INTO agent_bots (name, bot_id)
|
||
VALUES (%s, %s)
|
||
RETURNING id, created_at, updated_at
|
||
""", (request.name, bot_id))
|
||
row = await cursor.fetchone()
|
||
|
||
# 创建对应的设置记录
|
||
await cursor.execute("""
|
||
INSERT INTO agent_bot_settings (bot_id, language)
|
||
VALUES (%s, 'zh')
|
||
""", (str(row[0]),))
|
||
|
||
await conn.commit()
|
||
|
||
return BotResponse(
|
||
id=str(row[0]),
|
||
name=request.name,
|
||
bot_id=bot_id,
|
||
created_at=datetime_to_str(row[1]),
|
||
updated_at=datetime_to_str(row[2])
|
||
)
|
||
except Exception as e:
|
||
if "duplicate key" in str(e):
|
||
raise HTTPException(status_code=400, detail="Bot ID already exists")
|
||
raise
|
||
|
||
|
||
@router.put("/api/v1/bots/{bot_uuid}", response_model=BotResponse)
|
||
async def update_bot(
|
||
bot_uuid: str,
|
||
request: BotUpdate,
|
||
authorization: Optional[str] = Header(None)
|
||
):
|
||
"""
|
||
更新 Bot
|
||
|
||
Args:
|
||
bot_uuid: Bot 内部 UUID
|
||
request: Bot 更新请求
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
BotResponse: 更新后的 Bot 信息
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
# 构建更新字段
|
||
update_fields = []
|
||
values = []
|
||
|
||
if request.name is not None:
|
||
update_fields.append("name = %s")
|
||
values.append(request.name)
|
||
if request.bot_id is not None:
|
||
update_fields.append("bot_id = %s")
|
||
values.append(request.bot_id)
|
||
|
||
if not update_fields:
|
||
raise HTTPException(status_code=400, detail="No fields to update")
|
||
|
||
update_fields.append("updated_at = NOW()")
|
||
values.append(bot_uuid)
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute(f"""
|
||
UPDATE agent_bots
|
||
SET {', '.join(update_fields)}
|
||
WHERE id = %s
|
||
RETURNING id, name, bot_id, created_at, updated_at
|
||
""", values)
|
||
row = await cursor.fetchone()
|
||
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="Bot not found")
|
||
|
||
await conn.commit()
|
||
|
||
return BotResponse(
|
||
id=str(row[0]),
|
||
name=row[1],
|
||
bot_id=row[2],
|
||
created_at=datetime_to_str(row[3]),
|
||
updated_at=datetime_to_str(row[4])
|
||
)
|
||
|
||
|
||
@router.delete("/api/v1/bots/{bot_uuid}", response_model=SuccessResponse)
|
||
async def delete_bot(bot_uuid: str, authorization: Optional[str] = Header(None)):
|
||
"""
|
||
删除 Bot(级联删除相关设置、会话、MCP 配置等)
|
||
|
||
Args:
|
||
bot_uuid: Bot 内部 UUID
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
SuccessResponse: 删除结果
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("DELETE FROM agent_bots WHERE id = %s RETURNING id", (bot_uuid,))
|
||
row = await cursor.fetchone()
|
||
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="Bot not found")
|
||
|
||
await conn.commit()
|
||
|
||
return SuccessResponse(success=True, message="Bot deleted successfully")
|
||
|
||
|
||
# ============== Bot 设置 API ==============
|
||
|
||
@router.get("/api/v1/bots/{bot_uuid}/settings", response_model=BotSettingsResponse)
|
||
async def get_bot_settings(bot_uuid: str, authorization: Optional[str] = Header(None)):
|
||
"""
|
||
获取 Bot 设置
|
||
|
||
Args:
|
||
bot_uuid: Bot 内部 UUID
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
BotSettingsResponse: Bot 设置信息
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("""
|
||
SELECT bot_id, model_id,
|
||
language, robot_type, dataset_ids, system_prompt, user_identifier,
|
||
enable_memori, tool_response, skills, updated_at
|
||
FROM agent_bot_settings
|
||
WHERE bot_id = %s
|
||
""", (bot_uuid,))
|
||
row = await cursor.fetchone()
|
||
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="Bot settings not found")
|
||
|
||
# 获取关联的模型信息
|
||
model_info = None
|
||
model_id = row[1]
|
||
if model_id:
|
||
await cursor.execute("""
|
||
SELECT id, name, provider, model, server, api_key
|
||
FROM agent_models WHERE id = %s
|
||
""", (model_id,))
|
||
model_row = await cursor.fetchone()
|
||
if model_row:
|
||
model_info = ModelInfo(
|
||
id=str(model_row[0]),
|
||
name=model_row[1],
|
||
provider=model_row[2],
|
||
model=model_row[3],
|
||
server=model_row[4],
|
||
api_key=mask_api_key(model_row[5])
|
||
)
|
||
|
||
return BotSettingsResponse(
|
||
bot_id=str(row[0]),
|
||
model_id=str(model_id) if model_id else None,
|
||
model=model_info,
|
||
language=row[2] or 'zh',
|
||
robot_type=row[3],
|
||
dataset_ids=row[4],
|
||
system_prompt=row[5],
|
||
user_identifier=row[6],
|
||
enable_memori=row[7] or False,
|
||
tool_response=row[8] or False,
|
||
skills=row[9],
|
||
updated_at=datetime_to_str(row[10])
|
||
)
|
||
|
||
|
||
@router.put("/api/v1/bots/{bot_uuid}/settings", response_model=SuccessResponse)
|
||
async def update_bot_settings(
|
||
bot_uuid: str,
|
||
request: BotSettingsUpdate,
|
||
authorization: Optional[str] = Header(None)
|
||
):
|
||
"""
|
||
更新 Bot 设置
|
||
|
||
Args:
|
||
bot_uuid: Bot 内部 UUID
|
||
request: 设置更新请求
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
SuccessResponse: 更新结果
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
# 构建更新字段
|
||
update_fields = []
|
||
values = []
|
||
|
||
# 处理 model_id:将空字符串转换为 None
|
||
model_id_value = request.model_id.strip() if request.model_id else None
|
||
|
||
if request.model_id is not None:
|
||
# 验证 model_id 是否存在
|
||
if model_id_value:
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("SELECT id FROM agent_models WHERE id = %s", (model_id_value,))
|
||
if not await cursor.fetchone():
|
||
raise HTTPException(status_code=400, detail=f"Model with id '{request.model_id}' not found")
|
||
# 使用 NULL 或占位符
|
||
if model_id_value:
|
||
update_fields.append("model_id = %s")
|
||
values.append(model_id_value)
|
||
else:
|
||
update_fields.append("model_id = NULL")
|
||
if request.language is not None:
|
||
update_fields.append("language = %s")
|
||
values.append(request.language)
|
||
if request.robot_type is not None:
|
||
update_fields.append("robot_type = %s")
|
||
values.append(request.robot_type)
|
||
if request.dataset_ids is not None:
|
||
update_fields.append("dataset_ids = %s")
|
||
values.append(request.dataset_ids)
|
||
if request.system_prompt is not None:
|
||
update_fields.append("system_prompt = %s")
|
||
values.append(request.system_prompt)
|
||
if request.user_identifier is not None:
|
||
update_fields.append("user_identifier = %s")
|
||
values.append(request.user_identifier)
|
||
if request.enable_memori is not None:
|
||
update_fields.append("enable_memori = %s")
|
||
values.append(request.enable_memori)
|
||
if request.tool_response is not None:
|
||
update_fields.append("tool_response = %s")
|
||
values.append(request.tool_response)
|
||
if request.skills is not None:
|
||
update_fields.append("skills = %s")
|
||
values.append(request.skills)
|
||
|
||
if not update_fields:
|
||
raise HTTPException(status_code=400, detail="No fields to update")
|
||
|
||
update_fields.append("updated_at = NOW()")
|
||
values.append(bot_uuid)
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
# 检查设置是否存在
|
||
await cursor.execute("SELECT bot_id FROM agent_bot_settings WHERE bot_id = %s", (bot_uuid,))
|
||
if not await cursor.fetchone():
|
||
# 不存在则创建
|
||
# 需要分别处理带占位符和不带占位符的字段
|
||
insert_fields = []
|
||
insert_placeholders = []
|
||
insert_values = []
|
||
|
||
for field in update_fields[:-1]: # 去掉 updated_at
|
||
if "= NULL" in field:
|
||
# 不带占位符的 NULL 值
|
||
insert_fields.append(field.split(" = ")[0])
|
||
insert_placeholders.append("NULL")
|
||
elif "= %s" in field:
|
||
# 带占位符的字段
|
||
insert_fields.append(field.split(" = ")[0])
|
||
insert_placeholders.append("%s")
|
||
# 从 values 中取值(需要追踪索引)
|
||
# 这里简化处理:重新解析
|
||
|
||
# 重新构建:遍历原始请求字段
|
||
if model_id_value is not None:
|
||
insert_fields.append("model_id")
|
||
if model_id_value:
|
||
insert_placeholders.append("%s")
|
||
insert_values.append(model_id_value)
|
||
else:
|
||
insert_placeholders.append("NULL")
|
||
if request.language is not None:
|
||
insert_fields.append("language")
|
||
insert_placeholders.append("%s")
|
||
insert_values.append(request.language)
|
||
else:
|
||
insert_fields.append("language")
|
||
insert_placeholders.append("'zh'")
|
||
if request.robot_type is not None:
|
||
insert_fields.append("robot_type")
|
||
insert_placeholders.append("%s")
|
||
insert_values.append(request.robot_type)
|
||
if request.dataset_ids is not None:
|
||
insert_fields.append("dataset_ids")
|
||
insert_placeholders.append("%s")
|
||
insert_values.append(request.dataset_ids)
|
||
if request.system_prompt is not None:
|
||
insert_fields.append("system_prompt")
|
||
insert_placeholders.append("%s")
|
||
insert_values.append(request.system_prompt)
|
||
if request.user_identifier is not None:
|
||
insert_fields.append("user_identifier")
|
||
insert_placeholders.append("%s")
|
||
insert_values.append(request.user_identifier)
|
||
if request.enable_memori is not None:
|
||
insert_fields.append("enable_memori")
|
||
insert_placeholders.append("%s")
|
||
insert_values.append(request.enable_memori)
|
||
if request.tool_response is not None:
|
||
insert_fields.append("tool_response")
|
||
insert_placeholders.append("%s")
|
||
insert_values.append(request.tool_response)
|
||
if request.skills is not None:
|
||
insert_fields.append("skills")
|
||
insert_placeholders.append("%s")
|
||
insert_values.append(request.skills)
|
||
|
||
# 添加 bot_id
|
||
insert_fields.append("bot_id")
|
||
insert_placeholders.append("%s")
|
||
insert_values.append(bot_uuid)
|
||
|
||
# 构建 SQL:混合使用占位符和 NULL
|
||
values_clause = ", ".join(insert_placeholders)
|
||
sql = f"INSERT INTO agent_bot_settings ({', '.join(insert_fields)}) VALUES ({values_clause})"
|
||
|
||
await cursor.execute(sql, insert_values)
|
||
else:
|
||
# 存在则更新
|
||
await cursor.execute(f"""
|
||
UPDATE agent_bot_settings
|
||
SET {', '.join(update_fields)}
|
||
WHERE bot_id = %s
|
||
""", values)
|
||
|
||
await conn.commit()
|
||
|
||
return SuccessResponse(success=True, message="Bot settings updated successfully")
|
||
|
||
|
||
# ============== 会话管理 API ==============
|
||
|
||
@router.get("/api/v1/bots/{bot_uuid}/sessions", response_model=List[SessionResponse])
|
||
async def get_bot_sessions(bot_uuid: str, authorization: Optional[str] = Header(None)):
|
||
"""
|
||
获取 Bot 的会话列表
|
||
|
||
Args:
|
||
bot_uuid: Bot 内部 UUID
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
List[SessionResponse]: 会话列表
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("""
|
||
SELECT id, bot_id, title, created_at, updated_at
|
||
FROM agent_chat_sessions
|
||
WHERE bot_id = %s
|
||
ORDER BY updated_at DESC
|
||
""", (bot_uuid,))
|
||
rows = await cursor.fetchall()
|
||
|
||
return [
|
||
SessionResponse(
|
||
id=str(row[0]),
|
||
bot_id=str(row[1]),
|
||
title=row[2],
|
||
created_at=datetime_to_str(row[3]),
|
||
updated_at=datetime_to_str(row[4])
|
||
)
|
||
for row in rows
|
||
]
|
||
|
||
|
||
@router.post("/api/v1/bots/{bot_uuid}/sessions", response_model=SessionResponse)
|
||
async def create_session(
|
||
bot_uuid: str,
|
||
request: SessionCreate,
|
||
authorization: Optional[str] = Header(None)
|
||
):
|
||
"""
|
||
创建新会话
|
||
|
||
Args:
|
||
bot_uuid: Bot 内部 UUID
|
||
request: 会话创建请求
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
SessionResponse: 创建的会话信息
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
# 验证 Bot 是否存在
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("SELECT id FROM agent_bots WHERE id = %s", (bot_uuid,))
|
||
if not await cursor.fetchone():
|
||
raise HTTPException(status_code=404, detail="Bot not found")
|
||
|
||
# 创建会话
|
||
await cursor.execute("""
|
||
INSERT INTO agent_chat_sessions (bot_id, title)
|
||
VALUES (%s, %s)
|
||
RETURNING id, created_at, updated_at
|
||
""", (bot_uuid, request.title))
|
||
row = await cursor.fetchone()
|
||
|
||
await conn.commit()
|
||
|
||
return SessionResponse(
|
||
id=str(row[0]),
|
||
bot_id=bot_uuid,
|
||
title=request.title,
|
||
created_at=datetime_to_str(row[1]),
|
||
updated_at=datetime_to_str(row[2])
|
||
)
|
||
|
||
|
||
@router.delete("/api/v1/sessions/{session_id}", response_model=SuccessResponse)
|
||
async def delete_session(session_id: str, authorization: Optional[str] = Header(None)):
|
||
"""
|
||
删除会话
|
||
|
||
Args:
|
||
session_id: 会话 ID
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
SuccessResponse: 删除结果
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("DELETE FROM agent_chat_sessions WHERE id = %s RETURNING id", (session_id,))
|
||
row = await cursor.fetchone()
|
||
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="Session not found")
|
||
|
||
await conn.commit()
|
||
|
||
return SuccessResponse(success=True, message="Session deleted successfully")
|
||
|
||
|
||
# ============== MCP 服务器 API ==============
|
||
|
||
@router.get("/api/v1/bots/{bot_uuid}/mcp", response_model=List[MCPServerResponse])
|
||
async def get_mcp_servers(bot_uuid: str, authorization: Optional[str] = Header(None)):
|
||
"""
|
||
获取 Bot 的 MCP 服务器配置
|
||
|
||
Args:
|
||
bot_uuid: Bot 内部 UUID
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
List[MCPServerResponse]: MCP 服务器列表
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("""
|
||
SELECT id, bot_id, name, type, config, enabled, created_at, updated_at
|
||
FROM agent_mcp_servers
|
||
WHERE bot_id = %s
|
||
ORDER BY created_at DESC
|
||
""", (bot_uuid,))
|
||
rows = await cursor.fetchall()
|
||
|
||
return [
|
||
MCPServerResponse(
|
||
id=str(row[0]),
|
||
bot_id=str(row[1]),
|
||
name=row[2],
|
||
type=row[3],
|
||
config=row[4],
|
||
enabled=row[5],
|
||
created_at=datetime_to_str(row[6]),
|
||
updated_at=datetime_to_str(row[7])
|
||
)
|
||
for row in rows
|
||
]
|
||
|
||
|
||
@router.put("/api/v1/bots/{bot_uuid}/mcp", response_model=SuccessResponse)
|
||
async def update_mcp_servers(
|
||
bot_uuid: str,
|
||
servers: List[MCPServerCreate],
|
||
authorization: Optional[str] = Header(None)
|
||
):
|
||
"""
|
||
更新 Bot 的 MCP 服务器配置
|
||
|
||
Args:
|
||
bot_uuid: Bot 内部 UUID
|
||
servers: MCP 服务器列表
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
SuccessResponse: 更新结果
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
# 删除旧的 MCP 配置
|
||
await cursor.execute("DELETE FROM agent_mcp_servers WHERE bot_id = %s", (bot_uuid,))
|
||
|
||
# 插入新的 MCP 配置
|
||
for server in servers:
|
||
await cursor.execute("""
|
||
INSERT INTO agent_mcp_servers (bot_id, name, type, config, enabled)
|
||
VALUES (%s, %s, %s, %s, %s)
|
||
""", (
|
||
bot_uuid,
|
||
server.name,
|
||
server.type,
|
||
server.config,
|
||
server.enabled
|
||
))
|
||
|
||
await conn.commit()
|
||
|
||
return SuccessResponse(
|
||
success=True,
|
||
message=f"MCP servers updated successfully ({len(servers)} servers)"
|
||
)
|
||
|
||
|
||
@router.post("/api/v1/bots/{bot_uuid}/mcp", response_model=MCPServerResponse)
|
||
async def add_mcp_server(
|
||
bot_uuid: str,
|
||
request: MCPServerCreate,
|
||
authorization: Optional[str] = Header(None)
|
||
):
|
||
"""
|
||
添加单个 MCP 服务器
|
||
|
||
Args:
|
||
bot_uuid: Bot 内部 UUID
|
||
request: MCP 服务器创建请求
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
MCPServerResponse: 创建的 MCP 服务器信息
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
# 验证 Bot 是否存在
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("SELECT id FROM agent_bots WHERE id = %s", (bot_uuid,))
|
||
if not await cursor.fetchone():
|
||
raise HTTPException(status_code=404, detail="Bot not found")
|
||
|
||
# 创建 MCP 服务器
|
||
await cursor.execute("""
|
||
INSERT INTO agent_mcp_servers (bot_id, name, type, config, enabled)
|
||
VALUES (%s, %s, %s, %s, %s)
|
||
RETURNING id, created_at, updated_at
|
||
""", (
|
||
bot_uuid,
|
||
request.name,
|
||
request.type,
|
||
request.config,
|
||
request.enabled
|
||
))
|
||
row = await cursor.fetchone()
|
||
|
||
await conn.commit()
|
||
|
||
return MCPServerResponse(
|
||
id=str(row[0]),
|
||
bot_id=bot_uuid,
|
||
name=request.name,
|
||
type=request.type,
|
||
config=request.config,
|
||
enabled=request.enabled,
|
||
created_at=datetime_to_str(row[1]),
|
||
updated_at=datetime_to_str(row[2])
|
||
)
|
||
|
||
|
||
@router.delete("/api/v1/bots/{bot_uuid}/mcp/{mcp_id}", response_model=SuccessResponse)
|
||
async def delete_mcp_server(
|
||
bot_uuid: str,
|
||
mcp_id: str,
|
||
authorization: Optional[str] = Header(None)
|
||
):
|
||
"""
|
||
删除 MCP 服务器
|
||
|
||
Args:
|
||
bot_uuid: Bot 内部 UUID
|
||
mcp_id: MCP 服务器 ID
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
SuccessResponse: 删除结果
|
||
"""
|
||
verify_auth(authorization)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute(
|
||
"DELETE FROM agent_mcp_servers WHERE id = %s AND bot_id = %s RETURNING id",
|
||
(mcp_id, bot_uuid)
|
||
)
|
||
row = await cursor.fetchone()
|
||
|
||
if not row:
|
||
raise HTTPException(status_code=404, detail="MCP server not found")
|
||
|
||
await conn.commit()
|
||
|
||
return SuccessResponse(success=True, message="MCP server deleted successfully")
|
||
|
||
|
||
# ============== Admin 登录 API ==============
|
||
|
||
@router.post("/api/v1/admin/login", response_model=AdminLoginResponse)
|
||
async def admin_login(request: AdminLoginRequest):
|
||
"""
|
||
管理员登录
|
||
|
||
Args:
|
||
request: 登录请求(用户名和密码)
|
||
|
||
Returns:
|
||
AdminLoginResponse: 登录成功返回 token
|
||
"""
|
||
# 硬编码验证账号密码
|
||
if request.username != ADMIN_USERNAME or request.password != ADMIN_PASSWORD:
|
||
raise HTTPException(
|
||
status_code=401,
|
||
detail="用户名或密码错误"
|
||
)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
# 生成 token
|
||
token = secrets.token_urlsafe(32)
|
||
expires_at = datetime.now() + timedelta(hours=TOKEN_EXPIRE_HOURS)
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
# 清理该用户的旧 token
|
||
await cursor.execute("DELETE FROM agent_admin_tokens WHERE username = %s", (request.username,))
|
||
|
||
# 保存新 token
|
||
await cursor.execute("""
|
||
INSERT INTO agent_admin_tokens (username, token, expires_at)
|
||
VALUES (%s, %s, %s)
|
||
""", (request.username, token, expires_at))
|
||
|
||
await conn.commit()
|
||
|
||
return AdminLoginResponse(
|
||
token=token,
|
||
username=request.username,
|
||
expires_at=expires_at.isoformat()
|
||
)
|
||
|
||
|
||
@router.post("/api/v1/admin/verify", response_model=AdminVerifyResponse)
|
||
async def admin_verify(authorization: Optional[str] = Header(None)):
|
||
"""
|
||
验证管理员 token 是否有效
|
||
|
||
Args:
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
AdminVerifyResponse: 验证结果
|
||
"""
|
||
valid, username = await verify_admin_auth(authorization)
|
||
|
||
return AdminVerifyResponse(
|
||
valid=valid,
|
||
username=username
|
||
)
|
||
|
||
|
||
@router.post("/api/v1/admin/logout", response_model=SuccessResponse)
|
||
async def admin_logout(authorization: Optional[str] = Header(None)):
|
||
"""
|
||
管理员登出(删除 token)
|
||
|
||
Args:
|
||
authorization: Bearer token
|
||
|
||
Returns:
|
||
SuccessResponse: 登出结果
|
||
"""
|
||
provided_token = extract_api_key_from_auth(authorization)
|
||
if not provided_token:
|
||
raise HTTPException(
|
||
status_code=401,
|
||
detail="Authorization header is required"
|
||
)
|
||
|
||
pool = get_db_pool_manager().pool
|
||
|
||
async with pool.connection() as conn:
|
||
async with conn.cursor() as cursor:
|
||
await cursor.execute("DELETE FROM agent_admin_tokens WHERE token = %s", (provided_token,))
|
||
await conn.commit()
|
||
|
||
return SuccessResponse(success=True, message="Logged out successfully")
|