# 日志系统 + 任务历史 + 文件管理 设计文档

> **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
   - 端到端验证：操作 → 日志记录 → 任务历史 → 文件历史
