Files
orbit/docs/api/tasks.md

188 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 任务接口 v1
状态:P1 已实现并通过真实 MySQL 与 SDK 回归。当前提供受理、查询、分页和待执行取消;Worker 执行与自动重试在 P2–P3 实现。
## 公共约定
业务接口需要 `Authorization: Bearer <token>`;写入需要 `Content-Type: application/json` 和稳定的 `Idempotency-Key`。凭据决定命名空间,请求体不能覆盖它。详见 [鉴权与初始化](authentication.md)。
请求体上限 512 KiB。拒绝未知字段、大小写别名、重复 JSON 键、无效 UTF-8、孤立 Unicode 代理项和超过 32 层的嵌套。除可空时间外,字段不能显式传 null。请求追踪 ID 由服务端生成,与幂等键分开。
响应使用 `{"request_id":"…","data":…}`;X-Request-ID 与正文一致。revision 是十进制字符串;ID 是 32 位小写十六进制字符串。时间使用 RFC 3339,返回 UTC,最多微秒精度。
## POST /v1/tasks
权限:tasks:submit,并有目标队列的访问范围。首次提交返回 201,同键相同内容返回 200。两者均只表示**数据库已受理**,不表示业务处理完成。
| 字段 | 类型 | 必填 / 默认 | 含义与限制 |
| --- | --- | --- | --- |
| queue_id | string | 必填 | 同一命名空间内的队列 ID |
| handler | string | 必填 | 处理器名称,1–128 字符 |
| handler_version | string | 必填 | 固定处理器版本,1–64 字符 |
| payload_version | string | 必填 | 输入契约版本,1–32 字符 |
| payload | JSON | 必填 | 非 null JSON,最大 256 KiB;平台不解释业务字段 |
| business_key | string | 可选 | 最多 191 个 Unicode 字符,无 NUL/换行;用于关联,不提供业务幂等 |
| available_at | time / null | 受理时数据库时间 | 最早可执行时间;必须早于总截止时间,仅允许最多 1 秒的过去时间容差 |
| start_deadline_at | time / null | 无独立限制 | 必须晚于当前时间,不早于 available_at,不晚于总截止时间 |
| task_timeout_ms | uint64 | 0 或省略为 86400000 | 从受理时计算的总时限,最大 30 天(2592000000 毫秒) |
| recovery_safety | string | RECONCILE_ONLY | 也可为 IDEMPOTENT;必须基于处理器真实的幂等能力声明 |
| retry | object | 见下表 | 受理时冻结的执行策略;当前阶段仅保存 |
处理器及版本名称以 ASCII 字母或数字开头,后续只能包含字母、数字、`_ . : -`。时间年份为 2000–9998。
| retry 字段 | 默认与限制 |
| --- | --- |
| max_attempts | 0 或省略为 1;范围 1–100,包含首次尝试 |
| attempt_timeout_ms | 0 或省略为 30000;必须不超过任务总时限 |
| initial_delay_ms | 多次尝试时 0 或省略为 1000;必须为正且不超过 max_delay_ms;单次尝试默认 0 |
| max_delay_ms | 多次尝试时 0 或省略为 30000;单次尝试默认 0;最大 9223372036854,整体仍受总截止时间限制 |
这些值是当前接口保护边界,不代表已经验证相应吞吐或并发容量。
业务 JSON 以经过校验的文本持久化,保留大整数和小数的精确数字内容。不要在接入方先转成 float64;Go 使用 json.RawMessage、结构体中的精确类型或业务约定的字符串。单个数字最长 256 字符,显式指数和去除尾零后的指数均需在 -308 至 308 内,数值不能超出有限 double 的幅度;这只是可接受范围检查,摘要不使用浮点近似值。
请求示例,队列 ID 替换为初始化结果:
```sh
curl -i "$ORBIT_URL/v1/tasks" \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: conversion-operation-001' \
--data '{
"queue_id":"11111111111111111111111111111111",
"handler":"file.convert",
"handler_version":"v1",
"payload_version":"v1",
"payload":{"source_ref":"objects/input.txt","format":"pdf","sequence":9007199254740993},
"business_key":"conversion-001"
}'
```
首次受理的完整响应示例,ID 和时间为示意值:
```json
{
"request_id":"44444444444444444444444444444444",
"data":{
"deduplicated":false,
"task":{
"id":"22222222222222222222222222222222",
"namespace_id":"33333333333333333333333333333333",
"queue_id":"11111111111111111111111111111111",
"state":"WAITING",
"revision":"1",
"handler":"file.convert",
"handler_version":"v1",
"payload_version":"v1",
"payload":{"source_ref":"objects/input.txt","format":"pdf","sequence":9007199254740993},
"business_key":"conversion-001",
"available_at":"2026-09-17T00:00:00Z",
"deadline_at":"2026-09-18T00:00:00Z",
"recovery_safety":"RECONCILE_ONLY",
"retry":{"max_attempts":1,"initial_delay_ms":0,"max_delay_ms":0,"attempt_timeout_ms":30000},
"attempt_count":0,
"created_at":"2026-09-17T00:00:00Z",
"updated_at":"2026-09-17T00:00:00Z"
}
}
}
```
可选响应字段:start_deadline_at、result、cancel_requested_at、completed_at、business_key,未设置时省略。终态有 completed_at。状态集合为 WAITING / LEASED / RUNNING / NEEDS_REVIEW / SUCCEEDED / FAILED / CANCELLED;P1 仅主动产生 WAITING 和 CANCELLED。
### 幂等与失败处理
- 键为 1–128 个可打印 ASCII 字符,不含空格;作用域为命名空间 + 操作类型,取消额外包含任务 ID。不同凭据在同一命名空间共享该身份,重复请求仍需通过当前权限校验。
- 固定默认值、规范化 UTC 时间、排序 JSON 对象键、精确规范化十进制数字,再计算版本化 SHA-256 摘要。1 与 1.0 等价;数组顺序有意义。内部算法名 orbit-json-v1,不宣称采用 RFC 8785。
- 同键不同内容返回 409 IDEMPOTENCY_CONFLICT;不同键即使 business_key 相同,也可以产生两个任务。
- 同一事务保存任务、受理事件、提交者审计、幂等身份与原始回执;验证或写入失败整体回滚。提交审计记录凭据与处理器/队列,不复制业务参数。
- 同键重报返回**首次操作的回执快照**;任务之后取消也不会改写该回执。获取最新状态调用 GET。
- 网络中断、客户端超时或 503 时,不能认定提交未发生;保持原键和原内容查询或重报。SDK 不自动换键或自动重试写操作。
- 幂等记录设置 90 天保留截止时间;当前尚无清理任务,存在的记录持续生效。后续清理不得缩短已承诺窗口。
SDK:`Client.SubmitTask(ctx, key, request)`。
## GET /v1/tasks/{task_id}
权限:tasks:read 和任务所在队列范围。200 的 data 直接为上方 task 对象,返回当前状态和完整参数/已保存结果。不存在、跨命名空间或不在该凭据队列范围时返回 404。
```sh
curl "$ORBIT_URL/v1/tasks/$TASK_ID" -H "Authorization: Bearer $ORBIT_TOKEN"
```
SDK:`Client.GetTask(ctx, taskID)`。
## GET /v1/tasks
权限:tasks:read。查询参数均只能出现一次:
| 参数 | 要求 |
| --- | --- |
| queue_id | 必填,有权限的队列 ID |
| state | 可选,取上述状态之一 |
| limit | 可选,默认 20;1–100 |
| cursor | 可选,上一页返回值;必须保持 queue_id/state 不变,最长 1024 字符 |
按 (created_at DESC, id DESC) 游标分页,不使用大 offset。不承诺跨页快照:读取过程中状态可能变化。列表省略 payload/result/retry 等大字段,返回摘要。
```sh
curl "$ORBIT_URL/v1/tasks?queue_id=$ORBIT_QUEUE_ID&state=WAITING&limit=20" \
-H "Authorization: Bearer $ORBIT_TOKEN"
```
```json
{
"request_id":"44444444444444444444444444444444",
"data":{
"items":[{
"id":"22222222222222222222222222222222",
"queue_id":"11111111111111111111111111111111",
"state":"WAITING","revision":"1",
"handler":"file.convert","handler_version":"v1","business_key":"conversion-001",
"available_at":"2026-09-17T00:00:00Z","deadline_at":"2026-09-18T00:00:00Z",
"attempt_count":0,"created_at":"2026-09-17T00:00:00Z","updated_at":"2026-09-17T00:00:00Z"
}]
}
}
```
还有下一页时返回 data.next_cursor。空结果为 items: []。SDK:`Client.ListTasks(ctx, request)`。
## POST /v1/tasks/{task_id}:cancel
权限:tasks:cancel 和队列范围。当前只允许取消 WAITING 状态且不归属流程的任务。LEASED/RUNNING 或流程任务暂时返回 409,后续由执行与流程控制实现安全取消。
```sh
curl "$ORBIT_URL/v1/tasks/$TASK_ID:cancel" \
-H "Authorization: Bearer $ORBIT_TOKEN" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: conversion-operation-001-cancel' \
--data '{"expected_revision":"1","reason":"source object was replaced"}'
```
expected_revision 必填,为非零 uint64 的十进制字符串;reason 必填且不能全为空白,最多 1024 个 Unicode 字符。服务端以版本和状态条件更新,只有一个竞争请求能成功。
200 的 data 为 `{"task":<完整最新任务>,"deduplicated":false}`。示例任务会变为 state=CANCELLED、revision="2",写入 cancel_requested_at/completed_at,并同事务追加取消事件及审计。首次取消之后使用相同键、原因和**原 expected_revision**重报,返回原取消回执且 deduplicated=true。换键重报已取消任务返回 409。
SDK:`Client.CancelTask(ctx, taskID, key, request)`。此操作不承诺回滚任何已发生的外部业务效果。
## 错误与回归
| HTTP / code | 处理 |
| --- | --- |
| 400 INVALID_ARGUMENT | 修正字段、时间、游标或幂等键 |
| 401 UNAUTHENTICATED | 检查凭据、到期/撤销和应用/命名空间状态 |
| 403 PERMISSION_DENIED | 检查动作或提交/列表所需队列范围 |
| 404 NOT_FOUND | 资源不存在或单资源读取范围不匹配 |
| 409 IDEMPOTENCY_CONFLICT | 原键已对应其他内容,不应盲目换键重试 |
| 409 STATE_CONFLICT | 当前状态或版本不允许操作,先查询 |
| 413 BODY_TOO_LARGE | 减小请求或改为传递业务对象引用 |
| 415 UNSUPPORTED_MEDIA_TYPE | 使用 application/json |
| 503 UNAVAILABLE | 结果可能未知;保留原幂等身份,有限退避后核对 |
```json
{"request_id":"44444444444444444444444444444444","error":{"code":"IDEMPOTENCY_CONFLICT","message":"idempotency key was used with different content"}}
```
批量受理、执行历史、人工重试和运行中取消尚未实现。完整可运行程序见 [SDK 任务示例](../../sdk/go/examples/tasks/main.go),已验证异常场景见 [P1 回归报告](../reports/phase-1.md)。SDK 忽略未知响应字段;更改已有字段语义需升级契约和回归。