# 日志系统 + 任务历史 + 文件管理 设计文档 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:writing-plans to create an implementation plan from this spec. **Goal:** 为益选 OCR Web 系统添加持久化日志、任务历史和增强文件管理,提升生产环境可观测性和用户体验。 **Architecture:** 单一 SQLite 数据库 (`data/web_data.db`) 存储三类数据,FastAPI 中间件自动采集 HTTP 日志,TaskManager 改造为写入 DB,前端新增两个独立页面。 **Tech Stack:** FastAPI middleware, SQLite (via existing DBPool), Vue 3 + Element Plus, Pinia --- ## 1. 数据库设计 数据库文件: `data/web_data.db`,通过现有 `DBPool` 管理。 ### 1.1 `http_logs` 表 ```sql CREATE TABLE IF NOT EXISTS http_logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp TEXT NOT NULL, -- ISO 8601 method TEXT NOT NULL, -- GET/POST/PUT/DELETE path TEXT NOT NULL, -- /api/memory status_code INTEGER, -- 200, 404, 500 duration_ms REAL, -- 请求耗时(ms) user TEXT, -- 当前用户名 ip TEXT, -- 客户端 IP detail TEXT -- 错误详情/备注 ); CREATE INDEX IF NOT EXISTS idx_http_logs_timestamp ON http_logs(timestamp); CREATE INDEX IF NOT EXISTS idx_http_logs_status ON http_logs(status_code); ``` ### 1.2 `task_history` 表 ```sql CREATE TABLE IF NOT EXISTS task_history ( id TEXT PRIMARY KEY, -- 8-char UUID name TEXT NOT NULL, -- pipeline/ocr-batch/excel/merge/sync-push/sync-pull status TEXT NOT NULL, -- pending/running/completed/failed progress INTEGER DEFAULT 0, message TEXT, result_files TEXT, -- JSON array of filenames error TEXT, log_lines TEXT, -- JSON array of log strings created_at TEXT NOT NULL, -- ISO 8601 updated_at TEXT NOT NULL, -- ISO 8601 completed_at TEXT -- ISO 8601, null if not done ); CREATE INDEX IF NOT EXISTS idx_task_history_status ON task_history(status); CREATE INDEX IF NOT EXISTS idx_task_history_created ON task_history(created_at); ``` ### 1.3 `file_metadata` 表 ```sql CREATE TABLE IF NOT EXISTS file_metadata ( id INTEGER PRIMARY KEY AUTOINCREMENT, filename TEXT NOT NULL, directory TEXT NOT NULL, -- input/output/result size INTEGER, action TEXT NOT NULL, -- upload/delete/clear user TEXT, timestamp TEXT NOT NULL, -- ISO 8601 task_id TEXT -- 关联的任务 ID (可选) ); CREATE INDEX IF NOT EXISTS idx_file_metadata_timestamp ON file_metadata(timestamp); ``` ### 1.4 自动清理 30 天过期清理,在服务器启动时执行,之后每天通过 `asyncio` 定时任务执行一次: ```python async def cleanup_old_records(): cutoff = (datetime.now() - timedelta(days=30)).isoformat() await db_pool.execute_write("DELETE FROM http_logs WHERE timestamp < ?", cutoff) await db_pool.execute_write("DELETE FROM task_history WHERE created_at < ?", cutoff) await db_pool.execute_write("DELETE FROM file_metadata WHERE timestamp < ?", cutoff) ``` --- ## 2. 后端架构 ### 2.1 新增文件 | 文件 | 职责 | |------|------| | `web/backend/services/db_schema.py` | 建表 SQL + `init_db()` + `cleanup_old_records()` | | `web/backend/middleware/logging.py` | HTTP 请求日志中间件 | | `web/backend/routers/logs.py` | 日志查询 API | | `web/backend/routers/tasks.py` | 任务历史 API | ### 2.2 修改文件 | 文件 | 改动 | |------|------| | `web/backend/main.py` | lifespan 中调用 `init_db()`,挂载 logging 中间件,注册 logs/tasks 路由 | | `web/backend/services/task_manager.py` | `update_progress()` 和 `_finish()` 写入 task_history 表 | | `web/backend/routers/files.py` | upload/delete/clear 操作写入 file_metadata 表 | ### 2.3 API 端点 **日志 (`/api/logs`)** - `GET /api/logs` — 分页查询 - 参数: `page`, `page_size`, `method`, `status_code`, `path`(搜索), `start_date`, `end_date` - 返回: `{ items: [...], total: number }` - `GET /api/logs/stats` — 统计概览 - 返回: `{ today_count, error_count, avg_duration_ms, error_rate }` **任务历史 (`/api/tasks`)** - `GET /api/tasks` — 分页查询 - 参数: `page`, `page_size`, `status`, `name`(类型筛选), `search` - 返回: `{ items: [...], total: number }` - `GET /api/tasks/{task_id}` — 任务详情(含完整 log_lines) - `POST /api/tasks/{task_id}/retry` — 重试失败任务 - 根据 `name` 字段重新调用对应处理端点 **文件历史 (`/api/files`)** - `GET /api/files/history` — 文件操作记录 - 参数: `page`, `page_size`, `directory`, `action` - 返回: `{ items: [...], total: number }` - `GET /api/files/stats` — 存储统计 - 返回: `{ directories: [{ name, file_count, total_size }] }` ### 2.4 中间件设计 ```python async def logging_middleware(request: Request, call_next): # 跳过静态资源和 WebSocket if request.url.path.startswith("/assets") or request.url.path.startswith("/ws"): return await call_next(request) start = time.time() response = await call_next(request) duration_ms = (time.time() - start) * 1000 # 异步写入日志(不阻塞响应) asyncio.create_task(write_log( method=request.method, path=request.url.path, status_code=response.status_code, duration_ms=duration_ms, user=get_current_user_from_request(request), ip=request.client.host, )) return response ``` ### 2.5 TaskManager 改造 现有 `TaskManager.update_progress()` 和 `_finish()` 方法中增加 DB 写入: ```python async def update_progress(self, task_id: str, progress: int, message: str): task = self._tasks[task_id] task.progress = progress task.message = message task.log_lines.append(message) # 新增:写入 DB await self._db.execute_write( "UPDATE task_history SET progress=?, message=?, log_lines=?, updated_at=? WHERE id=?", progress, message, json.dumps(task.log_lines), datetime.now().isoformat(), task_id ) await self._broadcast(task) ``` --- ## 3. 前端设计 ### 3.1 新增页面 **侧边栏导航新增 2 项:** | 页面 | 路由 | 图标 | 标签 | |------|------|------|------| | 任务历史 | `/tasks` | `Timer` | - | | 日志中心 | `/logs` | `Notebook` | - | ### 3.2 任务历史页面 (`Tasks.vue`) **布局:** - 顶部统计卡片行(4 卡片):总任务数 / 成功 / 失败 / 运行中 - 筛选栏:状态下拉(全部/成功/失败/运行中)+ 类型下拉(全部/pipeline/ocr/excel/merge)+ 搜索框 - 表格列:任务ID、类型、状态(彩色标签)、进度条、耗时、创建时间、操作 - 操作:查看详情(弹窗显示完整日志流)、重试(仅失败任务) **详情弹窗:** - 任务基本信息(类型/状态/耗时/结果文件) - 终端风格日志流(复用 Dashboard 的 log-box 样式) - 结果文件列表(可下载) ### 3.3 日志中心页面 (`Logs.vue`) **布局:** - 顶部统计卡片行(4 卡片):今日请求 / 错误数 / 平均耗时 / 错误率 - 筛选栏:时间范围选择器(今天/7天/30天)+ 方法筛选(GET/POST/PUT/DELETE)+ 状态码筛选(2xx/4xx/5xx)+ 路径搜索 - 表格列:时间、方法(彩色标签)、路径、状态码(颜色区分)、耗时、用户 - 点击行展开详情面板(IP 地址、错误信息) ### 3.4 Dashboard 增强 - stats-row 第三列从硬编码 "记忆库 5591" 改为动态存储统计(磁盘用量) - 文件列表区新增「操作历史」按钮,弹窗显示该目录的 file_metadata 记录 ### 3.5 新增文件 | 文件 | 职责 | |------|------| | `web/frontend/src/views/Tasks.vue` | 任务历史页面 | | `web/frontend/src/views/Logs.vue` | 日志中心页面 | | `web/frontend/src/stores/tasks.ts` | 任务历史状态管理(可选,可用 api 直接调用) | ### 3.6 修改文件 | 文件 | 改动 | |------|------| | `web/frontend/src/views/Layout.vue` | navItems 新增 2 项 | | `web/frontend/src/router/index.ts` | 新增 2 个路由 | | `web/frontend/src/views/Dashboard.vue` | stats-row 动态化 + 文件历史弹窗 | --- ## 4. 安全与性能 - 日志查询 API 仅限认证用户 - HTTP 日志不记录请求体(避免泄露敏感数据) - 中间件使用 `asyncio.create_task()` 异步写入,不阻塞响应 - 日志表索引:`timestamp`、`status_code`、`path` - 任务表索引:`status`、`created_at` - 自动清理 30 天前的记录,防止数据库无限增长 - 分页查询默认 page_size=50,最大 200 --- ## 5. 实施顺序 1. **Phase 1: 数据库 + 后端** - db_schema.py(建表 + 清理) - logging 中间件 - task_manager 改造 - files.py 改造 - logs.py + tasks.py 路由 2. **Phase 2: 前端页面** - Tasks.vue - Logs.vue - Layout.vue 路由注册 - Dashboard.vue 增强 3. **Phase 3: 集成测试** - npm run build - 端到端验证:操作 → 日志记录 → 任务历史 → 文件历史