"""文件路由:上传 / 列表 / 下载 / 删除。""" import hashlib import uuid from pathlib import Path from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile from fastapi.responses import FileResponse from sqlalchemy.orm import Session from ..config import settings from ..core.deps import get_current_user from ..core.response import ok from ..database import get_db from ..models import FileEntry, User router = APIRouter(prefix="/files", tags=["文件"]) def _file_out(f: FileEntry) -> dict: return {"id": f.id, "project_id": f.project_id, "filename": f.filename, "size": f.size, "mime": f.mime, "created_at": f.created_at.isoformat()} @router.post("/upload") async def upload_file( file: UploadFile = File(...), project_id: int | None = Form(None), user: User = Depends(get_current_user), db: Session = Depends(get_db), ): data = await file.read() if len(data) > settings.MAX_UPLOAD_MB * 1024 * 1024: raise HTTPException(status_code=413, detail=f"文件超过 {settings.MAX_UPLOAD_MB}MB 限制") stored_name = f"{uuid.uuid4().hex}_{file.filename}" target = settings.UPLOAD_DIR / stored_name target.write_bytes(data) entry = FileEntry( user_id=user.id, project_id=project_id, filename=file.filename, stored_path=str(target), size=len(data), mime=file.content_type or "", sha256=hashlib.sha256(data).hexdigest(), ) db.add(entry) db.commit() db.refresh(entry) return ok(_file_out(entry)) @router.get("") def list_files(user: User = Depends(get_current_user), db: Session = Depends(get_db)): files = db.query(FileEntry).filter(FileEntry.user_id == user.id).order_by(FileEntry.id.desc()).all() return ok([_file_out(f) for f in files]) def _get_owned(db: Session, user: User, file_id: int) -> FileEntry: f = db.get(FileEntry, file_id) if not f or f.user_id != user.id: raise HTTPException(status_code=404, detail="文件不存在") return f @router.get("/{file_id}/content") def file_content(file_id: int, user: User = Depends(get_current_user), db: Session = Depends(get_db)): """内联内容(图片预览等,鉴权)。""" f = _get_owned(db, user, file_id) path = Path(f.stored_path) if not path.exists(): raise HTTPException(status_code=404, detail="文件已丢失") return FileResponse(path, media_type=f.mime or "application/octet-stream") @router.get("/{file_id}/download") def download_file(file_id: int, user: User = Depends(get_current_user), db: Session = Depends(get_db)): f = _get_owned(db, user, file_id) path = Path(f.stored_path) if not path.exists(): raise HTTPException(status_code=404, detail="文件已丢失") return FileResponse(path, filename=f.filename, media_type=f.mime or "application/octet-stream") @router.delete("/{file_id}") def delete_file(file_id: int, user: User = Depends(get_current_user), db: Session = Depends(get_db)): f = _get_owned(db, user, file_id) path = Path(f.stored_path) if path.exists(): path.unlink() db.delete(f) db.commit() return ok(message="已删除")