Compare commits

...

2 Commits

Author SHA1 Message Date
朱潮
51f988e535 update sql表名 2026-01-30 00:03:26 +08:00
朱潮
f88aca74f2 add table prefix 2026-01-29 15:59:42 +08:00
3 changed files with 256 additions and 63 deletions

View File

@ -4,7 +4,9 @@ Bot Manager API 路由
""" """
import logging import logging
import uuid import uuid
from datetime import datetime import hashlib
import secrets
from datetime import datetime, timedelta
from typing import Optional, List from typing import Optional, List
from fastapi import APIRouter, HTTPException, Header from fastapi import APIRouter, HTTPException, Header
from pydantic import BaseModel from pydantic import BaseModel
@ -16,9 +18,47 @@ logger = logging.getLogger('app')
router = APIRouter() 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: def verify_auth(authorization: Optional[str]) -> None:
""" """
验证请求认证 验证请求认证
@ -39,6 +79,26 @@ def verify_auth(authorization: Optional[str]) -> None:
# ============== Pydantic Models ============== # ============== 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): class ModelCreate(BaseModel):
"""创建模型请求""" """创建模型请求"""
@ -77,7 +137,6 @@ class ModelResponse(BaseModel):
class BotCreate(BaseModel): class BotCreate(BaseModel):
"""创建 Bot 请求""" """创建 Bot 请求"""
name: str name: str
bot_id: str
class BotUpdate(BaseModel): class BotUpdate(BaseModel):
@ -196,9 +255,23 @@ async def init_bot_manager_tables():
# SQL 表创建语句 # SQL 表创建语句
tables_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 表 # models 表
""" """
CREATE TABLE IF NOT EXISTS models ( CREATE TABLE IF NOT EXISTS agent_models (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
provider VARCHAR(100) NOT NULL, provider VARCHAR(100) NOT NULL,
@ -211,11 +284,11 @@ async def init_bot_manager_tables():
) )
""", """,
# models 索引 # models 索引
"CREATE INDEX IF NOT EXISTS idx_models_is_default ON models(is_default)", "CREATE INDEX IF NOT EXISTS idx_agent_models_is_default ON agent_models(is_default)",
# bots 表 # bots 表
""" """
CREATE TABLE IF NOT EXISTS bots ( CREATE TABLE IF NOT EXISTS agent_bots (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
bot_id VARCHAR(255) NOT NULL UNIQUE, bot_id VARCHAR(255) NOT NULL UNIQUE,
@ -224,13 +297,13 @@ async def init_bot_manager_tables():
) )
""", """,
# bots 索引 # bots 索引
"CREATE INDEX IF NOT EXISTS idx_bots_bot_id ON bots(bot_id)", "CREATE INDEX IF NOT EXISTS idx_agent_bots_bot_id ON agent_bots(bot_id)",
# bot_settings 表 # bot_settings 表
""" """
CREATE TABLE IF NOT EXISTS bot_settings ( CREATE TABLE IF NOT EXISTS agent_bot_settings (
bot_id UUID PRIMARY KEY REFERENCES bots(id) ON DELETE CASCADE, bot_id UUID PRIMARY KEY REFERENCES agent_bots(id) ON DELETE CASCADE,
model_id UUID REFERENCES models(id) ON DELETE SET NULL, model_id UUID REFERENCES agent_models(id) ON DELETE SET NULL,
language VARCHAR(10) DEFAULT 'zh', language VARCHAR(10) DEFAULT 'zh',
robot_type VARCHAR(50), robot_type VARCHAR(50),
dataset_ids TEXT, dataset_ids TEXT,
@ -245,9 +318,9 @@ async def init_bot_manager_tables():
# mcp_servers 表 # mcp_servers 表
""" """
CREATE TABLE IF NOT EXISTS mcp_servers ( CREATE TABLE IF NOT EXISTS agent_mcp_servers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bot_id UUID REFERENCES bots(id) ON DELETE CASCADE, bot_id UUID REFERENCES agent_bots(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL, type VARCHAR(50) NOT NULL,
config JSONB NOT NULL, config JSONB NOT NULL,
@ -257,22 +330,22 @@ async def init_bot_manager_tables():
) )
""", """,
# mcp_servers 索引 # mcp_servers 索引
"CREATE INDEX IF NOT EXISTS idx_mcp_servers_bot_id ON mcp_servers(bot_id)", "CREATE INDEX IF NOT EXISTS idx_agent_mcp_servers_bot_id ON agent_mcp_servers(bot_id)",
"CREATE INDEX IF NOT EXISTS idx_mcp_servers_enabled ON mcp_servers(enabled)", "CREATE INDEX IF NOT EXISTS idx_agent_mcp_servers_enabled ON agent_mcp_servers(enabled)",
# chat_sessions 表 # chat_sessions 表
""" """
CREATE TABLE IF NOT EXISTS chat_sessions ( CREATE TABLE IF NOT EXISTS agent_chat_sessions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
bot_id UUID REFERENCES bots(id) ON DELETE CASCADE, bot_id UUID REFERENCES agent_bots(id) ON DELETE CASCADE,
title VARCHAR(500), title VARCHAR(500),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
) )
""", """,
# chat_sessions 索引 # chat_sessions 索引
"CREATE INDEX IF NOT EXISTS idx_chat_sessions_bot_id ON chat_sessions(bot_id)", "CREATE INDEX IF NOT EXISTS idx_agent_chat_sessions_bot_id ON agent_chat_sessions(bot_id)",
"CREATE INDEX IF NOT EXISTS idx_chat_sessions_created ON chat_sessions(created_at DESC)", "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 pool.connection() as conn:
@ -321,7 +394,7 @@ async def get_models(authorization: Optional[str] = Header(None)):
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute(""" await cursor.execute("""
SELECT id, name, provider, model, server, api_key, is_default, created_at, updated_at SELECT id, name, provider, model, server, api_key, is_default, created_at, updated_at
FROM models FROM agent_models
ORDER BY is_default DESC, created_at DESC ORDER BY is_default DESC, created_at DESC
""") """)
rows = await cursor.fetchall() rows = await cursor.fetchall()
@ -362,13 +435,13 @@ async def create_model(request: ModelCreate, authorization: Optional[str] = Head
if request.is_default: if request.is_default:
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute("UPDATE models SET is_default = FALSE WHERE is_default = TRUE") await cursor.execute("UPDATE agent_models SET is_default = FALSE WHERE is_default = TRUE")
await conn.commit() await conn.commit()
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute(""" await cursor.execute("""
INSERT INTO models (name, provider, model, server, api_key, is_default) INSERT INTO agent_models (name, provider, model, server, api_key, is_default)
VALUES (%s, %s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s, %s)
RETURNING id, created_at, updated_at RETURNING id, created_at, updated_at
""", ( """, (
@ -449,13 +522,13 @@ async def update_model(
if request.is_default is True: if request.is_default is True:
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute("UPDATE models SET is_default = FALSE WHERE is_default = TRUE") await cursor.execute("UPDATE agent_models SET is_default = FALSE WHERE is_default = TRUE")
await conn.commit() await conn.commit()
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute(f""" await cursor.execute(f"""
UPDATE models UPDATE agent_models
SET {', '.join(update_fields)} SET {', '.join(update_fields)}
WHERE id = %s WHERE id = %s
RETURNING id, name, provider, model, server, api_key, is_default, created_at, updated_at RETURNING id, name, provider, model, server, api_key, is_default, created_at, updated_at
@ -498,7 +571,7 @@ async def delete_model(model_id: str, authorization: Optional[str] = Header(None
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute("DELETE FROM models WHERE id = %s RETURNING id", (model_id,)) await cursor.execute("DELETE FROM agent_models WHERE id = %s RETURNING id", (model_id,))
row = await cursor.fetchone() row = await cursor.fetchone()
if not row: if not row:
@ -528,17 +601,17 @@ async def set_default_model(model_id: str, authorization: Optional[str] = Header
# 首先检查模型是否存在 # 首先检查模型是否存在
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute("SELECT id FROM models WHERE id = %s", (model_id,)) await cursor.execute("SELECT id FROM agent_models WHERE id = %s", (model_id,))
row = await cursor.fetchone() row = await cursor.fetchone()
if not row: if not row:
raise HTTPException(status_code=404, detail="Model not found") raise HTTPException(status_code=404, detail="Model not found")
# 取消所有默认设置 # 取消所有默认设置
await cursor.execute("UPDATE models SET is_default = FALSE WHERE is_default = TRUE") await cursor.execute("UPDATE agent_models SET is_default = FALSE WHERE is_default = TRUE")
# 设置新的默认模型 # 设置新的默认模型
await cursor.execute("UPDATE models SET is_default = TRUE WHERE id = %s", (model_id,)) await cursor.execute("UPDATE agent_models SET is_default = TRUE WHERE id = %s", (model_id,))
await conn.commit() await conn.commit()
@ -566,7 +639,7 @@ async def get_bots(authorization: Optional[str] = Header(None)):
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute(""" await cursor.execute("""
SELECT id, name, bot_id, created_at, updated_at SELECT id, name, bot_id, created_at, updated_at
FROM bots FROM agent_bots
ORDER BY created_at DESC ORDER BY created_at DESC
""") """)
rows = await cursor.fetchall() rows = await cursor.fetchall()
@ -599,19 +672,22 @@ async def create_bot(request: BotCreate, authorization: Optional[str] = Header(N
pool = get_db_pool_manager().pool pool = get_db_pool_manager().pool
# 自动生成 bot_id
bot_id = str(uuid.uuid4())
try: try:
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute(""" await cursor.execute("""
INSERT INTO bots (name, bot_id) INSERT INTO agent_bots (name, bot_id)
VALUES (%s, %s) VALUES (%s, %s)
RETURNING id, created_at, updated_at RETURNING id, created_at, updated_at
""", (request.name, request.bot_id)) """, (request.name, bot_id))
row = await cursor.fetchone() row = await cursor.fetchone()
# 创建对应的设置记录 # 创建对应的设置记录
await cursor.execute(""" await cursor.execute("""
INSERT INTO bot_settings (bot_id, language) INSERT INTO agent_bot_settings (bot_id, language)
VALUES (%s, 'zh') VALUES (%s, 'zh')
""", (str(row[0]),)) """, (str(row[0]),))
@ -620,7 +696,7 @@ async def create_bot(request: BotCreate, authorization: Optional[str] = Header(N
return BotResponse( return BotResponse(
id=str(row[0]), id=str(row[0]),
name=request.name, name=request.name,
bot_id=request.bot_id, bot_id=bot_id,
created_at=datetime_to_str(row[1]), created_at=datetime_to_str(row[1]),
updated_at=datetime_to_str(row[2]) updated_at=datetime_to_str(row[2])
) )
@ -671,7 +747,7 @@ async def update_bot(
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute(f""" await cursor.execute(f"""
UPDATE bots UPDATE agent_bots
SET {', '.join(update_fields)} SET {', '.join(update_fields)}
WHERE id = %s WHERE id = %s
RETURNING id, name, bot_id, created_at, updated_at RETURNING id, name, bot_id, created_at, updated_at
@ -710,7 +786,7 @@ async def delete_bot(bot_uuid: str, authorization: Optional[str] = Header(None))
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute("DELETE FROM bots WHERE id = %s RETURNING id", (bot_uuid,)) await cursor.execute("DELETE FROM agent_bots WHERE id = %s RETURNING id", (bot_uuid,))
row = await cursor.fetchone() row = await cursor.fetchone()
if not row: if not row:
@ -745,7 +821,7 @@ async def get_bot_settings(bot_uuid: str, authorization: Optional[str] = Header(
SELECT bot_id, model_id, SELECT bot_id, model_id,
language, robot_type, dataset_ids, system_prompt, user_identifier, language, robot_type, dataset_ids, system_prompt, user_identifier,
enable_memori, tool_response, skills, updated_at enable_memori, tool_response, skills, updated_at
FROM bot_settings FROM agent_bot_settings
WHERE bot_id = %s WHERE bot_id = %s
""", (bot_uuid,)) """, (bot_uuid,))
row = await cursor.fetchone() row = await cursor.fetchone()
@ -759,7 +835,7 @@ async def get_bot_settings(bot_uuid: str, authorization: Optional[str] = Header(
if model_id: if model_id:
await cursor.execute(""" await cursor.execute("""
SELECT id, name, provider, model, server, api_key SELECT id, name, provider, model, server, api_key
FROM models WHERE id = %s FROM agent_models WHERE id = %s
""", (model_id,)) """, (model_id,))
model_row = await cursor.fetchone() model_row = await cursor.fetchone()
if model_row: if model_row:
@ -821,7 +897,7 @@ async def update_bot_settings(
if model_id_value: if model_id_value:
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute("SELECT id FROM models WHERE id = %s", (model_id_value,)) await cursor.execute("SELECT id FROM agent_models WHERE id = %s", (model_id_value,))
if not await cursor.fetchone(): if not await cursor.fetchone():
raise HTTPException(status_code=400, detail=f"Model with id '{request.model_id}' not found") raise HTTPException(status_code=400, detail=f"Model with id '{request.model_id}' not found")
# 使用 NULL 或占位符 # 使用 NULL 或占位符
@ -864,7 +940,7 @@ async def update_bot_settings(
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
# 检查设置是否存在 # 检查设置是否存在
await cursor.execute("SELECT bot_id FROM bot_settings WHERE bot_id = %s", (bot_uuid,)) await cursor.execute("SELECT bot_id FROM agent_bot_settings WHERE bot_id = %s", (bot_uuid,))
if not await cursor.fetchone(): if not await cursor.fetchone():
# 不存在则创建 # 不存在则创建
# 需要分别处理带占位符和不带占位符的字段 # 需要分别处理带占位符和不带占位符的字段
@ -935,13 +1011,13 @@ async def update_bot_settings(
# 构建 SQL混合使用占位符和 NULL # 构建 SQL混合使用占位符和 NULL
values_clause = ", ".join(insert_placeholders) values_clause = ", ".join(insert_placeholders)
sql = f"INSERT INTO bot_settings ({', '.join(insert_fields)}) VALUES ({values_clause})" sql = f"INSERT INTO agent_bot_settings ({', '.join(insert_fields)}) VALUES ({values_clause})"
await cursor.execute(sql, insert_values) await cursor.execute(sql, insert_values)
else: else:
# 存在则更新 # 存在则更新
await cursor.execute(f""" await cursor.execute(f"""
UPDATE bot_settings UPDATE agent_bot_settings
SET {', '.join(update_fields)} SET {', '.join(update_fields)}
WHERE bot_id = %s WHERE bot_id = %s
""", values) """, values)
@ -973,7 +1049,7 @@ async def get_bot_sessions(bot_uuid: str, authorization: Optional[str] = Header(
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute(""" await cursor.execute("""
SELECT id, bot_id, title, created_at, updated_at SELECT id, bot_id, title, created_at, updated_at
FROM chat_sessions FROM agent_chat_sessions
WHERE bot_id = %s WHERE bot_id = %s
ORDER BY updated_at DESC ORDER BY updated_at DESC
""", (bot_uuid,)) """, (bot_uuid,))
@ -1015,13 +1091,13 @@ async def create_session(
# 验证 Bot 是否存在 # 验证 Bot 是否存在
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute("SELECT id FROM bots WHERE id = %s", (bot_uuid,)) await cursor.execute("SELECT id FROM agent_bots WHERE id = %s", (bot_uuid,))
if not await cursor.fetchone(): if not await cursor.fetchone():
raise HTTPException(status_code=404, detail="Bot not found") raise HTTPException(status_code=404, detail="Bot not found")
# 创建会话 # 创建会话
await cursor.execute(""" await cursor.execute("""
INSERT INTO chat_sessions (bot_id, title) INSERT INTO agent_chat_sessions (bot_id, title)
VALUES (%s, %s) VALUES (%s, %s)
RETURNING id, created_at, updated_at RETURNING id, created_at, updated_at
""", (bot_uuid, request.title)) """, (bot_uuid, request.title))
@ -1056,7 +1132,7 @@ async def delete_session(session_id: str, authorization: Optional[str] = Header(
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute("DELETE FROM chat_sessions WHERE id = %s RETURNING id", (session_id,)) await cursor.execute("DELETE FROM agent_chat_sessions WHERE id = %s RETURNING id", (session_id,))
row = await cursor.fetchone() row = await cursor.fetchone()
if not row: if not row:
@ -1089,7 +1165,7 @@ async def get_mcp_servers(bot_uuid: str, authorization: Optional[str] = Header(N
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute(""" await cursor.execute("""
SELECT id, bot_id, name, type, config, enabled, created_at, updated_at SELECT id, bot_id, name, type, config, enabled, created_at, updated_at
FROM mcp_servers FROM agent_mcp_servers
WHERE bot_id = %s WHERE bot_id = %s
ORDER BY created_at DESC ORDER BY created_at DESC
""", (bot_uuid,)) """, (bot_uuid,))
@ -1134,12 +1210,12 @@ async def update_mcp_servers(
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
# 删除旧的 MCP 配置 # 删除旧的 MCP 配置
await cursor.execute("DELETE FROM mcp_servers WHERE bot_id = %s", (bot_uuid,)) await cursor.execute("DELETE FROM agent_mcp_servers WHERE bot_id = %s", (bot_uuid,))
# 插入新的 MCP 配置 # 插入新的 MCP 配置
for server in servers: for server in servers:
await cursor.execute(""" await cursor.execute("""
INSERT INTO mcp_servers (bot_id, name, type, config, enabled) INSERT INTO agent_mcp_servers (bot_id, name, type, config, enabled)
VALUES (%s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s)
""", ( """, (
bot_uuid, bot_uuid,
@ -1181,13 +1257,13 @@ async def add_mcp_server(
# 验证 Bot 是否存在 # 验证 Bot 是否存在
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute("SELECT id FROM bots WHERE id = %s", (bot_uuid,)) await cursor.execute("SELECT id FROM agent_bots WHERE id = %s", (bot_uuid,))
if not await cursor.fetchone(): if not await cursor.fetchone():
raise HTTPException(status_code=404, detail="Bot not found") raise HTTPException(status_code=404, detail="Bot not found")
# 创建 MCP 服务器 # 创建 MCP 服务器
await cursor.execute(""" await cursor.execute("""
INSERT INTO mcp_servers (bot_id, name, type, config, enabled) INSERT INTO agent_mcp_servers (bot_id, name, type, config, enabled)
VALUES (%s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s)
RETURNING id, created_at, updated_at RETURNING id, created_at, updated_at
""", ( """, (
@ -1237,7 +1313,7 @@ async def delete_mcp_server(
async with pool.connection() as conn: async with pool.connection() as conn:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
await cursor.execute( await cursor.execute(
"DELETE FROM mcp_servers WHERE id = %s AND bot_id = %s RETURNING id", "DELETE FROM agent_mcp_servers WHERE id = %s AND bot_id = %s RETURNING id",
(mcp_id, bot_uuid) (mcp_id, bot_uuid)
) )
row = await cursor.fetchone() row = await cursor.fetchone()
@ -1248,3 +1324,96 @@ async def delete_mcp_server(
await conn.commit() await conn.commit()
return SuccessResponse(success=True, message="MCP server deleted successfully") 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")

View File

@ -164,36 +164,60 @@ async def list_files(path: str = "", recursive: bool = False):
async def upload_file(file: UploadFile = File(...), path: str = Form("")): async def upload_file(file: UploadFile = File(...), path: str = Form("")):
""" """
上传文件 上传文件
Args: Args:
file: 上传的文件 file: 上传的文件
path: 目标路径相对于支持目录 path: 目标路径相对于支持目录如果不存在会自动创建
""" """
try: try:
target_path = resolve_path(path) if path else resolve_path("projects") # 如果没有指定路径,默认使用 projects 目录
if not target_path.exists() or not target_path.is_dir(): if not path:
target_path = resolve_path("projects") target_path = resolve_path("projects")
else:
# 验证路径格式是否合法(必须以支持的目录开头)
path_parts = Path(path).parts
if not path_parts or path_parts[0] not in SUPPORTED_DIRECTORIES:
raise HTTPException(
status_code=400,
detail=f"路径必须以以下目录之一开头: {', '.join(SUPPORTED_DIRECTORIES)}"
)
# 直接构建路径,不检查是否存在(稍后会创建)
target_path = PROJECTS_DIR / path
# 如果目标路径已存在且是文件,则使用其父目录
if target_path.exists() and target_path.is_file():
target_path = target_path.parent
# 创建目标目录(包括所有不存在的父目录)
target_path.mkdir(parents=True, exist_ok=True) target_path.mkdir(parents=True, exist_ok=True)
file_path = target_path / file.filename file_path = target_path / file.filename
# 如果文件已存在,检查是否覆盖 # 如果文件已存在,检查是否覆盖
if file_path.exists(): if file_path.exists():
# 可以添加版本控制或重命名逻辑 # 可以添加版本控制或重命名逻辑
pass pass
with open(file_path, "wb") as buffer: with open(file_path, "wb") as buffer:
content = await file.read() content = await file.read()
buffer.write(content) buffer.write(content)
# 计算返回的相对路径
try:
relative_path = file_path.relative_to(PROJECTS_DIR)
except ValueError:
relative_path = file_path
return { return {
"success": True, "success": True,
"message": "文件上传成功", "message": "文件上传成功",
"filename": file.filename, "filename": file.filename,
"path": str(Path(path) / file.filename), "path": str(relative_path),
"size": len(content) "size": len(content)
} }
except HTTPException:
raise
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=f"文件上传失败: {str(e)}") raise HTTPException(status_code=500, detail=f"文件上传失败: {str(e)}")

View File

@ -465,7 +465,7 @@ async def fetch_bot_config_from_db(bot_user_id: str) -> Dict[str, Any]:
async with conn.cursor() as cursor: async with conn.cursor() as cursor:
# 首先根据 bot_user_id 查找 bot 的 UUID # 首先根据 bot_user_id 查找 bot 的 UUID
await cursor.execute( await cursor.execute(
"SELECT id, name FROM bots WHERE bot_id = %s", "SELECT id, name FROM agent_bots WHERE bot_id = %s",
(bot_user_id,) (bot_user_id,)
) )
bot_row = await cursor.fetchone() bot_row = await cursor.fetchone()
@ -484,7 +484,7 @@ async def fetch_bot_config_from_db(bot_user_id: str) -> Dict[str, Any]:
SELECT model_id, SELECT model_id,
language, robot_type, dataset_ids, system_prompt, user_identifier, language, robot_type, dataset_ids, system_prompt, user_identifier,
enable_memori, tool_response, skills enable_memori, tool_response, skills
FROM bot_settings WHERE bot_id = %s FROM agent_bot_settings WHERE bot_id = %s
""", """,
(bot_uuid,) (bot_uuid,)
) )
@ -521,7 +521,7 @@ async def fetch_bot_config_from_db(bot_user_id: str) -> Dict[str, Any]:
await cursor.execute( await cursor.execute(
""" """
SELECT model, server, api_key SELECT model, server, api_key
FROM models WHERE id = %s FROM agent_models WHERE id = %s
""", """,
(model_id,) (model_id,)
) )
@ -570,7 +570,7 @@ async def fetch_bot_config_from_db(bot_user_id: str) -> Dict[str, Any]:
await cursor.execute( await cursor.execute(
""" """
SELECT name, type, config, enabled SELECT name, type, config, enabled
FROM mcp_servers WHERE bot_id = %s AND enabled = true FROM agent_mcp_servers WHERE bot_id = %s AND enabled = true
""", """,
(bot_uuid,) (bot_uuid,)
) )