feat: implement full-stack TaskPilot scheduler

This commit is contained in:
2026-10-18 22:05:45 +08:00
parent d88c21753d
commit 8ce02af13a
72 changed files with 17812 additions and 0 deletions
+464
View File
@@ -0,0 +1,464 @@
# TaskPilot 开发计划
> 更新时间:2026-10-18
> 目标后端:Rust
> 目标前端:React
> 首要平台:Windows
> 当前阶段:Phase 6 已完成,项目进入维护与迭代
## 1. 项目目标
将现有的单文件 HTML 原型升级为一个真实可用的本地定时任务管理平台,满足以下核心场景:
- 创建、编辑、启用、停用和删除定时任务。
- 使用 Cron 表达式按计划自动执行任务,也可以手动立即执行。
- 支持脚本、HTTP 请求和 Windows 二进制进程三类任务。
- 实时展示任务状态、下次执行时间和每次运行结果。
- 查看真实 stdout、stderr、退出码和耗时。
- 任务配置和运行历史在重启后仍然保留。
- 超时或取消时可靠终止目标进程及其子进程。
- React 前端成为真实 API 的使用者,不再用静态数据或 `alert` 模拟成功。
### 首版范围
- Windows 单机部署。
- 单用户、本地运行,默认只监听 `127.0.0.1`。
- 单后端实例。
- SQLite 持久化。
- 同一任务默认不并发执行,重叠触发时跳过并记录原因。
- 日志按运行实例保存,并配置容量和保留期限。
### 暂不纳入首版
- 多租户、登录和远程用户管理。
- 多机分布式调度。
- DAG、任务依赖和分布式队列。
- 集群高可用。
- 跨平台进程树管理。
- 移动端专用应用。
这些能力在单机版本稳定后再评估,避免首版范围过大。
## 2. 当前基线
仓库目前只有 `task_pilot.html` 和文档,没有前后端工程。现有页面具备完整视觉原型,但运行、删除、保存和日志均为演示行为。
已确认的本地工具链:
| 工具 | 版本 | 来源 |
| --- | --- | --- |
| Volta | 2.0.2 | 本地安装 |
| Node.js | 24.12.0 | Volta |
| npm | 11.6.2 | Volta |
| pnpm | 11.20.0 | Volta |
| Rust | 1.95.0 | `stable-x86_64-pc-windows-msvc` |
| Cargo | 1.95.0 | 本地 Rust 工具链 |
开发时使用 pnpm,并通过 `package.json` 的 `volta` 字段固定 Node.js 和 pnpm 版本。Rust 使用 `rust-toolchain.toml` 固定 MSVC 工具链。
## 3. 技术方案
### 3.1 前端
- React + TypeScript + Vite。
- pnpm 作为包管理器,Volta 固定工具版本。
- React Router 管理任务列表和任务详情路由。
- TanStack Query 管理服务端状态、缓存、刷新和错误状态。
- React Hook Form + Zod 管理表单和前端校验。
- 原生 `fetch` 封装类型化 API 客户端。
- 沿用现有 CSS 变量和视觉语言,组件样式使用 CSS Modules,不先引入大型 UI 框架。
- Vitest + React Testing Library 进行单元和组件测试。
- Playwright 进行关键业务流程端到端测试。
### 3.2 后端
- Rust + Axum + Tokio。
- Serde 定义 API 和持久化 DTO。
- SQLx + SQLite 负责迁移、查询和事务。
- `tracing` + `tracing-subscriber` 输出结构化运行日志。
- `reqwest` 执行 HTTP 任务。
- Windows Job Object 管理子进程树,实现超时和取消时的可靠终止。
- SSE 或轮询先用于运行状态与日志更新,后续再评估 WebSocket。
- Cron 引擎在 Phase 1 通过小型 POC 选定,必须支持动态增删任务、秒级 Cron、时区和下次执行时间计算。
初步候选为 `tokio-cron-scheduler`。进入正式实现前需要验证其维护状态、Cron 语义和动态任务能力;如果不能满足要求,则改用 Cron 解析 crate 与自建 Tokio 调度循环。
### 3.3 接口契约
- 后端 DTO 是 API 数据的唯一来源。
- 使用 OpenAPI 生成接口描述,并提交稳定快照供前端生成 TypeScript 类型。
- 前端不得手写与后端重复的 DTO 类型。
- 接口变更时,必须同时更新 OpenAPI 快照、生成类型和契约测试。
### 3.4 总体结构
```text
React SPA
|
| REST /api/v1 + SSE
v
Axum HTTP API
|
+--> Task Service --> SQLite
|
+--> Scheduler
|
+--> Script Executor
+--> HTTP Executor
+--> Process Executor
|
+--> Windows Job Object
+--> Log Capture
```
计划目录结构:
```text
task_pilot/
├── backend/
│ ├── Cargo.toml
│ ├── migrations/
│ ├── src/
│ └── tests/
├── frontend/
│ ├── package.json
│ ├── src/
│ └── tests/
├── contracts/
│ └── openapi.json
├── docs/
│ └── decisions/
├── task_pilot.html # 迁移完成前保留作视觉参考
├── PLAN.md
├── README.md
└── rust-toolchain.toml
```
## 4. 核心数据模型
### 4.1 Task
```text
Task
├── id 用户可读的唯一任务标识
├── name 显示名称
├── cron 5 位或 6 位 Cron 表达式
├── timezone 默认使用系统时区,可选择 IANA 时区
├── enabled 是否参与自动调度
├── timeout_secs 单次执行超时
├── concurrency_policy 首版固定为 skip
├── retry_policy 首版允许 0 次重试,结构上预留
├── action 带 type 标签的任务动作
├── created_at
└── updated_at
```
### 4.2 Action
```text
Action
├── script
│ ├── working_dir
│ ├── interpreter
│ ├── script
│ ├── args[]
│ └── env{}
├── http
│ ├── url
│ ├── method
│ ├── headers{}
│ ├── body
│ └── expected_status[]
└── process
├── executable
├── working_dir
├── args[]
└── env{}
```
- 参数必须使用结构化数组,禁止默认经过 Shell 拼接。
- 环境变量需要区分普通值和敏感值;敏感值返回前端时使用遮罩。
- 路径、URL、超时、Cron 和任务类型必须执行后端校验。
### 4.3 TaskRun
```text
TaskRun
├── id
├── task_id
├── trigger schedule / manual / retry
├── status queued / running / succeeded / failed / timed_out / cancelled / skipped
├── started_at
├── finished_at
├── duration_ms
├── exit_code
├── error_message
└── logs
```
## 5. API 草案
所有业务接口使用 `/api/v1` 前缀。
| 方法 | 路径 | 用途 |
| --- | --- | --- |
| `GET` | `/api/v1/health` | 健康检查 |
| `GET` | `/api/v1/tasks` | 查询任务,支持搜索、筛选和分页 |
| `POST` | `/api/v1/tasks` | 创建任务 |
| `GET` | `/api/v1/tasks/{id}` | 查询任务详情 |
| `PUT` | `/api/v1/tasks/{id}` | 更新任务 |
| `DELETE` | `/api/v1/tasks/{id}` | 删除任务 |
| `POST` | `/api/v1/tasks/{id}/enable` | 启用任务 |
| `POST` | `/api/v1/tasks/{id}/disable` | 停用任务 |
| `POST` | `/api/v1/tasks/{id}/run` | 手动执行 |
| `GET` | `/api/v1/tasks/{id}/runs` | 查询运行历史 |
| `GET` | `/api/v1/runs/{run_id}` | 查询单次运行结果 |
| `GET` | `/api/v1/runs/{run_id}/logs` | 分页或增量读取日志 |
| `GET` | `/api/v1/events` | SSE 状态与日志事件 |
统一错误结构至少包含:
```json
{
"code": "TASK_CRON_INVALID",
"message": "Cron expression is invalid",
"field": "cron",
"details": {}
}
```
## 6. 里程碑
### Phase 0:工程基线
状态:已完成
状态:进行中
- [x] 盘点现有 HTML 原型和功能。
- [x] 生成项目 README。
- [x] 确认 Volta、Node.js、pnpm 和 Rust 工具链。
- [x] 制定本开发计划。
- [x] 为 Cron 引擎和静态资源托管方式编写 ADR。
验收:计划中的技术边界明确,后续任务有清晰顺序。
### Phase 1:工程脚手架与契约
状态:已完成(2026-10-18)
- [x] 创建 `frontend/` React + TypeScript + Vite 工程。
- [x] 通过 Volta 固定 Node.js 24.12.0 和 pnpm 11.20.0。
- [x] 创建 `backend/` Axum 工程并固定 Rust 1.95.0。
- [x] 配置 Vite 开发代理到 Rust API。
- [x] 提供 `/api/v1/health`。
- [x] 配置 Cargo fmt、clippy、test,以及前端 lint、typecheck、test、build。
- [x] 添加 OpenAPI 生成与前端类型生成脚本。
- [x] 确定 Cron 引擎依赖并完成 POC。
- [x] 保留旧 HTML,作为迁移期间的视觉参考。
验收:
- `pnpm install`、`pnpm build`、`cargo test` 和 `cargo clippy` 可执行。
- 前端开发服务器能够通过代理访问 Rust 健康检查。
- OpenAPI 快照可以重复生成,前后端对接口类型没有手工漂移。
### Phase 2:任务 CRUD 与持久化
状态:已完成(2026-10-18)
- [x] 创建 SQLite 迁移和任务表。
- [x] 实现领域模型、仓储层和服务层。
- [x] 实现任务列表、详情、创建、更新、删除接口。
- [x] 实现 Cron、时区、超时、路径和 Action 类型校验。
- [x] 实现统一 API 错误格式。
- [x] 增加任务列表搜索、筛选和分页参数。
- [x] 为 CRUD、校验和持久化编写后端测试。
说明:独立启停接口调整到 Phase 3,与调度器注册和取消同时实现,避免产生只改数据库但不更新调度的中间状态。
验收:
- 创建三类任务后重启进程,数据仍然存在。
- 非法 Cron、空路径、无效 URL 和错误 Action 字段被后端拒绝。
- 删除不存在的任务返回明确错误,而不是成功提示。
### Phase 3:调度器与脚本任务闭环
状态:已完成(2026-10-18)
- [x] 启动时从 SQLite 加载启用任务并注册调度。
- [x] 支持创建、更新、启停和删除任务时动态刷新调度。
- [x] 实现手动立即执行。
- [x] 实现脚本任务执行,使用结构化参数,不经过 Shell。
- [x] 捕获 stdout、stderr、退出码和耗时。
- [x] 实现同一任务并发触发时跳过。
- [x] 通过 Windows Job Object 实现超时和取消时的进程树终止。
- [x] 记录每次运行状态并处理进程异常退出。
- [x] 引入确定性时间计算测试和实时秒级调度测试,验证调度逻辑。
验收:
- 创建一个每秒执行的临时脚本任务后,自动运行并生成可查看日志。
- 超时脚本及其子进程被终止,运行状态为 `timed_out`。
- 后端重启后调度恢复,错过的历史运行不会批量重复执行。
- 前端尚未接入前,可通过 API 完整验证脚本任务链路。
### Phase 4:React 前端迁移与垂直切片
状态:已完成(2026-10-18)
- [x] 将现有页面拆分为布局、指标卡、任务表格、任务编辑器和日志查看器。
- [x] 接入真实任务列表、详情、创建、更新、删除和启停 API。
- [x] 接入手动运行 API,并展示 `queued/running/succeeded/failed` 状态。
- [x] 使用表单校验替代浏览器无效的 `required` 假象。
- [x] 根据 Action 类型提交完整、不同的载荷。
- [x] 实现加载、空数据、失败、重试和保存中状态。
- [x] 日志页面先使用轮询读取真实运行日志。
- [x] 移除伪造的成功提示和静态任务数据。
验收:
- 页面中的每个 CRUD、启停和手动运行操作都对应真实 API。
- 刷新页面后任务状态与 SQLite 一致。
- HTTP 和进程任务不会在尚未实现执行器时被错误提交为脚本任务。
- 表单错误能定位到具体字段,Cron 校验错误来自前后端一致结果。
### Phase 5:HTTP、进程任务与实时日志
状态:已完成(2026-10-18)
- [x] 实现 HTTP Executor,支持 GET/POST、Header、Body 和期望状态码。
- [x] 实现二进制进程 Executor。
- [x] 将 SSE 接入任务状态和日志增量更新,轮询作为降级方案。
- [x] 支持取消正在运行的任务。
- [x] 增加运行历史列表、状态/触发筛选、退出码、错误摘要和日志下载。
- [x] 实现日志大小上限、保留期限和清理策略。
- [x] 对敏感环境变量和 Header 值进行输入遮罩,并在日志中脱敏。
验收:
- 三类任务均可从 React 页面创建、执行和查看结果。
- 运行中日志可以持续更新,完成后状态正确收敛。
- HTTP 非预期状态码和进程非零退出码能明确标记失败。
- 取消或超时不会遗留目标进程树。
### Phase 6:质量、安全与交付
状态:已完成(2026-10-18)
- [x] 补齐后端单元、集成和 Windows 平台测试。
- [x] 补齐前端组件测试和 Playwright 主流程测试。
- [x] 完成键盘导航、焦点管理、ARIA 和窄屏布局。
- [x] 移除所有用户输入的 `innerHTML` 拼接,统一安全文本渲染。
- [x] 后端默认绑定 `127.0.0.1`,配置严格 CORS 和 Origin 校验。
- [x] 增加本地管理令牌或等价的本机接口保护方案。
- [x] 检查路径逃逸、命令注入、日志注入和敏感信息泄露。
- [x] 由 Axum 托管生产构建后的 React 静态资源。
- [x] 提供一键启动脚本、生产构建说明和故障排查文档。
- [x] 更新 README,并评审是否将旧 HTML 移入 `legacy/`。
说明:旧 `task_pilot.html` 暂时保留在根目录作为视觉参考;正式运行入口为 React 构建产物。
验收:
- 完整测试、格式检查、静态检查和前端生产构建通过。
- 单机 Release 构建能够启动、自动打开或明确提示访问地址。
- 关闭程序后,任务数据仍保留;重新启动后启用任务恢复调度。
- README 中的命令全部经过实际验证。
## 7. 开发顺序与检查点
每个阶段按以下顺序推进:
1. 先确定数据模型和 API 契约。
2. 实现后端能力并补充自动化测试。
3. 通过 API 或命令行验证真实行为。
4. 接入 React 页面,不先做没有后端支持的假交互。
5. 运行完整检查并更新本计划的勾选状态。
阶段之间保留可运行状态,不进行长期无法启动的大规模重构。旧 HTML 一直保留到 React 版本完成等价迁移。
## 8. 测试策略
### 后端
- 单元测试:Cron 校验、任务校验、状态转换、重试和并发策略。
- 集成测试:使用临时 SQLite 验证 CRUD、迁移和重启恢复。
- 调度测试:使用可控时钟验证下次执行时间和动态调度。
- 平台测试:在 Windows 上验证脚本参数、退出码、超时和进程树终止。
- 契约测试:检查 OpenAPI 与实现一致。
### 前端
- 组件测试:表单类型切换、错误展示、状态徽章和权限式操作可用性。
- API Mock 测试:加载、失败、空数据和重试。
- E2E:创建脚本任务、手动执行、查看日志、编辑、停用和删除。
### 提交前检查
计划提供以下统一命令,具体脚本在 Phase 1 落地:
```powershell
# 前端
pnpm --dir frontend lint
pnpm --dir frontend typecheck
pnpm --dir frontend test
pnpm --dir frontend build
# 后端
cargo fmt --manifest-path backend/Cargo.toml --check
cargo clippy --manifest-path backend/Cargo.toml --all-targets -- -D warnings
cargo test --manifest-path backend/Cargo.toml
```
## 9. 安全与可靠性约束
- 后端默认只监听本机地址,不默认暴露到局域网。
- 脚本和进程任务使用结构化参数调用,不通过字符串拼接进入 Shell。
- 所有路径在执行前规范化并校验允许范围。
- 用户提交的文本只通过 React 文本节点渲染,禁止直接拼接 HTML。
- 环境变量中的敏感值在列表、详情、日志和错误信息中脱敏。
- stdout/stderr 必须并发读取,避免管道写满导致子进程卡死。
- 所有运行都有超时和取消路径,终止时覆盖子进程树。
- 日志设置单次大小和总容量上限,避免磁盘被无限占用。
- 数据库迁移必须可重复执行,并在失败时给出明确错误。
- 前端不得把请求失败或未接入能力显示为成功。
## 10. 主要风险
| 风险 | 影响 | 应对 |
| --- | --- | --- |
| Cron crate 语义或动态调度能力不满足要求 | 自动执行时间错误 | Phase 1 POC,覆盖 5/6 位、时区、日和星期语义 |
| Windows 子进程树难以可靠终止 | 超时后遗留进程 | 使用 Job Object,并增加真实 Windows 集成测试 |
| stdout/stderr 处理不当 | 子进程阻塞或日志丢失 | 异步并发读取,设置背压和日志上限 |
| SQLite 与调度器状态不一致 | 重启后漏跑或重复跑 | 事务更新、启动时重建调度、记录运行状态 |
| 前端迁移范围过大 | 长期无法交付 | 先完成脚本任务垂直切片,再扩展三类 Action |
| 敏感环境变量泄露 | 凭据暴露 | 遮罩、日志脱敏,后续接入系统凭据存储 |
| 动态依赖下载受网络影响 | 工程搭建受阻 | 锁定版本和 lockfile,必要时分阶段安装并记录依赖 |
| 本地接口被其他网页调用 | 未授权操作 | localhost 绑定、Origin 校验和本机管理令牌 |
## 11. MVP 完成标准
满足以下条件后,第一版可以视为完成:
- React 页面完全使用真实 API 数据。
- 任务 CRUD、启停和手动运行形成闭环。
- 脚本、HTTP 和二进制进程任务至少各有一条成功路径和一条失败路径验证。
- Cron 自动调度、时区和下次运行时间可验证。
- 任务配置、运行历史和日志重启后保留。
- 超时和取消可以终止 Windows 进程树。
- 页面能够展示运行中、成功、失败、超时和跳过状态。
- 后端默认仅本机可访问,敏感值不会出现在日志中。
- 自动化测试、格式检查和 Release 构建通过。
## 12. 后续维护
六个计划阶段已经完成。后续版本优先评估:
1. 自动重试与退避策略。
2. Windows 服务安装和开机自启。
3. 系统凭据存储与更严格的秘密管理。
4. 任务模板、导入导出和批量操作。
5. 运行指标、告警和长期监控。
6. HTTP 响应断言、认证方案和请求重放。