- **文件管理**: - 支持文件上传、解析和删除。 - 限制支持格式(txt, md, docx, pdf),最大 10MB。 - 提供前端上传区、文件列表及后端解析服务。 - **技能管理**: - 新增技能的创建、更新及删除功能。 - 支持分配自定义提示词和工具权限。 - 提供技能列表及分组工具选择交互界面。 同时调整相关 API 和前端组件,优化协作体验。
This commit is contained in:
130
backend/app/routers/files.py
Normal file
130
backend/app/routers/files.py
Normal file
@@ -0,0 +1,130 @@
|
||||
"""文件上传/管理路由"""
|
||||
|
||||
import uuid
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, HTTPException, UploadFile, File
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from ..database import engine, DATA_DIR
|
||||
from ..models import Novel, NovelFile
|
||||
from ..schemas import FileRead, FileList
|
||||
from ..services.file_parser import parse_file, ALLOWED_EXTENSIONS, MAX_FILE_SIZE
|
||||
|
||||
router = APIRouter(prefix="/api/novels/{novel_id}/files", tags=["files"])
|
||||
|
||||
# 上传文件存储根目录
|
||||
UPLOAD_DIR = DATA_DIR / "uploads"
|
||||
|
||||
|
||||
def _get_novel_upload_dir(novel_id: int) -> Path:
|
||||
"""获取小说的上传目录,不存在则创建"""
|
||||
d = UPLOAD_DIR / str(novel_id)
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
return d
|
||||
|
||||
|
||||
@router.post("", response_model=FileRead, status_code=201)
|
||||
async def upload_file(novel_id: int, file: UploadFile = File(...)):
|
||||
"""上传文件并解析为纯文本"""
|
||||
with Session(engine) as session:
|
||||
# 校验小说存在
|
||||
novel = session.get(Novel, novel_id)
|
||||
if not novel:
|
||||
raise HTTPException(status_code=404, detail="小说不存在")
|
||||
|
||||
# 校验扩展名
|
||||
if not file.filename:
|
||||
raise HTTPException(status_code=400, detail="缺少文件名")
|
||||
ext = Path(file.filename).suffix.lower()
|
||||
if ext not in ALLOWED_EXTENSIONS:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"不支持的文件格式 {ext},仅支持: {', '.join(sorted(ALLOWED_EXTENSIONS))}",
|
||||
)
|
||||
|
||||
# 读取文件内容
|
||||
content = await file.read()
|
||||
file_size = len(content)
|
||||
|
||||
if file_size > MAX_FILE_SIZE:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"文件过大({file_size / 1024 / 1024:.1f}MB),最大允许 10MB",
|
||||
)
|
||||
|
||||
# 生成存储名并保存原始文件
|
||||
stored_name = f"{uuid.uuid4().hex}{ext}"
|
||||
upload_dir = _get_novel_upload_dir(novel_id)
|
||||
file_path = upload_dir / stored_name
|
||||
file_path.write_bytes(content)
|
||||
|
||||
# 解析纯文本
|
||||
try:
|
||||
text_content = parse_file(file_path, file.content_type or "")
|
||||
except Exception as e:
|
||||
# 解析失败,清理已保存的文件
|
||||
file_path.unlink(missing_ok=True)
|
||||
raise HTTPException(status_code=400, detail=f"文件解析失败: {e}")
|
||||
|
||||
# 保存纯文本缓存
|
||||
text_path = upload_dir / f"{stored_name}.txt"
|
||||
text_path.write_text(text_content, encoding="utf-8")
|
||||
|
||||
# 写入数据库
|
||||
with Session(engine) as session:
|
||||
novel_file = NovelFile(
|
||||
novel_id=novel_id,
|
||||
filename=file.filename,
|
||||
stored_name=stored_name,
|
||||
file_size=file_size,
|
||||
mime_type=file.content_type or "",
|
||||
text_length=len(text_content),
|
||||
)
|
||||
session.add(novel_file)
|
||||
session.commit()
|
||||
session.refresh(novel_file)
|
||||
return novel_file
|
||||
|
||||
|
||||
@router.get("", response_model=FileList)
|
||||
def list_files(novel_id: int):
|
||||
"""列出小说关联的所有文件"""
|
||||
with Session(engine) as session:
|
||||
stmt = (
|
||||
select(NovelFile)
|
||||
.where(NovelFile.novel_id == novel_id)
|
||||
.order_by(NovelFile.created_at.desc())
|
||||
)
|
||||
files = session.exec(stmt).all()
|
||||
items = [
|
||||
FileRead(
|
||||
id=f.id, # type: ignore
|
||||
novel_id=f.novel_id,
|
||||
filename=f.filename,
|
||||
file_size=f.file_size,
|
||||
mime_type=f.mime_type,
|
||||
text_length=f.text_length,
|
||||
created_at=f.created_at,
|
||||
)
|
||||
for f in files
|
||||
]
|
||||
return FileList(items=items, total=len(items))
|
||||
|
||||
|
||||
@router.delete("/{file_id}", status_code=204)
|
||||
def delete_file(novel_id: int, file_id: int):
|
||||
"""删除文件(数据库记录 + 磁盘文件)"""
|
||||
with Session(engine) as session:
|
||||
novel_file = session.get(NovelFile, file_id)
|
||||
if not novel_file or novel_file.novel_id != novel_id:
|
||||
raise HTTPException(status_code=404, detail="文件不存在")
|
||||
|
||||
# 删除磁盘文件
|
||||
upload_dir = _get_novel_upload_dir(novel_id)
|
||||
(upload_dir / novel_file.stored_name).unlink(missing_ok=True)
|
||||
(upload_dir / f"{novel_file.stored_name}.txt").unlink(missing_ok=True)
|
||||
|
||||
# 删除数据库记录
|
||||
session.delete(novel_file)
|
||||
session.commit()
|
||||
Reference in New Issue
Block a user