113 lines
4.6 KiB
Go
113 lines
4.6 KiB
Go
// Package approval 实现通用审批实例的 PostgreSQL 持久化 Adapter。
|
|
package approval
|
|
|
|
import (
|
|
"context"
|
|
stderrors "errors"
|
|
|
|
"gorm.io/datatypes"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
|
|
approvaldomain "github.com/break/junhong_cmp_fiber/internal/domain/approval"
|
|
"github.com/break/junhong_cmp_fiber/internal/model"
|
|
"github.com/break/junhong_cmp_fiber/pkg/errors"
|
|
)
|
|
|
|
// Repository 实现通用审批实例写侧仓储。
|
|
type Repository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// RepositoryProvider 为 Application 用例创建事务作用域 Repository。
|
|
type RepositoryProvider struct{}
|
|
|
|
// NewRepositoryProvider 创建通用审批 Repository Provider。
|
|
func NewRepositoryProvider() *RepositoryProvider {
|
|
return &RepositoryProvider{}
|
|
}
|
|
|
|
// ForDB 使用指定数据库会话创建领域 Repository。
|
|
func (p *RepositoryProvider) ForDB(db *gorm.DB) approvaldomain.Repository {
|
|
return NewRepository(db)
|
|
}
|
|
|
|
// GetForUpdate 在当前事务中锁定并读取通用审批实例。
|
|
func (r *Repository) GetForUpdate(ctx context.Context, instanceID uint) (*approvaldomain.Instance, error) {
|
|
if r == nil || r.db == nil {
|
|
return nil, errors.New(errors.CodeInternalError, "通用审批实例 Repository 未配置数据库会话")
|
|
}
|
|
var record model.ApprovalInstance
|
|
err := r.db.WithContext(ctx).Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ?", instanceID).First(&record).Error
|
|
if err == gorm.ErrRecordNotFound {
|
|
return nil, errors.New(errors.CodeForbidden, "无权限操作该资源或资源不存在")
|
|
}
|
|
if err != nil {
|
|
return nil, errors.Wrap(errors.CodeDatabaseError, err, "读取通用审批实例失败")
|
|
}
|
|
return instanceFromModel(record), nil
|
|
}
|
|
|
|
// SaveDecision 以旧状态和旧版本为条件保存标准决策,防止并发重复终态。
|
|
func (r *Repository) SaveDecision(
|
|
ctx context.Context,
|
|
instance *approvaldomain.Instance,
|
|
expectedStatus int,
|
|
expectedVersion int,
|
|
) (bool, error) {
|
|
if r == nil || r.db == nil || instance == nil {
|
|
return false, errors.New(errors.CodeInternalError, "通用审批实例 Repository 未完整配置")
|
|
}
|
|
result := r.db.WithContext(ctx).Model(&model.ApprovalInstance{}).
|
|
Where("id = ? AND status = ? AND version = ?", instance.ID, expectedStatus, expectedVersion).
|
|
Updates(map[string]any{
|
|
"status": instance.Status, "decision_snapshot": datatypes.JSON(instance.DecisionSnapshot),
|
|
"status_changed_at": instance.StatusChangedAt, "version": instance.Version, "updated_at": instance.UpdatedAt,
|
|
})
|
|
if result.Error != nil {
|
|
return false, errors.Wrap(errors.CodeDatabaseError, result.Error, "保存通用审批决策失败")
|
|
}
|
|
return result.RowsAffected == 1, nil
|
|
}
|
|
|
|
func instanceFromModel(record model.ApprovalInstance) *approvaldomain.Instance {
|
|
return &approvaldomain.Instance{
|
|
ID: record.ID, BusinessType: record.BusinessType, BusinessID: record.BusinessID,
|
|
SubmitterAccountID: record.SubmitterAccountID, SubmitterSnapshot: append([]byte(nil), record.SubmitterSnapshot...),
|
|
Provider: record.Provider, ExternalRef: record.ExternalRef, Status: record.Status,
|
|
RequestSnapshot: append([]byte(nil), record.RequestSnapshot...),
|
|
DecisionSnapshot: append([]byte(nil), record.DecisionSnapshot...), CorrelationID: record.CorrelationID,
|
|
Version: record.Version, StatusChangedAt: record.StatusChangedAt,
|
|
CreatedAt: record.CreatedAt, UpdatedAt: record.UpdatedAt,
|
|
}
|
|
}
|
|
|
|
// NewRepository 创建通用审批实例 PostgreSQL Repository。
|
|
func NewRepository(db *gorm.DB) *Repository {
|
|
return &Repository{db: db}
|
|
}
|
|
|
|
// Create 使用 Repository 持有的数据库会话创建一条唯一通用审批实例。
|
|
// 需要原子写入业务事实时,调用方必须以当前 GORM 事务创建 Repository。
|
|
func (r *Repository) Create(ctx context.Context, instance *approvaldomain.Instance) error {
|
|
if r == nil || r.db == nil {
|
|
return stderrors.New("通用审批实例 Repository 未配置数据库会话")
|
|
}
|
|
if instance == nil {
|
|
return stderrors.New("通用审批实例不能为空")
|
|
}
|
|
record := model.ApprovalInstance{
|
|
BusinessType: instance.BusinessType, BusinessID: instance.BusinessID,
|
|
SubmitterAccountID: instance.SubmitterAccountID, SubmitterSnapshot: datatypes.JSON(instance.SubmitterSnapshot),
|
|
Provider: instance.Provider, ExternalRef: instance.ExternalRef, Status: instance.Status,
|
|
RequestSnapshot: datatypes.JSON(instance.RequestSnapshot), DecisionSnapshot: datatypes.JSON(instance.DecisionSnapshot),
|
|
CorrelationID: instance.CorrelationID, Version: instance.Version, StatusChangedAt: instance.StatusChangedAt,
|
|
CreatedAt: instance.CreatedAt, UpdatedAt: instance.UpdatedAt,
|
|
}
|
|
if err := r.db.WithContext(ctx).Create(&record).Error; err != nil {
|
|
return err
|
|
}
|
|
instance.ID = record.ID
|
|
return nil
|
|
}
|