SimpleScheduler 是一个基于配置文件驱动的轻量级服务调度器。它的核心设计理念是将 服务执行的细节(如 API 地址、命令行工具)与客户端调用分离。客户端只需关心业务参数,而无需了解后端的具体实现。
这种模式使得服务配置更加集中、安全,并且易于管理和扩展,尤其适合需要统一管理多种异构任务(如 API 调用、脚本执行)的场景。
在传统架构中,客户端通常直接调用特定的 API 地址或执行命令。当服务地址、参数或实现方式变更时,往往需要修改客户端代码。
SimpleScheduler 通过引入一个 服务配置层 来解决这个问题:
- 🧑💼 管理员 在
config/services.json中定义一系列服务,每个服务包含固定的执行配置(如 URL、命令、参数映射关系)。 - 💻 客户端 只需提供
serviceId和业务相关的payload(如city,userId)。 - ⚙️ 调度器 根据
serviceId查找服务配置,验证并映射客户端传入的参数,然后执行任务(调用 API 或运行本地命令)。
Note
这种方式将 “做什么”(客户端业务请求)与 “怎么做”(服务端具体实现)完全解耦。
- 📄 配置驱动:所有可执行服务均在
services.json中定义,无需修改代码即可增删服务。 - 🛠️ 多任务类型:原生支持
WEB_SERVICE(调用 HTTP/S API)和LOCAL_TOOL(执行本地命令行工具)。 - 🔗 参数映射:强大的参数映射功能,可将客户端的业务参数灵活映射到 URL 路径、查询参数、请求体、命令行参数或环境变量。
- 🛡️ 安全隔离:
- 服务白名单机制,只有预定义的服务才能执行。
- 禁用
shell模式执行命令,防止命令注入。 - 客户端无法指定执行的 URL 或命令,只能传递业务参数。
- 💾 持久化存储:所有任务状态和执行结果都通过
unstorage持久化到本地文件系统(.data/目录)。 - 🚦 并发控制:通过多队列机制(
config/queues.json)管理不同优先级任务的并发数。 - 📡 实时事件流:客户端可通过 SSE (Server-Sent Events) 实时获取任务的状态更新、日志和最终结果。
SimpleScheduler 基于 Nitro 框架构建,其核心服务均为单例模式,在应用启动时自动初始化。
ConfigManager: 加载并验证services.json和queues.json配置文件。StorageService: 负责任务和结果的持久化存储。JobManager: 管理任务的生命周期(创建、状态变更、事件通知)。QueueManager: 基于并发配置调度任务队列。TaskExecutor: 根据服务类型(WEB_SERVICE或LOCAL_TOOL)执行具体任务。CleanupService: 定期清理过期的任务和资源。
- Node.js >= 18.0.0
- Yarn
-
克隆仓库
git clone https://github.com/ShouChenICU/SimpleScheduler.git cd simplescheduler -
安装依赖
yarn install
-
启动开发服务器
yarn dev
[!IMPORTANT] 服务将运行在
http://localhost:3005。
通过 POST /api/v1/schedule 发起一个新任务。
请求体:
{
"serviceId": "weather-api",
"payload": {
"city": "Beijing"
},
"queue": "default"
}serviceId: 对应config/services.json中定义的服务 ID。payload: 业务参数,必须符合服务定义中的parameters约束。queue(可选): 指定任务队列,默认为default。
成功响应:
{
"jobId": "a1b2c3d4-e5f6-7890-1234-567890abcdef"
}通过 GET /api/v1/stream/{jobId} 订阅任务的 Server-Sent Events。
curl -N http://localhost:3005/api/v1/stream/a1b2c3d4-e5f6-7890-1234-567890abcdef事件流示例:
event: status_update
data: {"status":"RUNNING"}
event: log
data: {"message":"Executing web service request to https://api.openweathermap.org/data/2.5/weather"}
event: result
data: {"weather":{"main":"Clear","description":"clear sky"},"main":{"temp":20,"feels_like":19.5}}
event: status_update
data: {"status":"COMPLETED"}
如果不想使用 SSE,也可以通过 GET /api/v1/status/{jobId} 查询任务的最终状态和结果。
所有服务都在 config/services.json 中定义。下面是一个 WEB_SERVICE 和一个 LOCAL_TOOL 的示例。
Caution
服务配置是系统的核心,不正确的配置可能导致任务执行失败或产生安全风险。
此服务用于查询天气,客户端只需提供 city。apiKey 从服务端配置中自动附加。
{
"id": "weather-api",
"type": "WEB_SERVICE",
"description": "查询城市天气信息",
"parameters": {
"city": { "type": "string", "required": true }
},
"config": {
"url": "https://api.openweathermap.org/data/2.5/weather",
"method": "GET",
"paramMapping": {
"query": {
"city": "q",
"apiKey": "appid"
}
}
},
"output": "JSON"
}此服务用于执行一个备份脚本,客户端提供 database 名称。
{
"id": "database-backup",
"type": "LOCAL_TOOL",
"description": "执行数据库备份",
"parameters": {
"database": { "type": "string", "required": true }
},
"config": {
"command": "/usr/local/bin/backup-tool",
"args": ["--format", "sql"],
"paramMapping": {
"args": {
"database": 0
}
}
},
"output": "TEXT"
}paramMapping.args: 将客户端的database参数插入到args数组的第0个位置。
当 output 类型为 FILE 时,SimpleScheduler 支持两种方式获取二进制数据:
{
"id": "file-output-from-stdout",
"type": "LOCAL_TOOL",
"description": "从标准输出获取二进制数据",
"parameters": {
"content": { "type": "string", "required": true }
},
"config": {
"command": "/bin/echo",
"args": ["-n"],
"paramMapping": {
"args": { "content": 0 }
}
},
"output": "FILE"
}特点:
- 🚀 直接从
stdout读取二进制数据 - 📊 不打印日志,避免二进制内容乱码
- 💾 数据直接保存为资源文件
{
"id": "image-resize",
"type": "LOCAL_TOOL",
"description": "调整图片大小",
"parameters": {
"inputFile": { "type": "string", "required": true },
"width": { "type": "number", "required": true },
"height": { "type": "number", "required": true }
},
"config": {
"command": "/usr/bin/convert",
"args": ["-resize"],
"cwd": "/tmp",
"paramMapping": {
"args": {
"inputFile": 0,
"width": 1,
"height": 2
},
"outputFile": 3
}
},
"output": "FILE"
}工作流程:
- 🎲 系统自动生成随机文件名(如
output_1699999999999_abc123.tmp) - 📝 将完整路径插入到
paramMapping.outputFile指定的参数位置(位置 3) - ⚙️ 执行命令:
/usr/bin/convert -resize input.jpg 800 600 /tmp/output_xxx.tmp - 📖 命令完成后读取该文件内容
- 🗑️ 自动删除临时文件
- 💾 将内容保存为资源
特点:
- ✅ 适用于需要指定输出文件的工具(ImageMagick、FFmpeg、tar 等)
- 🔒 自动生成唯一文件名,避免并发冲突
- 🧹 自动清理临时文件
- 📝 可以打印命令的进度日志(stdout)
paramMapping 支持多种映射方式,适用于不同场景:
"paramMapping": {
"query": { "city": "q", "apiKey": "appid" }, // URL 查询参数
"path": { "userId": "id" }, // URL 路径参数 /user/{id}
"body": { "email": "user_email" } // 请求体参数
}"paramMapping": {
"args": { "database": 0, "format": 1 }, // 命令行参数位置
"env": { "apiKey": "API_KEY" }, // 环境变量
"outputFile": 2 // 输出文件参数位置(仅用于 FILE 类型)
}TEXT: 文本输出,从stdout读取并保存为字符串JSON: JSON 输出,尝试解析stdout为 JSON 对象FILE: 二进制文件输出,根据paramMapping.outputFile配置决定获取方式:- 未配置:从
stdout获取二进制数据(不打印日志) - 已配置:从生成的临时文件读取内容(可打印日志)
- 未配置:从
项目包含一个简单的测试脚本,用于验证核心功能。
node test.mjsyarn build
yarn preview欢迎提交 Pull Request 或 Issue 来改进项目!
本项目基于 MIT License 开源。