From c1826918aa0bcc15e3ca4a0cdf96a43ead3829a1 Mon Sep 17 00:00:00 2001 From: houhuan Date: Tue, 5 May 2026 11:19:14 +0800 Subject: [PATCH] docs: add design spec for logging, task history, and file management Co-Authored-By: Claude Opus 4.7 --- .../2026-05-05-logging-tasks-files-design.md | 256 ++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-05-logging-tasks-files-design.md diff --git a/docs/superpowers/specs/2026-05-05-logging-tasks-files-design.md b/docs/superpowers/specs/2026-05-05-logging-tasks-files-design.md new file mode 100644 index 0000000..476d512 --- /dev/null +++ b/docs/superpowers/specs/2026-05-05-logging-tasks-files-design.md @@ -0,0 +1,256 @@ +# 日志系统 + 任务历史 + 文件管理 设计文档 + +> **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 + - 端到端验证:操作 → 日志记录 → 任务历史 → 文件历史