4. OpenAPI 数据传输模型:请求/响应结构、分页、排序、过滤、批量操作、异步任务接口设计

任务调度平台的 OpenAPI 网关是外部系统与调度引擎之间的统一契约层。数据传输模型的设计直接决定了 API 的可维护性、调用效率以及上下游集成的复杂度。本章围绕请求/响应结构、分页、排序、过滤、批量操作和异步任务接口六大核心要素,建立一套标准化的数据传输规范。

4.1 请求/响应结构:统一信封模式

所有 OpenAPI 端点采用统一的 JSON 信封结构,将业务数据与元数据分离。请求体与响应体遵循相同的顶层字段布局,降低客户端解析成本。

字段请求体响应体说明
requestId必填必填(回显)客户端生成的 UUID,用于链路追踪
timestamp必填必填ISO 8601 格式,服务端以收到时间为准
data业务参数业务结果核心载荷,类型随接口变化
signature可选可选HMAC-SHA256 签名,用于敏感操作
error—失败时必填包含 code、message、details
设计原则:请求体中的 data 字段禁止嵌套信封元数据;响应体中的 error 仅在非 2xx 时出现,避免成功响应携带冗余错误结构。
// 请求示例:创建定时任务
{
  "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "timestamp": "2025-03-21T10:30:00Z",
  "data": {
    "jobName": "data_cleanup",
    "cronExpr": "0 0 2 * * ?",
    "type": "SHELL",
    "timeout": 300
  }
}

// 响应示例:成功
{
  "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "timestamp": "2025-03-21T10:30:01Z",
  "data": {
    "jobId": "job-987654",
    "status": "CREATED"
  }
}

// 响应示例:失败
{
  "requestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "timestamp": "2025-03-21T10:30:01Z",
  "error": {
    "code": "JOB_CRON_INVALID",
    "message": "Cron expression is not valid",
    "details": "Field 'cronExpr' failed validation at position 3"
  }
}

4.2 分页模型:游标与偏移量双模式

任务调度场景下,任务实例列表、执行历史等数据量可能达到百万级。OpenAPI 同时支持偏移量分页和游标分页,客户端根据场景选择。

模式适用场景请求参数响应字段
偏移量分页随机跳页、管理后台page, sizetotal, page, size, items
游标分页实时流式读取、持续同步cursor, sizenextCursor, hasMore, items
推荐策略:对于任务实例列表这类高频读取且数据持续增长的接口,优先使用游标分页。游标基于任务实例的创建时间戳或自增 ID 编码,避免偏移量分页在数据插入时导致的重复或遗漏。
// 偏移量分页请求
GET /api/v1/jobs?page=1&size=20

// 偏移量分页响应
{
  "data": {
    "items": [ ... ],
    "page": 1,
    "size": 20,
    "total": 1542
  }
}

// 游标分页请求
GET /api/v1/jobs?cursor=MTYyNTAwMDAwMDAwMA==&size=20

// 游标分页响应
{
  "data": {
    "items": [ ... ],
    "nextCursor": "MTYyNTAwMDAwMDEwMA==",
    "hasMore": true
  }
}

4.3 排序与过滤:声明式查询语法

排序和过滤参数通过查询字符串传递,采用可组合的声明式语法,避免为每种排序组合定义独立端点。

排序规范

  • 参数名:sort,支持多字段排序,用逗号分隔。
  • 方向标识:字段名前加 + 表示升序(默认),加 - 表示降序。
  • 示例:sort=-createdAt,+jobName 表示按创建时间降序,相同则按任务名升序。

过滤规范

  • 参数名:直接使用字段名作为查询参数,支持操作符后缀。
  • 操作符:__eq(等于,默认)、__ne、__gt、__gte、__lt、__lte、__in、__like。
  • 示例:status__in=RUNNING,PAUSED&createdAt__gte=2025-03-01。
// 完整查询示例:获取 2025年3月1日之后创建的、状态为运行中或暂停的任务,
// 按创建时间降序排列,每页 10 条
GET /api/v1/jobs?status__in=RUNNING,PAUSED&createdAt__gte=2025-03-01&sort=-createdAt&size=10
安全约束:过滤字段白名单必须在服务端硬编码,禁止直接将查询参数映射到数据库列名。对于 __like 操作符,必须转义 % 和 _ 通配符,防止 SQL 注入或索引失效。

4.4 批量操作:原子性与幂等性

任务调度场景中,批量创建、暂停、删除任务是高频需求。批量接口采用数组输入,并保证整体原子性——要么全部成功,要么全部回滚。

