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, size | total, page, size, items |
| 游标分页 | 实时流式读取、持续同步 | cursor, size | nextCursor, 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,随后通过轮询或回调获取最终结果。
异步任务接口定义
| 端点 | 方法 | 说明 |
|---|---|---|
/api/v1/async/jobs/import | POST | 提交批量导入任务,返回 taskId |
/api/v1/async/tasks/{taskId} | GET | 查询任务状态:PENDING / RUNNING / SUCCESS / FAILED |
/api/v1/async/tasks/{taskId}/cancel | POST | 取消正在运行的任务(幂等) |
// 提交异步任务
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 的
requestId 直接映射为 Kafka 消息的 Key,确保同一请求的所有事件(创建、更新、删除)路由到同一分区,保证顺序性。异步任务的 taskId 作为幂等键,防止 Kafka 生产者重试导致的消息重复。
4.7 本章小结
- 统一信封:所有 API 使用
requestId、timestamp、data、error四字段结构,链路追踪与错误处理标准化。 - 双模式分页:偏移量分页用于管理后台,游标分页用于实时同步,避免大数据集下的性能问题。
- 声明式查询:排序和过滤通过查询字符串组合,支持多字段排序和操作符后缀,灵活且可扩展。
- 批量原子性:批量操作整体成功或回滚,
requestId保证幂等,防止网络重试导致的数据不一致。 - 异步任务模式:耗时操作采用提交-查询模式,支持轮询和回调两种结果获取方式,避免 HTTP 连接长时间占用。
- Kafka 映射:OpenAPI 数据模型与 Kafka 消息结构一一对应,
requestId作为分区键保证顺序,taskId作为幂等键防止重复。