feat: implement full-stack TaskPilot scheduler
This commit is contained in:
@@ -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)。六个开发阶段均已完成,后续进入维护、功能反馈和版本迭代。
|
||||
Reference in New Issue
Block a user