diff --git a/app/api/routers/database.py b/app/api/routers/database.py index a3517de..d77f313 100644 --- a/app/api/routers/database.py +++ b/app/api/routers/database.py @@ -1,9 +1,12 @@ +from contextlib import suppress from datetime import datetime +from pathlib import Path from typing import Optional import pandas as pd from fastapi import APIRouter, Body, HTTPException from fastapi.responses import FileResponse +from starlette.background import BackgroundTask from app import state from app.config import CACHE_DIR @@ -13,6 +16,11 @@ from app.database import DatabaseManager router = APIRouter(tags=["database"]) +def _remove_file(path: Path) -> None: + with suppress(OSError): + path.unlink() + + @router.post("/api/database/test") async def test_database(): db = DatabaseManager(state.config) @@ -161,6 +169,9 @@ async def download_table( table_name: str = Body(..., embed=True), file_format: str = Body("csv", alias="format"), ): + if file_format not in {"csv", "xlsx"}: + raise HTTPException(status_code=400, detail="不支持的导出格式") + db = DatabaseManager(state.config) try: result = db.query_table(table_name, page=1, page_size=1000000) @@ -173,13 +184,22 @@ async def download_table( timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"{table_name}_{timestamp}.{file_format}" filepath = CACHE_DIR / filename + CACHE_DIR.mkdir(parents=True, exist_ok=True) - if file_format == "csv": - df.to_csv(filepath, index=False, encoding="utf-8-sig") - media_type = "text/csv" - else: - df.to_excel(filepath, index=False) - media_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" - - return FileResponse(path=str(filepath), filename=filename, media_type=media_type) + try: + if file_format == "csv": + df.to_csv(filepath, index=False, encoding="utf-8-sig") + media_type = "text/csv" + else: + df.to_excel(filepath, index=False) + media_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" + except Exception: + _remove_file(filepath) + raise + return FileResponse( + path=str(filepath), + filename=filename, + media_type=media_type, + background=BackgroundTask(_remove_file, filepath), + ) diff --git a/docs/project_context.md b/docs/project_context.md index 8138d4b..49a9874 100644 --- a/docs/project_context.md +++ b/docs/project_context.md @@ -1,5 +1,12 @@ # 项目上下文记录 +## 2026-05-19:优化数据表导出临时文件清理 + +- `app/api/routers/database.py` 的 `/api/download` 导出接口增加格式校验,只允许 `csv` 和 `xlsx`。 +- 导出文件仍临时写入 `cache/`,但 `FileResponse` 发送完成后会通过 `BackgroundTask` 自动删除;写入失败时也会清理半成品文件,避免导出残留占用服务器磁盘。 +- 已清理 `cache/` 中旧的导出缓存文件 2 个,仅保留处理历史目录和 `history.json`。 +- 已执行 `.venv\Scripts\python.exe -m compileall app`,编译检查通过。 + ## 2026-05-19:调整上传框操作按钮为换行显示 - `frontend/src/components/FileWorkflow.vue` 移除上传框操作区里无效的 `
`,避免在 flex 布局中形成异常间距。