2025-07-02 16:17:59 +08:00
|
|
|
|
package saga
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"fmt"
|
|
|
|
|
|
"sync"
|
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
|
|
"go.uber.org/zap"
|
|
|
|
|
|
|
|
|
|
|
|
"tyapi-server/internal/shared/interfaces"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// SagaStatus Saga状态
|
|
|
|
|
|
type SagaStatus int
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
// StatusPending 等待中
|
|
|
|
|
|
StatusPending SagaStatus = iota
|
|
|
|
|
|
// StatusRunning 执行中
|
|
|
|
|
|
StatusRunning
|
|
|
|
|
|
// StatusCompleted 已完成
|
|
|
|
|
|
StatusCompleted
|
|
|
|
|
|
// StatusFailed 失败
|
|
|
|
|
|
StatusFailed
|
|
|
|
|
|
// StatusCompensating 补偿中
|
|
|
|
|
|
StatusCompensating
|
|
|
|
|
|
// StatusCompensated 已补偿
|
|
|
|
|
|
StatusCompensated
|
|
|
|
|
|
// StatusAborted 已中止
|
|
|
|
|
|
StatusAborted
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func (s SagaStatus) String() string {
|
|
|
|
|
|
switch s {
|
|
|
|
|
|
case StatusPending:
|
|
|
|
|
|
return "PENDING"
|
|
|
|
|
|
case StatusRunning:
|
|
|
|
|
|
return "RUNNING"
|
|
|
|
|
|
case StatusCompleted:
|
|
|
|
|
|
return "COMPLETED"
|
|
|
|
|
|
case StatusFailed:
|
|
|
|
|
|
return "FAILED"
|
|
|
|
|
|
case StatusCompensating:
|
|
|
|
|
|
return "COMPENSATING"
|
|
|
|
|
|
case StatusCompensated:
|
|
|
|
|
|
return "COMPENSATED"
|
|
|
|
|
|
case StatusAborted:
|
|
|
|
|
|
return "ABORTED"
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "UNKNOWN"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// StepStatus 步骤状态
|
|
|
|
|
|
type StepStatus int
|
|
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
|
// StepPending 等待执行
|
|
|
|
|
|
StepPending StepStatus = iota
|
|
|
|
|
|
// StepRunning 执行中
|
|
|
|
|
|
StepRunning
|
|
|
|
|
|
// StepCompleted 完成
|
|
|
|
|
|
StepCompleted
|
|
|
|
|
|
// StepFailed 失败
|
|
|
|
|
|
StepFailed
|
|
|
|
|
|
// StepCompensated 已补偿
|
|
|
|
|
|
StepCompensated
|
|
|
|
|
|
// StepSkipped 跳过
|
|
|
|
|
|
StepSkipped
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
func (s StepStatus) String() string {
|
|
|
|
|
|
switch s {
|
|
|
|
|
|
case StepPending:
|
|
|
|
|
|
return "PENDING"
|
|
|
|
|
|
case StepRunning:
|
|
|
|
|
|
return "RUNNING"
|
|
|
|
|
|
case StepCompleted:
|
|
|
|
|
|
return "COMPLETED"
|
|
|
|
|
|
case StepFailed:
|
|
|
|
|
|
return "FAILED"
|
|
|
|
|
|
case StepCompensated:
|
|
|
|
|
|
return "COMPENSATED"
|
|
|
|
|
|
case StepSkipped:
|
|
|
|
|
|
return "SKIPPED"
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "UNKNOWN"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SagaStep Saga步骤
|
|
|
|
|
|
type SagaStep struct {
|
|
|
|
|
|
Name string
|
|
|
|
|
|
Action func(ctx context.Context, data interface{}) error
|
|
|
|
|
|
Compensate func(ctx context.Context, data interface{}) error
|
|
|
|
|
|
Status StepStatus
|
|
|
|
|
|
Error error
|
|
|
|
|
|
StartTime time.Time
|
|
|
|
|
|
EndTime time.Time
|
|
|
|
|
|
RetryCount int
|
|
|
|
|
|
MaxRetries int
|
|
|
|
|
|
Timeout time.Duration
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SagaConfig Saga配置
|
|
|
|
|
|
type SagaConfig struct {
|
|
|
|
|
|
// 默认超时时间
|
|
|
|
|
|
DefaultTimeout time.Duration
|
|
|
|
|
|
// 默认重试次数
|
|
|
|
|
|
DefaultMaxRetries int
|
|
|
|
|
|
// 是否并行执行(当前只支持串行)
|
|
|
|
|
|
Parallel bool
|
|
|
|
|
|
// 事件发布器
|
|
|
|
|
|
EventBus interfaces.EventBus
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// DefaultSagaConfig 默认Saga配置
|
|
|
|
|
|
func DefaultSagaConfig() SagaConfig {
|
|
|
|
|
|
return SagaConfig{
|
|
|
|
|
|
DefaultTimeout: 30 * time.Second,
|
|
|
|
|
|
DefaultMaxRetries: 3,
|
|
|
|
|
|
Parallel: false,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Saga 分布式事务
|
|
|
|
|
|
type Saga struct {
|
|
|
|
|
|
ID string
|
|
|
|
|
|
Name string
|
|
|
|
|
|
Steps []*SagaStep
|
|
|
|
|
|
Status SagaStatus
|
|
|
|
|
|
Data interface{}
|
|
|
|
|
|
StartTime time.Time
|
|
|
|
|
|
EndTime time.Time
|
|
|
|
|
|
Error error
|
|
|
|
|
|
Config SagaConfig
|
|
|
|
|
|
logger *zap.Logger
|
|
|
|
|
|
mutex sync.RWMutex
|
|
|
|
|
|
currentStep int
|
|
|
|
|
|
result interface{}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewSaga 创建新的Saga
|
|
|
|
|
|
func NewSaga(id, name string, config SagaConfig, logger *zap.Logger) *Saga {
|
|
|
|
|
|
return &Saga{
|
|
|
|
|
|
ID: id,
|
|
|
|
|
|
Name: name,
|
|
|
|
|
|
Steps: make([]*SagaStep, 0),
|
|
|
|
|
|
Status: StatusPending,
|
|
|
|
|
|
Config: config,
|
|
|
|
|
|
logger: logger,
|
|
|
|
|
|
currentStep: -1,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AddStep 添加步骤
|
|
|
|
|
|
func (s *Saga) AddStep(name string, action, compensate func(ctx context.Context, data interface{}) error) *Saga {
|
|
|
|
|
|
step := &SagaStep{
|
|
|
|
|
|
Name: name,
|
|
|
|
|
|
Action: action,
|
|
|
|
|
|
Compensate: compensate,
|
|
|
|
|
|
Status: StepPending,
|
|
|
|
|
|
MaxRetries: s.Config.DefaultMaxRetries,
|
|
|
|
|
|
Timeout: s.Config.DefaultTimeout,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.mutex.Lock()
|
|
|
|
|
|
s.Steps = append(s.Steps, step)
|
|
|
|
|
|
s.mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Debug("Added step to saga",
|
|
|
|
|
|
zap.String("saga_id", s.ID),
|
|
|
|
|
|
zap.String("step_name", name))
|
|
|
|
|
|
|
|
|
|
|
|
return s
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AddStepWithConfig 添加带配置的步骤
|
|
|
|
|
|
func (s *Saga) AddStepWithConfig(name string, action, compensate func(ctx context.Context, data interface{}) error, maxRetries int, timeout time.Duration) *Saga {
|
|
|
|
|
|
step := &SagaStep{
|
|
|
|
|
|
Name: name,
|
|
|
|
|
|
Action: action,
|
|
|
|
|
|
Compensate: compensate,
|
|
|
|
|
|
Status: StepPending,
|
|
|
|
|
|
MaxRetries: maxRetries,
|
|
|
|
|
|
Timeout: timeout,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.mutex.Lock()
|
|
|
|
|
|
s.Steps = append(s.Steps, step)
|
|
|
|
|
|
s.mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Debug("Added step with config to saga",
|
|
|
|
|
|
zap.String("saga_id", s.ID),
|
|
|
|
|
|
zap.String("step_name", name),
|
|
|
|
|
|
zap.Int("max_retries", maxRetries),
|
|
|
|
|
|
zap.Duration("timeout", timeout))
|
|
|
|
|
|
|
|
|
|
|
|
return s
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Execute 执行Saga
|
|
|
|
|
|
func (s *Saga) Execute(ctx context.Context, data interface{}) error {
|
|
|
|
|
|
s.mutex.Lock()
|
|
|
|
|
|
if s.Status != StatusPending {
|
|
|
|
|
|
s.mutex.Unlock()
|
|
|
|
|
|
return fmt.Errorf("saga %s is not in pending status", s.ID)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.Status = StatusRunning
|
|
|
|
|
|
s.Data = data
|
|
|
|
|
|
s.StartTime = time.Now()
|
|
|
|
|
|
s.mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("Starting saga execution",
|
|
|
|
|
|
zap.String("saga_id", s.ID),
|
|
|
|
|
|
zap.String("saga_name", s.Name),
|
|
|
|
|
|
zap.Int("total_steps", len(s.Steps)))
|
|
|
|
|
|
|
|
|
|
|
|
// 发布Saga开始事件
|
|
|
|
|
|
s.publishEvent(ctx, "saga.started")
|
|
|
|
|
|
|
|
|
|
|
|
// 执行所有步骤
|
|
|
|
|
|
for i, step := range s.Steps {
|
|
|
|
|
|
s.mutex.Lock()
|
|
|
|
|
|
s.currentStep = i
|
|
|
|
|
|
s.mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
if err := s.executeStep(ctx, step, data); err != nil {
|
|
|
|
|
|
s.logger.Error("Step execution failed",
|
|
|
|
|
|
zap.String("saga_id", s.ID),
|
|
|
|
|
|
zap.String("step_name", step.Name),
|
|
|
|
|
|
zap.Error(err))
|
|
|
|
|
|
|
|
|
|
|
|
// 执行补偿
|
|
|
|
|
|
if compensateErr := s.compensate(ctx, i-1); compensateErr != nil {
|
|
|
|
|
|
s.logger.Error("Compensation failed",
|
|
|
|
|
|
zap.String("saga_id", s.ID),
|
|
|
|
|
|
zap.Error(compensateErr))
|
|
|
|
|
|
|
|
|
|
|
|
s.setStatus(StatusAborted)
|
|
|
|
|
|
s.publishEvent(ctx, "saga.aborted")
|
|
|
|
|
|
return fmt.Errorf("saga execution failed and compensation failed: %w", compensateErr)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.setStatus(StatusCompensated)
|
|
|
|
|
|
s.publishEvent(ctx, "saga.compensated")
|
|
|
|
|
|
return fmt.Errorf("saga execution failed: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 所有步骤成功完成
|
|
|
|
|
|
s.setStatus(StatusCompleted)
|
|
|
|
|
|
s.EndTime = time.Now()
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("Saga completed successfully",
|
|
|
|
|
|
zap.String("saga_id", s.ID),
|
|
|
|
|
|
zap.Duration("duration", s.EndTime.Sub(s.StartTime)))
|
|
|
|
|
|
|
|
|
|
|
|
s.publishEvent(ctx, "saga.completed")
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// executeStep 执行单个步骤
|
|
|
|
|
|
func (s *Saga) executeStep(ctx context.Context, step *SagaStep, data interface{}) error {
|
|
|
|
|
|
step.Status = StepRunning
|
|
|
|
|
|
step.StartTime = time.Now()
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Debug("Executing step",
|
|
|
|
|
|
zap.String("saga_id", s.ID),
|
|
|
|
|
|
zap.String("step_name", step.Name))
|
|
|
|
|
|
|
|
|
|
|
|
// 设置超时上下文
|
|
|
|
|
|
stepCtx, cancel := context.WithTimeout(ctx, step.Timeout)
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
|
|
|
|
// 重试逻辑
|
|
|
|
|
|
var lastErr error
|
|
|
|
|
|
for attempt := 0; attempt <= step.MaxRetries; attempt++ {
|
|
|
|
|
|
if attempt > 0 {
|
|
|
|
|
|
s.logger.Debug("Retrying step",
|
|
|
|
|
|
zap.String("saga_id", s.ID),
|
|
|
|
|
|
zap.String("step_name", step.Name),
|
|
|
|
|
|
zap.Int("attempt", attempt))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
err := step.Action(stepCtx, data)
|
|
|
|
|
|
if err == nil {
|
|
|
|
|
|
step.Status = StepCompleted
|
|
|
|
|
|
step.EndTime = time.Now()
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Debug("Step completed successfully",
|
|
|
|
|
|
zap.String("saga_id", s.ID),
|
|
|
|
|
|
zap.String("step_name", step.Name),
|
|
|
|
|
|
zap.Duration("duration", step.EndTime.Sub(step.StartTime)))
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
lastErr = err
|
|
|
|
|
|
step.RetryCount = attempt
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否应该重试
|
|
|
|
|
|
if attempt < step.MaxRetries {
|
|
|
|
|
|
select {
|
|
|
|
|
|
case <-stepCtx.Done():
|
|
|
|
|
|
// 上下文被取消,停止重试
|
|
|
|
|
|
break
|
|
|
|
|
|
case <-time.After(time.Duration(attempt+1) * 100 * time.Millisecond):
|
|
|
|
|
|
// 等待一段时间后重试
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 所有重试都失败了
|
|
|
|
|
|
step.Status = StepFailed
|
|
|
|
|
|
step.Error = lastErr
|
|
|
|
|
|
step.EndTime = time.Now()
|
|
|
|
|
|
|
|
|
|
|
|
return lastErr
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// compensate 执行补偿
|
|
|
|
|
|
func (s *Saga) compensate(ctx context.Context, fromStep int) error {
|
|
|
|
|
|
s.setStatus(StatusCompensating)
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("Starting compensation",
|
|
|
|
|
|
zap.String("saga_id", s.ID),
|
|
|
|
|
|
zap.Int("from_step", fromStep))
|
|
|
|
|
|
|
|
|
|
|
|
// 逆序执行补偿
|
|
|
|
|
|
for i := fromStep; i >= 0; i-- {
|
|
|
|
|
|
step := s.Steps[i]
|
|
|
|
|
|
|
|
|
|
|
|
// 只补偿已完成的步骤
|
|
|
|
|
|
if step.Status != StepCompleted {
|
|
|
|
|
|
step.Status = StepSkipped
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if step.Compensate == nil {
|
|
|
|
|
|
s.logger.Warn("No compensation function for step",
|
|
|
|
|
|
zap.String("saga_id", s.ID),
|
|
|
|
|
|
zap.String("step_name", step.Name))
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Debug("Compensating step",
|
|
|
|
|
|
zap.String("saga_id", s.ID),
|
|
|
|
|
|
zap.String("step_name", step.Name))
|
|
|
|
|
|
|
|
|
|
|
|
// 设置超时上下文
|
|
|
|
|
|
compensateCtx, cancel := context.WithTimeout(ctx, step.Timeout)
|
|
|
|
|
|
|
|
|
|
|
|
err := step.Compensate(compensateCtx, s.Data)
|
|
|
|
|
|
cancel()
|
|
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
s.logger.Error("Compensation failed for step",
|
|
|
|
|
|
zap.String("saga_id", s.ID),
|
|
|
|
|
|
zap.String("step_name", step.Name),
|
|
|
|
|
|
zap.Error(err))
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
step.Status = StepCompensated
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Debug("Step compensated successfully",
|
|
|
|
|
|
zap.String("saga_id", s.ID),
|
|
|
|
|
|
zap.String("step_name", step.Name))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
s.logger.Info("Compensation completed",
|
|
|
|
|
|
zap.String("saga_id", s.ID))
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// setStatus 设置状态
|
|
|
|
|
|
func (s *Saga) setStatus(status SagaStatus) {
|
|
|
|
|
|
s.mutex.Lock()
|
|
|
|
|
|
defer s.mutex.Unlock()
|
|
|
|
|
|
s.Status = status
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetStatus 获取状态
|
|
|
|
|
|
func (s *Saga) GetStatus() SagaStatus {
|
|
|
|
|
|
s.mutex.RLock()
|
|
|
|
|
|
defer s.mutex.RUnlock()
|
|
|
|
|
|
return s.Status
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetProgress 获取进度
|
|
|
|
|
|
func (s *Saga) GetProgress() SagaProgress {
|
|
|
|
|
|
s.mutex.RLock()
|
|
|
|
|
|
defer s.mutex.RUnlock()
|
|
|
|
|
|
|
|
|
|
|
|
completed := 0
|
|
|
|
|
|
for _, step := range s.Steps {
|
|
|
|
|
|
if step.Status == StepCompleted {
|
|
|
|
|
|
completed++
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var percentage float64
|
|
|
|
|
|
if len(s.Steps) > 0 {
|
|
|
|
|
|
percentage = float64(completed) / float64(len(s.Steps)) * 100
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return SagaProgress{
|
|
|
|
|
|
SagaID: s.ID,
|
|
|
|
|
|
Status: s.Status.String(),
|
|
|
|
|
|
TotalSteps: len(s.Steps),
|
|
|
|
|
|
CompletedSteps: completed,
|
|
|
|
|
|
CurrentStep: s.currentStep + 1,
|
|
|
|
|
|
PercentComplete: percentage,
|
|
|
|
|
|
StartTime: s.StartTime,
|
|
|
|
|
|
Duration: time.Since(s.StartTime),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetStepStatus 获取所有步骤状态
|
|
|
|
|
|
func (s *Saga) GetStepStatus() []StepProgress {
|
|
|
|
|
|
s.mutex.RLock()
|
|
|
|
|
|
defer s.mutex.RUnlock()
|
|
|
|
|
|
|
|
|
|
|
|
progress := make([]StepProgress, len(s.Steps))
|
|
|
|
|
|
for i, step := range s.Steps {
|
|
|
|
|
|
progress[i] = StepProgress{
|
|
|
|
|
|
Name: step.Name,
|
|
|
|
|
|
Status: step.Status.String(),
|
|
|
|
|
|
RetryCount: step.RetryCount,
|
|
|
|
|
|
StartTime: step.StartTime,
|
|
|
|
|
|
EndTime: step.EndTime,
|
|
|
|
|
|
Duration: step.EndTime.Sub(step.StartTime),
|
|
|
|
|
|
Error: "",
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if step.Error != nil {
|
|
|
|
|
|
progress[i].Error = step.Error.Error()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return progress
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// publishEvent 发布事件
|
|
|
|
|
|
func (s *Saga) publishEvent(ctx context.Context, eventType string) {
|
|
|
|
|
|
if s.Config.EventBus == nil {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
event := &SagaEvent{
|
|
|
|
|
|
SagaID: s.ID,
|
|
|
|
|
|
SagaName: s.Name,
|
|
|
|
|
|
EventType: eventType,
|
|
|
|
|
|
Status: s.Status.String(),
|
|
|
|
|
|
Timestamp: time.Now(),
|
|
|
|
|
|
Data: s.Data,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 这里应该实现Event接口,简化处理
|
|
|
|
|
|
_ = event
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SagaProgress Saga进度
|
|
|
|
|
|
type SagaProgress struct {
|
|
|
|
|
|
SagaID string `json:"saga_id"`
|
|
|
|
|
|
Status string `json:"status"`
|
|
|
|
|
|
TotalSteps int `json:"total_steps"`
|
|
|
|
|
|
CompletedSteps int `json:"completed_steps"`
|
|
|
|
|
|
CurrentStep int `json:"current_step"`
|
|
|
|
|
|
PercentComplete float64 `json:"percent_complete"`
|
|
|
|
|
|
StartTime time.Time `json:"start_time"`
|
|
|
|
|
|
Duration time.Duration `json:"duration"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// StepProgress 步骤进度
|
|
|
|
|
|
type StepProgress struct {
|
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
|
Status string `json:"status"`
|
|
|
|
|
|
RetryCount int `json:"retry_count"`
|
|
|
|
|
|
StartTime time.Time `json:"start_time"`
|
|
|
|
|
|
EndTime time.Time `json:"end_time"`
|
|
|
|
|
|
Duration time.Duration `json:"duration"`
|
|
|
|
|
|
Error string `json:"error,omitempty"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SagaEvent Saga事件
|
|
|
|
|
|
type SagaEvent struct {
|
|
|
|
|
|
SagaID string `json:"saga_id"`
|
|
|
|
|
|
SagaName string `json:"saga_name"`
|
|
|
|
|
|
EventType string `json:"event_type"`
|
|
|
|
|
|
Status string `json:"status"`
|
|
|
|
|
|
Timestamp time.Time `json:"timestamp"`
|
|
|
|
|
|
Data interface{} `json:"data,omitempty"`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SagaManager Saga管理器
|
|
|
|
|
|
type SagaManager struct {
|
|
|
|
|
|
sagas map[string]*Saga
|
|
|
|
|
|
logger *zap.Logger
|
|
|
|
|
|
mutex sync.RWMutex
|
|
|
|
|
|
config SagaConfig
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewSagaManager 创建Saga管理器
|
|
|
|
|
|
func NewSagaManager(config SagaConfig, logger *zap.Logger) *SagaManager {
|
|
|
|
|
|
return &SagaManager{
|
|
|
|
|
|
sagas: make(map[string]*Saga),
|
|
|
|
|
|
logger: logger,
|
|
|
|
|
|
config: config,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// CreateSaga 创建Saga
|
|
|
|
|
|
func (sm *SagaManager) CreateSaga(id, name string) *Saga {
|
|
|
|
|
|
saga := NewSaga(id, name, sm.config, sm.logger.Named("saga"))
|
|
|
|
|
|
|
|
|
|
|
|
sm.mutex.Lock()
|
|
|
|
|
|
sm.sagas[id] = saga
|
|
|
|
|
|
sm.mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
sm.logger.Info("Created saga",
|
|
|
|
|
|
zap.String("saga_id", id),
|
|
|
|
|
|
zap.String("saga_name", name))
|
|
|
|
|
|
|
|
|
|
|
|
return saga
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetSaga 获取Saga
|
|
|
|
|
|
func (sm *SagaManager) GetSaga(id string) (*Saga, bool) {
|
|
|
|
|
|
sm.mutex.RLock()
|
|
|
|
|
|
defer sm.mutex.RUnlock()
|
|
|
|
|
|
|
|
|
|
|
|
saga, exists := sm.sagas[id]
|
|
|
|
|
|
return saga, exists
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ListSagas 列出所有Saga
|
|
|
|
|
|
func (sm *SagaManager) ListSagas() []*Saga {
|
|
|
|
|
|
sm.mutex.RLock()
|
|
|
|
|
|
defer sm.mutex.RUnlock()
|
|
|
|
|
|
|
|
|
|
|
|
sagas := make([]*Saga, 0, len(sm.sagas))
|
|
|
|
|
|
for _, saga := range sm.sagas {
|
|
|
|
|
|
sagas = append(sagas, saga)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return sagas
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetSagaProgress 获取Saga进度
|
|
|
|
|
|
func (sm *SagaManager) GetSagaProgress(id string) (SagaProgress, bool) {
|
|
|
|
|
|
saga, exists := sm.GetSaga(id)
|
|
|
|
|
|
if !exists {
|
|
|
|
|
|
return SagaProgress{}, false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return saga.GetProgress(), true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RemoveSaga 移除Saga
|
|
|
|
|
|
func (sm *SagaManager) RemoveSaga(id string) {
|
|
|
|
|
|
sm.mutex.Lock()
|
|
|
|
|
|
defer sm.mutex.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
delete(sm.sagas, id)
|
|
|
|
|
|
sm.logger.Debug("Removed saga", zap.String("saga_id", id))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetStats 获取统计信息
|
|
|
|
|
|
func (sm *SagaManager) GetStats() map[string]interface{} {
|
|
|
|
|
|
sm.mutex.RLock()
|
|
|
|
|
|
defer sm.mutex.RUnlock()
|
|
|
|
|
|
|
|
|
|
|
|
statusCount := make(map[string]int)
|
|
|
|
|
|
for _, saga := range sm.sagas {
|
|
|
|
|
|
status := saga.GetStatus().String()
|
|
|
|
|
|
statusCount[status]++
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return map[string]interface{}{
|
|
|
|
|
|
"total_sagas": len(sm.sagas),
|
|
|
|
|
|
"status_count": statusCount,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 实现Service接口
|
|
|
|
|
|
|
|
|
|
|
|
// Name 返回服务名称
|
|
|
|
|
|
func (sm *SagaManager) Name() string {
|
|
|
|
|
|
return "saga-manager"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize 初始化服务
|
|
|
|
|
|
func (sm *SagaManager) Initialize(ctx context.Context) error {
|
|
|
|
|
|
sm.logger.Info("Saga manager service initialized")
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// HealthCheck 健康检查
|
|
|
|
|
|
func (sm *SagaManager) HealthCheck(ctx context.Context) error {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Shutdown 关闭服务
|
|
|
|
|
|
func (sm *SagaManager) Shutdown(ctx context.Context) error {
|
|
|
|
|
|
sm.logger.Info("Saga manager service shutdown")
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2025-07-20 20:53:26 +08:00
|
|
|
|
|
|
|
|
|
|
// ==================== Saga构建器 ====================
|
|
|
|
|
|
|
|
|
|
|
|
// StepBuilder Saga步骤构建器
|
|
|
|
|
|
type StepBuilder struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
action func(ctx context.Context, data interface{}) error
|
|
|
|
|
|
compensate func(ctx context.Context, data interface{}) error
|
|
|
|
|
|
timeout time.Duration
|
|
|
|
|
|
maxRetries int
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Step 创建步骤构建器
|
|
|
|
|
|
func Step(name string) *StepBuilder {
|
|
|
|
|
|
return &StepBuilder{
|
|
|
|
|
|
name: name,
|
|
|
|
|
|
timeout: 30 * time.Second,
|
|
|
|
|
|
maxRetries: 3,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Action 设置正向操作
|
|
|
|
|
|
func (sb *StepBuilder) Action(action func(ctx context.Context, data interface{}) error) *StepBuilder {
|
|
|
|
|
|
sb.action = action
|
|
|
|
|
|
return sb
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Compensate 设置补偿操作
|
|
|
|
|
|
func (sb *StepBuilder) Compensate(compensate func(ctx context.Context, data interface{}) error) *StepBuilder {
|
|
|
|
|
|
sb.compensate = compensate
|
|
|
|
|
|
return sb
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Timeout 设置超时时间
|
|
|
|
|
|
func (sb *StepBuilder) Timeout(timeout time.Duration) *StepBuilder {
|
|
|
|
|
|
sb.timeout = timeout
|
|
|
|
|
|
return sb
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// MaxRetries 设置最大重试次数
|
|
|
|
|
|
func (sb *StepBuilder) MaxRetries(maxRetries int) *StepBuilder {
|
|
|
|
|
|
sb.maxRetries = maxRetries
|
|
|
|
|
|
return sb
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Build 构建Saga步骤
|
|
|
|
|
|
func (sb *StepBuilder) Build() *SagaStep {
|
|
|
|
|
|
return &SagaStep{
|
|
|
|
|
|
Name: sb.name,
|
|
|
|
|
|
Action: sb.action,
|
|
|
|
|
|
Compensate: sb.compensate,
|
|
|
|
|
|
Status: StepPending,
|
|
|
|
|
|
MaxRetries: sb.maxRetries,
|
|
|
|
|
|
Timeout: sb.timeout,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SagaBuilder Saga构建器
|
|
|
|
|
|
type SagaBuilder struct {
|
|
|
|
|
|
manager *SagaManager
|
|
|
|
|
|
saga *Saga
|
|
|
|
|
|
steps []*SagaStep
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewSagaBuilder 创建Saga构建器
|
|
|
|
|
|
func NewSagaBuilder(manager *SagaManager, id, name string) *SagaBuilder {
|
|
|
|
|
|
saga := manager.CreateSaga(id, name)
|
|
|
|
|
|
return &SagaBuilder{
|
|
|
|
|
|
manager: manager,
|
|
|
|
|
|
saga: saga,
|
|
|
|
|
|
steps: make([]*SagaStep, 0),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AddStep 添加步骤
|
|
|
|
|
|
func (sb *SagaBuilder) AddStep(step *SagaStep) *SagaBuilder {
|
|
|
|
|
|
sb.steps = append(sb.steps, step)
|
|
|
|
|
|
sb.saga.AddStepWithConfig(step.Name, step.Action, step.Compensate, step.MaxRetries, step.Timeout)
|
|
|
|
|
|
return sb
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// AddSteps 批量添加步骤
|
|
|
|
|
|
func (sb *SagaBuilder) AddSteps(steps ...*SagaStep) *SagaBuilder {
|
|
|
|
|
|
for _, step := range steps {
|
|
|
|
|
|
sb.AddStep(step)
|
|
|
|
|
|
}
|
|
|
|
|
|
return sb
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Execute 执行Saga
|
|
|
|
|
|
func (sb *SagaBuilder) Execute(ctx context.Context, data interface{}) error {
|
|
|
|
|
|
return sb.saga.Execute(ctx, data)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// GetSaga 获取Saga实例
|
|
|
|
|
|
func (sb *SagaBuilder) GetSaga() *Saga {
|
|
|
|
|
|
return sb.saga
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 便捷函数
|
|
|
|
|
|
|
|
|
|
|
|
// CreateSaga 快速创建Saga
|
|
|
|
|
|
func CreateSaga(manager *SagaManager, name string) *SagaBuilder {
|
|
|
|
|
|
id := fmt.Sprintf("%s_%d", name, time.Now().Unix())
|
|
|
|
|
|
return NewSagaBuilder(manager, id, name)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ExecuteSaga 快速执行Saga
|
|
|
|
|
|
func ExecuteSaga(manager *SagaManager, name string, steps []*SagaStep, data interface{}, logger *zap.Logger) error {
|
|
|
|
|
|
saga := CreateSaga(manager, name)
|
|
|
|
|
|
saga.AddSteps(steps...)
|
|
|
|
|
|
|
|
|
|
|
|
logger.Info("开始执行Saga",
|
|
|
|
|
|
zap.String("saga_name", name),
|
|
|
|
|
|
zap.Int("steps_count", len(steps)))
|
|
|
|
|
|
|
|
|
|
|
|
return saga.Execute(context.Background(), data)
|
|
|
|
|
|
}
|