# PRD：UI 自动化定时任务模块 + 系统配置-被测系统 URL

> **版本**：v1.0
> **创建日期**：2026-08-19
> **状态**：待实现
> **关联项目**：平台自动化测试可视化系统 (platform-auto-test)
> **关联文档**：`_PRD_需求文档_UI自动化定时任务模块与系统配置被测系统URL_计划执行.md`

---

## 一、需求背景

当前 UI 自动化用例只能**手动**发起执行（执行中心选择用例 → 执行）。当需要周期性回归测试（如每晚跑一遍登录/会议/审批等核心模块）时，必须人工到点点击，无法自动化、无人值守。同时，被测系统地址目前隐藏在「钉钉对接配置」卡片里，且仅在**保存时**写入内存（`settings.TARGET_URL`），**服务重启后回退到环境变量默认值**，配置不能真正持久生效。

本 PRD 提出两个目标：

1. **UI 自动化定时任务模块**：设置定时任务自动执行指定模块的用例，执行完成后自动生成 UI 自动化测试报告到报告中心，报告明确展示执行了哪些模块、哪些用例、耗时等。
2. **系统配置-被测系统设置**：在系统管理的系统配置界面提供独立的「被测系统」设置项，填写系统访问 URL，保存后 UI 自动化执行即使用该地址，且服务重启后依然生效。

---

## 二、现状分析与复用基础

| 现状 | 结论 |
|------|------|
| `Execution` 模型已含 `trigger_type`（manual/scheduled）+ `trigger_by`（触发人/任务名） | 定时任务产生的执行记录可直接打标 `trigger_type=scheduled`、`trigger_by=任务名`，无需改模型 |
| `ExecutionService.create_execution()` 已接受 `trigger_type` / `trigger_by` 参数 | 定时任务复用现成接口创建执行记录 |
| `ExecutionService.run_execution()` 已实现串行执行锁 + Playwright 线程池 + WebSocket 广播 | 定时任务直接复用执行引擎，天然串行、不冲突 |
| 报告中心已存在（`/reports/ui` + `/api/reports/generate|export|download|list`），HTML 报告已显示触发方式/耗时/通过率 | 定时任务完成后的报告自动进入报告中心，无需新建报告页面 |
| 报告 HTML **只列用例、不分组显示模块** | 需增强：报告按模块分组展示，明确"执行了哪些模块及用例" |
| 系统配置页（`Settings.vue`）钉钉卡片里已有 `target_url` 字段，保存时写入 `settings.TARGET_URL` | 需独立成「被测系统」卡片 + **启动时从 DB 加载**，保证重启后仍生效 |
| **无任何调度器**（`backend/app/tasks/` 为空，requirements 无 APScheduler） | 需自建后台调度循环（沿用 `message_consumer_loop` 的 asyncio 后台任务模式，避免引入新依赖 + 容器重装） |

---

## 三、需求描述

### 3.1 功能一：UI 自动化定时任务模块

#### 3.1.1 菜单入口

侧边栏「自动化测试」父菜单下新增子菜单 **「定时任务」**（位于「执行中心」与「报告中心」之间）：

| 菜单项 | 路由 |
|--------|------|
| 定时任务 | `/scheduled-tasks` |

#### 3.1.2 定时任务列表页

展示所有定时任务，表格列：

| 列 | 说明 |
|----|------|
| 任务名称 | 用户自定义 |
| 执行模块 | 该任务选择的模块名称列表（逗号分隔） |
| 周期 | 人类可读周期描述，如「每 30 分钟」「每天 22:00」「每周一 22:00」 |
| 状态 | 启用/停用 开关 |
| 上次执行 | 最近一次运行开始时间 |
| 下次执行 | 计算出的下次触发时间 |
| 累计执行 | 已成功触发的次数 |
| 操作 | 立即执行 / 编辑 / 删除 |

支持：刷新列表、启用/停用切换（Switch）、立即执行（手动触发一次）。

