fix: knowledge base name collision, orphan dirs, and document download

- Name uniqueness check scoped to user domain (system KBs no longer block user KBs)
- Delete KB now cleans up its directory with shutil.rmtree
- User KB uploads saved to knowledge_bases/{kb_name}/ subdirectory
- New /documents/{id}/download endpoint with auth-aware FileResponse
- Frontend uses fetch+token instead of direct window.open for downloads

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 11:30:23 +08:00
parent 5f768e5c83
commit 3250e87b30
2 changed files with 95 additions and 15 deletions
+68 -10
View File
@@ -3,10 +3,12 @@
"""
import logging
import os
import shutil
import uuid
from pathlib import Path
from typing import List, Optional, Dict, Any
from fastapi import APIRouter, Depends, HTTPException, status, UploadFile, File
from fastapi.responses import FileResponse
from sqlalchemy.orm import Session
from sqlalchemy import or_, and_
from pydantic import BaseModel
@@ -131,10 +133,20 @@ async def create_knowledge_base(
detail="用户不存在"
)
# 检查知识库名称是否已存在
existing_kb = db.query(KnowledgeBase).filter(
KnowledgeBase.name == data.name
).first()
# 管理员创建的知识库标记为系统知识库
is_system = user.is_superuser
# 检查知识库名称是否在同域内已存在(系统知识库与用户知识库互不冲突)
if is_system:
existing_kb = db.query(KnowledgeBase).filter(
KnowledgeBase.name == data.name,
KnowledgeBase.is_system == True
).first()
else:
existing_kb = db.query(KnowledgeBase).filter(
KnowledgeBase.name == data.name,
KnowledgeBase.user_id == user.id
).first()
if existing_kb:
raise HTTPException(
@@ -142,9 +154,6 @@ async def create_knowledge_base(
detail="知识库名称已存在"
)
# 管理员创建的知识库标记为系统知识库
is_system = user.is_superuser
# 创建知识库
knowledge_base = KnowledgeBase(
name=data.name,
@@ -403,7 +412,16 @@ async def delete_knowledge_base(
# 继续处理其他文档
logger.info(f"文档清理完成: 成功 {success_count} 个,失败 {error_count}")
# 清理知识库目录
if knowledge_base.is_system:
kb_dir = Path(settings.knowledge_base_dir) / knowledge_base.name
else:
kb_dir = Path(settings.upload_dir) / user.username / "knowledge_bases" / knowledge_base.name
if kb_dir.exists():
shutil.rmtree(kb_dir)
logger.info(f"已删除知识库目录: {kb_dir}")
# 3. 删除知识库(级联删除文档记录)
db.delete(knowledge_base)
db.commit()
@@ -498,8 +516,8 @@ async def upload_document_to_knowledge_base(
save_dir = Path(settings.knowledge_base_dir) / knowledge_base.name
source_type = "knowledge_base"
else:
# 用户知识库:保存到 uploads/{username}/
save_dir = Path(settings.upload_dir) / user.username
# 用户知识库:保存到 uploads/{username}/knowledge_bases/{kb_name}/
save_dir = Path(settings.upload_dir) / user.username / "knowledge_bases" / knowledge_base.name
source_type = "upload"
save_dir.mkdir(parents=True, exist_ok=True)
@@ -740,6 +758,46 @@ async def reindex_document(
)
@router.get("/documents/{document_id}/download")
async def download_document(
document_id: int,
current_user: str = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""下载/查看知识库文档"""
try:
user = db.query(User).filter(User.username == current_user).first()
if not user:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
document = db.query(Document).filter(Document.id == document_id).first()
if not document:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="文档不存在")
# 权限检查:系统知识库所有人可访问,用户知识库仅限所有者
if not document.knowledge_base:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="知识库不存在")
if not document.knowledge_base.is_system and document.knowledge_base.user_id != user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="无权访问此文档")
if not os.path.exists(document.file_path):
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="文件不存在")
return FileResponse(
document.file_path,
filename=document.original_filename,
media_type="application/octet-stream"
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"下载文档失败: {str(e)}"
)
@router.delete("/documents/{document_id}", response_model=ReindexResponse)
async def delete_knowledge_base_document(
document_id: int,