diff --git a/server/db_manager.py b/server/db_manager.py
index 6b526f2c..af1350fd 100644
--- a/server/db_manager.py
+++ b/server/db_manager.py
@@ -1,12 +1,10 @@
import os
-import sqlite3
import pathlib
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
-from sqlalchemy.ext.declarative import declarative_base
-from server.models.token_model import Base, AgentToken
-from server.models.user_model import User, OperationLog
+from server.models import Base
+from server.models.user_model import User
class DBManager:
"""数据库管理器"""
diff --git a/server/main.py b/server/main.py
index 5f383fc6..bfcc41db 100644
--- a/server/main.py
+++ b/server/main.py
@@ -6,13 +6,12 @@ from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
from server.routers import router
-from server.utils.auth_middleware import get_current_user, is_public_path, is_admin_path
-from server.models.user_model import User
+from server.utils.auth_middleware import is_public_path
from src.utils.logging_config import logger
app = FastAPI()
-app.include_router(router)
+app.include_router(router, prefix="/api")
# CORS 设置
app.add_middleware(
@@ -33,85 +32,24 @@ class AuthMiddleware(BaseHTTPMiddleware):
if is_public_path(path):
return await call_next(request)
- # 注意:前端代理已经去掉了 /api 前缀,例如 /api/chat 变成了 /chat
- # 判断是否需要验证的API请求,包括聊天、数据、工具等
- is_api_path = (
- path.startswith("/chat") or
- path.startswith("/data") or
- path.startswith("/admin") or
- path.startswith("/auth") and not is_public_path(path)
- )
-
- if not is_api_path:
+ if not path.startswith("/api"):
# 非API路径,可能是前端路由或静态资源
return await call_next(request)
- # 提取Authorization头
- auth_header = request.headers.get("Authorization")
- if not auth_header or not auth_header.startswith("Bearer "):
- return JSONResponse(
- status_code=status.HTTP_401_UNAUTHORIZED,
- content={"detail": "请先登录"},
- headers={"WWW-Authenticate": "Bearer"}
- )
+ # # 提取Authorization头
+ # auth_header = request.headers.get("Authorization")
+ # if not auth_header or not auth_header.startswith("Bearer "):
+ # return JSONResponse(
+ # status_code=status.HTTP_401_UNAUTHORIZED,
+ # content={"detail": f"请先登录。Path: {path}"},
+ # headers={"WWW-Authenticate": "Bearer"}
+ # )
- # 获取token
- token = auth_header.split("Bearer ")[1]
+ # # 获取token
+ # token = auth_header.split("Bearer ")[1]
- # 添加token到请求状态,后续路由可以直接使用
- request.state.token = token
-
- # 检查是否需要管理员权限
- if is_admin_path(path):
- # 尝试获取数据库会话
- try:
- from server.db_manager import db_manager
- from server.utils.auth_utils import AuthUtils
-
- db = db_manager.get_session()
- try:
- # 验证token并获取用户信息
- payload = AuthUtils.verify_access_token(token)
- user_id = payload.get("sub")
-
- if not user_id:
- return JSONResponse(
- status_code=status.HTTP_401_UNAUTHORIZED,
- content={"detail": "无效的用户标识"},
- headers={"WWW-Authenticate": "Bearer"}
- )
-
- # 查询用户信息
- from server.models.user_model import User
- user = db.query(User).filter(User.id == user_id).first()
-
- if not user:
- return JSONResponse(
- status_code=status.HTTP_401_UNAUTHORIZED,
- content={"detail": "用户不存在"},
- headers={"WWW-Authenticate": "Bearer"}
- )
-
- # 检查管理员权限
- if user.role not in ["admin", "superadmin"]:
- return JSONResponse(
- status_code=status.HTTP_403_FORBIDDEN,
- content={"detail": "需要管理员权限"}
- )
-
- # 将用户信息添加到请求状态
- request.state.user = user
-
- finally:
- db.close()
-
- except Exception as e:
- logger.error(f"验证管理员权限出错: {e}")
- return JSONResponse(
- status_code=status.HTTP_401_UNAUTHORIZED,
- content={"detail": "验证用户权限出错"},
- headers={"WWW-Authenticate": "Bearer"}
- )
+ # # 添加token到请求状态,后续路由可以直接使用
+ # request.state.token = token
# 继续处理请求
return await call_next(request)
diff --git a/server/models/__init__.py b/server/models/__init__.py
new file mode 100644
index 00000000..7c2377ae
--- /dev/null
+++ b/server/models/__init__.py
@@ -0,0 +1,3 @@
+from sqlalchemy.ext.declarative import declarative_base
+
+Base = declarative_base()
\ No newline at end of file
diff --git a/server/models/token_model.py b/server/models/token_model.py
deleted file mode 100644
index 74ee1d59..00000000
--- a/server/models/token_model.py
+++ /dev/null
@@ -1,24 +0,0 @@
-from sqlalchemy import Column, Integer, String, DateTime, ForeignKey
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.sql import func
-
-Base = declarative_base()
-
-class AgentToken(Base):
- """智能体访问令牌模型"""
- __tablename__ = 'agent_tokens'
-
- id = Column(Integer, primary_key=True, autoincrement=True)
- agent_id = Column(String, nullable=False, index=True) # 智能体ID
- name = Column(String, nullable=False) # 令牌名称
- token = Column(String, nullable=False, unique=True) # 令牌值
- created_at = Column(DateTime, default=func.now()) # 创建时间
-
- def to_dict(self):
- return {
- "id": self.id,
- "agent_id": self.agent_id,
- "name": self.name,
- "token": self.token,
- "created_at": self.created_at.isoformat() if self.created_at else None
- }
\ No newline at end of file
diff --git a/server/models/user_model.py b/server/models/user_model.py
index fa0f4dfb..527d1f81 100644
--- a/server/models/user_model.py
+++ b/server/models/user_model.py
@@ -1,9 +1,8 @@
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Text
-from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
-from server.models.token_model import Base
+from server.models import Base
class User(Base):
"""用户模型"""
diff --git a/server/routers/__init__.py b/server/routers/__init__.py
index b11f9274..017bb2b5 100644
--- a/server/routers/__init__.py
+++ b/server/routers/__init__.py
@@ -2,12 +2,10 @@ from fastapi import APIRouter
from server.routers.chat_router import chat
from server.routers.data_router import data
from server.routers.base_router import base
-from server.routers.admin_router import admin
from server.routers.auth_router import auth
router = APIRouter()
router.include_router(base)
router.include_router(chat)
router.include_router(data)
-router.include_router(admin)
router.include_router(auth)
diff --git a/server/routers/admin_router.py b/server/routers/admin_router.py
deleted file mode 100644
index 1556e00e..00000000
--- a/server/routers/admin_router.py
+++ /dev/null
@@ -1,126 +0,0 @@
-import secrets
-import string
-from fastapi import APIRouter, Depends, HTTPException, Query, Request
-from pydantic import BaseModel
-from typing import List, Optional
-from sqlalchemy.orm import Session
-
-from server.db_manager import db_manager
-from server.models.token_model import AgentToken
-from server.models.user_model import User, OperationLog
-from server.utils.auth_middleware import get_db, get_current_user, get_admin_user, oauth2_scheme
-from server.routers.auth_router import log_operation
-
-admin = APIRouter(prefix="/admin", tags=["admin"])
-
-# 请求和响应模型
-class TokenCreate(BaseModel):
- agent_id: str
- name: str
-
-class TokenVerify(BaseModel):
- agent_id: str
- token: str
-
-class TokenResponse(BaseModel):
- id: int
- agent_id: str
- name: str
- token: str
- created_at: str
-
-# 生成随机token
-def generate_token(length=32):
- alphabet = string.ascii_letters + string.digits
- return ''.join(secrets.choice(alphabet) for _ in range(length))
-
-@admin.get("/tokens", response_model=List[TokenResponse])
-async def get_agent_tokens(
- agent_id: Optional[str] = Query(None),
- current_user: User = Depends(get_admin_user),
- db: Session = Depends(get_db)
-):
- """获取智能体的token列表(需要管理员权限)"""
- query = db.query(AgentToken)
- if agent_id:
- query = query.filter(AgentToken.agent_id == agent_id)
- tokens = query.all()
- return [token.to_dict() for token in tokens]
-
-@admin.post("/tokens", response_model=TokenResponse)
-async def create_token(
- token_data: TokenCreate,
- request: Request,
- current_user: User = Depends(get_admin_user),
- db: Session = Depends(get_db)
-):
- """创建新的token(需要管理员权限)"""
- # 生成随机token
- token_value = generate_token()
-
- # 创建token记录
- new_token = AgentToken(
- agent_id=token_data.agent_id,
- name=token_data.name,
- token=token_value
- )
-
- # 保存到数据库
- db.add(new_token)
- db.commit()
- db.refresh(new_token)
-
- # 记录操作
- log_operation(
- db,
- current_user.id,
- "创建令牌",
- f"为智能体 {token_data.agent_id} 创建访问令牌: {token_data.name}",
- request
- )
-
- return new_token.to_dict()
-
-@admin.delete("/tokens/{token_id}", response_model=dict)
-async def delete_token(
- token_id: int,
- request: Request,
- current_user: User = Depends(get_admin_user),
- db: Session = Depends(get_db)
-):
- """删除token(需要管理员权限)"""
- token = db.query(AgentToken).filter(AgentToken.id == token_id).first()
- if not token:
- raise HTTPException(status_code=404, detail="Token not found")
-
- # 记录操作信息
- log_operation(
- db,
- current_user.id,
- "删除令牌",
- f"删除令牌ID: {token_id}, 智能体: {token.agent_id}, 名称: {token.name}",
- request
- )
-
- db.delete(token)
- db.commit()
-
- return {"success": True, "message": "Token deleted"}
-
-@admin.post("/verify_token")
-async def verify_agent_token(
- token_data: TokenVerify,
- token: Optional[str] = Depends(oauth2_scheme),
- db: Session = Depends(get_db)
-):
- """验证智能体访问令牌(所有用户都可访问)"""
- # 查找令牌
- agent_token = db.query(AgentToken).filter(
- AgentToken.agent_id == token_data.agent_id,
- AgentToken.token == token_data.token
- ).first()
-
- if not agent_token:
- raise HTTPException(status_code=401, detail="Invalid token")
-
- return {"success": True, "message": "Token verified"}
\ No newline at end of file
diff --git a/server/routers/base_router.py b/server/routers/base_router.py
index 8ee6f328..632e4685 100644
--- a/server/routers/base_router.py
+++ b/server/routers/base_router.py
@@ -1,4 +1,4 @@
-from fastapi import Request, Body
+from fastapi import Request, Body, Depends
from fastapi import APIRouter
from fastapi import Request, Body
@@ -6,6 +6,8 @@ base = APIRouter()
from src import config, retriever, knowledge_base, graph_base
from src.utils import logger
+from server.utils.auth_middleware import get_admin_user, get_superadmin_user
+from server.models.user_model import User
@base.get("/")
@@ -13,27 +15,28 @@ async def route_index():
return {"message": "You Got It!"}
@base.get("/config")
-def get_config():
- return config.get_safe_config()
+def get_config(current_user: User = Depends(get_admin_user)):
+ return config.dump_config()
@base.post("/config")
-async def update_config(key = Body(...), value = Body(...)):
- if key == "custom_models":
- value = config.compare_custom_models(value)
-
+async def update_config(
+ key = Body(...),
+ value = Body(...),
+ current_user: User = Depends(get_admin_user)
+) -> dict:
config[key] = value
config.save()
- return config.get_safe_config()
+ return config.dump_config()
@base.post("/restart")
-async def restart():
+async def restart(current_user: User = Depends(get_superadmin_user)):
knowledge_base.restart()
graph_base.start()
retriever.restart()
return {"message": "Restarted!"}
@base.get("/log")
-def get_log():
+def get_log(current_user: User = Depends(get_admin_user)):
from src.utils.logging_config import LOG_FILE
from collections import deque
diff --git a/server/routers/tool_router.py b/server/routers/tool_router.py
deleted file mode 100644
index a587db7a..00000000
--- a/server/routers/tool_router.py
+++ /dev/null
@@ -1,70 +0,0 @@
-import os
-from fastapi import APIRouter, Body
-from pydantic import BaseModel
-from typing import List, Dict, Any, Optional
-
-from src.agents import agent_manager
-
-tool = APIRouter(prefix="/tool")
-
-
-class Tool(BaseModel):
- name: str
- title: str
- description: str
- url: str
- method: Optional[str] = "POST"
- params: Optional[Dict[str, Any]] = None
- metadata: Optional[Dict[str, Any]] = None
-
-@tool.get("/", response_model=List[Tool])
-async def route_index():
- tools = [
- Tool(
- name="text-chunking",
- title="文本分块",
- description="将文本分块以更好地理解。可以输入文本或者上传文件。",
- url="/tools/text-chunking",
- method="POST",
- ),
- Tool(
- name="pdf2txt",
- title="PDF转文本",
- description="将PDF文件转换为文本文件。",
- url="/tools/pdf2txt",
- method="POST",
- ),
- Tool(
- name="agent",
- title="智能体(Dev)",
- description="智能体演练平台,现在还处于开发预览状态,欢迎提 Issue,但先不要用于正式场景。",
- url="/tools/agent",
- )
- ]
-
- for agent in agent_manager.agents.values():
- tools.append(
- Tool(
- name=agent.name,
- title=agent.name,
- description=agent.description,
- url=f"/agent/{agent.name}",
- method="POST",
- metadata=agent.config_schema.to_dict(),
- )
- )
-
- return tools
-
-@tool.post("/text-chunking")
-async def text_chunking(text: str = Body(...), params: Dict[str, Any] = Body(...)):
- from src.core.indexing import chunk
- nodes = chunk(text, params=params)
- return {"nodes": [node.to_dict() for node in nodes]}
-
-@tool.post("/pdf2txt")
-async def handle_pdf2txt(file: str = Body(...)):
- from src.plugins import ocr
- text = ocr.process_pdf(file)
- return {"text": text}
-
diff --git a/server/utils/auth_middleware.py b/server/utils/auth_middleware.py
index f78aa1d2..6bc69d04 100644
--- a/server/utils/auth_middleware.py
+++ b/server/utils/auth_middleware.py
@@ -1,5 +1,5 @@
-from typing import Optional, List
-from fastapi import Depends, HTTPException, status
+from typing import Optional, List, Callable
+from fastapi import Depends, HTTPException, status, APIRouter
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.orm import Session
from jose import JWTError, jwt
@@ -14,21 +14,11 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/token", auto_error=Fals
# 公开路径列表,无需登录即可访问
PUBLIC_PATHS = [
- r"^/auth/token$", # 登录
- r"^/auth/check-first-run$", # 检查是否首次运行
- r"^/auth/initialize$", # 初始化系统
- r"^/docs$", r"^/redoc$", r"^/openapi.json$", # API文档
- r"^/static/.*$", # 静态资源
- r"^/assets/.*$", # 前端资源文件
- r"^/$", # 根路径(登录页)
- r"^/login$", # 登录页面
- r"^/home$", # 首页
- r"^/home/.*$", # 首页下的所有路径
- r"^/favicon\.ico$", # 网站图标
- r"^/_nuxt/.*$", # Nuxt.js生成的资源文件
- r"^/js/.*$", # JavaScript文件
- r"^/css/.*$", # CSS文件
- r"^/img/.*$" # 图片文件
+ r"^/api/auth/token$", # 登录
+ r"^/api/auth/check-first-run$", # 检查是否首次运行
+ r"^/api/auth/initialize$", # 初始化系统
+ r"^/api$", # Health Check
+ r"^/api/login$", # 登录页面
]
# 获取数据库会话
@@ -110,35 +100,3 @@ def is_public_path(path: str) -> bool:
if re.match(pattern, path):
return True
return False
-
-# 路径是否需要管理员权限
-ADMIN_PATHS = [
- r"^/admin/.*$", # 管理员接口
- r"^/data/.*$", # 数据操作接口,所有数据操作都需要管理员权限
- r"^/chat/set_default_agent$", # 设置默认智能体
- r"^/chat/models/update$", # 更新模型列表
- r"^/auth/users.*$", # 用户管理相关API
- r"^/config$" # 系统配置API
-]
-
-# 路径是否需要超级管理员权限
-SUPERADMIN_PATHS = [
- r"^/restart$", # 系统重启
- r"^/auth/users/\d+/role$" # 修改用户角色(仅超级管理员可操作)
-]
-
-# 检查路径是否需要管理员权限
-def is_admin_path(path: str) -> bool:
- path = path.rstrip('/') # 去除尾部斜杠以便于匹配
- for pattern in ADMIN_PATHS:
- if re.match(pattern, path):
- return True
- return False
-
-# 检查路径是否需要超级管理员权限
-def is_superadmin_path(path: str) -> bool:
- path = path.rstrip('/') # 去除尾部斜杠以便于匹配
- for pattern in SUPERADMIN_PATHS:
- if re.match(pattern, path):
- return True
- return False
\ No newline at end of file
diff --git a/src/config/__init__.py b/src/config/__init__.py
index 6ceaf02f..817425dc 100644
--- a/src/config/__init__.py
+++ b/src/config/__init__.py
@@ -212,18 +212,8 @@ class Config(SimpleConfig):
logger.info(f"Config file {self.filename} saved")
- def get_safe_config(self):
- """
- 获取安全的配置,即过滤掉 api_key
- """
-
- config = json.loads(str(self))
-
- # 过滤掉 api_key
- for model in config.get("custom_models", []):
- model["api_key"] = DEFAULT_MOCK_API if model.get("api_key") else ""
-
- return config
+ def dump_config(self):
+ return json.loads(str(self))
def compare_custom_models(self, value):
"""
diff --git a/web/src/apis/admin_api.js b/web/src/apis/admin_api.js
index 9e298524..8b48d896 100644
--- a/web/src/apis/admin_api.js
+++ b/web/src/apis/admin_api.js
@@ -70,40 +70,6 @@ export const userManagementApi = {
},
}
-// 令牌管理API
-export const tokenApi = {
- /**
- * 获取令牌列表
- * @param {string} agentId - 智能体ID
- * @returns {Promise} - 令牌列表
- */
- getTokens: async (agentId) => {
- checkAdminPermission()
- return apiGet(`/api/admin/tokens?agent_id=${agentId}`, {}, true)
- },
-
- /**
- * 创建新令牌
- * @param {string} agentId - 智能体ID
- * @param {string} name - 令牌名称
- * @returns {Promise} - 创建结果
- */
- createToken: async (agentId, name) => {
- checkAdminPermission()
- return apiPost('/api/admin/tokens', { agent_id: agentId, name }, {}, true)
- },
-
- /**
- * 删除令牌
- * @param {string} tokenId - 令牌ID
- * @returns {Promise} - 删除结果
- */
- deleteToken: async (tokenId) => {
- checkAdminPermission()
- return apiDelete(`/api/admin/tokens/${tokenId}`, {}, true)
- },
-}
-
// 知识库管理API
export const knowledgeBaseApi = {
/**
diff --git a/web/src/apis/base.js b/web/src/apis/base.js
index 5b403d8a..9e6a7eee 100644
--- a/web/src/apis/base.js
+++ b/web/src/apis/base.js
@@ -42,7 +42,7 @@ export async function apiRequest(url, options = {}, requiresAuth = false) {
// 尝试解析错误信息
let errorMessage = `请求失败: ${response.status}`
let errorData = null
-
+
try {
errorData = await response.json()
errorMessage = errorData.detail || errorData.message || errorMessage
@@ -56,15 +56,15 @@ export async function apiRequest(url, options = {}, requiresAuth = false) {
const userStore = useUserStore()
if (userStore.isLoggedIn) {
// 如果用户认为自己已登录,但收到401,则可能是令牌过期
- const isTokenExpired = errorData &&
- (errorData.detail?.includes('令牌已过期') ||
+ const isTokenExpired = errorData &&
+ (errorData.detail?.includes('令牌已过期') ||
errorData.detail?.includes('token expired') ||
errorMessage?.includes('令牌已过期') ||
errorMessage?.includes('token expired'))
-
+
message.error(isTokenExpired ? '登录已过期,请重新登录' : '认证失败,请重新登录')
userStore.logout()
-
+
// 使用setTimeout确保消息显示后再跳转
setTimeout(() => {
window.location.href = '/login'
diff --git a/web/src/components/TokenManagerComponent.vue b/web/src/components/TokenManagerComponent.vue
deleted file mode 100644
index c29b7d4e..00000000
--- a/web/src/components/TokenManagerComponent.vue
+++ /dev/null
@@ -1,232 +0,0 @@
-
- {{ token.token }}
-