Files
junhong_cmp_fiber/internal/infrastructure/asynctask/store.go
break 17782d5f8e
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 9m20s
实现七月迭代公共技术基础
2026-07-23 17:52:48 +09:00

118 lines
4.8 KiB
Go
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.
// Package asynctask 提供业务自有任务表复用的 PostgreSQL 条件更新 Adapter。
package asynctask
import (
"context"
stderrors "errors"
"regexp"
"time"
"gorm.io/gorm"
contract "github.com/break/junhong_cmp_fiber/pkg/asynctask"
)
var identifierPattern = regexp.MustCompile(`^[a-z][a-z0-9_]*$`)
// Definition 描述业务自有任务表如何映射统一字段。
type Definition struct {
Table string
IDColumn string
StatusColumn string
LeaseOwnerColumn string
LeaseExpiresColumn string
TotalColumn string
SuccessColumn string
FailedColumn string
ProgressColumn string
ErrorCodeColumn string
ErrorSummaryColumn string
StartedAtColumn string
CompletedAtColumn string
UpdatedAtColumn string
}
// Store 对一个已注册的业务任务表执行统一条件更新。
type Store struct {
db *gorm.DB
definition Definition
}
// NewStore 创建业务任务表 Adapter不创建或迁移任何万能任务表。
func NewStore(db *gorm.DB, definition Definition) (*Store, error) {
columns := []string{
definition.Table, definition.IDColumn, definition.StatusColumn, definition.LeaseOwnerColumn,
definition.LeaseExpiresColumn, definition.TotalColumn, definition.SuccessColumn,
definition.FailedColumn, definition.ProgressColumn, definition.ErrorCodeColumn,
definition.ErrorSummaryColumn, definition.StartedAtColumn, definition.CompletedAtColumn, definition.UpdatedAtColumn,
}
for _, identifier := range columns {
if !identifierPattern.MatchString(identifier) {
return nil, stderrors.New("异步任务表或字段标识不合法")
}
}
return &Store{db: db, definition: definition}, nil
}
// Claim 使用预期状态和过期租约条件领取任务。
func (s *Store) Claim(ctx context.Context, id any, owner string, now time.Time, duration time.Duration) (bool, error) {
if owner == "" || duration <= 0 {
return false, stderrors.New("任务租约所有者和时长不能为空")
}
d := s.definition
result := s.db.WithContext(ctx).Table(d.Table).
Where(d.IDColumn+" = ? AND ("+d.StatusColumn+" = ? OR ("+d.StatusColumn+" = ? AND "+d.LeaseExpiresColumn+" <= ?))",
id, contract.StatusPending, contract.StatusProcessing, now).
Updates(map[string]any{
d.StatusColumn: contract.StatusProcessing, d.LeaseOwnerColumn: owner,
d.LeaseExpiresColumn: now.Add(duration), d.StartedAtColumn: gorm.Expr("COALESCE("+d.StartedAtColumn+", ?)", now),
d.UpdatedAtColumn: now,
})
return result.RowsAffected == 1, result.Error
}
// Renew 只允许当前所有者在租约有效时续期处理中任务。
func (s *Store) Renew(ctx context.Context, id any, owner string, now time.Time, duration time.Duration) (bool, error) {
if owner == "" || duration <= 0 {
return false, stderrors.New("任务租约所有者和时长不能为空")
}
d := s.definition
updated := s.db.WithContext(ctx).Table(d.Table).
Where(d.IDColumn+" = ? AND "+d.StatusColumn+" = ? AND "+d.LeaseOwnerColumn+" = ? AND "+d.LeaseExpiresColumn+" > ?",
id, contract.StatusProcessing, owner, now).
Updates(map[string]any{d.LeaseExpiresColumn: now.Add(duration), d.UpdatedAtColumn: now})
return updated.RowsAffected == 1, updated.Error
}
// Finish 只允许有效租约所有者把处理中任务推进到统一终态。
func (s *Store) Finish(ctx context.Context, id any, owner string, result contract.TerminalResult, now time.Time) (bool, error) {
projection, err := contract.NewTerminalProjection(result)
if err != nil {
return false, err
}
d := s.definition
updated := s.db.WithContext(ctx).Table(d.Table).
Where(d.IDColumn+" = ? AND "+d.StatusColumn+" = ? AND "+d.LeaseOwnerColumn+" = ? AND "+d.LeaseExpiresColumn+" > ?",
id, contract.StatusProcessing, owner, now).
Updates(map[string]any{
d.StatusColumn: projection.Status, d.TotalColumn: projection.TotalCount,
d.SuccessColumn: projection.SuccessCount, d.FailedColumn: projection.FailedCount,
d.ProgressColumn: projection.Progress, d.ErrorCodeColumn: projection.ErrorCode,
d.ErrorSummaryColumn: projection.ErrorSummary, d.CompletedAtColumn: now,
d.LeaseOwnerColumn: nil, d.LeaseExpiresColumn: nil, d.UpdatedAtColumn: now,
})
return updated.RowsAffected == 1, updated.Error
}
// Cancel 只允许业务明确支持时从待处理或处理中进入已取消。
func (s *Store) Cancel(ctx context.Context, id any, now time.Time) (bool, error) {
d := s.definition
updated := s.db.WithContext(ctx).Table(d.Table).
Where(d.IDColumn+" = ? AND "+d.StatusColumn+" IN ?", id, []int{contract.StatusPending, contract.StatusProcessing}).
Updates(map[string]any{
d.StatusColumn: contract.StatusCancelled, d.ProgressColumn: 100,
d.CompletedAtColumn: now, d.LeaseOwnerColumn: nil, d.LeaseExpiresColumn: nil, d.UpdatedAtColumn: now,
})
return updated.RowsAffected == 1, updated.Error
}