操作HTTP 方法端点请求体 data 结构
批量创建POST/api/v1/jobs/batch{ "jobs": [ {...}, {...} ] }
批量暂停POST/api/v1/jobs/batch/pause{ "jobIds": ["id1","id2"] }
批量删除POST/api/v1/jobs/batch/delete{ "jobIds": ["id1","id2"] }
幂等性保证:每个批量请求必须携带 requestId,服务端基于 requestId 做去重。如果客户端因网络超时重发相同 requestId 的请求,服务端返回上一次的执行结果,不重复执行。
// 批量创建请求
POST /api/v1/jobs/batch
{
  "requestId": "batch-001-uuid",
  "data": {
    "jobs": [
      { "jobName": "cleanup_a", "cronExpr": "0 0 3 * * ?" },
      { "jobName": "cleanup_b", "cronExpr": "0 0 4 * * ?" }
    ]
  }
}

// 批量创建响应(整体成功)
{
  "requestId": "batch-001-uuid",
  "data": {
    "successCount": 2,
    "failedCount": 0,
    "results": [
      { "index": 0, "jobId": "job-001", "status": "CREATED" },
      { "index": 1, "jobId": "job-002", "status": "CREATED" }
    ]
  }
}

4.5 异步任务接口:提交-查询模式

对于执行时间超过 5 秒的操作(如大规模任务导入、全量日志导出),OpenAPI 采用异步任务模式。客户端提交请求后立即获得一个 taskId,随后通过轮询或回调获取最终结果。

异步任务接口交互流程 客户端 OpenAPI 网关 任务引擎 1. POST /async/jobs/import 2. 202 { taskId } 3. 转发到引擎 4. GET /async/tasks/{taskId} 轮询 5. 200 { status: "SUCCESS", result } 说明: • 客户端提交后立即收到 202 Accepted,不阻塞连接。 • 轮询间隔建议指数退避:1s → 2s → 4s → 8s,最大间隔 30s。

异步任务接口定义

端点方法说明
/api/v1/async/jobs/importPOST提交批量导入任务,返回 taskId
/api/v1/async/tasks/{taskId}GET查询任务状态:PENDING / RUNNING / SUCCESS / FAILED
/api/v1/async/tasks/{taskId}/cancelPOST取消正在运行的任务(幂等)
// 提交异步任务
POST /api/v1/async/jobs/import
{
  "requestId": "async-import-001",
  "data": {
    "fileUrl": "s3://scheduler-batch/jobs_20250321.csv",
    "format": "CSV"
  }
}

// 响应:202 Accepted
{
  "requestId": "async-import-001",
  "data": {
    "taskId": "task-abc-123",
    "status": "PENDING",
    "estimatedSeconds": 30
  }
}

// 轮询任务状态
GET /api/v1/async/tasks/task-abc-123

// 响应:任务完成
{
  "data": {
    "taskId": "task-abc-123",
    "status": "SUCCESS",
    "result": {
      "totalJobs": 500,
      "successCount": 498,
      "failedCount": 2,
      "failedDetails": [
        { "row": 42, "reason": "Invalid cron expression" },
        { "row": 157, "reason": "Duplicate job name" }
      ]
    },
    "completedAt": "2025-03-21T10:35:12Z"
  }
}
回调替代方案:如果客户端支持接收回调,可在提交时传入 callbackUrl 参数。任务完成后,服务端向该 URL 发送 POST 请求,携带与轮询响应相同的数据结构。回调与轮询可同时启用,互不冲突。

4.6 数据传输模型与 Kafka 的映射关系

OpenAPI 的数据传输模型与 Kafka 消息结构之间存在直接映射。理解这种映射有助于在集成优化时保持数据一致性。

OpenAPI 数据模型 → Kafka 消息映射 OpenAPI 请求/响应 • requestId → 消息 key • timestamp → 消息时间戳 • data → 消息 value 主体 • 分页参数 → 消息分区策略 • 过滤条件 → 消费者过滤 • taskId → 消息去重 ID 序列化 Kafka 消息结构 • Key: requestId (分区路由) • Headers: timestamp, contentType • Value: 序列化后的 data JSON • Partition: hash(requestId) % N • Offset: 顺序消费保证 • Idempotent: enable.idempotence=true
集成要点:OpenAPI 的 requestId 直接映射为 Kafka 消息的 Key,确保同一请求的所有事件(创建、更新、删除)路由到同一分区,保证顺序性。异步任务的 taskId 作为幂等键,防止 Kafka 生产者重试导致的消息重复。

4.7 本章小结

  • 统一信封:所有 API 使用 requestId、timestamp、data、error 四字段结构,链路追踪与错误处理标准化。
  • 双模式分页:偏移量分页用于管理后台,游标分页用于实时同步,避免大数据集下的性能问题。
  • 声明式查询:排序和过滤通过查询字符串组合,支持多字段排序和操作符后缀,灵活且可扩展。
  • 批量原子性:批量操作整体成功或回滚,requestId 保证幂等,防止网络重试导致的数据不一致。
  • 异步任务模式:耗时操作采用提交-查询模式,支持轮询和回调两种结果获取方式,避免 HTTP 连接长时间占用。
  • Kafka 映射:OpenAPI 数据模型与 Kafka 消息结构一一对应,requestId 作为分区键保证顺序,taskId 作为幂等键防止重复。