#### 3.1.3 创建 / 编辑定时任务（对话框）

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| 任务名称 | 文本 | 是 | 唯一标识，执行记录的 `trigger_by` 取该值 |
| 执行模块 | 多选（Checkbox 组/多选下拉） | 是 | 数据源 `/api/modules?case_type=ui`（UI 测试模块），每项展示模块名 + 用例数 |
| 周期模式 | 单选 | 是 | `interval`（按间隔）/ `daily`（每天定时）/ `weekly`（每周定时） |
| 间隔值 | 数字 | 模式=interval | 如 30 |
| 间隔单位 | 单选 | 模式=interval | `minutes` / `hours` / `days` |
| 执行时间 | 时间选择器 HH:MM | 模式=daily | 每天该时刻执行 |
| 星期 + 执行时间 | 多选星期 + 时间 | 模式=weekly | 每周选中日的该时刻执行 |
| 自动生成报告 | 开关 | 否 | 默认开启；执行完成后自动生成 HTML 报告到报告中心 |

**保存即计算 `next_run_at`**，创建/编辑后写入数据库。

#### 3.1.4 立即执行

点击「立即执行」→ 立即收集该任务选中模块的用例 → 创建执行记录并运行 → 完成后生成报告。与手动执行中心效果一致，执行记录标记 `trigger_type=scheduled`、`trigger_by=任务名`。

#### 3.1.5 后台调度引擎

- 应用启动（`lifespan`）时启动一个 asyncio 后台循环任务（沿用 `message_consumer_loop` 模式，`backend/app/services/scheduler_service.py`）；
- 每 **30 秒** 轮询一次数据库：找出 `enabled=True` 且 `next_run_at <= now`（或从未运行过）的任务；
- 对每个到期任务执行触发流程（见 3.1.6），触发后重算 `next_run_at` 并更新 `last_run_at` / `run_count`；
- 调度循环异常捕获并记日志，不中断整个循环。

#### 3.1.6 触发流程

```
到期任务 → 收集模块用例
   → 查询 TestCase where module_id ∈ 任务.module_ids AND status='active' AND case_type='ui'，按 module_id+order 排序
   → 若用例数为 0 → 记日志，重算下次执行，跳过本次
   → 若 is_execution_running() == True（已有手动/其他任务在执行）→ 记日志，重算下次执行，跳过本次（不创建失败记录）
   → create_execution(case_ids, name="{任务名}-{yyyyMMddHHmmss}", trigger_type="scheduled", trigger_by=任务名, case_type="ui")
   → run_execution(exec_id)   # 复用执行引擎，串行锁保证不并发
   → 执行完成后：若 auto_report=True → ReportService.generate_html() + save_report()（报告进入报告中心）
   → 更新任务 last_run_at / run_count / next_run_at
```

**执行冲突策略**：因 UI 用例必须串行执行（全局执行锁），到期时若已有执行在运行，**跳过本次**并在日志中记录「任务 X 已到点但存在运行中的执行，跳过」；下次周期自动再触发。不在前台创建一条注定失败的执行记录。

#### 3.1.7 运行历史

每个定时任务运行都会在报告中心产生一条执行记录（`trigger_type=scheduled`、`trigger_by=任务名`，可查看/下载报告）。定时任务页提供「运行记录」查看入口（抽屉/弹窗），列出该任务的历史执行：执行时间、状态、通过率、耗时。

#### 3.1.8 删除任务

删除定时任务仅删除任务定义，**不影响**已产生的执行记录与报告。

### 3.2 功能二：系统配置-被测系统设置

#### 3.2.1 配置界面

系统管理 → 系统配置（`/system/settings`）页面新增独立 **「被测系统」** 卡片：

- 字段：被测系统访问 URL（如 `https://192.168.5.44`）
- 校验：必填、须以 `http://` 或 `https://` 开头
- 保存按钮 + 重置按钮
- 原「钉钉对接配置」卡片移除 `target_url` 字段（避免两处维护同一概念），其余钉钉字段保持不变

#### 3.2.2 生效机制

| 时机 | 行为 |
|------|------|
| 保存时 | 持久化到数据库，并立即更新 `settings.TARGET_URL`（运行时全局生效） |
| 应用启动时 | `lifespan` 内从数据库读取被测系统 URL → 写入 `settings.TARGET_URL`（**修复重启回退问题**） |
| 执行用例时 | `ExecutionService.run_execution()` 已 `executor_config.setdefault("base_url", settings.TARGET_URL)`，执行器自动使用该地址（无需改执行引擎） |

#### 3.2.3 持久化方案

复用现有单行配置表 `dingtalk_configs`（id=1）的 `target_url` 字段作为唯一持久化存储（该字段已存在，**无需数据库迁移**）。新增独立 API 面 `GET/PUT /api/system/sut-config` 读写该字段，语义清晰、与钉钉配置解耦。

