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
+360
View File
@@ -0,0 +1,360 @@
# TaskPilot
TaskPilot 是一个面向 Windows 的本地定时任务管理平台。项目目标是用 React 提供任务管理界面,用 Rust 实现 Cron 调度、Windows 进程管理和运行日志。
当前已完成 Phase 1 至 Phase 6:React 任务管理界面、Rust API、SQLite 持久化、Cron 调度、三类任务执行器、实时日志、安全边界、生产静态托管和 Windows 本地打包均已落地。
仓库根目录的 `task_pilot.html` 是最初的单文件视觉原型,目前只作为 React 迁移期间的设计参考,不是正式前端入口。
React 页面沿用该原型作为视觉基线:1040px 主容器、紧凑指标卡、任务表格、660px 编辑弹窗、Cron 预览区和深色日志查看器均在保留真实功能的前提下复刻。
## 当前能力
- React 页面可以检测 Rust 后端连接状态。
- Vite 开发服务器将 `/api` 代理到 Axum 后端。
- Rust 服务提供健康检查和 OpenAPI 文档。
- OpenAPI 可以导出为稳定快照,并生成前端 TypeScript 类型。
- Cron POC 支持 5/6 位表达式、秒级调度、别名、月份/星期名称和 POSIX 日/星期语义。
- 任务可以持久化到 SQLite,并支持创建、查询、更新和删除。
- 任务列表支持名称/ID 搜索、启用状态筛选、Action 类型筛选和分页。
- 脚本、HTTP 和二进制进程任务具有独立的类型化载荷与后端校验。
- 启用的脚本任务由 Tokio 调度循环自动执行,并支持启动后恢复调度。
- 后端重启后,已启用任务仍可持久化并重新注册。
- 支持手动立即运行,重复触发时记录为 `skipped`,不会并发执行同一任务。
- 每次运行记录触发来源、状态、耗时、退出码和错误信息。
- 已实现 Windows Job Object,超时或关闭时会终止目标进程树。
- stdout 和 stderr 会写入独立运行日志,并可通过 API 增量读取。
- HTTP Action 支持 GET/POST、Header、Body、超时和期望状态码。
- 二进制进程 Action 使用 Job Object 执行,并支持工作目录、参数和环境变量。
- 运行中任务可以通过 API 或前端日志抽屉取消。
- `/api/v1/events` 使用 SSE 推送运行状态和日志增量,前端断线时自动回退到轮询。
- 运行历史支持按状态和触发方式筛选,并支持下载完整日志。
- 敏感环境变量和敏感 Header 的值不会写入系统日志,输出中的匹配值会替换为 `[REDACTED]`。
- 日志支持保留期限和总容量清理,单次日志仍有 5 MiB 上限。
- React 页面已接入真实任务列表、搜索筛选、分页、CRUD、启停、手动运行、运行历史和日志轮询。
- 三类 Action 使用不同动态表单,并以类型化载荷提交,不再固定为脚本任务。
- Playwright 已覆盖运行日志、编辑、停用、删除和窄屏布局主流程。
- 后端只监听 `127.0.0.1`,拒绝非本地 Origin。
- 浏览器写操作需要运行期随机管理令牌,进程重启后自动更换。
- Axum 在生产模式托管 React 静态资源,并支持 SPA 路由回退。
- 弹窗和日志抽屉支持初始焦点、Tab 循环、Escape 关闭和焦点恢复。
- 提供 Windows Release 打包和一键启动脚本。
- 前端与后端均具备基础测试和统一质量检查命令。
## 技术栈
### 前端
- React 19
- TypeScript 5.9
- Vite 8
- React Router
- TanStack Query
- React Hook Form
- Zod
- Vitest + Testing Library
- Playwright
### 后端
- Rust 1.95.0 MSVC
- Axum
- Tokio
- Serde
- Utoipa / OpenAPI
- Croner
- SQLx / SQLite
- Tower HTTP
### 工具链
- Volta 2.0.2
- Node.js 24.12.0
- pnpm 11.20.0
Node.js 和 pnpm 版本固定在 `frontend/package.json` 的 `volta` 字段中,Rust 版本固定在根目录的 `rust-toolchain.toml` 中。
## 环境要求
- Windows 10/11
- Volta
- Rustup
- Visual Studio C++ Build Tools,供 Rust MSVC 工具链使用
首次使用 Volta 时会自动切换到项目固定版本:
```powershell
volta --version
node --version
pnpm --version
rustc --version
```
## 本地开发
首次拉取项目后安装前端依赖:
```powershell
pnpm --dir frontend install
```
终端一,启动 Rust API:
```powershell
cargo run --manifest-path backend/Cargo.toml
```
终端二,启动 React 前端:
```powershell
pnpm --dir frontend dev
```
访问 <http://127.0.0.1:5173>。Rust 服务默认监听 `127.0.0.1:3000`,可通过 `TASK_PILOT_PORT` 修改端口:
```powershell
$env:TASK_PILOT_PORT = "3100"
cargo run --manifest-path backend/Cargo.toml
```
修改后端端口后,还需要同步调整 `frontend/vite.config.ts` 中的代理目标。
## Windows Release
构建前端生产资源、Rust Release 可执行文件和可分发目录:
```powershell
.\scripts\build-release.ps1
```
产物位于 `release/TaskPilot/`。启动打包版本:
```powershell
.\release\TaskPilot\start.ps1
```
访问 <http://127.0.0.1:3000>。打包使用同源 API,因此 Release 模式修改 `TASK_PILOT_PORT` 后无需重新构建前端。
打包目录中的 [RELEASE.md](docs/RELEASE.md) 会作为使用与故障排查说明一并复制。
SQLite 数据库默认创建在启动目录的 `data/task_pilot.db`。通过以下环境变量可以使用其他数据库路径:
```powershell
$env:TASK_PILOT_DATABASE_URL = "sqlite://data/custom.db"
cargo run --manifest-path backend/Cargo.toml
```
数据库迁移会在后端启动时自动执行。
运行日志默认写入 `logs/{task_id}/{run_id}.log`,可通过以下环境变量配置:
```powershell
$env:TASK_PILOT_LOG_DIR = "logs"
$env:TASK_PILOT_LOG_RETENTION_DAYS = "30"
$env:TASK_PILOT_LOG_MAX_TOTAL_MB = "512"
```
单次运行日志达到 5 MiB 后会停止记录后续输出。程序启动和每次运行结束时会清理过期日志,并在总容量超限时优先删除最旧日志。
## API
| 方法 | 路径 | 用途 |
| --- | --- | --- |
| `GET` | `/api/v1/health` | 后端健康检查 |
| `GET` | `/api/v1/session` | 获取当前进程的本地管理令牌 |
| `GET` | `/api/v1/openapi.json` | 运行时 OpenAPI 文档 |
| `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?offset=0` | 增量读取运行日志 |
| `GET` | `/api/v1/runs/{run_id}/logs/download` | 下载完整运行日志 |
| `POST` | `/api/v1/runs/{run_id}/cancel` | 取消正在运行的任务 |
| `GET` | `/api/v1/events` | SSE 运行状态和日志事件 |
任务列表查询参数:
- `q`:按任务 ID 或名称搜索。
- `enabled`:按启用状态筛选。
- `action_type`:`script`、`http` 或 `process`。
- `page`:页码,默认 `1`。
- `page_size`:每页数量,默认 `20`,最大 `100`。
运行历史查询额外支持 `status` 和 `trigger` 过滤。
健康检查示例:
```json
{
"status": "ok",
"service": "task-pilot-backend",
"version": "0.1.0"
}
```
创建脚本任务示例:
```json
{
"id": "sync_data",
"name": "数据同步",
"cron": "0 0 2 * * *",
"timezone": "Asia/Shanghai",
"enabled": true,
"timeout_secs": 300,
"retry_policy": {
"max_attempts": 0,
"delay_secs": 0
},
"action": {
"type": "script",
"working_dir": "D:\\Projects\\sync",
"interpreter": "python.exe",
"script": "main.py",
"args": ["--mode", "full"],
"env": {
"PYTHONUNBUFFERED": "1"
}
}
}
```
校验失败和资源不存在时统一返回结构化错误:
```json
{
"code": "VALIDATION_ERROR",
"message": "Cron 表达式无效",
"field": "cron",
"details": {}
}
```
## 开发命令
### 完整检查
在项目根目录执行:
```powershell
.\scripts\check.ps1
```
该脚本依次运行:
- 前端 lint
- TypeScript 类型检查
- Vitest
- 前端生产构建
- Rust 格式检查
- Rust Clippy
- Rust 单元和集成测试
- Playwright 浏览器端到端测试
### 浏览器端到端测试
```powershell
pnpm --dir frontend test:e2e
```
端到端测试会清理并重建 `.e2e-data/`,启动临时 Rust 服务和 Vite,使用本机 Chrome 完成真实任务执行和页面操作。
### 生成接口契约和类型
修改后端 API DTO 或路由后执行:
```powershell
.\scripts\generate-api.ps1
```
该命令会更新:
- `contracts/openapi.json`
- `frontend/src/api/generated.ts`
生成文件不应手工修改。
### 单独检查
```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
```
## 项目结构
```text
task_pilot/
├── backend/
│ ├── src/
│ │ ├── api.rs
│ │ ├── cron.rs
│ │ ├── database.rs
│ │ ├── error.rs
│ │ ├── lib.rs
│ │ ├── main.rs
│ │ ├── process_job.rs
│ │ ├── scheduler.rs
│ │ ├── security.rs
│ │ ├── state.rs
│ │ ├── runs/
│ │ └── tasks/
│ ├── migrations/
│ ├── tests/
│ ├── Cargo.toml
│ └── Cargo.lock
├── contracts/
│ └── openapi.json
├── docs/
│ └── decisions/
├── frontend/
│ ├── e2e/
│ ├── src/
│ │ ├── components/
│ │ ├── lib/
│ │ └── pages/
│ ├── package.json
│ ├── playwright.config.ts
│ ├── vite.config.ts
│ └── vitest.config.ts
├── scripts/
│ ├── check.ps1
│ ├── build-release.ps1
│ ├── e2e.ps1
│ ├── generate-api.ps1
│ └── start.ps1
├── task_pilot.html
├── PLAN.md
├── README.md
└── rust-toolchain.toml
```
架构决策记录:
- [ADR-0001:Cron 解析与任务调度](docs/decisions/0001-cron-engine.md)
- [ADR-0002:前后端开发与生产托管](docs/decisions/0002-static-hosting.md)
## 当前限制
- 重试策略字段已持久化,但自动重试尚未实现。
- HTTP 任务执行器尚未提供响应 Header 转换等高级断言,只校验状态码。
- 敏感值脱敏基于变量名关键词和值替换,不是系统凭据保险箱。
- 当前版本是本地单用户工具,不支持多用户账户和远程部署。
- 尚未安装为 Windows 系统服务,关闭启动脚本后调度器也会停止。
- 旧 `task_pilot.html` 暂时保留在根目录作为设计参考,正式入口为 React 应用。
## 开发计划
完整架构、数据模型、API 草案、阶段任务和验收标准见 [PLAN.md](PLAN.md)。六个开发阶段均已完成,后续进入维护、功能反馈和版本迭代。