Skip to content

Latest commit

 

History

History
449 lines (352 loc) · 7.83 KB

File metadata and controls

449 lines (352 loc) · 7.83 KB

服务设计理念说明

核心设计原则

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 变更无需修改客户端
  • ✅ 安全:客户端只能调用预先批准的服务
  • ✅ 易于审计和监控

参数映射详解

1. Web Service 参数映射

路径参数映射

配置

{
  "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
}

2. Local Tool 参数映射

参数位置映射

配置

{
  "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

安全优势

1. 服务白名单

  • 只有在 config/services.json 中定义的服务才能被执行
  • 防止客户端调用任意 URL 或执行任意命令
  • 集中控制和审批服务访问

2. 凭证管理

  • API Token、密钥等敏感信息存储在服务端
  • 客户端无需知道和管理凭证
  • 凭证泄露风险降低

3. 参数验证

  • 严格的参数类型检查
  • 必需参数验证
  • 防止注入攻击

4. 命令执行安全

  • 命令白名单验证
  • 禁用 shell 执行(shell: false
  • 参数数组隔离

使用场景示例

场景 1:第三方 API 集成

需求:集成天气 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' }
  })
})

场景 2:数据库备份

需求:允许用户触发备份,但不暴露备份工具的具体位置和参数

服务定义

{
  "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' }
  })
})

场景 3:报表生成

需求:生成 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'
    }
  })
})

最佳实践

1. 服务命名

  • 使用清晰的、业务导向的服务 ID
  • 例如:get-user-profilesend-emailgenerate-invoice
  • 避免技术细节:❌ http-get-api-v2-users

2. 参数设计

  • 参数名应该反映业务含义
  • 例如:userIdorderNumberreportDate
  • 避免技术术语:❌ path_param_1query_string_value

3. 服务粒度

  • 一个服务应该代表一个明确的业务操作
  • ✅ 好:create-ordercancel-orderupdate-order
  • ❌ 差:order-api(过于宽泛)

4. 版本管理

  • 当 API 变更时,创建新版本的服务
  • 例如:weather-forecast-v1weather-forecast-v2
  • 允许逐步迁移客户端

5. 文档化

  • description 中清晰说明服务的用途
  • 在参数定义中添加详细的 description
  • 维护服务目录文档

总结

这种设计模式实现了:

  1. 关注点分离:业务逻辑与技术实现分离
  2. 安全性:集中控制和审计
  3. 可维护性:修改服务实现无需更改客户端
  4. 灵活性:支持多种参数映射方式
  5. 可扩展性:易于添加新服务

这是构建企业级任务调度系统的最佳实践。