暂存一下,防止丢失
This commit is contained in:
132
internal/domain/approval/instance.go
Normal file
132
internal/domain/approval/instance.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user