Files
tyapi-server/internal/domains/certification/services/state_machine/certification_state_machine.go

455 lines
14 KiB
Go
Raw Normal View History

2025-07-21 15:13:26 +08:00
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)
}