// 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 }