### 3.3 报告增强：明确"执行了哪些模块及用例，耗时"

为满足"报告要明确当前执行了哪些模块及用例，耗时等"，对 HTML 报告做以下增强（**同时惠及手动与定时执行**）：

1. **报告按模块分组**：`ReportService.generate_html` 关联 `TestCase.module_id → Module.name`，将用例结果按模块分组渲染，每个模块显示模块名 + 该模块通过/失败统计，组内列出各用例及其状态、耗时、错误；每个用例下保留步骤明细。
2. **报告头部已含**：执行时间、总耗时、触发方式（manual/scheduled）、触发人（任务名）——现有模板已支持，无需改动。
3. **报告中心列表**新增「触发方式」列：手动 / 定时，定时执行一眼可辨（`/api/reports/list` 响应补充 `trigger_type` / `trigger_by`）。

---

## 四、数据模型设计

### 4.1 新增表 `scheduled_tasks`

| 字段 | 类型 | 说明 |
|------|------|------|
| id | String(64) PK | `scheduled_` 前缀 UUID |
| name | String(100) | 任务名称（= 执行记录 `trigger_by`） |
| enabled | Boolean | 是否启用（默认 True） |
| module_ids | JSON(list) | 选中的模块 ID 列表 |
| schedule_type | String(20) | `interval` / `daily` / `weekly` |
| interval_value | Integer | 间隔值（模式=interval，默认 0） |
| interval_unit | String(10) | `minutes` / `hours` / `days` |
| run_time | String(5) | `HH:MM`（模式=daily/weekly） |
| weekdays | JSON(list) | [0-6]（模式=weekly） |
| auto_report | Boolean | 执行完成后自动生成报告（默认 True） |
| last_run_at | DateTime nullable | 最近一次触发时间 |
| next_run_at | DateTime nullable | 下次触发时间 |
| run_count | Integer | 累计触发次数（默认 0） |
| created_at | DateTime | 创建时间 |
| updated_at | DateTime | 更新时间 |

模型新增 `backend/app/models/scheduled_task.py`，并在 `database.py` 的 `_ensure_columns` 依赖导入中注册（保证 `create_all` 建表）。

### 4.2 不新增/不修改

- `executions`、`case_results`、`test_cases`、`modules`、`dingtalk_configs` 均无结构变更（`dingtalk_configs.target_url` 复用）。

---

## 五、接口设计（后端新增）

