暂存一下,防止丢失

This commit is contained in:
2026-07-24 16:07:18 +08:00
parent 5d6e23f1a5
commit a18ed8bc8d
180 changed files with 13597 additions and 1986 deletions

View File

@@ -0,0 +1,127 @@
package approval
import (
"context"
"strconv"
"strings"
"time"
"gorm.io/gorm"
approvaldomain "github.com/break/junhong_cmp_fiber/internal/domain/approval"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// CreationService 实现业务侧 Approval Port并保持渠道前置检查与业务事务分离。
type CreationService struct {
providers ProviderPort
repositories RepositoryProvider
eventWriter SubmissionEventWriter
now func() time.Time
}
// NewCreationService 创建通用审批申请创建用例。
func NewCreationService(
providers ProviderPort,
repositories RepositoryProvider,
eventWriter SubmissionEventWriter,
now func() time.Time,
) *CreationService {
if now == nil {
now = time.Now
}
return &CreationService{providers: providers, repositories: repositories, eventWriter: eventWriter, now: now}
}
// Prepare 在任何业务事实写入前确认 Adapter、场景和真实发起身份可用。
func (s *CreationService) Prepare(ctx context.Context, request PrepareRequest) (Preparation, error) {
if s == nil || s.providers == nil || s.repositories == nil || s.eventWriter == nil {
return Preparation{}, errors.New(errors.CodeServiceUnavailable, "审批能力尚未配置")
}
request.BusinessType = strings.TrimSpace(request.BusinessType)
request.CorrelationID = strings.TrimSpace(request.CorrelationID)
if request.BusinessType == "" || request.SubmitterAccountID == 0 || request.CorrelationID == "" {
return Preparation{}, errors.New(errors.CodeInvalidParam)
}
providerContext, err := s.providers.Prepare(ctx, request)
if err != nil {
return Preparation{}, err
}
providerContext.Provider = strings.TrimSpace(providerContext.Provider)
if providerContext.Provider == "" {
return Preparation{}, errors.New(errors.CodeServiceUnavailable, "审批渠道未返回有效 provider")
}
return Preparation{
provider: providerContext.Provider, businessType: request.BusinessType,
submitterAccountID: request.SubmitterAccountID, correlationID: request.CorrelationID,
expiresAt: s.now().UTC().Add(constants.ApprovalPreparationTTL), issuer: s,
providerContext: providerContext,
}, nil
}
// CreateInTx 使用调用方业务事务原子创建通用实例、渠道上下文和提交 Outbox。
func (s *CreationService) CreateInTx(ctx context.Context, tx *gorm.DB, request CreateRequest) (Reference, error) {
if s == nil || tx == nil || s.repositories == nil || s.providers == nil || s.eventWriter == nil {
return Reference{}, errors.New(errors.CodeInternalError, "通用审批创建用例未完整配置")
}
now := s.now().UTC()
if err := s.validatePreparation(request, now); err != nil {
return Reference{}, err
}
instance, err := approvaldomain.NewInstance(approvaldomain.NewInstanceParams{
BusinessType: request.BusinessType, BusinessID: request.BusinessID,
SubmitterAccountID: request.SubmitterAccountID, SubmitterSnapshot: request.SubmitterSnapshot,
Provider: request.Preparation.provider, RequestSnapshot: request.RequestSnapshot,
CorrelationID: request.CorrelationID,
}, now)
if err != nil {
return Reference{}, err
}
repository := s.repositories.ForDB(tx)
if repository == nil {
return Reference{}, errors.New(errors.CodeInternalError, "通用审批 Repository 未配置")
}
if err := repository.Create(ctx, instance); err != nil {
return Reference{}, err
}
if err := s.providers.CreateContextInTx(ctx, tx, request.Preparation.providerContext, instance.ID); err != nil {
return Reference{}, err
}
event := SubmissionRequestedEvent{
EventID: "approval:" + strconv.FormatUint(uint64(instance.ID), 10) + ":submission",
InstanceID: instance.ID, BusinessType: instance.BusinessType, BusinessID: instance.BusinessID,
SubmitterAccountID: instance.SubmitterAccountID, Provider: instance.Provider,
CorrelationID: instance.CorrelationID, OccurredAt: instance.CreatedAt,
}
if err := s.eventWriter.Append(ctx, tx, event); err != nil {
return Reference{}, err
}
return Reference{InstanceID: instance.ID, Status: instance.Status}, nil
}
func (s *CreationService) validatePreparation(request CreateRequest, now time.Time) error {
preparation := request.Preparation
if preparation.issuer != s || preparation.expiresAt.IsZero() || !preparation.expiresAt.After(now) {
return errors.New(errors.CodeServiceUnavailable, "审批可用性检查已失效,请重新提交")
}
if request.BusinessType != preparation.businessType ||
request.SubmitterAccountID != preparation.submitterAccountID ||
request.CorrelationID != preparation.correlationID {
return errors.New(errors.CodeInvalidParam, "审批准备结果与业务申请不匹配")
}
return nil
}
// UnavailableProviderPort 在当前环境未装配有效审批 Adapter 时失败关闭。
type UnavailableProviderPort struct{}
// Prepare 拒绝在缺少有效 Adapter、场景或发起身份时创建业务审批。
func (UnavailableProviderPort) Prepare(_ context.Context, _ PrepareRequest) (ProviderPreparation, error) {
return ProviderPreparation{}, errors.New(errors.CodeServiceUnavailable, "当前环境没有可用审批渠道")
}
// CreateContextInTx 防止未配置渠道上下文时误写审批事实。
func (UnavailableProviderPort) CreateContextInTx(_ context.Context, _ *gorm.DB, _ ProviderPreparation, _ uint) error {
return errors.New(errors.CodeServiceUnavailable, "当前环境没有可用审批渠道")
}