docs: add design spec for logging, task history, and file management
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
- 端到端验证:操作 → 日志记录 → 任务历史 → 文件历史
|
||||
Reference in New Issue
Block a user