### 5.1 定时任务管理 `routers/scheduled_tasks.py`，前缀 `/api/scheduled-tasks`

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/scheduled-tasks` | 任务列表（含下次执行时间） |
| POST | `/api/scheduled-tasks` | 创建任务（服务端计算 `next_run_at`） |
| PUT | `/api/scheduled-tasks/{id}` | 更新任务 |
| DELETE | `/api/scheduled-tasks/{id}` | 删除任务（不影响已产生执行记录） |
| POST | `/api/scheduled-tasks/{id}/run` | 立即执行（后台异步创建执行 + 运行 + 生成报告） |
| POST | `/api/scheduled-tasks/{id}/toggle` | 启用/停用（body: `{enabled: bool}`） |
| GET | `/api/scheduled-tasks/{id}/runs` | 运行历史（`executions` where trigger_type=scheduled AND trigger_by=任务名） |

### 5.2 被测系统配置（新增到 `routers/system.py`）

| 方法 | 路径 | 说明 |
|------|------|------|
| GET | `/api/system/sut-config` | 读取被测系统 URL（DB 优先，回退环境变量） |
| PUT | `/api/system/sut-config` | 保存被测系统 URL（持久化 + 更新 `settings.TARGET_URL`） |

### 5.3 小改（兼容扩展）

| 文件 | 改动 |
|------|------|
| `routers/executions.py` 列表接口 | 增加可选 `trigger_type` / `trigger_by` 查询参数（供定时任务运行历史使用，向后兼容） |
| `routers/reports.py` 列表接口 | 响应补充 `trigger_type` / `trigger_by` 字段（供报告中心显示触发方式） |
| `services/report_service.py` | 报告按模块分组渲染（见 3.3） |

---

## 六、变更范围

| 层面 | 变更内容 | 涉及文件 |
|------|---------|---------|
| **后端模型** | 新增 `ScheduledTask` | `backend/app/models/scheduled_task.py`（新建）、`backend/app/database.py` |
| **后端服务** | 调度引擎 + 触发流程 | `backend/app/services/scheduler_service.py`（新建） |
| **后端路由** | 定时任务 CRUD + 立即执行 + 运行历史 | `backend/app/routers/scheduled_tasks.py`（新建）、`backend/app/main.py`（注册） |
| **后端路由** | 被测系统配置接口 | `backend/app/routers/system.py` |
| **后端小改** | 执行列表/报告列表参数补充、报告按模块分组 | `backend/app/routers/executions.py`、`backend/app/routers/reports.py`、`backend/app/services/report_service.py` |
| **后端启动** | lifespan 加载被测系统 URL + 启动调度循环 | `backend/app/main.py` |
| **前端 API** | 定时任务 + 被测系统 API 封装 | `frontend/src/api/scheduledTasks.ts`（新建）、`frontend/src/api/system.ts` |
| **前端页面** | 定时任务列表页 | `frontend/src/views/ScheduledTasks.vue`（新建） |
| **前端页面** | 系统配置新增「被测系统」卡片 | `frontend/src/views/system/Settings.vue` |
| **前端路由/菜单** | 新增 `/scheduled-tasks` 路由 + 侧边栏菜单项 | `frontend/src/router/index.ts`、`frontend/src/App.vue` |
| **前端报告中心** | 增加「触发方式」列 | `frontend/src/views/Reports.vue` |

---

## 七、验收标准

### 7.1 定时任务模块

1. ✅ 侧边栏「自动化测试」下显示「定时任务」子菜单，可进入列表页
2. ✅ 可创建定时任务：选择多个 UI 模块、设置间隔/定点周期、开启自动报告
3. ✅ 列表正确显示任务名、执行模块、周期、状态、上次/下次执行时间
4. ✅ 启用任务后到点自动触发执行（无需人工操作），执行记录 `trigger_type=scheduled`、`trigger_by=任务名`
5. ✅ 执行完成后自动生成 HTML 报告，出现在报告中心，报告按模块分组展示用例、状态、耗时
6. ✅ 立即执行按钮可手动触发一次，产生执行记录 + 报告
7. ✅ 执行进行中到点触发的任务自动跳过本次（不产生失败记录），下次周期恢复
8. ✅ 运行历史列出该任务的历史执行（时间/状态/通过率/耗时）
9. ✅ 停用任务后不再触发；删除任务不影响已产生的执行记录与报告
10. ✅ 服务重启后，启用的定时任务自动恢复调度（读取 DB 中的 `next_run_at`）

### 7.2 被测系统配置

1. ✅ 系统配置页有独立「被测系统」卡片，显示当前 URL
2. ✅ 保存后立即生效：随后发起的 UI 执行使用新 URL
3. ✅ 服务重启后仍使用保存的 URL（启动时从 DB 加载，不回退到环境变量默认值）
4. ✅ 钉钉配置卡片不再包含 target_url 字段（单一来源）

### 7.3 兼容性

- ✅ 手动执行功能不受影响（执行锁共用，串行互斥）
- ✅ 现有报告查看/下载功能正常
- ✅ 后端测试套件通过（`pytest`），前端构建通过（`npm run build`）

---

## 八、注意事项与风险

| 风险/注意 | 说明 | 缓解 |
|-----------|------|------|
| UI 用例必须串行 | 定时任务到点时若已有执行在跑，只能跳过 | 跳过策略 + 30s 轮询粒度，业务可接受；后续可支持"顺延执行" |
| 会议室占用 | 定时跑会议类模块，若某次执行异常未结束会议，房间可能被占用，影响下次执行 | 依赖执行器结束会议步骤 + 会议模块用例本身已具备零残留验收；定时任务建议与手动执行错峰 |
| 调度循环与 Playwright 线程 | 调度器只负责"创建 + 等待"，真正跑 Playwright 的仍是执行器专用线程 | 复用 `run_execution`，不新增并发路径 |
| 无用户权限系统 | 定时任务创建/启停无鉴权 | 与现有平台一致，仅内网使用 |
| 时区 | `next_run_at` 用服务器本地时间计算与比较 | 与现有 `datetime.now()` 用法一致，部署服务器为国内时区 |

---

## 九、后续规划（本次不实现）

- 定时任务失败重试/告警（钉钉通知）
- cron 表达式高级周期
- 任务级超时控制与强制终止

---

*本文档由 Claude Code 生成，遵循项目 PRD 文档规范。*
