Skip to content

Latest commit

 

History

History
332 lines (262 loc) · 7.82 KB

File metadata and controls

332 lines (262 loc) · 7.82 KB

预定义服务模式 - 代码重构说明

概述

本次重构实现了真正的预定义服务模式:服务定义包含具体的 URL/命令和参数映射配置,客户端只需要提供业务参数,系统负责将业务参数映射到实际的 API 调用或命令执行。

核心理念

❌ 错误理解

客户端传入执行细节(url、command、headers 等):

{
  "serviceId": "web-api",
  "payload": {
    "url": "https://api.example.com/data",  // ❌ 客户端传入 URL
    "method": "GET",                        // ❌ 客户端传入方法
    "headers": {...}                        // ❌ 客户端传入请求头
  }
}

✅ 正确理解

服务定义包含执行细节,客户端只传业务参数:

服务定义(config/services.json)

{
  "id": "weather-api",
  "type": "WEB_SERVICE",
  "description": "查询城市天气",
  "parameters": {
    "city": {
      "type": "string",
      "required": true,
      "description": "城市名称"
    }
  },
  "config": {
    "url": "https://api.openweathermap.org/data/2.5/weather", // ✅ 服务定义中的 URL
    "method": "GET", // ✅ 服务定义中的方法
    "headers": { "Accept": "application/json" }, // ✅ 服务定义中的请求头
    "paramMapping": {
      "query": { "city": "q" } // ✅ 参数映射规则
    }
  }
}

客户端请求

{
  "serviceId": "weather-api",
  "payload": {
    "city": "London" // ✅ 只传业务参数
  }
}

系统自动构建

GET https://api.openweathermap.org/data/2.5/weather?q=London

核心变更

1. 增强的服务配置结构

文件: server/types/schemas.ts

新增了 WebServiceConfigLocalToolConfig 两种配置类型:

WebServiceConfig

{
  url: string                    // 具体的 API 端点(可包含路径参数占位符)
  method: 'GET' | 'POST' | ...   // HTTP 方法
  headers: Record<string, string> // 固定的请求头
  timeout: number                // 超时时间
  paramMapping: {
    query: Record<string, string>  // 查询参数映射 {"clientParam": "apiParam"}
    body: Record<string, string>   // 请求体参数映射
    path: Record<string, string>   // 路径参数映射
  }
}

LocalToolConfig

{
  command: string                // 具体的命令或脚本路径
  args: string[]                 // 固定参数
  cwd: string                    // 工作目录
  paramMapping: {
    args: Record<string, number>   // 参数位置映射 {"clientParam": 0}
    env: Record<string, string>    // 环境变量映射 {"clientParam": "ENV_VAR"}
  }
}

2. 参数映射机制

系统支持三种参数映射方式:

Web Service 参数映射

  1. 查询参数映射 (query)

    {
      "paramMapping": {
        "query": {
          "city": "q", // 客户端的 city 映射到 API 的 q 参数
          "units": "units"
        }
      }
    }

    客户端传入 {"city": "London"} → API 调用 ?q=London

  2. 路径参数映射 (path)

    {
      "url": "https://api.example.com/users/{userId}/posts",
      "paramMapping": {
        "path": {
          "userId": "userId"
        }
      }
    }

    客户端传入 {"userId": "123"} → URL 变为 /users/123/posts

  3. 请求体参数映射 (body)

    {
      "paramMapping": {
        "body": {
          "username": "user",
          "email": "email"
        }
      }
    }

    客户端传入 {"username": "john"} → 请求体 {"user": "john"}

Local Tool 参数映射

  1. 参数位置映射 (args)

    {
      "command": "backup-tool",
      "args": ["--format", "sql"], // 固定参数
      "paramMapping": {
        "args": {
          "database": 2, // 客户端的 database 放在位置 2
          "outputDir": 3
        }
      }
    }

    最终命令:backup-tool --format sql {database} {outputDir}

  2. 环境变量映射 (env)

    {
      "paramMapping": {
        "env": {
          "apiKey": "API_KEY", // 客户端的 apiKey 映射到环境变量 API_KEY
          "token": "AUTH_TOKEN"
        }
      }
    }

3. 更新的服务配置文件

文件: config/services.json

现在包含完整的执行配置:

[
  {
    "id": "weather-api",
    "type": "WEB_SERVICE",
    "description": "查询城市天气信息",
    "parameters": {
      "city": {
        "type": "string",
        "required": true,
        "description": "城市名称"
      }
    },
    "config": {
      "url": "https://api.openweathermap.org/data/2.5/weather",
      "method": "GET",
      "paramMapping": {
        "query": { "city": "q" }
      }
    },
    "output": "JSON"
  }
]

4. 重构的 TaskExecutor

文件: server/services/TaskExecutor.ts

WebServiceExecutor 现在:

  • 从服务配置中读取 URL、method、headers
  • 根据 paramMapping 构建实际的请求
  • 支持路径参数替换({param} 占位符)
  • 支持查询参数和请求体参数映射

LocalToolExecutor 现在:

  • 从服务配置中读取 command、args、cwd
  • 根据 paramMapping 构建完整的命令行参数
  • 支持环境变量映射
  • 支持参数位置映射

安全性增强

1. 服务白名单

  • 只有在 config/services.json 中定义的服务才能被执行
  • 客户端无法注入任意服务定义

2. 参数验证

  • 严格验证参数类型
  • 拒绝未定义的参数
  • 确保必需参数都已提供

3. 命令执行安全

  • 命令白名单(只允许字母、数字、下划线、连字符、点和斜杠)
  • 禁用 shell 执行(shell: false
  • 参数数组隔离(防止命令注入)

使用流程

1. 系统管理员:定义服务

config/services.json 中添加新服务:

{
  "id": "my-new-service",
  "type": "WEB_SERVICE",
  "description": "我的新服务",
  "parameters": {
    "endpoint": {
      "type": "string",
      "required": true,
      "description": "服务端点"
    },
    "timeout": {
      "type": "number",
      "required": false,
      "description": "超时时间(毫秒)"
    }
  },
  "output": "JSON"
}

2. 客户端:发现可用服务

curl http://localhost:3005/api/v1/services

3. 客户端:提交任务

curl -X POST http://localhost:3005/api/v1/schedule \
  -H "Content-Type: application/json" \
  -d '{
    "serviceId": "my-new-service",
    "payload": {
      "endpoint": "https://api.example.com/data"
    },
    "queue": "default"
  }'

关键要点

  1. 服务必须预先定义 - 在配置文件中,不是运行时传入
  2. 参数严格验证 - 类型、必需性、是否允许
  3. 服务发现 API - 客户端可以查询可用服务
  4. 清晰的错误消息 - 指导用户正确使用
  5. 安全第一 - 多层防护,防止任意代码执行

与之前的区别

方面 之前 现在
服务定义 可能暗示可以动态传入 明确必须预先定义
参数验证 基本的 Zod 验证 详细的类型和必需性验证
服务发现 GET /api/v1/services
参数结构 简单字符串 结构化对象
错误消息 简单 详细且有指导性
测试脚本 基本功能测试 包含参数验证和服务发现测试

总结

本次重构完全实现了预定义服务模式,确保:

  • 系统管理员完全控制可执行的服务
  • 客户端只能引用预先批准的服务
  • 严格的参数验证防止错误使用
  • 清晰的 API 让客户端发现可用服务
  • 多层安全措施保护系统安全

这种设计模式是构建安全、可控的任务调度系统的最佳实践。