Files
tyapi-server/internal/domains/certification/services/state_machine/certification_state_machine.go
2025-07-21 15:13:26 +08:00

455 lines
14 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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)
}