SimpleScheduler 采用预定义服务+参数映射的设计模式,实现了业务参数与执行细节的完全分离。
在传统的任务调度系统中,客户端需要知道执行细节:
{
"type": "http",
"url": "https://api.example.com/users/123/profile",
"method": "GET",
"headers": { "Authorization": "Bearer xxx" }
}问题:
- ❌ 客户端需要知道 API 的具体地址
- ❌ 客户端需要管理认证信息
- ❌ API 地址变更需要修改所有客户端代码
- ❌ 安全风险:客户端可以调用任意 URL
- ❌ 难以统一管理和审计
服务端定义(config/services.json):
{
"id": "get-user-profile",
"type": "WEB_SERVICE",
"description": "获取用户资料",
"parameters": {
"userId": {
"type": "string",
"required": true,
"description": "用户 ID"
}
},
"config": {
"url": "https://api.example.com/users/{userId}/profile",
"method": "GET",
"headers": {
"Authorization": "Bearer internal-token-xxx",
"Accept": "application/json"
},
"paramMapping": {
"path": {
"userId": "userId"
}
}
},
"output": "JSON"
}客户端调用:
{
"serviceId": "get-user-profile",
"payload": {
"userId": "123"
}
}优势:
- ✅ 客户端只需要知道业务参数(userId)
- ✅ API 地址、认证信息等集中管理
- ✅ API 变更无需修改客户端
- ✅ 安全:客户端只能调用预先批准的服务
- ✅ 易于审计和监控
配置:
{
"url": "https://api.example.com/users/{userId}/posts/{postId}",
"paramMapping": {
"path": {
"userId": "userId",
"postId": "postId"
}
}
}客户端传入:
{
"userId": "123",
"postId": "456"
}实际请求:
GET https://api.example.com/users/123/posts/456
配置:
{
"url": "https://api.weather.com/forecast",
"paramMapping": {
"query": {
"city": "location",
"days": "forecast_days",
"units": "temp_unit"
}
}
}客户端传入:
{
"city": "London",
"days": 7,
"units": "celsius"
}实际请求:
GET https://api.weather.com/forecast?location=London&forecast_days=7&temp_unit=celsius
配置:
{
"url": "https://api.example.com/users",
"method": "POST",
"paramMapping": {
"body": {
"name": "full_name",
"email": "email_address",
"age": "user_age"
}
}
}客户端传入:
{
"name": "John Doe",
"email": "john@example.com",
"age": 30
}实际请求:
POST https://api.example.com/users
Content-Type: application/json
{
"full_name": "John Doe",
"email_address": "john@example.com",
"user_age": 30
}
配置:
{
"command": "backup-tool",
"args": ["--verbose", "--format", "sql"],
"paramMapping": {
"args": {
"database": 3,
"outputPath": 4
}
}
}客户端传入:
{
"database": "mydb",
"outputPath": "/backups/mydb.sql"
}实际命令:
backup-tool --verbose --format sql mydb /backups/mydb.sql配置:
{
"command": "python3",
"args": ["/opt/scripts/process.py"],
"paramMapping": {
"args": {
"inputFile": 1
},
"env": {
"apiKey": "API_KEY",
"region": "AWS_REGION"
}
}
}客户端传入:
{
"inputFile": "/data/input.txt",
"apiKey": "secret-key-123",
"region": "us-west-2"
}实际执行:
API_KEY=secret-key-123 AWS_REGION=us-west-2 python3 /opt/scripts/process.py /data/input.txt- 只有在
config/services.json中定义的服务才能被执行 - 防止客户端调用任意 URL 或执行任意命令
- 集中控制和审批服务访问
- API Token、密钥等敏感信息存储在服务端
- 客户端无需知道和管理凭证
- 凭证泄露风险降低
- 严格的参数类型检查
- 必需参数验证
- 防止注入攻击
- 命令白名单验证
- 禁用 shell 执行(
shell: false) - 参数数组隔离
需求:集成天气 API,但不想让客户端知道 API Key
服务定义:
{
"id": "weather-forecast",
"type": "WEB_SERVICE",
"parameters": {
"city": { "type": "string", "required": true }
},
"config": {
"url": "https://api.openweathermap.org/data/2.5/weather",
"paramMapping": {
"query": {
"city": "q",
"apiKey": "appid"
}
}
}
}客户端调用:
fetch('/api/v1/schedule', {
method: 'POST',
body: JSON.stringify({
serviceId: 'weather-forecast',
payload: { city: 'Tokyo' }
})
})需求:允许用户触发备份,但不暴露备份工具的具体位置和参数
服务定义:
{
"id": "mysql-backup",
"type": "LOCAL_TOOL",
"parameters": {
"database": { "type": "string", "required": true }
},
"config": {
"command": "/usr/local/bin/mysqldump",
"args": ["-u", "backup_user", "-p${MYSQL_PASSWORD}"],
"paramMapping": {
"args": { "database": 2 }
}
}
}客户端调用:
fetch('/api/v1/schedule', {
method: 'POST',
body: JSON.stringify({
serviceId: 'mysql-backup',
payload: { database: 'production' }
})
})需求:生成 PDF 报表,客户端只需指定报表类型和日期范围
服务定义:
{
"id": "sales-report",
"type": "LOCAL_TOOL",
"parameters": {
"reportType": { "type": "string", "required": true },
"startDate": { "type": "string", "required": true },
"endDate": { "type": "string", "required": true }
},
"config": {
"command": "python3",
"args": ["/opt/reports/generator.py"],
"paramMapping": {
"args": {
"reportType": 1,
"startDate": 2,
"endDate": 3
}
}
},
"output": "FILE"
}客户端调用:
fetch('/api/v1/schedule', {
method: 'POST',
body: JSON.stringify({
serviceId: 'sales-report',
payload: {
reportType: 'monthly',
startDate: '2025-01-01',
endDate: '2025-01-31'
}
})
})- 使用清晰的、业务导向的服务 ID
- 例如:
get-user-profile、send-email、generate-invoice - 避免技术细节:❌
http-get-api-v2-users
- 参数名应该反映业务含义
- 例如:
userId、orderNumber、reportDate - 避免技术术语:❌
path_param_1、query_string_value
- 一个服务应该代表一个明确的业务操作
- ✅ 好:
create-order、cancel-order、update-order - ❌ 差:
order-api(过于宽泛)
- 当 API 变更时,创建新版本的服务
- 例如:
weather-forecast-v1、weather-forecast-v2 - 允许逐步迁移客户端
- 在
description中清晰说明服务的用途 - 在参数定义中添加详细的
description - 维护服务目录文档
这种设计模式实现了:
- 关注点分离:业务逻辑与技术实现分离
- 安全性:集中控制和审计
- 可维护性:修改服务实现无需更改客户端
- 灵活性:支持多种参数映射方式
- 可扩展性:易于添加新服务
这是构建企业级任务调度系统的最佳实践。