455 lines
14 KiB
Go
455 lines
14 KiB
Go
package state_machine
|
||
|
||
import (
|
||
"context"
|
||
"fmt"
|
||
"time"
|
||
|
||
"tyapi-server/internal/domains/certification/entities"
|
||
"tyapi-server/internal/domains/certification/enums"
|
||
"tyapi-server/internal/domains/certification/repositories"
|
||
|
||
"go.uber.org/zap"
|
||
)
|
||
|
||
// CertificationStateMachine 认证状态机
|
||
// 负责管理认证流程的状态转换、业务规则验证和事件发布
|
||
type CertificationStateMachine struct {
|
||
configManager *StateConfigManager
|
||
repository repositories.CertificationCommandRepository
|
||
eventPublisher interface{} // TODO: 使用 interfaces.EventPublisher
|
||
logger *zap.Logger
|
||
}
|
||
|
||
// NewCertificationStateMachine 创建认证状态机
|
||
func NewCertificationStateMachine(
|
||
repository repositories.CertificationCommandRepository,
|
||
eventPublisher interface{}, // TODO: 使用 interfaces.EventPublisher
|
||
logger *zap.Logger,
|
||
) *CertificationStateMachine {
|
||
return &CertificationStateMachine{
|
||
configManager: NewStateConfigManager(),
|
||
repository: repository,
|
||
eventPublisher: eventPublisher,
|
||
logger: logger,
|
||
}
|
||
}
|
||
|
||
// StateTransitionRequest 状态转换请求
|
||
type StateTransitionRequest struct {
|
||
CertificationID string `json:"certification_id"`
|
||
TargetStatus enums.CertificationStatus `json:"target_status"`
|
||
Actor enums.ActorType `json:"actor"`
|
||
ActorID string `json:"actor_id"`
|
||
Reason string `json:"reason"`
|
||
Context map[string]interface{} `json:"context"`
|
||
AllowRollback bool `json:"allow_rollback"`
|
||
}
|
||
|
||
// StateTransitionResult 状态转换结果
|
||
type StateTransitionResult struct {
|
||
Success bool `json:"success"`
|
||
OldStatus enums.CertificationStatus `json:"old_status"`
|
||
NewStatus enums.CertificationStatus `json:"new_status"`
|
||
Message string `json:"message"`
|
||
TransitionedAt time.Time `json:"transitioned_at"`
|
||
Events []interface{} `json:"events,omitempty"`
|
||
}
|
||
|
||
// CanTransition 检查是否可以执行状态转换
|
||
func (sm *CertificationStateMachine) CanTransition(
|
||
cert *entities.Certification,
|
||
targetStatus enums.CertificationStatus,
|
||
actor enums.ActorType,
|
||
) (bool, string) {
|
||
// 1. 检查基本状态转换规则
|
||
canTransition, message := sm.configManager.CanTransition(cert.Status, targetStatus, actor)
|
||
if !canTransition {
|
||
return false, message
|
||
}
|
||
|
||
// 2. 检查认证实体的业务规则
|
||
if canTransition, message := cert.CanTransitionTo(targetStatus, actor); !canTransition {
|
||
return false, message
|
||
}
|
||
|
||
// 3. 检查是否为最终状态
|
||
if cert.IsFinalStatus() {
|
||
return false, "认证已完成,无法进行状态转换"
|
||
}
|
||
|
||
return true, ""
|
||
}
|
||
|
||
// ExecuteTransition 执行状态转换
|
||
func (sm *CertificationStateMachine) ExecuteTransition(
|
||
ctx context.Context,
|
||
req *StateTransitionRequest,
|
||
) (*StateTransitionResult, error) {
|
||
sm.logger.Info("开始执行状态转换",
|
||
zap.String("certification_id", req.CertificationID),
|
||
zap.String("target_status", string(req.TargetStatus)),
|
||
zap.String("actor", string(req.Actor)),
|
||
zap.String("actor_id", req.ActorID))
|
||
|
||
// 1. 加载认证聚合根
|
||
cert, err := sm.loadCertification(ctx, req.CertificationID)
|
||
if err != nil {
|
||
return sm.createFailureResult(cert.Status, req.TargetStatus, fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
|
||
}
|
||
|
||
oldStatus := cert.Status
|
||
|
||
// 2. 验证转换合法性
|
||
if canTransition, message := sm.CanTransition(cert, req.TargetStatus, req.Actor); !canTransition {
|
||
return sm.createFailureResult(oldStatus, req.TargetStatus, message), fmt.Errorf("状态转换验证失败: %s", message)
|
||
}
|
||
|
||
// 3. 验证业务规则
|
||
if err := sm.validateBusinessRules(cert, req); err != nil {
|
||
return sm.createFailureResult(oldStatus, req.TargetStatus, fmt.Sprintf("业务规则验证失败: %s", err.Error())), err
|
||
}
|
||
|
||
// 4. 执行状态转换
|
||
if err := cert.TransitionTo(req.TargetStatus, req.Actor, req.ActorID, req.Reason); err != nil {
|
||
return sm.createFailureResult(oldStatus, req.TargetStatus, fmt.Sprintf("状态转换执行失败: %s", err.Error())), err
|
||
}
|
||
|
||
// 5. 保存到数据库
|
||
if err := sm.repository.Update(ctx, *cert); err != nil {
|
||
// 如果保存失败,需要回滚状态
|
||
sm.logger.Error("状态转换保存失败,尝试回滚",
|
||
zap.String("certification_id", req.CertificationID),
|
||
zap.Error(err))
|
||
|
||
if req.AllowRollback {
|
||
if rollbackErr := sm.rollbackStateTransition(ctx, cert, oldStatus, req.Actor, req.ActorID); rollbackErr != nil {
|
||
sm.logger.Error("状态回滚失败", zap.Error(rollbackErr))
|
||
}
|
||
}
|
||
|
||
return sm.createFailureResult(oldStatus, req.TargetStatus, fmt.Sprintf("保存状态转换失败: %s", err.Error())), err
|
||
}
|
||
|
||
// 6. 发布领域事件
|
||
events := cert.GetDomainEvents()
|
||
for _, event := range events {
|
||
// TODO: 实现事件发布
|
||
// if err := sm.eventPublisher.PublishEvent(ctx, event); err != nil {
|
||
// sm.logger.Error("发布领域事件失败",
|
||
// zap.String("certification_id", req.CertificationID),
|
||
// zap.Error(err))
|
||
// }
|
||
sm.logger.Info("领域事件待发布",
|
||
zap.String("certification_id", req.CertificationID),
|
||
zap.Any("event", event))
|
||
}
|
||
|
||
// 7. 清理领域事件
|
||
cert.ClearDomainEvents()
|
||
|
||
// 8. 记录成功日志
|
||
sm.logger.Info("状态转换执行成功",
|
||
zap.String("certification_id", req.CertificationID),
|
||
zap.String("from_status", string(oldStatus)),
|
||
zap.String("to_status", string(req.TargetStatus)),
|
||
zap.String("actor", string(req.Actor)))
|
||
|
||
// 9. 返回成功结果
|
||
return &StateTransitionResult{
|
||
Success: true,
|
||
OldStatus: oldStatus,
|
||
NewStatus: req.TargetStatus,
|
||
Message: "状态转换成功",
|
||
TransitionedAt: time.Now(),
|
||
Events: events,
|
||
}, nil
|
||
}
|
||
|
||
// GetValidTransitions 获取有效的状态转换
|
||
func (sm *CertificationStateMachine) GetValidTransitions(
|
||
cert *entities.Certification,
|
||
actor enums.ActorType,
|
||
) []*StateTransitionRule {
|
||
return sm.configManager.GetAllowedTransitions(cert.Status, actor)
|
||
}
|
||
|
||
// GetStateInfo 获取状态信息
|
||
func (sm *CertificationStateMachine) GetStateInfo(status enums.CertificationStatus) *StateConfig {
|
||
return sm.configManager.GetStateConfig(status)
|
||
}
|
||
|
||
// ValidateBusinessRules 验证业务规则
|
||
func (sm *CertificationStateMachine) ValidateBusinessRules(
|
||
cert *entities.Certification,
|
||
req *StateTransitionRequest,
|
||
) error {
|
||
return sm.validateBusinessRules(cert, req)
|
||
}
|
||
|
||
// IsUserActionRequired 检查是否需要用户操作
|
||
func (sm *CertificationStateMachine) IsUserActionRequired(status enums.CertificationStatus) bool {
|
||
return sm.configManager.IsUserActionRequired(status)
|
||
}
|
||
|
||
// GetProgressPercentage 获取进度百分比
|
||
func (sm *CertificationStateMachine) GetProgressPercentage(status enums.CertificationStatus) int {
|
||
return sm.configManager.GetStateProgress(status)
|
||
}
|
||
|
||
// ================ 私有方法 ================
|
||
|
||
// loadCertification 加载认证聚合根
|
||
func (sm *CertificationStateMachine) loadCertification(ctx context.Context, certificationID string) (*entities.Certification, error) {
|
||
// 这里需要通过查询仓储获取认证信息
|
||
// 由于当前只有命令仓储,这里使用简单的方法
|
||
// 在实际实现中,应该使用查询仓储
|
||
cert := &entities.Certification{ID: certificationID}
|
||
|
||
// TODO: 实现从查询仓储加载认证信息
|
||
// cert, err := sm.queryRepository.GetByID(ctx, certificationID)
|
||
// if err != nil {
|
||
// return nil, fmt.Errorf("认证信息不存在: %w", err)
|
||
// }
|
||
|
||
return cert, nil
|
||
}
|
||
|
||
// validateBusinessRules 验证业务规则
|
||
func (sm *CertificationStateMachine) validateBusinessRules(
|
||
cert *entities.Certification,
|
||
req *StateTransitionRequest,
|
||
) error {
|
||
// 获取转换规则
|
||
rule := sm.configManager.GetTransitionRule(cert.Status, req.TargetStatus)
|
||
if rule == nil {
|
||
return fmt.Errorf("找不到状态转换规则")
|
||
}
|
||
|
||
// 如果不需要验证,直接返回
|
||
if !rule.RequiresValidation {
|
||
return nil
|
||
}
|
||
|
||
// 构建验证上下文
|
||
context := make(map[string]interface{})
|
||
|
||
// 添加认证基本信息
|
||
context["certification_id"] = cert.ID
|
||
context["user_id"] = cert.UserID
|
||
context["current_status"] = string(cert.Status)
|
||
context["retry_count"] = cert.RetryCount
|
||
context["auth_flow_id"] = cert.AuthFlowID
|
||
|
||
// 添加请求中的上下文信息
|
||
for key, value := range req.Context {
|
||
context[key] = value
|
||
}
|
||
|
||
// 执行业务规则验证
|
||
return sm.configManager.ValidateBusinessRules(rule, context)
|
||
}
|
||
|
||
// rollbackStateTransition 回滚状态转换
|
||
func (sm *CertificationStateMachine) rollbackStateTransition(
|
||
ctx context.Context,
|
||
cert *entities.Certification,
|
||
originalStatus enums.CertificationStatus,
|
||
actor enums.ActorType,
|
||
actorID string,
|
||
) error {
|
||
sm.logger.Info("开始回滚状态转换",
|
||
zap.String("certification_id", cert.ID),
|
||
zap.String("original_status", string(originalStatus)),
|
||
zap.String("current_status", string(cert.Status)))
|
||
|
||
// 直接设置回原状态(跳过业务规则验证)
|
||
cert.Status = originalStatus
|
||
|
||
// 更新审计信息
|
||
now := time.Now()
|
||
cert.LastTransitionAt = &now
|
||
cert.LastTransitionBy = actor
|
||
cert.LastTransitionActor = actorID
|
||
|
||
// 保存回滚结果
|
||
if err := sm.repository.Update(ctx, *cert); err != nil {
|
||
return fmt.Errorf("保存回滚状态失败: %w", err)
|
||
}
|
||
|
||
sm.logger.Info("状态转换回滚成功",
|
||
zap.String("certification_id", cert.ID),
|
||
zap.String("rollback_to_status", string(originalStatus)))
|
||
|
||
return nil
|
||
}
|
||
|
||
// createFailureResult 创建失败结果
|
||
func (sm *CertificationStateMachine) createFailureResult(
|
||
oldStatus, targetStatus enums.CertificationStatus,
|
||
message string,
|
||
) *StateTransitionResult {
|
||
return &StateTransitionResult{
|
||
Success: false,
|
||
OldStatus: oldStatus,
|
||
NewStatus: targetStatus,
|
||
Message: message,
|
||
TransitionedAt: time.Now(),
|
||
Events: []interface{}{},
|
||
}
|
||
}
|
||
|
||
// ================ 状态转换快捷方法 ================
|
||
|
||
// TransitionToInfoSubmitted 转换到已提交企业信息状态
|
||
func (sm *CertificationStateMachine) TransitionToInfoSubmitted(
|
||
ctx context.Context,
|
||
certificationID string,
|
||
actor enums.ActorType,
|
||
actorID string,
|
||
enterpriseInfo interface{},
|
||
) (*StateTransitionResult, error) {
|
||
req := &StateTransitionRequest{
|
||
CertificationID: certificationID,
|
||
TargetStatus: enums.StatusInfoSubmitted,
|
||
Actor: actor,
|
||
ActorID: actorID,
|
||
Reason: "用户提交企业信息",
|
||
Context: map[string]interface{}{
|
||
"enterprise_info": enterpriseInfo,
|
||
},
|
||
AllowRollback: true,
|
||
}
|
||
|
||
return sm.ExecuteTransition(ctx, req)
|
||
}
|
||
|
||
// TransitionToEnterpriseVerified 转换到已企业认证状态
|
||
func (sm *CertificationStateMachine) TransitionToEnterpriseVerified(
|
||
ctx context.Context,
|
||
certificationID string,
|
||
authFlowID string,
|
||
) (*StateTransitionResult, error) {
|
||
req := &StateTransitionRequest{
|
||
CertificationID: certificationID,
|
||
TargetStatus: enums.StatusEnterpriseVerified,
|
||
Actor: enums.ActorTypeEsign,
|
||
ActorID: "esign_system",
|
||
Reason: "e签宝企业认证成功",
|
||
Context: map[string]interface{}{
|
||
"auth_flow_id": authFlowID,
|
||
},
|
||
AllowRollback: false,
|
||
}
|
||
|
||
return sm.ExecuteTransition(ctx, req)
|
||
}
|
||
|
||
// TransitionToInfoRejected 转换到企业信息被拒绝状态
|
||
func (sm *CertificationStateMachine) TransitionToInfoRejected(
|
||
ctx context.Context,
|
||
certificationID string,
|
||
failureReason enums.FailureReason,
|
||
failureMessage string,
|
||
) (*StateTransitionResult, error) {
|
||
req := &StateTransitionRequest{
|
||
CertificationID: certificationID,
|
||
TargetStatus: enums.StatusInfoRejected,
|
||
Actor: enums.ActorTypeEsign,
|
||
ActorID: "esign_system",
|
||
Reason: "e签宝企业认证失败",
|
||
Context: map[string]interface{}{
|
||
"failure_reason": failureReason,
|
||
"failure_message": failureMessage,
|
||
},
|
||
AllowRollback: false,
|
||
}
|
||
|
||
return sm.ExecuteTransition(ctx, req)
|
||
}
|
||
|
||
// TransitionToContractApplied 转换到已申请合同状态
|
||
func (sm *CertificationStateMachine) TransitionToContractApplied(
|
||
ctx context.Context,
|
||
certificationID string,
|
||
actor enums.ActorType,
|
||
actorID string,
|
||
) (*StateTransitionResult, error) {
|
||
req := &StateTransitionRequest{
|
||
CertificationID: certificationID,
|
||
TargetStatus: enums.StatusContractApplied,
|
||
Actor: actor,
|
||
ActorID: actorID,
|
||
Reason: "用户申请合同签署",
|
||
Context: map[string]interface{}{},
|
||
AllowRollback: true,
|
||
}
|
||
|
||
return sm.ExecuteTransition(ctx, req)
|
||
}
|
||
|
||
// TransitionToContractSigned 转换到已签署合同状态(认证完成)
|
||
func (sm *CertificationStateMachine) TransitionToContractSigned(
|
||
ctx context.Context,
|
||
certificationID string,
|
||
contractURL string,
|
||
) (*StateTransitionResult, error) {
|
||
req := &StateTransitionRequest{
|
||
CertificationID: certificationID,
|
||
TargetStatus: enums.StatusContractSigned,
|
||
Actor: enums.ActorTypeEsign,
|
||
ActorID: "esign_system",
|
||
Reason: "e签宝合同签署成功,认证完成",
|
||
Context: map[string]interface{}{
|
||
"contract_url": contractURL,
|
||
},
|
||
AllowRollback: false,
|
||
}
|
||
|
||
return sm.ExecuteTransition(ctx, req)
|
||
}
|
||
|
||
// TransitionToContractRejected 转换到合同被拒签状态
|
||
func (sm *CertificationStateMachine) TransitionToContractRejected(
|
||
ctx context.Context,
|
||
certificationID string,
|
||
failureReason enums.FailureReason,
|
||
failureMessage string,
|
||
) (*StateTransitionResult, error) {
|
||
req := &StateTransitionRequest{
|
||
CertificationID: certificationID,
|
||
TargetStatus: enums.StatusContractRejected,
|
||
Actor: enums.ActorTypeEsign,
|
||
ActorID: "esign_system",
|
||
Reason: "合同签署失败",
|
||
Context: map[string]interface{}{
|
||
"failure_reason": failureReason,
|
||
"failure_message": failureMessage,
|
||
},
|
||
AllowRollback: false,
|
||
}
|
||
|
||
return sm.ExecuteTransition(ctx, req)
|
||
}
|
||
|
||
// TransitionToContractExpired 转换到合同签署超时状态
|
||
func (sm *CertificationStateMachine) TransitionToContractExpired(
|
||
ctx context.Context,
|
||
certificationID string,
|
||
failureMessage string,
|
||
) (*StateTransitionResult, error) {
|
||
req := &StateTransitionRequest{
|
||
CertificationID: certificationID,
|
||
TargetStatus: enums.StatusContractExpired,
|
||
Actor: enums.ActorTypeSystem,
|
||
ActorID: "timeout_monitor",
|
||
Reason: "合同签署超时",
|
||
Context: map[string]interface{}{
|
||
"failure_reason": enums.FailureReasonContractExpired,
|
||
"failure_message": failureMessage,
|
||
},
|
||
AllowRollback: false,
|
||
}
|
||
|
||
return sm.ExecuteTransition(ctx, req)
|
||
} |