暂存一下,防止丢失

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,132 @@
// Package approval 提供渠道无关的通用审批领域事实。
package approval
import (
"strings"
"time"
"github.com/bytedance/sonic"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
)
// Instance 是只保存渠道无关事实的通用审批实例。
type Instance struct {
ID uint
BusinessType string
BusinessID uint
SubmitterAccountID uint
SubmitterSnapshot []byte
Provider string
ExternalRef string
Status int
RequestSnapshot []byte
DecisionSnapshot []byte
CorrelationID string
Version int
StatusChangedAt time.Time
CreatedAt time.Time
UpdatedAt time.Time
}
// NewInstanceParams 是创建通用审批实例所需的稳定业务事实。
type NewInstanceParams struct {
BusinessType string
BusinessID uint
SubmitterAccountID uint
SubmitterSnapshot []byte
Provider string
RequestSnapshot []byte
CorrelationID string
}
// NewInstance 创建处于提交中的渠道无关审批实例。
func NewInstance(params NewInstanceParams, now time.Time) (*Instance, error) {
businessType := strings.TrimSpace(params.BusinessType)
provider := strings.TrimSpace(params.Provider)
correlationID := strings.TrimSpace(params.CorrelationID)
if businessType == "" || params.BusinessID == 0 || params.SubmitterAccountID == 0 || provider == "" || correlationID == "" {
return nil, errors.New(errors.CodeInvalidParam, "通用审批实例的业务引用、提交人、渠道和关联 ID 不能为空")
}
if !isJSONObject(params.SubmitterSnapshot) || !isJSONObject(params.RequestSnapshot) {
return nil, errors.New(errors.CodeInvalidParam, "通用审批实例快照必须是有效 JSON 对象")
}
if now.IsZero() {
return nil, errors.New(errors.CodeInvalidParam, "通用审批实例创建时间不能为空")
}
now = now.UTC()
return &Instance{
BusinessType: businessType, BusinessID: params.BusinessID,
SubmitterAccountID: params.SubmitterAccountID, SubmitterSnapshot: cloneBytes(params.SubmitterSnapshot),
Provider: provider, Status: constants.ApprovalStatusSubmitting,
RequestSnapshot: cloneBytes(params.RequestSnapshot), CorrelationID: correlationID,
Version: constants.ApprovalInitialVersion, StatusChangedAt: now, CreatedAt: now, UpdatedAt: now,
}, nil
}
// StatusForDecision 将渠道 Adapter 输出的标准决策映射为通用审批终态。
func StatusForDecision(decision string) (int, error) {
switch decision {
case constants.ApprovalDecisionApproved:
return constants.ApprovalStatusApproved, nil
case constants.ApprovalDecisionRejected:
return constants.ApprovalStatusRejected, nil
case constants.ApprovalDecisionCancelled:
return constants.ApprovalStatusCancelled, nil
case constants.ApprovalDecisionDeleted:
return constants.ApprovalStatusDeleted, nil
case constants.ApprovalDecisionRevokedAfterApproved:
return constants.ApprovalStatusRevokedAfterApproved, nil
default:
return 0, errors.New(errors.CodeInvalidParam, "审批渠道返回了不受支持的标准决策")
}
}
// IsTerminalStatus 判断状态是否为可分发给业务消费者的标准终态。
func IsTerminalStatus(status int) bool {
return status == constants.ApprovalStatusApproved ||
status == constants.ApprovalStatusRejected ||
status == constants.ApprovalStatusCancelled ||
status == constants.ApprovalStatusDeleted ||
status == constants.ApprovalStatusRevokedAfterApproved
}
// ApplyDecision 校验标准决策状态迁移并冻结首次到达该终态的决策快照。
func (i *Instance) ApplyDecision(decision string, snapshot []byte, now time.Time) (bool, error) {
if i == nil || now.IsZero() || !isJSONObject(snapshot) {
return false, errors.New(errors.CodeInvalidParam, "审批决策实例、快照和决策时间不能为空")
}
targetStatus, err := StatusForDecision(decision)
if err != nil {
return false, err
}
if i.Status == targetStatus {
return false, nil
}
if targetStatus == constants.ApprovalStatusRevokedAfterApproved {
if i.Status != constants.ApprovalStatusApproved {
return false, errors.New(errors.CodeInvalidStatus, "只有已通过审批可以进入通过后撤销状态")
}
} else if IsTerminalStatus(i.Status) {
return false, errors.New(errors.CodeInvalidStatus, "审批已进入其他标准终态")
}
now = now.UTC()
i.Status = targetStatus
i.DecisionSnapshot = cloneBytes(snapshot)
i.StatusChangedAt = now
i.UpdatedAt = now
i.Version++
return true, nil
}
func isJSONObject(value []byte) bool {
var object map[string]any
return len(value) > 0 && sonic.Unmarshal(value, &object) == nil && object != nil
}
func cloneBytes(value []byte) []byte {
cloned := make([]byte, len(value))
copy(cloned, value)
return cloned
}