package batch import ( "context" "errors" "github.com/gogf/gf/v2/os/gtime" "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/entity" ) const ( Type3 = 3 Type4 = 4 Type11 = 11 Type12 = 12 Type40 = 40 Type41 = 41 Type101 = 101 Type102 = 102 Type103 = 103 Type104 = 104 Type105 = 105 Type106 = 106 Type107 = 107 Type108 = 108 Type109 = 109 Type110 = 110 AsyncStatusPending = 1 AsyncStatusProcessing = 2 AsyncStatusSuccess = 3 AsyncStatusFailed = 4 RunResultRunning = 1 RunResultSuccess = 2 RunResultFailed = 3 DetailStatusPending = 0 DetailStatusRunning = 1 DetailStatusSuccess = 2 DetailStatusFailed = 3 ) type Message struct { BatchNo string `json:"batchNo"` BatchType int `json:"batchType"` FailureReason string `json:"failureReason,omitempty"` Retryable bool `json:"retryable,omitempty"` } // RetryableError marks an executor failure that may succeed after delayed redelivery. // The batch remains in processing until retries are exhausted and finalized. type RetryableError struct { Err error } func (e *RetryableError) Error() string { if e == nil || e.Err == nil { return "retryable batch execution error" } return e.Err.Error() } func (e *RetryableError) Unwrap() error { if e == nil { return nil } return e.Err } func Retryable(err error) error { if err == nil { return nil } if IsRetryable(err) { return err } return &RetryableError{Err: err} } func IsRetryable(err error) bool { var retryableErr *RetryableError return errors.As(err, &retryableErr) } // FailureMarkError 表示执行已结束,但失败终态尚未成功写入数据库。 // 消费者重投这类消息时只能补写失败终态,不能再次执行具体业务。 type FailureMarkError struct { Reason string Err error } func (e *FailureMarkError) Error() string { return "mark batch failed after executor error: " + e.Reason + ": " + e.Err.Error() } func (e *FailureMarkError) Unwrap() error { return e.Err } type Executor interface { Execute(ctx context.Context, batchNo string) error } type ProcessingExecutor interface { CanExecuteProcessing(ctx context.Context, main *entity.BatchMain) (bool, error) } // SelfClaimingProcessingExecutor marks executors that perform their own // processing-state CAS before executing a re-entry phase. type SelfClaimingProcessingExecutor interface { ClaimsProcessingReentry() bool } // DurableFailureHandler takes ownership before any generic MarkFailed fallback. type DurableFailureHandler interface { ProtectExecutionFailure(ctx context.Context, batchNo string, batchType int, reason string) (bool, error) } type ExecutionFailureHandler interface { HandleExecutionFailure(ctx context.Context, batchNo string, batchType int, reason string) error } type ProcessingClaimer interface { ReclaimProcessing(ctx context.Context, batchNo string, batchType int, startedAt *gtime.Time) (bool, error) } type Dispatcher struct { executors map[int]Executor } func NewDispatcher(executor Executor) *Dispatcher { return &Dispatcher{executors: map[int]Executor{ Type103: executor, Type104: executor, }} } func (d *Dispatcher) Register(batchType int, executor Executor) { if d == nil || executor == nil { return } d.executors[batchType] = executor } func (d *Dispatcher) Executor(batchType int) (Executor, bool) { if d == nil { return nil, false } executor, ok := d.executors[batchType] return executor, ok && executor != nil } func (d *Dispatcher) Has(batchType int) bool { _, ok := d.Executor(batchType) return ok }