122 lines
4.6 KiB
Go
122 lines
4.6 KiB
Go
package orbit
|
||
|
||
import (
|
||
"context"
|
||
"errors"
|
||
"time"
|
||
)
|
||
|
||
// MaxBatchTasks 是单次批量受理的项数上限,总请求还受512 KiB限制。
|
||
const MaxBatchTasks = 32
|
||
|
||
type SubmitTasksRequest struct {
|
||
// Items 1至32项独立任务,总请求不超过512 KiB,不保证整批原子性。
|
||
Items []SubmitTasksItem `json:"items"`
|
||
}
|
||
|
||
type SubmitTasksItem struct {
|
||
// IdempotencyKey 调用方持久化的稳定键,与SubmitTask共用幂等身份;批内不得重复。
|
||
IdempotencyKey string `json:"idempotency_key"`
|
||
// Task 原始任务参数,重报必须保留原值。
|
||
Task SubmitTaskRequest `json:"task"`
|
||
}
|
||
|
||
type BatchItemStatus string
|
||
|
||
const (
|
||
BatchAccepted BatchItemStatus = "ACCEPTED"
|
||
BatchRejected BatchItemStatus = "REJECTED"
|
||
BatchUnknown BatchItemStatus = "UNKNOWN"
|
||
BatchNotAttempted BatchItemStatus = "NOT_ATTEMPTED"
|
||
)
|
||
|
||
type BatchTaskReceipt struct {
|
||
// TaskID 已受理任务标识,实际结果需要查询或订阅。
|
||
TaskID string `json:"task_id"`
|
||
// QueueID 任务所属队列。
|
||
QueueID string `json:"queue_id"`
|
||
// Revision 原始受理版本,JSON为十进制字符串,不代表当前版本。
|
||
Revision uint64 `json:"revision,string"`
|
||
// CreatedAt 原始受理时间,UTC微秒精度。
|
||
CreatedAt time.Time `json:"created_at"`
|
||
// Deduplicated 是否复用相同身份的已有回执。
|
||
Deduplicated bool `json:"deduplicated"`
|
||
}
|
||
|
||
type BatchItemError struct {
|
||
// Code 稳定错误码;UNKNOWN结果使用UNAVAILABLE,不证明回滚。
|
||
Code string `json:"code"`
|
||
// Message 不包含数据库错误或输入的对外说明。
|
||
Message string `json:"message"`
|
||
// RetryAfterMS 建议最小退避毫秒数,零为未指定,SDK不自动重试。
|
||
RetryAfterMS uint32 `json:"retry_after_ms,omitempty"`
|
||
}
|
||
|
||
type SubmitTasksItemResult struct {
|
||
// Index 本次请求内从0开始的索引,与Items输入同序。
|
||
Index int `json:"index"`
|
||
// Status 每项独立状态;HTTP成功不表示全部任务已受理。
|
||
Status BatchItemStatus `json:"status"`
|
||
// Receipt 仅ACCEPTED存在,省略大参数以保证回执大小有界。
|
||
Receipt *BatchTaskReceipt `json:"receipt,omitempty"`
|
||
// Error 仅非ACCEPTED存在,调用方需分别处理明确拒绝、未知及未尝试。
|
||
Error *BatchItemError `json:"error,omitempty"`
|
||
}
|
||
|
||
type SubmitTasksResult struct {
|
||
// Items 与输入数量和顺序一致的逐项结果。
|
||
Items []SubmitTasksItemResult `json:"items"`
|
||
}
|
||
|
||
// SubmitTasks 逐项提交独立任务。返回error时整批可能已部分或全部受理,必须保留原键及参数核对。
|
||
// error为nil时仍需检查每项Status。此方法不会自动重试、拆批、生成新键或启动并发请求。
|
||
func (c *Client) SubmitTasks(ctx context.Context, in SubmitTasksRequest) (SubmitTasksResult, error) {
|
||
if len(in.Items) < 1 || len(in.Items) > MaxBatchTasks {
|
||
return SubmitTasksResult{}, errors.New("items must contain 1 to 32 tasks")
|
||
}
|
||
keys := make(map[string]struct{}, len(in.Items))
|
||
for _, item := range in.Items {
|
||
if err := validateIdempotencyKey(item.IdempotencyKey); err != nil {
|
||
return SubmitTasksResult{}, err
|
||
}
|
||
if _, exists := keys[item.IdempotencyKey]; exists {
|
||
return SubmitTasksResult{}, errors.New("item idempotency keys must be distinct within a batch")
|
||
}
|
||
keys[item.IdempotencyKey] = struct{}{}
|
||
}
|
||
var out SubmitTasksResult
|
||
if err := c.callJSON(ctx, "POST", "/v1/tasks:batch", "", in, &out); err != nil {
|
||
return SubmitTasksResult{}, err
|
||
}
|
||
// 拒绝不完整或错误关联的回执,避免调用方将截断的成功响应误认为全部已受理。
|
||
if len(out.Items) != len(in.Items) {
|
||
return SubmitTasksResult{}, errors.New("invalid Orbit batch result count")
|
||
}
|
||
for i, item := range out.Items {
|
||
valid := item.Index == i
|
||
switch item.Status {
|
||
case BatchAccepted:
|
||
r := item.Receipt
|
||
valid = valid && item.Error == nil && r != nil && validID(r.TaskID) && r.QueueID == in.Items[i].Task.QueueID && validID(r.QueueID) && r.Revision > 0 && !r.CreatedAt.IsZero()
|
||
case BatchRejected, BatchUnknown, BatchNotAttempted:
|
||
e := item.Error
|
||
valid = valid && item.Receipt == nil && e != nil && len(e.Code) > 0 && len(e.Code) <= 64 && len(e.Message) > 0 && len(e.Message) <= 1024 && e.RetryAfterMS <= 3600000
|
||
if valid && item.Status == BatchUnknown {
|
||
valid = e.Code == "UNAVAILABLE"
|
||
}
|
||
if valid && item.Status == BatchNotAttempted {
|
||
valid = e.Code == "BATCH_STOPPED"
|
||
}
|
||
if valid && item.Status == BatchRejected {
|
||
valid = e.Code != "UNAVAILABLE" && e.Code != "BATCH_STOPPED"
|
||
}
|
||
default:
|
||
valid = false
|
||
}
|
||
if !valid {
|
||
return SubmitTasksResult{}, errors.New("invalid Orbit batch item result; retry using original identities")
|
||
}
|
||
}
|
||
return out, nil
|
||
}
|