All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s
104 lines
3.5 KiB
Go
104 lines
3.5 KiB
Go
// Package asynctask 定义业务异步任务的统一公开契约。
|
||
package asynctask
|
||
|
||
import (
|
||
stderrors "errors"
|
||
"reflect"
|
||
"time"
|
||
)
|
||
|
||
const (
|
||
// StatusPending 表示任务待处理。
|
||
StatusPending = 1
|
||
// StatusProcessing 表示任务处理中。
|
||
StatusProcessing = 2
|
||
// StatusCompleted 表示任务已到达业务处理终点。
|
||
StatusCompleted = 3
|
||
// StatusFailed 表示任务整体无法执行到业务终点。
|
||
StatusFailed = 4
|
||
// StatusCancelled 表示业务明确支持且已经取消任务。
|
||
StatusCancelled = 5
|
||
)
|
||
|
||
var statusNames = map[int]string{
|
||
StatusPending: "待处理",
|
||
StatusProcessing: "处理中",
|
||
StatusCompleted: "已完成",
|
||
StatusFailed: "已失败",
|
||
StatusCancelled: "已取消",
|
||
}
|
||
|
||
// Projection 是跨业务统一的任务公开投影。
|
||
type Projection struct {
|
||
TaskID string `json:"task_id"`
|
||
Status int `json:"status"`
|
||
StatusName string `json:"status_name"`
|
||
TotalCount int `json:"total_count"`
|
||
SuccessCount int `json:"success_count"`
|
||
FailedCount int `json:"failed_count"`
|
||
Progress int `json:"progress"`
|
||
ErrorCode string `json:"error_code,omitempty"`
|
||
ErrorSummary string `json:"error_summary,omitempty"`
|
||
StartedAt *time.Time `json:"started_at,omitempty"`
|
||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||
UpdatedAt time.Time `json:"updated_at"`
|
||
}
|
||
|
||
// TerminalResult 是构造终态投影所需的业务结果。
|
||
type TerminalResult struct {
|
||
TaskID string
|
||
Status int
|
||
TotalCount int
|
||
SuccessCount int
|
||
FailedCount int
|
||
ErrorCode string
|
||
ErrorSummary string
|
||
StartedAt *time.Time
|
||
CompletedAt *time.Time
|
||
UpdatedAt time.Time
|
||
}
|
||
|
||
// StatusName 返回固定任务状态的中文名称。
|
||
func StatusName(status int) string {
|
||
return statusNames[status]
|
||
}
|
||
|
||
// IsTerminal 判断状态是否为统一终态。
|
||
func IsTerminal(status int) bool {
|
||
return status == StatusCompleted || status == StatusFailed || status == StatusCancelled
|
||
}
|
||
|
||
// NewTerminalProjection 校验终态守恒并构造公开投影。
|
||
func NewTerminalProjection(result TerminalResult) (Projection, error) {
|
||
if !IsTerminal(result.Status) {
|
||
return Projection{}, stderrors.New("任务状态不是终态")
|
||
}
|
||
if result.TotalCount < 0 || result.SuccessCount < 0 || result.FailedCount < 0 {
|
||
return Projection{}, stderrors.New("任务结果计数不能为负数")
|
||
}
|
||
if result.Status == StatusCompleted && result.TotalCount != result.SuccessCount+result.FailedCount {
|
||
return Projection{}, stderrors.New("已完成任务的结果计数不守恒")
|
||
}
|
||
return Projection{
|
||
TaskID: result.TaskID, Status: result.Status, StatusName: StatusName(result.Status),
|
||
TotalCount: result.TotalCount, SuccessCount: result.SuccessCount, FailedCount: result.FailedCount,
|
||
Progress: 100, ErrorCode: result.ErrorCode, ErrorSummary: result.ErrorSummary,
|
||
StartedAt: result.StartedAt, CompletedAt: result.CompletedAt, UpdatedAt: result.UpdatedAt,
|
||
}, nil
|
||
}
|
||
|
||
// ValidatePayload 确保统一队列客户端接收 struct 或 map,而非预序列化字节。
|
||
func ValidatePayload(payload any) error {
|
||
if payload == nil {
|
||
return stderrors.New("任务载荷不能为空")
|
||
}
|
||
kind := reflect.TypeOf(payload).Kind()
|
||
if kind != reflect.Struct && kind != reflect.Map && kind != reflect.Ptr {
|
||
return stderrors.New("任务载荷必须是结构体或 map")
|
||
}
|
||
if kind == reflect.Ptr && reflect.TypeOf(payload).Elem().Kind() != reflect.Struct {
|
||
return stderrors.New("任务载荷指针必须指向结构体")
|
||
}
|
||
return nil
|
||
}
|