This commit is contained in:
2025-07-21 15:13:26 +08:00
parent 8ad1d7288e
commit b03129667a
41 changed files with 8497 additions and 3968 deletions

View File

@@ -1,43 +1,55 @@
package entities
import (
"errors"
"fmt"
"time"
"tyapi-server/internal/domains/certification/entities/value_objects"
"tyapi-server/internal/domains/certification/enums"
"github.com/google/uuid"
"gorm.io/gorm"
)
// Certification 认证申请实体
// 这是企业认证流程的核心实体,负责管理整个认证申请的生命周期
// Certification 认证聚合根
// 这是企业认证流程的核心聚合根,封装了完整的认证业务逻辑和状态管理
type Certification struct {
// 基础信息
// === 基础信息 ===
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"认证申请唯一标识"`
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"申请用户ID"`
Status enums.CertificationStatus `gorm:"type:varchar(50);not null;index" json:"status" comment:"当前认证状态"`
// 流程节点时间戳 - 记录每个关键步骤的完成时间
// === 流程时间戳 - 记录每个关键步骤的完成时间 ===
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty" comment:"企业认证完成时间"`
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty" comment:"合同申请时间"`
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty" comment:"合同签署完成时间"`
CompletedAt *time.Time `json:"completed_at,omitempty" comment:"认证完成时间"`
// 合同信息 - 电子合同相关链接
// === e签宝相关信息 ===
AuthFlowID string `gorm:"type:varchar(500)" json:"auth_flow_id,omitempty" comment:"企业认证流程ID"`
ContractFileID string `gorm:"type:varchar(500)" json:"contract_file_id,omitempty" comment:"合同文件ID"`
EsignFlowID string `gorm:"type:varchar(500)" json:"esign_flow_id,omitempty" comment:"签署流程ID"`
ContractURL string `gorm:"type:varchar(500)" json:"contract_url,omitempty" comment:"合同文件访问链接"`
ContractSignURL string `gorm:"type:varchar(500)" json:"contract_sign_url,omitempty" comment:"合同签署链接"`
// 认证信息
AuthFlowID string `gorm:"type:varchar(500)" json:"auth_flow_id,omitempty" comment:"认证流程ID"`
// === 失败信息 ===
FailureReason enums.FailureReason `gorm:"type:varchar(100)" json:"failure_reason,omitempty" comment:"失败原因"`
FailureMessage string `gorm:"type:text" json:"failure_message,omitempty" comment:"失败详细信息"`
RetryCount int `gorm:"default:0" json:"retry_count" comment:"重试次数"`
// 时间戳字段
// === 审计信息 ===
LastTransitionAt *time.Time `json:"last_transition_at,omitempty" comment:"最后状态转换时间"`
LastTransitionBy enums.ActorType `gorm:"type:varchar(20)" json:"last_transition_by,omitempty" comment:"最后操作者类型"`
LastTransitionActor string `gorm:"type:varchar(100)" json:"last_transition_actor,omitempty" comment:"最后操作者ID"`
// === 系统字段 ===
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
// === 领域事件 (不持久化) ===
domainEvents []interface{} `gorm:"-" json:"-"`
}
// TableName 指定数据库表名
@@ -50,98 +62,622 @@ func (c *Certification) BeforeCreate(tx *gorm.DB) error {
if c.ID == "" {
c.ID = uuid.New().String()
}
// 设置初始状态
if c.Status == "" {
c.Status = enums.StatusPending
}
return nil
}
// IsStatusChangeable 检查状态是否可以变更
func (c *Certification) IsStatusChangeable() bool {
return !enums.IsFinalStatus(c.Status)
// ================ 工厂方法 ================
// NewCertification 创建新的认证申请
func NewCertification(userID string) (*Certification, error) {
if userID == "" {
return nil, errors.New("用户ID不能为空")
}
certification := &Certification{
ID: uuid.New().String(),
UserID: userID,
Status: enums.StatusPending,
RetryCount: 0,
domainEvents: make([]interface{}, 0),
}
// 添加领域事件
certification.addDomainEvent(&CertificationCreatedEvent{
CertificationID: certification.ID,
UserID: userID,
CreatedAt: time.Now(),
})
return certification, nil
}
// GetStatusName 获取状态名称
func (c *Certification) GetStatusName() string {
// ================ 状态转换方法 ================
// CanTransitionTo 检查是否可以转换到目标状态
func (c *Certification) CanTransitionTo(targetStatus enums.CertificationStatus, actor enums.ActorType) (bool, string) {
// 检查状态转换规则
if !enums.CanTransitionTo(c.Status, targetStatus) {
return false, fmt.Sprintf("不允许从 %s 转换到 %s", enums.GetStatusName(c.Status), enums.GetStatusName(targetStatus))
}
// 检查操作者权限
if !c.validateActorPermission(targetStatus, actor) {
return false, fmt.Sprintf("%s 无权执行此状态转换", enums.GetActorTypeName(actor))
}
// 检查业务规则
if err := c.validateBusinessRules(targetStatus, actor); err != nil {
return false, err.Error()
}
return true, ""
}
// TransitionTo 执行状态转换
func (c *Certification) TransitionTo(targetStatus enums.CertificationStatus, actor enums.ActorType, actorID string, reason string) error {
// 验证转换合法性
canTransition, message := c.CanTransitionTo(targetStatus, actor)
if !canTransition {
return fmt.Errorf("状态转换失败: %s", message)
}
oldStatus := c.Status
// 执行状态转换
c.Status = targetStatus
c.updateTimestampByStatus(targetStatus)
c.updateTransitionAudit(actor, actorID)
// 清除失败信息(如果转换到成功状态)
if !enums.IsFailureStatus(targetStatus) {
c.clearFailureInfo()
}
// 添加状态转换事件
c.addDomainEvent(&CertificationStatusChangedEvent{
CertificationID: c.ID,
UserID: c.UserID,
FromStatus: oldStatus,
ToStatus: targetStatus,
Actor: actor,
ActorID: actorID,
Reason: reason,
TransitionedAt: time.Now(),
})
return nil
}
// ================ 业务操作方法 ================
// SubmitEnterpriseInfo 提交企业信息
func (c *Certification) SubmitEnterpriseInfo(enterpriseInfo *value_objects.EnterpriseInfo) error {
// 验证当前状态
if c.Status != enums.StatusPending && c.Status != enums.StatusInfoRejected {
return fmt.Errorf("当前状态 %s 不允许提交企业信息", enums.GetStatusName(c.Status))
}
// 验证企业信息
if err := enterpriseInfo.Validate(); err != nil {
return fmt.Errorf("企业信息验证失败: %w", err)
}
// 状态转换
if err := c.TransitionTo(enums.StatusInfoSubmitted, enums.ActorTypeUser, c.UserID, "用户提交企业信息"); err != nil {
return err
}
// 添加业务事件
c.addDomainEvent(&EnterpriseInfoSubmittedEvent{
CertificationID: c.ID,
UserID: c.UserID,
EnterpriseInfo: enterpriseInfo,
SubmittedAt: time.Now(),
})
return nil
}
// HandleEnterpriseVerificationCallback 处理企业认证回调
func (c *Certification) HandleEnterpriseVerificationCallback(success bool, authFlowID string, failureReason enums.FailureReason, message string) error {
// 验证当前状态
if c.Status != enums.StatusInfoSubmitted {
return fmt.Errorf("当前状态 %s 不允许处理企业认证回调", enums.GetStatusName(c.Status))
}
c.AuthFlowID = authFlowID
if success {
// 认证成功
if err := c.TransitionTo(enums.StatusEnterpriseVerified, enums.ActorTypeEsign, "esign_system", "企业认证成功"); err != nil {
return err
}
c.addDomainEvent(&EnterpriseVerificationSuccessEvent{
CertificationID: c.ID,
UserID: c.UserID,
AuthFlowID: authFlowID,
VerifiedAt: time.Now(),
})
} else {
// 认证失败
c.setFailureInfo(failureReason, message)
if err := c.TransitionTo(enums.StatusInfoRejected, enums.ActorTypeEsign, "esign_system", "企业认证失败"); err != nil {
return err
}
c.addDomainEvent(&EnterpriseVerificationFailedEvent{
CertificationID: c.ID,
UserID: c.UserID,
AuthFlowID: authFlowID,
FailureReason: failureReason,
FailureMessage: message,
FailedAt: time.Now(),
})
}
return nil
}
// ApplyContract 申请合同签署
func (c *Certification) ApplyContract() error {
// 验证当前状态
if c.Status != enums.StatusEnterpriseVerified {
return fmt.Errorf("当前状态 %s 不允许申请合同", enums.GetStatusName(c.Status))
}
// 状态转换
if err := c.TransitionTo(enums.StatusContractApplied, enums.ActorTypeUser, c.UserID, "用户申请合同签署"); err != nil {
return err
}
// 添加业务事件
c.addDomainEvent(&ContractAppliedEvent{
CertificationID: c.ID,
UserID: c.UserID,
AppliedAt: time.Now(),
})
return nil
}
// UpdateContractInfo 更新合同信息
func (c *Certification) UpdateContractInfo(contractInfo *value_objects.ContractInfo) error {
// 验证合同信息
if err := contractInfo.Validate(); err != nil {
return fmt.Errorf("合同信息验证失败: %w", err)
}
// 更新合同相关字段
c.ContractFileID = contractInfo.ContractFileID
c.EsignFlowID = contractInfo.EsignFlowID
c.ContractURL = contractInfo.ContractURL
c.ContractSignURL = contractInfo.ContractSignURL
return nil
}
// HandleContractSignCallback 处理合同签署回调
func (c *Certification) HandleContractSignCallback(success bool, contractURL string, failureReason enums.FailureReason, message string) error {
// 验证当前状态
if c.Status != enums.StatusContractApplied {
return fmt.Errorf("当前状态 %s 不允许处理合同签署回调", enums.GetStatusName(c.Status))
}
if success {
// 签署成功 - 认证完成
c.ContractURL = contractURL
if err := c.TransitionTo(enums.StatusContractSigned, enums.ActorTypeEsign, "esign_system", "合同签署成功,认证完成"); err != nil {
return err
}
c.addDomainEvent(&ContractSignedEvent{
CertificationID: c.ID,
UserID: c.UserID,
ContractURL: contractURL,
SignedAt: time.Now(),
})
c.addDomainEvent(&CertificationCompletedEvent{
CertificationID: c.ID,
UserID: c.UserID,
CompletedAt: time.Now(),
})
} else {
// 签署失败
c.setFailureInfo(failureReason, message)
var targetStatus enums.CertificationStatus
if failureReason == enums.FailureReasonContractExpired {
targetStatus = enums.StatusContractExpired
} else {
targetStatus = enums.StatusContractRejected
}
if err := c.TransitionTo(targetStatus, enums.ActorTypeEsign, "esign_system", "合同签署失败"); err != nil {
return err
}
c.addDomainEvent(&ContractSignFailedEvent{
CertificationID: c.ID,
UserID: c.UserID,
FailureReason: failureReason,
FailureMessage: message,
FailedAt: time.Now(),
})
}
return nil
}
// RetryFromFailure 从失败状态重试
func (c *Certification) RetryFromFailure(actor enums.ActorType, actorID string) error {
if !enums.IsFailureStatus(c.Status) {
return errors.New("当前状态不是失败状态,无需重试")
}
// 检查重试次数限制
if c.RetryCount >= 3 {
return errors.New("已达到最大重试次数限制")
}
// 检查失败原因是否可重试
if !enums.IsRetryable(c.FailureReason) {
return fmt.Errorf("失败原因 %s 不支持重试", enums.GetFailureReasonName(c.FailureReason))
}
var targetStatus enums.CertificationStatus
var reason string
switch c.Status {
case enums.StatusInfoRejected:
targetStatus = enums.StatusInfoSubmitted
reason = "重新提交企业信息"
case enums.StatusContractRejected, enums.StatusContractExpired:
targetStatus = enums.StatusEnterpriseVerified
reason = "重置状态,准备重新申请合同"
default:
return fmt.Errorf("不支持从状态 %s 重试", enums.GetStatusName(c.Status))
}
// 增加重试次数
c.RetryCount++
// 状态转换
if err := c.TransitionTo(targetStatus, actor, actorID, reason); err != nil {
return err
}
// 添加重试事件
c.addDomainEvent(&CertificationRetryEvent{
CertificationID: c.ID,
UserID: c.UserID,
FromStatus: c.Status,
ToStatus: targetStatus,
RetryCount: c.RetryCount,
RetriedAt: time.Now(),
})
return nil
}
// ================ 查询方法 ================
// GetProgress 获取认证进度百分比
func (c *Certification) GetProgress() int {
return enums.GetProgressPercentage(c.Status)
}
// IsUserActionRequired 是否需要用户操作
func (c *Certification) IsUserActionRequired() bool {
return enums.IsUserActionRequired(c.Status)
}
// GetCurrentStatusName 获取当前状态名称
func (c *Certification) GetCurrentStatusName() string {
return enums.GetStatusName(c.Status)
}
// IsFinalStatus 判断是否为最终状态
// GetUserActionHint 获取用户操作提示
func (c *Certification) GetUserActionHint() string {
return enums.GetUserActionHint(c.Status)
}
// GetAvailableActions 获取当前可执行的操作
func (c *Certification) GetAvailableActions() []string {
actions := make([]string, 0)
switch c.Status {
case enums.StatusPending:
actions = append(actions, "submit_enterprise_info")
case enums.StatusEnterpriseVerified:
actions = append(actions, "apply_contract")
case enums.StatusInfoRejected, enums.StatusContractRejected, enums.StatusContractExpired:
if enums.IsRetryable(c.FailureReason) && c.RetryCount < 3 {
actions = append(actions, "retry")
}
}
return actions
}
// IsFinalStatus 是否为最终状态
func (c *Certification) IsFinalStatus() bool {
return enums.IsFinalStatus(c.Status)
}
// GetStatusCategory 获取状态分类
func (c *Certification) GetStatusCategory() string {
return enums.GetStatusCategory(c.Status)
}
// GetStatusPriority 获取状态优先级
func (c *Certification) GetStatusPriority() int {
return enums.GetStatusPriority(c.Status)
}
// GetProgressPercentage 获取进度百分比
func (c *Certification) GetProgressPercentage() int {
progressMap := map[enums.CertificationStatus]int{
enums.StatusPending: 0,
enums.StatusInfoSubmitted: 20,
enums.StatusEnterpriseVerified: 40,
enums.StatusContractApplied: 60,
enums.StatusContractSigned: 80,
enums.StatusCompleted: 100,
}
if progress, exists := progressMap[c.Status]; exists {
return progress
}
return 0
}
// IsUserActionRequired 检查是否需要用户操作
func (c *Certification) IsUserActionRequired() bool {
userActionRequired := map[enums.CertificationStatus]bool{
enums.StatusPending: true,
enums.StatusInfoSubmitted: true,
enums.StatusEnterpriseVerified: true,
enums.StatusContractApplied: true,
enums.StatusContractSigned: false,
enums.StatusCompleted: false,
}
if required, exists := userActionRequired[c.Status]; exists {
return required
}
return false
// IsCompleted 是否已完成认证
func (c *Certification) IsCompleted() bool {
return c.Status == enums.StatusContractSigned
}
// GetNextValidStatuses 获取下一个有效状态
func (c *Certification) GetNextValidStatuses() []enums.CertificationStatus {
nextStatusMap := map[enums.CertificationStatus][]enums.CertificationStatus{
enums.StatusPending: {enums.StatusInfoSubmitted},
enums.StatusInfoSubmitted: {enums.StatusEnterpriseVerified, enums.StatusInfoSubmitted}, // 可以重新提交
enums.StatusEnterpriseVerified: {enums.StatusContractApplied},
enums.StatusContractApplied: {enums.StatusContractSigned},
enums.StatusContractSigned: {enums.StatusCompleted},
enums.StatusCompleted: {},
}
if nextStatuses, exists := nextStatusMap[c.Status]; exists {
return nextStatuses
}
return []enums.CertificationStatus{}
return enums.GetNextValidStatuses(c.Status)
}
// CanTransitionTo 检查是否可以转换到指定状态
func (c *Certification) CanTransitionTo(targetStatus enums.CertificationStatus, isUser bool) (bool, string) {
nextStatuses := c.GetNextValidStatuses()
// GetFailureInfo 获取失败信息
func (c *Certification) GetFailureInfo() (enums.FailureReason, string) {
return c.FailureReason, c.FailureMessage
}
for _, nextStatus := range nextStatuses {
if nextStatus == targetStatus {
// 检查权限
if isUser && !c.IsUserActionRequired() {
return false, "当前状态不需要用户操作"
}
return true, ""
// ================ 业务规则验证 ================
// ValidateBusinessRules 验证业务规则
func (c *Certification) ValidateBusinessRules() error {
// 基础验证
if c.UserID == "" {
return errors.New("用户ID不能为空")
}
if !enums.IsValidStatus(c.Status) {
return fmt.Errorf("无效的认证状态: %s", c.Status)
}
// 状态相关验证
switch c.Status {
case enums.StatusEnterpriseVerified:
if c.AuthFlowID == "" {
return errors.New("企业认证状态下必须有认证流程ID")
}
case enums.StatusContractApplied:
if c.AuthFlowID == "" {
return errors.New("合同申请状态下必须有企业认证流程ID")
}
case enums.StatusContractSigned:
if c.ContractFileID == "" || c.EsignFlowID == "" {
return errors.New("合同签署状态下必须有完整的合同信息")
}
}
return false, "不支持的状态转换"
// 失败状态验证
if enums.IsFailureStatus(c.Status) {
if c.FailureReason == "" {
return errors.New("失败状态下必须有失败原因")
}
if !enums.IsValidFailureReason(c.FailureReason) {
return fmt.Errorf("无效的失败原因: %s", c.FailureReason)
}
}
return nil
}
// validateActorPermission 验证操作者权限
func (c *Certification) validateActorPermission(targetStatus enums.CertificationStatus, actor enums.ActorType) bool {
// 定义状态转换的权限规则
permissions := map[enums.CertificationStatus][]enums.ActorType{
enums.StatusInfoSubmitted: {enums.ActorTypeUser},
enums.StatusEnterpriseVerified: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusInfoRejected: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusContractApplied: {enums.ActorTypeUser},
enums.StatusContractSigned: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusContractRejected: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusContractExpired: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
}
allowedActors, exists := permissions[targetStatus]
if !exists {
return false
}
for _, allowedActor := range allowedActors {
if actor == allowedActor {
return true
}
}
return false
}
// validateBusinessRules 验证业务规则
func (c *Certification) validateBusinessRules(targetStatus enums.CertificationStatus, actor enums.ActorType) error {
// 用户操作验证
if actor == enums.ActorTypeUser {
switch targetStatus {
case enums.StatusInfoSubmitted:
// 用户提交企业信息时的验证
if c.Status != enums.StatusPending && c.Status != enums.StatusInfoRejected {
return fmt.Errorf("当前状态 %s 不允许提交企业信息", enums.GetStatusName(c.Status))
}
case enums.StatusContractApplied:
// 用户申请合同时的验证
if c.Status != enums.StatusEnterpriseVerified {
return fmt.Errorf("必须先完成企业认证才能申请合同")
}
if c.AuthFlowID == "" {
return errors.New("缺少企业认证流程ID")
}
}
}
// e签宝回调验证
if actor == enums.ActorTypeEsign {
switch targetStatus {
case enums.StatusEnterpriseVerified, enums.StatusInfoRejected:
if c.Status != enums.StatusInfoSubmitted {
return fmt.Errorf("当前状态 %s 不允许处理企业认证回调", enums.GetStatusName(c.Status))
}
case enums.StatusContractSigned, enums.StatusContractRejected, enums.StatusContractExpired:
if c.Status != enums.StatusContractApplied {
return fmt.Errorf("当前状态 %s 不允许处理合同签署回调", enums.GetStatusName(c.Status))
}
}
}
return nil
}
// ================ 辅助方法 ================
// updateTimestampByStatus 根据状态更新对应的时间戳
func (c *Certification) updateTimestampByStatus(status enums.CertificationStatus) {
now := time.Now()
switch status {
case enums.StatusInfoSubmitted:
c.InfoSubmittedAt = &now
case enums.StatusEnterpriseVerified:
c.EnterpriseVerifiedAt = &now
case enums.StatusContractApplied:
c.ContractAppliedAt = &now
case enums.StatusContractSigned:
c.ContractSignedAt = &now
}
}
// updateTransitionAudit 更新状态转换审计信息
func (c *Certification) updateTransitionAudit(actor enums.ActorType, actorID string) {
now := time.Now()
c.LastTransitionAt = &now
c.LastTransitionBy = actor
c.LastTransitionActor = actorID
}
// setFailureInfo 设置失败信息
func (c *Certification) setFailureInfo(reason enums.FailureReason, message string) {
c.FailureReason = reason
c.FailureMessage = message
}
// clearFailureInfo 清除失败信息
func (c *Certification) clearFailureInfo() {
c.FailureReason = ""
c.FailureMessage = ""
}
// ================ 领域事件管理 ================
// addDomainEvent 添加领域事件
func (c *Certification) addDomainEvent(event interface{}) {
if c.domainEvents == nil {
c.domainEvents = make([]interface{}, 0)
}
c.domainEvents = append(c.domainEvents, event)
}
// GetDomainEvents 获取领域事件
func (c *Certification) GetDomainEvents() []interface{} {
return c.domainEvents
}
// ClearDomainEvents 清除领域事件
func (c *Certification) ClearDomainEvents() {
c.domainEvents = make([]interface{}, 0)
}
// ================ 领域事件定义 ================
// CertificationCreatedEvent 认证创建事件
type CertificationCreatedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
CreatedAt time.Time `json:"created_at"`
}
// CertificationStatusChangedEvent 认证状态变更事件
type CertificationStatusChangedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
FromStatus enums.CertificationStatus `json:"from_status"`
ToStatus enums.CertificationStatus `json:"to_status"`
Actor enums.ActorType `json:"actor"`
ActorID string `json:"actor_id"`
Reason string `json:"reason"`
TransitionedAt time.Time `json:"transitioned_at"`
}
// EnterpriseInfoSubmittedEvent 企业信息提交事件
type EnterpriseInfoSubmittedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info"`
SubmittedAt time.Time `json:"submitted_at"`
}
// EnterpriseVerificationSuccessEvent 企业认证成功事件
type EnterpriseVerificationSuccessEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
AuthFlowID string `json:"auth_flow_id"`
VerifiedAt time.Time `json:"verified_at"`
}
// EnterpriseVerificationFailedEvent 企业认证失败事件
type EnterpriseVerificationFailedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
AuthFlowID string `json:"auth_flow_id"`
FailureReason enums.FailureReason `json:"failure_reason"`
FailureMessage string `json:"failure_message"`
FailedAt time.Time `json:"failed_at"`
}
// ContractAppliedEvent 合同申请事件
type ContractAppliedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
AppliedAt time.Time `json:"applied_at"`
}
// ContractSignedEvent 合同签署成功事件
type ContractSignedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
ContractURL string `json:"contract_url"`
SignedAt time.Time `json:"signed_at"`
}
// ContractSignFailedEvent 合同签署失败事件
type ContractSignFailedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
FailureReason enums.FailureReason `json:"failure_reason"`
FailureMessage string `json:"failure_message"`
FailedAt time.Time `json:"failed_at"`
}
// CertificationCompletedEvent 认证完成事件
type CertificationCompletedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
CompletedAt time.Time `json:"completed_at"`
}
// CertificationRetryEvent 认证重试事件
type CertificationRetryEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
FromStatus enums.CertificationStatus `json:"from_status"`
ToStatus enums.CertificationStatus `json:"to_status"`
RetryCount int `json:"retry_count"`
RetriedAt time.Time `json:"retried_at"`
}

View File

@@ -9,9 +9,9 @@ import (
// EnterpriseInfoSubmitRecord 企业信息提交记录
type EnterpriseInfoSubmitRecord struct {
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
// 企业信息
CompanyName string `json:"company_name" gorm:"type:varchar(200);not null"`
UnifiedSocialCode string `json:"unified_social_code" gorm:"type:varchar(50);not null;index"`
@@ -19,12 +19,12 @@ type EnterpriseInfoSubmitRecord struct {
LegalPersonID string `json:"legal_person_id" gorm:"type:varchar(50);not null"`
LegalPersonPhone string `json:"legal_person_phone" gorm:"type:varchar(50);not null"`
// 提交状态
Status string `json:"status" gorm:"type:varchar(20);not null;default:'submitted'"` // submitted, verified, failed
SubmitAt time.Time `json:"submit_at" gorm:"not null"`
VerifiedAt *time.Time `json:"verified_at"`
FailedAt *time.Time `json:"failed_at"`
FailureReason string `json:"failure_reason" gorm:"type:text"`
Status string `json:"status" gorm:"type:varchar(20);not null;default:'submitted'"` // submitted, verified, failed
SubmitAt time.Time `json:"submit_at" gorm:"not null"`
VerifiedAt *time.Time `json:"verified_at"`
FailedAt *time.Time `json:"failed_at"`
FailureReason string `json:"failure_reason" gorm:"type:text"`
// 系统字段
CreatedAt time.Time `json:"created_at" gorm:"not null"`
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
@@ -80,4 +80,4 @@ func (r *EnterpriseInfoSubmitRecord) IsVerified() bool {
// IsFailed 检查是否验证失败
func (r *EnterpriseInfoSubmitRecord) IsFailed() bool {
return r.Status == "failed"
}
}

View File

@@ -0,0 +1,516 @@
package value_objects
import (
"errors"
"fmt"
"regexp"
"strings"
"time"
)
// ContractInfo 合同信息值对象
// 封装电子合同相关的核心信息,包含合同状态和签署流程管理
type ContractInfo struct {
// 合同基本信息
ContractFileID string `json:"contract_file_id"` // 合同文件ID
EsignFlowID string `json:"esign_flow_id"` // e签宝签署流程ID
ContractURL string `json:"contract_url"` // 合同文件访问链接
ContractSignURL string `json:"contract_sign_url"` // 合同签署链接
// 合同元数据
ContractTitle string `json:"contract_title"` // 合同标题
ContractVersion string `json:"contract_version"` // 合同版本
TemplateID string `json:"template_id"` // 模板ID
// 签署相关信息
SignerAccount string `json:"signer_account"` // 签署人账号
SignerName string `json:"signer_name"` // 签署人姓名
TransactorPhone string `json:"transactor_phone"` // 经办人手机号
TransactorName string `json:"transactor_name"` // 经办人姓名
TransactorIDCardNum string `json:"transactor_id_card_num"` // 经办人身份证号
// 时间信息
GeneratedAt *time.Time `json:"generated_at,omitempty"` // 合同生成时间
SignFlowCreatedAt *time.Time `json:"sign_flow_created_at,omitempty"` // 签署流程创建时间
SignedAt *time.Time `json:"signed_at,omitempty"` // 签署完成时间
ExpiresAt *time.Time `json:"expires_at,omitempty"` // 签署链接过期时间
// 状态信息
Status string `json:"status"` // 合同状态
SignProgress int `json:"sign_progress"` // 签署进度
// 附加信息
Metadata map[string]interface{} `json:"metadata,omitempty"` // 元数据
}
// ContractStatus 合同状态常量
const (
ContractStatusDraft = "draft" // 草稿
ContractStatusGenerated = "generated" // 已生成
ContractStatusSigning = "signing" // 签署中
ContractStatusSigned = "signed" // 已签署
ContractStatusExpired = "expired" // 已过期
ContractStatusRejected = "rejected" // 被拒绝
ContractStatusCancelled = "cancelled" // 已取消
)
// NewContractInfo 创建合同信息值对象
func NewContractInfo(contractFileID, esignFlowID, contractURL, contractSignURL string) (*ContractInfo, error) {
info := &ContractInfo{
ContractFileID: strings.TrimSpace(contractFileID),
EsignFlowID: strings.TrimSpace(esignFlowID),
ContractURL: strings.TrimSpace(contractURL),
ContractSignURL: strings.TrimSpace(contractSignURL),
Status: ContractStatusGenerated,
SignProgress: 0,
Metadata: make(map[string]interface{}),
}
if err := info.Validate(); err != nil {
return nil, fmt.Errorf("合同信息验证失败: %w", err)
}
return info, nil
}
// Validate 验证合同信息的完整性和格式
func (c *ContractInfo) Validate() error {
if err := c.validateContractFileID(); err != nil {
return err
}
if err := c.validateEsignFlowID(); err != nil {
return err
}
if err := c.validateContractURL(); err != nil {
return err
}
if err := c.validateContractSignURL(); err != nil {
return err
}
if err := c.validateSignerInfo(); err != nil {
return err
}
if err := c.validateStatus(); err != nil {
return err
}
return nil
}
// validateContractFileID 验证合同文件ID
func (c *ContractInfo) validateContractFileID() error {
if c.ContractFileID == "" {
return errors.New("合同文件ID不能为空")
}
// 简单的格式验证
if len(c.ContractFileID) < 10 {
return errors.New("合同文件ID格式不正确")
}
return nil
}
// validateEsignFlowID 验证e签宝流程ID
func (c *ContractInfo) validateEsignFlowID() error {
if c.EsignFlowID == "" {
return errors.New("e签宝流程ID不能为空")
}
// 简单的格式验证
if len(c.EsignFlowID) < 10 {
return errors.New("e签宝流程ID格式不正确")
}
return nil
}
// validateContractURL 验证合同访问链接
func (c *ContractInfo) validateContractURL() error {
if c.ContractURL == "" {
return errors.New("合同访问链接不能为空")
}
// URL格式验证
urlPattern := `^https?://.*`
matched, err := regexp.MatchString(urlPattern, c.ContractURL)
if err != nil {
return fmt.Errorf("合同访问链接格式验证错误: %w", err)
}
if !matched {
return errors.New("合同访问链接格式不正确必须以http://或https://开头")
}
return nil
}
// validateContractSignURL 验证合同签署链接
func (c *ContractInfo) validateContractSignURL() error {
if c.ContractSignURL == "" {
return errors.New("合同签署链接不能为空")
}
// URL格式验证
urlPattern := `^https?://.*`
matched, err := regexp.MatchString(urlPattern, c.ContractSignURL)
if err != nil {
return fmt.Errorf("合同签署链接格式验证错误: %w", err)
}
if !matched {
return errors.New("合同签署链接格式不正确必须以http://或https://开头")
}
return nil
}
// validateSignerInfo 验证签署人信息
func (c *ContractInfo) validateSignerInfo() error {
// 如果有签署人信息,进行验证
if c.SignerAccount != "" || c.SignerName != "" || c.TransactorPhone != "" {
if c.SignerAccount == "" {
return errors.New("签署人账号不能为空")
}
if c.SignerName == "" {
return errors.New("签署人姓名不能为空")
}
if c.TransactorPhone != "" {
// 手机号格式验证
phonePattern := `^1[3-9]\d{9}$`
matched, err := regexp.MatchString(phonePattern, c.TransactorPhone)
if err != nil {
return fmt.Errorf("经办人手机号格式验证错误: %w", err)
}
if !matched {
return errors.New("经办人手机号格式不正确")
}
}
if c.TransactorIDCardNum != "" {
// 身份证号格式验证
idPattern := `^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$`
matched, err := regexp.MatchString(idPattern, c.TransactorIDCardNum)
if err != nil {
return fmt.Errorf("经办人身份证号格式验证错误: %w", err)
}
if !matched {
return errors.New("经办人身份证号格式不正确")
}
}
}
return nil
}
// validateStatus 验证合同状态
func (c *ContractInfo) validateStatus() error {
validStatuses := []string{
ContractStatusDraft,
ContractStatusGenerated,
ContractStatusSigning,
ContractStatusSigned,
ContractStatusExpired,
ContractStatusRejected,
ContractStatusCancelled,
}
for _, status := range validStatuses {
if c.Status == status {
return nil
}
}
return fmt.Errorf("无效的合同状态: %s", c.Status)
}
// SetSignerInfo 设置签署人信息
func (c *ContractInfo) SetSignerInfo(signerAccount, signerName, transactorPhone, transactorName, transactorIDCardNum string) error {
c.SignerAccount = strings.TrimSpace(signerAccount)
c.SignerName = strings.TrimSpace(signerName)
c.TransactorPhone = strings.TrimSpace(transactorPhone)
c.TransactorName = strings.TrimSpace(transactorName)
c.TransactorIDCardNum = strings.TrimSpace(transactorIDCardNum)
return c.validateSignerInfo()
}
// UpdateStatus 更新合同状态
func (c *ContractInfo) UpdateStatus(status string) error {
oldStatus := c.Status
c.Status = status
if err := c.validateStatus(); err != nil {
c.Status = oldStatus // 回滚
return err
}
// 根据状态更新进度
c.updateProgressByStatus()
return nil
}
// updateProgressByStatus 根据状态更新进度
func (c *ContractInfo) updateProgressByStatus() {
progressMap := map[string]int{
ContractStatusDraft: 0,
ContractStatusGenerated: 25,
ContractStatusSigning: 50,
ContractStatusSigned: 100,
ContractStatusExpired: 50,
ContractStatusRejected: 50,
ContractStatusCancelled: 0,
}
if progress, exists := progressMap[c.Status]; exists {
c.SignProgress = progress
}
}
// MarkAsSigning 标记为签署中
func (c *ContractInfo) MarkAsSigning() error {
c.Status = ContractStatusSigning
c.SignProgress = 50
now := time.Now()
c.SignFlowCreatedAt = &now
return nil
}
// MarkAsSigned 标记为已签署
func (c *ContractInfo) MarkAsSigned() error {
c.Status = ContractStatusSigned
c.SignProgress = 100
now := time.Now()
c.SignedAt = &now
return nil
}
// MarkAsExpired 标记为已过期
func (c *ContractInfo) MarkAsExpired() error {
c.Status = ContractStatusExpired
now := time.Now()
c.ExpiresAt = &now
return nil
}
// MarkAsRejected 标记为被拒绝
func (c *ContractInfo) MarkAsRejected() error {
c.Status = ContractStatusRejected
c.SignProgress = 50
return nil
}
// IsExpired 检查合同是否已过期
func (c *ContractInfo) IsExpired() bool {
if c.ExpiresAt == nil {
return false
}
return time.Now().After(*c.ExpiresAt)
}
// IsSigned 检查合同是否已签署
func (c *ContractInfo) IsSigned() bool {
return c.Status == ContractStatusSigned
}
// CanSign 检查是否可以签署
func (c *ContractInfo) CanSign() bool {
return c.Status == ContractStatusGenerated || c.Status == ContractStatusSigning
}
// GetStatusName 获取状态的中文名称
func (c *ContractInfo) GetStatusName() string {
statusNames := map[string]string{
ContractStatusDraft: "草稿",
ContractStatusGenerated: "已生成",
ContractStatusSigning: "签署中",
ContractStatusSigned: "已签署",
ContractStatusExpired: "已过期",
ContractStatusRejected: "被拒绝",
ContractStatusCancelled: "已取消",
}
if name, exists := statusNames[c.Status]; exists {
return name
}
return c.Status
}
// GetDisplayTitle 获取显示用的合同标题
func (c *ContractInfo) GetDisplayTitle() string {
if c.ContractTitle != "" {
return c.ContractTitle
}
return "企业认证服务合同"
}
// GetMaskedSignerAccount 获取脱敏的签署人账号
func (c *ContractInfo) GetMaskedSignerAccount() string {
if len(c.SignerAccount) <= 6 {
return c.SignerAccount
}
// 保留前3位和后3位中间用*替代
return c.SignerAccount[:3] + "***" + c.SignerAccount[len(c.SignerAccount)-3:]
}
// GetMaskedTransactorPhone 获取脱敏的经办人手机号
func (c *ContractInfo) GetMaskedTransactorPhone() string {
if len(c.TransactorPhone) != 11 {
return c.TransactorPhone
}
// 保留前3位和后4位中间用*替代
return c.TransactorPhone[:3] + "****" + c.TransactorPhone[7:]
}
// GetMaskedTransactorIDCardNum 获取脱敏的经办人身份证号
func (c *ContractInfo) GetMaskedTransactorIDCardNum() string {
if len(c.TransactorIDCardNum) != 18 {
return c.TransactorIDCardNum
}
// 保留前6位和后4位中间用*替代
return c.TransactorIDCardNum[:6] + "********" + c.TransactorIDCardNum[14:]
}
// AddMetadata 添加元数据
func (c *ContractInfo) AddMetadata(key string, value interface{}) {
if c.Metadata == nil {
c.Metadata = make(map[string]interface{})
}
c.Metadata[key] = value
}
// GetMetadata 获取元数据
func (c *ContractInfo) GetMetadata(key string) (interface{}, bool) {
if c.Metadata == nil {
return nil, false
}
value, exists := c.Metadata[key]
return value, exists
}
// Equals 比较两个合同信息是否相等
func (c *ContractInfo) Equals(other *ContractInfo) bool {
if other == nil {
return false
}
return c.ContractFileID == other.ContractFileID &&
c.EsignFlowID == other.EsignFlowID &&
c.Status == other.Status
}
// Clone 创建合同信息的副本
func (c *ContractInfo) Clone() *ContractInfo {
cloned := &ContractInfo{
ContractFileID: c.ContractFileID,
EsignFlowID: c.EsignFlowID,
ContractURL: c.ContractURL,
ContractSignURL: c.ContractSignURL,
ContractTitle: c.ContractTitle,
ContractVersion: c.ContractVersion,
TemplateID: c.TemplateID,
SignerAccount: c.SignerAccount,
SignerName: c.SignerName,
TransactorPhone: c.TransactorPhone,
TransactorName: c.TransactorName,
TransactorIDCardNum: c.TransactorIDCardNum,
Status: c.Status,
SignProgress: c.SignProgress,
}
// 复制时间字段
if c.GeneratedAt != nil {
generatedAt := *c.GeneratedAt
cloned.GeneratedAt = &generatedAt
}
if c.SignFlowCreatedAt != nil {
signFlowCreatedAt := *c.SignFlowCreatedAt
cloned.SignFlowCreatedAt = &signFlowCreatedAt
}
if c.SignedAt != nil {
signedAt := *c.SignedAt
cloned.SignedAt = &signedAt
}
if c.ExpiresAt != nil {
expiresAt := *c.ExpiresAt
cloned.ExpiresAt = &expiresAt
}
// 复制元数据
if c.Metadata != nil {
cloned.Metadata = make(map[string]interface{})
for k, v := range c.Metadata {
cloned.Metadata[k] = v
}
}
return cloned
}
// String 返回合同信息的字符串表示
func (c *ContractInfo) String() string {
return fmt.Sprintf("合同信息[文件ID:%s, 流程ID:%s, 状态:%s, 进度:%d%%]",
c.ContractFileID,
c.EsignFlowID,
c.GetStatusName(),
c.SignProgress)
}
// ToMap 转换为map格式用于序列化
func (c *ContractInfo) ToMap() map[string]interface{} {
result := map[string]interface{}{
"contract_file_id": c.ContractFileID,
"esign_flow_id": c.EsignFlowID,
"contract_url": c.ContractURL,
"contract_sign_url": c.ContractSignURL,
"contract_title": c.ContractTitle,
"contract_version": c.ContractVersion,
"template_id": c.TemplateID,
"signer_account": c.SignerAccount,
"signer_name": c.SignerName,
"transactor_phone": c.TransactorPhone,
"transactor_name": c.TransactorName,
"transactor_id_card_num": c.TransactorIDCardNum,
"status": c.Status,
"sign_progress": c.SignProgress,
}
// 添加时间字段
if c.GeneratedAt != nil {
result["generated_at"] = c.GeneratedAt
}
if c.SignFlowCreatedAt != nil {
result["sign_flow_created_at"] = c.SignFlowCreatedAt
}
if c.SignedAt != nil {
result["signed_at"] = c.SignedAt
}
if c.ExpiresAt != nil {
result["expires_at"] = c.ExpiresAt
}
// 添加元数据
if c.Metadata != nil {
result["metadata"] = c.Metadata
}
return result
}

View File

@@ -0,0 +1,352 @@
package value_objects
import (
"errors"
"fmt"
"regexp"
"strings"
)
// EnterpriseInfo 企业信息值对象
// 封装企业认证所需的核心信息,包含完整的业务规则验证
type EnterpriseInfo struct {
// 企业基本信息
CompanyName string `json:"company_name"` // 企业名称
UnifiedSocialCode string `json:"unified_social_code"` // 统一社会信用代码
// 法定代表人信息
LegalPersonName string `json:"legal_person_name"` // 法定代表人姓名
LegalPersonID string `json:"legal_person_id"` // 法定代表人身份证号
LegalPersonPhone string `json:"legal_person_phone"` // 法定代表人手机号
// 企业详细信息
RegisteredAddress string `json:"registered_address"` // 注册地址
BusinessScope string `json:"business_scope"` // 经营范围
RegisteredCapital string `json:"registered_capital"` // 注册资本
EstablishmentDate string `json:"establishment_date"` // 成立日期
}
// NewEnterpriseInfo 创建企业信息值对象
func NewEnterpriseInfo(companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone string) (*EnterpriseInfo, error) {
info := &EnterpriseInfo{
CompanyName: strings.TrimSpace(companyName),
UnifiedSocialCode: strings.TrimSpace(unifiedSocialCode),
LegalPersonName: strings.TrimSpace(legalPersonName),
LegalPersonID: strings.TrimSpace(legalPersonID),
LegalPersonPhone: strings.TrimSpace(legalPersonPhone),
}
if err := info.Validate(); err != nil {
return nil, fmt.Errorf("企业信息验证失败: %w", err)
}
return info, nil
}
// Validate 验证企业信息的完整性和格式
func (e *EnterpriseInfo) Validate() error {
if err := e.validateCompanyName(); err != nil {
return err
}
if err := e.validateUnifiedSocialCode(); err != nil {
return err
}
if err := e.validateLegalPersonName(); err != nil {
return err
}
if err := e.validateLegalPersonID(); err != nil {
return err
}
if err := e.validateLegalPersonPhone(); err != nil {
return err
}
return nil
}
// validateCompanyName 验证企业名称
func (e *EnterpriseInfo) validateCompanyName() error {
if e.CompanyName == "" {
return errors.New("企业名称不能为空")
}
if len(e.CompanyName) < 2 {
return errors.New("企业名称长度不能少于2个字符")
}
if len(e.CompanyName) > 100 {
return errors.New("企业名称长度不能超过100个字符")
}
// 检查是否包含非法字符
invalidChars := []string{"`", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "+", "=", "{", "}", "[", "]", "\\", "|", ";", ":", "'", "\"", "<", ">", ",", ".", "?", "/"}
for _, char := range invalidChars {
if strings.Contains(e.CompanyName, char) {
return fmt.Errorf("企业名称不能包含特殊字符: %s", char)
}
}
return nil
}
// validateUnifiedSocialCode 验证统一社会信用代码
func (e *EnterpriseInfo) validateUnifiedSocialCode() error {
if e.UnifiedSocialCode == "" {
return errors.New("统一社会信用代码不能为空")
}
// 统一社会信用代码格式验证18位数字和字母
pattern := `^[0-9A-HJ-NPQRTUWXY]{2}[0-9]{6}[0-9A-HJ-NPQRTUWXY]{10}$`
matched, err := regexp.MatchString(pattern, e.UnifiedSocialCode)
if err != nil {
return fmt.Errorf("统一社会信用代码格式验证错误: %w", err)
}
if !matched {
return errors.New("统一社会信用代码格式不正确应为18位数字和字母组合")
}
return nil
}
// validateLegalPersonName 验证法定代表人姓名
func (e *EnterpriseInfo) validateLegalPersonName() error {
if e.LegalPersonName == "" {
return errors.New("法定代表人姓名不能为空")
}
if len(e.LegalPersonName) < 2 {
return errors.New("法定代表人姓名长度不能少于2个字符")
}
if len(e.LegalPersonName) > 50 {
return errors.New("法定代表人姓名长度不能超过50个字符")
}
// 中文姓名格式验证
pattern := `^[\u4e00-\u9fa5·]+$`
matched, err := regexp.MatchString(pattern, e.LegalPersonName)
if err != nil {
return fmt.Errorf("法定代表人姓名格式验证错误: %w", err)
}
if !matched {
return errors.New("法定代表人姓名只能包含中文字符和间隔号")
}
return nil
}
// validateLegalPersonID 验证法定代表人身份证号
func (e *EnterpriseInfo) validateLegalPersonID() error {
if e.LegalPersonID == "" {
return errors.New("法定代表人身份证号不能为空")
}
// 身份证号格式验证18位
if len(e.LegalPersonID) != 18 {
return errors.New("身份证号必须为18位")
}
pattern := `^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$`
matched, err := regexp.MatchString(pattern, e.LegalPersonID)
if err != nil {
return fmt.Errorf("身份证号格式验证错误: %w", err)
}
if !matched {
return errors.New("身份证号格式不正确")
}
// 身份证号校验码验证
if !e.validateIDChecksum() {
return errors.New("身份证号校验码错误")
}
return nil
}
// validateIDChecksum 验证身份证号校验码
func (e *EnterpriseInfo) validateIDChecksum() bool {
if len(e.LegalPersonID) != 18 {
return false
}
// 加权因子
weights := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
// 校验码对应表
checkCodes := []string{"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"}
sum := 0
for i := 0; i < 17; i++ {
digit := int(e.LegalPersonID[i] - '0')
sum += digit * weights[i]
}
checkCodeIndex := sum % 11
expectedCheckCode := checkCodes[checkCodeIndex]
actualCheckCode := strings.ToUpper(string(e.LegalPersonID[17]))
return expectedCheckCode == actualCheckCode
}
// validateLegalPersonPhone 验证法定代表人手机号
func (e *EnterpriseInfo) validateLegalPersonPhone() error {
if e.LegalPersonPhone == "" {
return errors.New("法定代表人手机号不能为空")
}
// 手机号格式验证11位数字1开头
pattern := `^1[3-9]\d{9}$`
matched, err := regexp.MatchString(pattern, e.LegalPersonPhone)
if err != nil {
return fmt.Errorf("手机号格式验证错误: %w", err)
}
if !matched {
return errors.New("手机号格式不正确应为11位数字且以1开头")
}
return nil
}
// IsComplete 检查企业信息是否完整
func (e *EnterpriseInfo) IsComplete() bool {
return e.CompanyName != "" &&
e.UnifiedSocialCode != "" &&
e.LegalPersonName != "" &&
e.LegalPersonID != "" &&
e.LegalPersonPhone != ""
}
// IsDetailComplete 检查企业详细信息是否完整
func (e *EnterpriseInfo) IsDetailComplete() bool {
return e.IsComplete() &&
e.RegisteredAddress != "" &&
e.BusinessScope != "" &&
e.RegisteredCapital != "" &&
e.EstablishmentDate != ""
}
// GetDisplayName 获取显示用的企业名称
func (e *EnterpriseInfo) GetDisplayName() string {
if e.CompanyName == "" {
return "未知企业"
}
return e.CompanyName
}
// GetMaskedUnifiedSocialCode 获取脱敏的统一社会信用代码
func (e *EnterpriseInfo) GetMaskedUnifiedSocialCode() string {
if len(e.UnifiedSocialCode) != 18 {
return e.UnifiedSocialCode
}
// 保留前6位和后4位中间用*替代
return e.UnifiedSocialCode[:6] + "********" + e.UnifiedSocialCode[14:]
}
// GetMaskedLegalPersonID 获取脱敏的法定代表人身份证号
func (e *EnterpriseInfo) GetMaskedLegalPersonID() string {
if len(e.LegalPersonID) != 18 {
return e.LegalPersonID
}
// 保留前6位和后4位中间用*替代
return e.LegalPersonID[:6] + "********" + e.LegalPersonID[14:]
}
// GetMaskedLegalPersonPhone 获取脱敏的法定代表人手机号
func (e *EnterpriseInfo) GetMaskedLegalPersonPhone() string {
if len(e.LegalPersonPhone) != 11 {
return e.LegalPersonPhone
}
// 保留前3位和后4位中间用*替代
return e.LegalPersonPhone[:3] + "****" + e.LegalPersonPhone[7:]
}
// Equals 比较两个企业信息是否相等
func (e *EnterpriseInfo) Equals(other *EnterpriseInfo) bool {
if other == nil {
return false
}
return e.CompanyName == other.CompanyName &&
e.UnifiedSocialCode == other.UnifiedSocialCode &&
e.LegalPersonName == other.LegalPersonName &&
e.LegalPersonID == other.LegalPersonID &&
e.LegalPersonPhone == other.LegalPersonPhone
}
// Clone 创建企业信息的副本
func (e *EnterpriseInfo) Clone() *EnterpriseInfo {
return &EnterpriseInfo{
CompanyName: e.CompanyName,
UnifiedSocialCode: e.UnifiedSocialCode,
LegalPersonName: e.LegalPersonName,
LegalPersonID: e.LegalPersonID,
LegalPersonPhone: e.LegalPersonPhone,
RegisteredAddress: e.RegisteredAddress,
BusinessScope: e.BusinessScope,
RegisteredCapital: e.RegisteredCapital,
EstablishmentDate: e.EstablishmentDate,
}
}
// String 返回企业信息的字符串表示
func (e *EnterpriseInfo) String() string {
return fmt.Sprintf("企业信息[名称:%s, 信用代码:%s, 法人:%s]",
e.CompanyName,
e.GetMaskedUnifiedSocialCode(),
e.LegalPersonName)
}
// ToMap 转换为map格式用于序列化
func (e *EnterpriseInfo) ToMap() map[string]interface{} {
return map[string]interface{}{
"company_name": e.CompanyName,
"unified_social_code": e.UnifiedSocialCode,
"legal_person_name": e.LegalPersonName,
"legal_person_id": e.LegalPersonID,
"legal_person_phone": e.LegalPersonPhone,
"registered_address": e.RegisteredAddress,
"business_scope": e.BusinessScope,
"registered_capital": e.RegisteredCapital,
"establishment_date": e.EstablishmentDate,
}
}
// FromMap 从map格式创建企业信息用于反序列化
func FromMap(data map[string]interface{}) (*EnterpriseInfo, error) {
getString := func(key string) string {
if val, exists := data[key]; exists {
if str, ok := val.(string); ok {
return strings.TrimSpace(str)
}
}
return ""
}
info := &EnterpriseInfo{
CompanyName: getString("company_name"),
UnifiedSocialCode: getString("unified_social_code"),
LegalPersonName: getString("legal_person_name"),
LegalPersonID: getString("legal_person_id"),
LegalPersonPhone: getString("legal_person_phone"),
RegisteredAddress: getString("registered_address"),
BusinessScope: getString("business_scope"),
RegisteredCapital: getString("registered_capital"),
EstablishmentDate: getString("establishment_date"),
}
if err := info.Validate(); err != nil {
return nil, fmt.Errorf("从Map创建企业信息失败: %w", err)
}
return info, nil
}

View File

@@ -0,0 +1,215 @@
package enums
// ActorType 操作者类型枚举
type ActorType string
const (
// === 操作者类型 ===
ActorTypeUser ActorType = "user" // 用户操作
ActorTypeSystem ActorType = "system" // 系统操作
ActorTypeAdmin ActorType = "admin" // 管理员操作
ActorTypeEsign ActorType = "esign" // e签宝回调操作
)
// AllActorTypes 所有操作者类型列表
var AllActorTypes = []ActorType{
ActorTypeUser,
ActorTypeSystem,
ActorTypeAdmin,
ActorTypeEsign,
}
// IsValidActorType 检查操作者类型是否有效
func IsValidActorType(actorType ActorType) bool {
for _, validType := range AllActorTypes {
if actorType == validType {
return true
}
}
return false
}
// GetActorTypeName 获取操作者类型的中文名称
func GetActorTypeName(actorType ActorType) string {
typeNames := map[ActorType]string{
ActorTypeUser: "用户",
ActorTypeSystem: "系统",
ActorTypeAdmin: "管理员",
ActorTypeEsign: "e签宝",
}
if name, exists := typeNames[actorType]; exists {
return name
}
return string(actorType)
}
// GetActorTypeDescription 获取操作者类型描述
func GetActorTypeDescription(actorType ActorType) string {
descriptions := map[ActorType]string{
ActorTypeUser: "用户主动操作",
ActorTypeSystem: "系统自动操作",
ActorTypeAdmin: "管理员操作",
ActorTypeEsign: "e签宝回调操作",
}
if desc, exists := descriptions[actorType]; exists {
return desc
}
return string(actorType)
}
// IsAutomatedActor 判断是否为自动化操作者
func IsAutomatedActor(actorType ActorType) bool {
automatedActors := map[ActorType]bool{
ActorTypeUser: false, // 用户手动操作
ActorTypeSystem: true, // 系统自动操作
ActorTypeAdmin: false, // 管理员手动操作
ActorTypeEsign: true, // e签宝自动回调
}
if automated, exists := automatedActors[actorType]; exists {
return automated
}
return false
}
// IsHumanActor 判断是否为人工操作者
func IsHumanActor(actorType ActorType) bool {
return !IsAutomatedActor(actorType)
}
// GetActorTypePriority 获取操作者类型优先级(用于日志排序等)
func GetActorTypePriority(actorType ActorType) int {
priorities := map[ActorType]int{
ActorTypeUser: 1, // 用户操作最重要
ActorTypeAdmin: 2, // 管理员操作次之
ActorTypeEsign: 3, // e签宝回调
ActorTypeSystem: 4, // 系统操作最后
}
if priority, exists := priorities[actorType]; exists {
return priority
}
return 999
}
// GetPermissionLevel 获取权限级别
func GetPermissionLevel(actorType ActorType) int {
levels := map[ActorType]int{
ActorTypeUser: 1, // 普通用户权限
ActorTypeSystem: 2, // 系统权限
ActorTypeEsign: 2, // e签宝权限与系统同级
ActorTypeAdmin: 3, // 管理员最高权限
}
if level, exists := levels[actorType]; exists {
return level
}
return 0
}
// CanPerformAction 检查操作者是否可以执行指定操作
func CanPerformAction(actorType ActorType, action string) bool {
permissions := map[ActorType][]string{
ActorTypeUser: {
"submit_enterprise_info", // 提交企业信息
"apply_contract", // 申请合同
"view_certification", // 查看认证信息
},
ActorTypeSystem: {
"auto_transition", // 自动状态转换
"system_maintenance", // 系统维护
"data_cleanup", // 数据清理
},
ActorTypeAdmin: {
"manual_transition", // 手动状态转换
"view_all_certifications", // 查看所有认证
"system_configuration", // 系统配置
"user_management", // 用户管理
},
ActorTypeEsign: {
"callback_notification", // 回调通知
"status_update", // 状态更新
"verification_result", // 验证结果
},
}
if actions, exists := permissions[actorType]; exists {
for _, permittedAction := range actions {
if permittedAction == action {
return true
}
}
}
return false
}
// GetAllowedActions 获取操作者允许执行的所有操作
func GetAllowedActions(actorType ActorType) []string {
permissions := map[ActorType][]string{
ActorTypeUser: {
"submit_enterprise_info",
"apply_contract",
"view_certification",
},
ActorTypeSystem: {
"auto_transition",
"system_maintenance",
"data_cleanup",
},
ActorTypeAdmin: {
"manual_transition",
"view_all_certifications",
"system_configuration",
"user_management",
},
ActorTypeEsign: {
"callback_notification",
"status_update",
"verification_result",
},
}
if actions, exists := permissions[actorType]; exists {
return actions
}
return []string{}
}
// GetActorTypeFromContext 从上下文推断操作者类型
func GetActorTypeFromContext(context map[string]interface{}) ActorType {
// 检查是否为e签宝回调
if _, exists := context["esign_callback"]; exists {
return ActorTypeEsign
}
// 检查是否为管理员操作
if isAdmin, exists := context["is_admin"]; exists && isAdmin.(bool) {
return ActorTypeAdmin
}
// 检查是否为用户操作
if userID, exists := context["user_id"]; exists && userID != nil {
return ActorTypeUser
}
// 默认为系统操作
return ActorTypeSystem
}
// FormatActorInfo 格式化操作者信息
func FormatActorInfo(actorType ActorType, actorID string) string {
switch actorType {
case ActorTypeUser:
return "用户(" + actorID + ")"
case ActorTypeAdmin:
return "管理员(" + actorID + ")"
case ActorTypeSystem:
return "系统"
case ActorTypeEsign:
return "e签宝回调"
default:
return string(actorType) + "(" + actorID + ")"
}
}

View File

@@ -4,23 +4,50 @@ package enums
type CertificationStatus string
const (
// 主流程状态
StatusPending CertificationStatus = "pending" // 待认证
StatusInfoSubmitted CertificationStatus = "info_submitted" // 已提交企业信息
StatusEnterpriseVerified CertificationStatus = "enterprise_verified" // 已企业认证
StatusContractApplied CertificationStatus = "contract_applied" // 已申请签署合同
StatusContractSigned CertificationStatus = "contract_signed" // 已签署合同
StatusCompleted CertificationStatus = "completed" // 认证完成
// === 主流程状态 ===
StatusPending CertificationStatus = "pending" // 待认证
StatusInfoSubmitted CertificationStatus = "info_submitted" // 已提交企业信息
StatusEnterpriseVerified CertificationStatus = "enterprise_verified" // 已企业认证
StatusContractApplied CertificationStatus = "contract_applied" // 已申请签署合同
StatusContractSigned CertificationStatus = "contract_signed" // 已签署合同(认证完成)
// === 失败状态 ===
StatusInfoRejected CertificationStatus = "info_rejected" // 企业信息被拒绝
StatusContractRejected CertificationStatus = "contract_rejected" // 合同被拒签
StatusContractExpired CertificationStatus = "contract_expired" // 合同签署超时
)
// AllStatuses 所有有效状态列表
var AllStatuses = []CertificationStatus{
StatusPending,
StatusInfoSubmitted,
StatusEnterpriseVerified,
StatusContractApplied,
StatusContractSigned,
StatusInfoRejected,
StatusContractRejected,
StatusContractExpired,
}
// MainFlowStatuses 主流程状态列表
var MainFlowStatuses = []CertificationStatus{
StatusPending,
StatusInfoSubmitted,
StatusEnterpriseVerified,
StatusContractApplied,
StatusContractSigned,
}
// FailureStatuses 失败状态列表
var FailureStatuses = []CertificationStatus{
StatusInfoRejected,
StatusContractRejected,
StatusContractExpired,
}
// IsValidStatus 检查状态是否有效
func IsValidStatus(status CertificationStatus) bool {
validStatuses := []CertificationStatus{
StatusPending, StatusInfoSubmitted, StatusEnterpriseVerified,
StatusContractApplied, StatusContractSigned, StatusCompleted,
}
for _, validStatus := range validStatuses {
for _, validStatus := range AllStatuses {
if status == validStatus {
return true
}
@@ -31,12 +58,14 @@ func IsValidStatus(status CertificationStatus) bool {
// GetStatusName 获取状态的中文名称
func GetStatusName(status CertificationStatus) string {
statusNames := map[CertificationStatus]string{
StatusPending: "待认证",
StatusInfoSubmitted: "已提交企业信息",
StatusPending: "待认证",
StatusInfoSubmitted: "已提交企业信息",
StatusEnterpriseVerified: "已企业认证",
StatusContractApplied: "已申请合同",
StatusContractSigned: "已签署合同",
StatusCompleted: "认证完成",
StatusContractApplied: "已申请签署合同",
StatusContractSigned: "认证完成",
StatusInfoRejected: "企业信息被拒绝",
StatusContractRejected: "合同被拒签",
StatusContractExpired: "合同签署超时",
}
if name, exists := statusNames[status]; exists {
@@ -47,32 +76,51 @@ func GetStatusName(status CertificationStatus) string {
// IsFinalStatus 判断是否为最终状态
func IsFinalStatus(status CertificationStatus) bool {
return status == StatusCompleted
return status == StatusContractSigned
}
// IsFailureStatus 判断是否为失败状态
func IsFailureStatus(status CertificationStatus) bool {
for _, failureStatus := range FailureStatuses {
if status == failureStatus {
return true
}
}
return false
}
// IsMainFlowStatus 判断是否为主流程状态
func IsMainFlowStatus(status CertificationStatus) bool {
for _, mainStatus := range MainFlowStatuses {
if status == mainStatus {
return true
}
}
return false
}
// GetStatusCategory 获取状态分类
func GetStatusCategory(status CertificationStatus) string {
switch status {
case StatusPending:
return "initial"
case StatusInfoSubmitted, StatusEnterpriseVerified, StatusContractApplied, StatusContractSigned:
return "processing"
case StatusCompleted:
return "completed"
default:
return "unknown"
if IsMainFlowStatus(status) {
return "主流程"
}
if IsFailureStatus(status) {
return "失败状态"
}
return "未知"
}
// GetStatusPriority 获取状态优先级(用于排序)
func GetStatusPriority(status CertificationStatus) int {
priorities := map[CertificationStatus]int{
StatusPending: 0,
StatusInfoSubmitted: 1,
StatusEnterpriseVerified: 2,
StatusContractApplied: 3,
StatusContractSigned: 4,
StatusCompleted: 5,
StatusPending: 1,
StatusInfoSubmitted: 2,
StatusEnterpriseVerified: 3,
StatusContractApplied: 4,
StatusContractSigned: 5,
StatusInfoRejected: 6,
StatusContractRejected: 7,
StatusContractExpired: 8,
}
if priority, exists := priorities[status]; exists {
@@ -80,3 +128,131 @@ func GetStatusPriority(status CertificationStatus) int {
}
return 999
}
// GetProgressPercentage 获取进度百分比
func GetProgressPercentage(status CertificationStatus) int {
progressMap := map[CertificationStatus]int{
StatusPending: 0,
StatusInfoSubmitted: 25,
StatusEnterpriseVerified: 50,
StatusContractApplied: 75,
StatusContractSigned: 100,
StatusInfoRejected: 25,
StatusContractRejected: 75,
StatusContractExpired: 75,
}
if progress, exists := progressMap[status]; exists {
return progress
}
return 0
}
// IsUserActionRequired 检查是否需要用户操作
func IsUserActionRequired(status CertificationStatus) bool {
userActionRequired := map[CertificationStatus]bool{
StatusPending: true, // 需要提交企业信息
StatusInfoSubmitted: false, // 等待系统验证
StatusEnterpriseVerified: true, // 需要申请合同
StatusContractApplied: true, // 需要签署合同
StatusContractSigned: false, // 已完成
StatusInfoRejected: true, // 需要重新提交
StatusContractRejected: true, // 需要重新申请
StatusContractExpired: true, // 需要重新申请
}
if required, exists := userActionRequired[status]; exists {
return required
}
return false
}
// GetUserActionHint 获取用户操作提示
func GetUserActionHint(status CertificationStatus) string {
hints := map[CertificationStatus]string{
StatusPending: "请提交企业信息",
StatusInfoSubmitted: "系统正在验证企业信息,请稍候",
StatusEnterpriseVerified: "企业认证完成,请申请签署合同",
StatusContractApplied: "请在规定时间内完成合同签署",
StatusContractSigned: "认证已完成",
StatusInfoRejected: "企业信息验证失败,请修正后重新提交",
StatusContractRejected: "合同签署被拒绝,可重新申请",
StatusContractExpired: "合同签署已超时,请重新申请",
}
if hint, exists := hints[status]; exists {
return hint
}
return ""
}
// GetNextValidStatuses 获取当前状态的下一个有效状态列表
func GetNextValidStatuses(currentStatus CertificationStatus) []CertificationStatus {
nextStatusMap := map[CertificationStatus][]CertificationStatus{
StatusPending: {
StatusInfoSubmitted,
},
StatusInfoSubmitted: {
StatusEnterpriseVerified,
StatusInfoRejected,
},
StatusEnterpriseVerified: {
StatusContractApplied,
},
StatusContractApplied: {
StatusContractSigned,
StatusContractRejected,
StatusContractExpired,
},
StatusContractSigned: {
// 最终状态,无后续状态
},
StatusInfoRejected: {
StatusInfoSubmitted, // 可以重新提交
},
StatusContractRejected: {
StatusEnterpriseVerified, // 重置到企业认证状态
},
StatusContractExpired: {
StatusEnterpriseVerified, // 重置到企业认证状态
},
}
if nextStatuses, exists := nextStatusMap[currentStatus]; exists {
return nextStatuses
}
return []CertificationStatus{}
}
// CanTransitionTo 检查是否可以从当前状态转换到目标状态
func CanTransitionTo(currentStatus, targetStatus CertificationStatus) bool {
validNextStatuses := GetNextValidStatuses(currentStatus)
for _, validStatus := range validNextStatuses {
if validStatus == targetStatus {
return true
}
}
return false
}
// GetTransitionReason 获取状态转换的原因描述
func GetTransitionReason(from, to CertificationStatus) string {
transitionReasons := map[string]string{
string(StatusPending) + "->" + string(StatusInfoSubmitted): "用户提交企业信息",
string(StatusInfoSubmitted) + "->" + string(StatusEnterpriseVerified): "e签宝企业认证成功",
string(StatusInfoSubmitted) + "->" + string(StatusInfoRejected): "e签宝企业认证失败",
string(StatusEnterpriseVerified) + "->" + string(StatusContractApplied): "用户申请签署合同",
string(StatusContractApplied) + "->" + string(StatusContractSigned): "e签宝合同签署成功",
string(StatusContractApplied) + "->" + string(StatusContractRejected): "用户拒绝签署合同",
string(StatusContractApplied) + "->" + string(StatusContractExpired): "合同签署超时",
string(StatusInfoRejected) + "->" + string(StatusInfoSubmitted): "用户重新提交企业信息",
string(StatusContractRejected) + "->" + string(StatusEnterpriseVerified): "重置状态,准备重新申请",
string(StatusContractExpired) + "->" + string(StatusEnterpriseVerified): "重置状态,准备重新申请",
}
key := string(from) + "->" + string(to)
if reason, exists := transitionReasons[key]; exists {
return reason
}
return "未知转换"
}

View File

@@ -0,0 +1,270 @@
package enums
// FailureReason 失败原因枚举
type FailureReason string
const (
// === 企业信息验证失败原因 ===
FailureReasonEnterpriseNotExists FailureReason = "enterprise_not_exists" // 企业不存在
FailureReasonEnterpriseInfoMismatch FailureReason = "enterprise_info_mismatch" // 企业信息不匹配
FailureReasonEnterpriseStatusAbnormal FailureReason = "enterprise_status_abnormal" // 企业状态异常
FailureReasonLegalPersonMismatch FailureReason = "legal_person_mismatch" // 法定代表人信息不匹配
FailureReasonEsignVerificationFailed FailureReason = "esign_verification_failed" // e签宝验证失败
FailureReasonInvalidDocument FailureReason = "invalid_document" // 证件信息无效
// === 合同签署失败原因 ===
FailureReasonContractRejectedByUser FailureReason = "contract_rejected_by_user" // 用户拒绝签署
FailureReasonContractExpired FailureReason = "contract_expired" // 合同签署超时
FailureReasonSignProcessFailed FailureReason = "sign_process_failed" // 签署流程失败
FailureReasonContractGenFailed FailureReason = "contract_gen_failed" // 合同生成失败
FailureReasonEsignFlowError FailureReason = "esign_flow_error" // e签宝流程错误
// === 系统错误原因 ===
FailureReasonSystemError FailureReason = "system_error" // 系统错误
FailureReasonNetworkError FailureReason = "network_error" // 网络错误
FailureReasonTimeout FailureReason = "timeout" // 操作超时
FailureReasonUnknownError FailureReason = "unknown_error" // 未知错误
)
// AllFailureReasons 所有失败原因列表
var AllFailureReasons = []FailureReason{
// 企业信息验证失败
FailureReasonEnterpriseNotExists,
FailureReasonEnterpriseInfoMismatch,
FailureReasonEnterpriseStatusAbnormal,
FailureReasonLegalPersonMismatch,
FailureReasonEsignVerificationFailed,
FailureReasonInvalidDocument,
// 合同签署失败
FailureReasonContractRejectedByUser,
FailureReasonContractExpired,
FailureReasonSignProcessFailed,
FailureReasonContractGenFailed,
FailureReasonEsignFlowError,
// 系统错误
FailureReasonSystemError,
FailureReasonNetworkError,
FailureReasonTimeout,
FailureReasonUnknownError,
}
// EnterpriseVerificationFailureReasons 企业验证失败原因列表
var EnterpriseVerificationFailureReasons = []FailureReason{
FailureReasonEnterpriseNotExists,
FailureReasonEnterpriseInfoMismatch,
FailureReasonEnterpriseStatusAbnormal,
FailureReasonLegalPersonMismatch,
FailureReasonEsignVerificationFailed,
FailureReasonInvalidDocument,
}
// ContractSignFailureReasons 合同签署失败原因列表
var ContractSignFailureReasons = []FailureReason{
FailureReasonContractRejectedByUser,
FailureReasonContractExpired,
FailureReasonSignProcessFailed,
FailureReasonContractGenFailed,
FailureReasonEsignFlowError,
}
// SystemErrorReasons 系统错误原因列表
var SystemErrorReasons = []FailureReason{
FailureReasonSystemError,
FailureReasonNetworkError,
FailureReasonTimeout,
FailureReasonUnknownError,
}
// IsValidFailureReason 检查失败原因是否有效
func IsValidFailureReason(reason FailureReason) bool {
for _, validReason := range AllFailureReasons {
if reason == validReason {
return true
}
}
return false
}
// GetFailureReasonName 获取失败原因的中文名称
func GetFailureReasonName(reason FailureReason) string {
reasonNames := map[FailureReason]string{
// 企业信息验证失败
FailureReasonEnterpriseNotExists: "企业不存在",
FailureReasonEnterpriseInfoMismatch: "企业信息不匹配",
FailureReasonEnterpriseStatusAbnormal: "企业状态异常",
FailureReasonLegalPersonMismatch: "法定代表人信息不匹配",
FailureReasonEsignVerificationFailed: "e签宝验证失败",
FailureReasonInvalidDocument: "证件信息无效",
// 合同签署失败
FailureReasonContractRejectedByUser: "用户拒绝签署",
FailureReasonContractExpired: "合同签署超时",
FailureReasonSignProcessFailed: "签署流程失败",
FailureReasonContractGenFailed: "合同生成失败",
FailureReasonEsignFlowError: "e签宝流程错误",
// 系统错误
FailureReasonSystemError: "系统错误",
FailureReasonNetworkError: "网络错误",
FailureReasonTimeout: "操作超时",
FailureReasonUnknownError: "未知错误",
}
if name, exists := reasonNames[reason]; exists {
return name
}
return string(reason)
}
// GetFailureReasonCategory 获取失败原因分类
func GetFailureReasonCategory(reason FailureReason) string {
categories := map[FailureReason]string{
// 企业信息验证失败
FailureReasonEnterpriseNotExists: "企业验证",
FailureReasonEnterpriseInfoMismatch: "企业验证",
FailureReasonEnterpriseStatusAbnormal: "企业验证",
FailureReasonLegalPersonMismatch: "企业验证",
FailureReasonEsignVerificationFailed: "企业验证",
FailureReasonInvalidDocument: "企业验证",
// 合同签署失败
FailureReasonContractRejectedByUser: "合同签署",
FailureReasonContractExpired: "合同签署",
FailureReasonSignProcessFailed: "合同签署",
FailureReasonContractGenFailed: "合同签署",
FailureReasonEsignFlowError: "合同签署",
// 系统错误
FailureReasonSystemError: "系统错误",
FailureReasonNetworkError: "系统错误",
FailureReasonTimeout: "系统错误",
FailureReasonUnknownError: "系统错误",
}
if category, exists := categories[reason]; exists {
return category
}
return "未知"
}
// IsEnterpriseVerificationFailure 判断是否为企业验证失败
func IsEnterpriseVerificationFailure(reason FailureReason) bool {
for _, verifyReason := range EnterpriseVerificationFailureReasons {
if reason == verifyReason {
return true
}
}
return false
}
// IsContractSignFailure 判断是否为合同签署失败
func IsContractSignFailure(reason FailureReason) bool {
for _, signReason := range ContractSignFailureReasons {
if reason == signReason {
return true
}
}
return false
}
// IsSystemError 判断是否为系统错误
func IsSystemError(reason FailureReason) bool {
for _, systemReason := range SystemErrorReasons {
if reason == systemReason {
return true
}
}
return false
}
// GetSuggestedAction 获取建议的后续操作
func GetSuggestedAction(reason FailureReason) string {
actions := map[FailureReason]string{
// 企业信息验证失败
FailureReasonEnterpriseNotExists: "请检查企业名称和统一社会信用代码是否正确",
FailureReasonEnterpriseInfoMismatch: "请核对企业信息是否与工商登记信息一致",
FailureReasonEnterpriseStatusAbnormal: "请确认企业状态正常,如有疑问请联系客服",
FailureReasonLegalPersonMismatch: "请核对法定代表人信息是否正确",
FailureReasonEsignVerificationFailed: "请稍后重试,如持续失败请联系客服",
FailureReasonInvalidDocument: "请检查证件信息是否有效",
// 合同签署失败
FailureReasonContractRejectedByUser: "您可以重新申请签署合同",
FailureReasonContractExpired: "请重新申请签署合同",
FailureReasonSignProcessFailed: "请重新尝试签署,如持续失败请联系客服",
FailureReasonContractGenFailed: "系统正在处理,请稍后重试",
FailureReasonEsignFlowError: "请稍后重试,如持续失败请联系客服",
// 系统错误
FailureReasonSystemError: "系统暂时不可用,请稍后重试",
FailureReasonNetworkError: "网络连接异常,请检查网络后重试",
FailureReasonTimeout: "操作超时,请重新尝试",
FailureReasonUnknownError: "发生未知错误,请联系客服",
}
if action, exists := actions[reason]; exists {
return action
}
return "请联系客服处理"
}
// IsRetryable 判断是否可以重试
func IsRetryable(reason FailureReason) bool {
retryableReasons := map[FailureReason]bool{
// 企业信息验证失败 - 用户数据问题,可重试
FailureReasonEnterpriseNotExists: true,
FailureReasonEnterpriseInfoMismatch: true,
FailureReasonEnterpriseStatusAbnormal: false, // 企业状态问题,需要外部解决
FailureReasonLegalPersonMismatch: true,
FailureReasonEsignVerificationFailed: true, // 可能是临时问题
FailureReasonInvalidDocument: true,
// 合同签署失败
FailureReasonContractRejectedByUser: true, // 用户可以改变主意
FailureReasonContractExpired: true, // 可以重新申请
FailureReasonSignProcessFailed: true, // 可能是临时问题
FailureReasonContractGenFailed: true, // 可能是临时问题
FailureReasonEsignFlowError: true, // 可能是临时问题
// 系统错误 - 大部分可重试
FailureReasonSystemError: true,
FailureReasonNetworkError: true,
FailureReasonTimeout: true,
FailureReasonUnknownError: false, // 未知错误,不建议自动重试
}
if retryable, exists := retryableReasons[reason]; exists {
return retryable
}
return false
}
// GetRetrySuggestion 获取重试建议
func GetRetrySuggestion(reason FailureReason) string {
if !IsRetryable(reason) {
return "此问题不建议重试,请联系客服处理"
}
suggestions := map[FailureReason]string{
FailureReasonEnterpriseNotExists: "请修正企业信息后重新提交",
FailureReasonEnterpriseInfoMismatch: "请核对企业信息后重新提交",
FailureReasonLegalPersonMismatch: "请确认法定代表人信息后重新提交",
FailureReasonEsignVerificationFailed: "请稍后重新尝试",
FailureReasonInvalidDocument: "请检查证件信息后重新提交",
FailureReasonContractRejectedByUser: "如需要可重新申请合同",
FailureReasonContractExpired: "请重新申请合同签署",
FailureReasonSignProcessFailed: "请重新尝试签署",
FailureReasonContractGenFailed: "请稍后重新申请",
FailureReasonEsignFlowError: "请稍后重新尝试",
FailureReasonSystemError: "请稍后重试",
FailureReasonNetworkError: "请检查网络连接后重试",
FailureReasonTimeout: "请重新尝试操作",
}
if suggestion, exists := suggestions[reason]; exists {
return suggestion
}
return "请重新尝试操作"
}

View File

@@ -0,0 +1,29 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/shared/interfaces"
)
// CertificationCommandRepository 认证命令仓储接口
// 专门处理认证数据的变更操作符合CQRS模式
type CertificationCommandRepository interface {
// 基础CRUD操作
Create(ctx context.Context, cert entities.Certification) error
Update(ctx context.Context, cert entities.Certification) error
Delete(ctx context.Context, id string) error
// 业务特定的更新操作
UpdateStatus(ctx context.Context, id string, status enums.CertificationStatus) error
UpdateAuthFlowID(ctx context.Context, id string, authFlowID string) error
UpdateContractInfo(ctx context.Context, id string, contractFileID, esignFlowID, contractURL, contractSignURL string) error
UpdateFailureInfo(ctx context.Context, id string, reason enums.FailureReason, message string) error
// 批量操作
BatchUpdateStatus(ctx context.Context, ids []string, status enums.CertificationStatus) error
// 事务支持
WithTx(tx interfaces.Transaction) CertificationCommandRepository
}

View File

@@ -0,0 +1,122 @@
package repositories
import (
"context"
"time"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories/queries"
)
// CertificationQueryRepository 认证查询仓储接口
// 专门处理认证数据的查询操作符合CQRS模式
type CertificationQueryRepository interface {
// 基础查询操作
GetByID(ctx context.Context, id string) (*entities.Certification, error)
GetByUserID(ctx context.Context, userID string) (*entities.Certification, error)
Exists(ctx context.Context, id string) (bool, error)
// 列表查询
List(ctx context.Context, query *queries.ListCertificationsQuery) ([]*entities.Certification, int64, error)
ListByUserIDs(ctx context.Context, userIDs []string) ([]*entities.Certification, error)
ListByStatus(ctx context.Context, status enums.CertificationStatus, limit int) ([]*entities.Certification, error)
// 业务查询
FindByAuthFlowID(ctx context.Context, authFlowID string) (*entities.Certification, error)
FindByEsignFlowID(ctx context.Context, esignFlowID string) (*entities.Certification, error)
ListPendingRetry(ctx context.Context, maxRetryCount int) ([]*entities.Certification, error)
GetPendingCertifications(ctx context.Context) ([]*entities.Certification, error)
GetExpiredContracts(ctx context.Context) ([]*entities.Certification, error)
GetCertificationsByDateRange(ctx context.Context, startDate, endDate time.Time) ([]*entities.Certification, error)
GetUserActiveCertification(ctx context.Context, userID string) (*entities.Certification, error)
// 统计查询
GetStatistics(ctx context.Context, period CertificationTimePeriod) (*CertificationStatistics, error)
CountByStatus(ctx context.Context, status enums.CertificationStatus) (int64, error)
CountByFailureReason(ctx context.Context, reason enums.FailureReason) (int64, error)
GetProgressStatistics(ctx context.Context) (*CertificationProgressStats, error)
// 搜索查询
SearchByCompanyName(ctx context.Context, companyName string, limit int) ([]*entities.Certification, error)
SearchByLegalPerson(ctx context.Context, legalPersonName string, limit int) ([]*entities.Certification, error)
// 缓存相关
InvalidateCache(ctx context.Context, keys ...string) error
RefreshCache(ctx context.Context, certificationID string) error
}
// CertificationTimePeriod 时间周期枚举
type CertificationTimePeriod string
const (
PeriodDaily CertificationTimePeriod = "daily"
PeriodWeekly CertificationTimePeriod = "weekly"
PeriodMonthly CertificationTimePeriod = "monthly"
PeriodYearly CertificationTimePeriod = "yearly"
)
// CertificationStatistics 认证统计信息
type CertificationStatistics struct {
Period CertificationTimePeriod `json:"period"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
// 总体统计
TotalCertifications int64 `json:"total_certifications"`
CompletedCount int64 `json:"completed_count"`
FailedCount int64 `json:"failed_count"`
InProgressCount int64 `json:"in_progress_count"`
// 状态分布
StatusDistribution map[enums.CertificationStatus]int64 `json:"status_distribution"`
// 失败原因分布
FailureDistribution map[enums.FailureReason]int64 `json:"failure_distribution"`
// 成功率统计
SuccessRate float64 `json:"success_rate"`
EnterpriseVerifyRate float64 `json:"enterprise_verify_rate"`
ContractSignRate float64 `json:"contract_sign_rate"`
// 时间统计
AvgProcessingTime time.Duration `json:"avg_processing_time"`
AvgVerificationTime time.Duration `json:"avg_verification_time"`
AvgSigningTime time.Duration `json:"avg_signing_time"`
// 重试统计
RetryStats *CertificationRetryStats `json:"retry_stats"`
}
// CertificationProgressStats 进度统计信息
type CertificationProgressStats struct {
StatusProgress map[enums.CertificationStatus]int64 `json:"status_progress"`
ProgressDistribution map[int]int64 `json:"progress_distribution"` // key: progress percentage
// 各阶段耗时统计
StageTimeStats map[string]*CertificationStageTimeInfo `json:"stage_time_stats"`
}
// CertificationStageTimeInfo 阶段耗时信息
type CertificationStageTimeInfo struct {
StageName string `json:"stage_name"`
AverageTime time.Duration `json:"average_time"`
MinTime time.Duration `json:"min_time"`
MaxTime time.Duration `json:"max_time"`
SampleCount int64 `json:"sample_count"`
}
// CertificationRetryStats 重试统计信息
type CertificationRetryStats struct {
TotalRetries int64 `json:"total_retries"`
SuccessfulRetries int64 `json:"successful_retries"`
FailedRetries int64 `json:"failed_retries"`
RetrySuccessRate float64 `json:"retry_success_rate"`
// 各阶段重试统计
EnterpriseRetries int64 `json:"enterprise_retries"`
ContractRetries int64 `json:"contract_retries"`
// 重试原因分布
RetryReasonStats map[enums.FailureReason]int64 `json:"retry_reason_stats"`
}

View File

@@ -1,91 +0,0 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/repositories/queries"
"tyapi-server/internal/shared/interfaces"
)
// CertificationStats 认证统计信息
type CertificationStats struct {
TotalCertifications int64
PendingCertifications int64
CompletedCertifications int64
TodaySubmissions int64
}
// CertificationRepository 认证申请仓储接口
type CertificationRepository interface {
interfaces.Repository[entities.Certification]
// 基础查询 - 直接使用实体
GetByUserID(ctx context.Context, userID string) (*entities.Certification, error)
GetByStatus(ctx context.Context, status string) ([]*entities.Certification, error)
GetPendingCertifications(ctx context.Context) ([]*entities.Certification, error)
GetByAuthFlowID(ctx context.Context, authFlowID string) (entities.Certification, error)
GetByEsignFlowID(ctx context.Context, esignFlowID string) (entities.Certification, error)
// 复杂查询 - 使用查询参数
ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) ([]*entities.Certification, int64, error)
// 业务操作
UpdateStatus(ctx context.Context, certificationID string, status string) error
// 统计信息
GetStats(ctx context.Context) (*CertificationStats, error)
GetStatsByDateRange(ctx context.Context, startDate, endDate string) (*CertificationStats, error)
}
// EnterpriseInfoSubmitRecordRepository 企业信息提交记录仓储接口
type EnterpriseInfoSubmitRecordRepository interface {
interfaces.Repository[entities.EnterpriseInfoSubmitRecord]
// 基础查询
GetByUserID(ctx context.Context, userID string) ([]*entities.EnterpriseInfoSubmitRecord, error)
GetLatestByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error)
// 复杂查询
ListRecords(ctx context.Context, query *queries.ListEnterpriseInfoSubmitRecordsQuery) ([]*entities.EnterpriseInfoSubmitRecord, int64, error)
// 业务操作
UpdateStatus(ctx context.Context, recordID string, status string, reason string) error
}
// EsignContractGenerateRecordRepository e签宝生成合同记录仓储接口
type EsignContractGenerateRecordRepository interface {
interfaces.Repository[entities.EsignContractGenerateRecord]
// 基础查询
GetByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractGenerateRecord, error)
GetByUserID(ctx context.Context, userID string) ([]*entities.EsignContractGenerateRecord, error)
GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractGenerateRecord, error)
// 复杂查询
ListRecords(ctx context.Context, query *queries.ListEsignContractGenerateRecordsQuery) ([]*entities.EsignContractGenerateRecord, int64, error)
// 业务操作
UpdateStatus(ctx context.Context, recordID string, status string, reason string) error
UpdateSuccessInfo(ctx context.Context, recordID, esignFlowID, contractFileID, contractURL string) error
IncrementRetry(ctx context.Context, recordID string) error
}
// EsignContractSignRecordRepository e签宝签署合同记录仓储接口
type EsignContractSignRecordRepository interface {
interfaces.Repository[entities.EsignContractSignRecord]
// 基础查询
GetByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractSignRecord, error)
GetByUserID(ctx context.Context, userID string) ([]*entities.EsignContractSignRecord, error)
GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractSignRecord, error)
GetByGenerateRecordID(ctx context.Context, generateRecordID string) (*entities.EsignContractSignRecord, error)
// 复杂查询
ListRecords(ctx context.Context, query *queries.ListEsignContractSignRecordsQuery) ([]*entities.EsignContractSignRecord, int64, error)
// 业务操作
UpdateStatus(ctx context.Context, recordID string, status string, reason string) error
UpdateSuccessInfo(ctx context.Context, recordID, signedFileURL string) error
SetSignURL(ctx context.Context, recordID, signURL string) error
IncrementRetry(ctx context.Context, recordID string) error
MarkExpiredRecords(ctx context.Context) error
}

View File

@@ -1,62 +1,279 @@
package queries
import "tyapi-server/internal/domains/certification/enums"
import (
"fmt"
"time"
// ListCertificationsQuery 认证申请列表查询参数
"tyapi-server/internal/domains/certification/enums"
)
// GetCertificationQuery 获取单个认证查询
type GetCertificationQuery struct {
ID string `json:"id" validate:"required"`
UserID string `json:"user_id,omitempty"` // 可选的用户ID用于权限验证
}
// ListCertificationsQuery 认证列表查询
type ListCertificationsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
UserID string `json:"user_id"`
Status enums.CertificationStatus `json:"status"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
EnterpriseName string `json:"enterprise_name"`
// 分页参数
Page int `json:"page" validate:"min=1"`
PageSize int `json:"page_size" validate:"min=1,max=100"`
// 排序参数
SortBy string `json:"sort_by"` // 排序字段: created_at, updated_at, status, progress
SortOrder string `json:"sort_order"` // 排序方向: asc, desc
// 过滤条件
UserID string `json:"user_id,omitempty"`
Status enums.CertificationStatus `json:"status,omitempty"`
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
FailureReason enums.FailureReason `json:"failure_reason,omitempty"`
// 时间范围过滤
CreatedAfter *time.Time `json:"created_after,omitempty"`
CreatedBefore *time.Time `json:"created_before,omitempty"`
UpdatedAfter *time.Time `json:"updated_after,omitempty"`
UpdatedBefore *time.Time `json:"updated_before,omitempty"`
// 企业信息过滤
CompanyName string `json:"company_name,omitempty"`
LegalPersonName string `json:"legal_person_name,omitempty"`
// 业务状态过滤
IsCompleted *bool `json:"is_completed,omitempty"`
IsFailed *bool `json:"is_failed,omitempty"`
IsUserActionRequired *bool `json:"is_user_action_required,omitempty"`
// 高级过滤
MinRetryCount *int `json:"min_retry_count,omitempty"`
MaxRetryCount *int `json:"max_retry_count,omitempty"`
MinProgress *int `json:"min_progress,omitempty"`
MaxProgress *int `json:"max_progress,omitempty"`
// 搜索参数
SearchKeyword string `json:"search_keyword,omitempty"` // 通用搜索关键词
// 包含关联数据
IncludeMetadata bool `json:"include_metadata,omitempty"`
}
// ListEnterprisesQuery 企业信息列表查询参数
type ListEnterprisesQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
UserID string `json:"user_id"`
EnterpriseName string `json:"enterprise_name"`
LicenseNumber string `json:"license_number"`
LegalPersonName string `json:"legal_person_name"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
// DefaultValues 设置默认值
func (q *ListCertificationsQuery) DefaultValues() {
if q.Page <= 0 {
q.Page = 1
}
if q.PageSize <= 0 {
q.PageSize = 20
}
if q.SortBy == "" {
q.SortBy = "created_at"
}
if q.SortOrder == "" {
q.SortOrder = "desc"
}
}
// ListEnterpriseInfoSubmitRecordsQuery 企业信息提交记录列表查询参数
type ListEnterpriseInfoSubmitRecordsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
Status string `json:"status"`
CompanyName string `json:"company_name"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
// GetOffset 计算分页偏移量
func (q *ListCertificationsQuery) GetOffset() int {
return (q.Page - 1) * q.PageSize
}
// ListEsignContractGenerateRecordsQuery e签宝生成合同记录列表查询参数
type ListEsignContractGenerateRecordsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
Status string `json:"status"`
ContractType string `json:"contract_type"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
// GetLimit 获取查询限制数量
func (q *ListCertificationsQuery) GetLimit() int {
return q.PageSize
}
// ListEsignContractSignRecordsQuery e签宝签署合同记录列表查询参数
type ListEsignContractSignRecordsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
Status string `json:"status"`
SignerName string `json:"signer_name"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
// HasTimeFilter 检查是否有时间过滤条件
func (q *ListCertificationsQuery) HasTimeFilter() bool {
return q.CreatedAfter != nil || q.CreatedBefore != nil ||
q.UpdatedAfter != nil || q.UpdatedBefore != nil
}
// HasStatusFilter 检查是否有状态过滤条件
func (q *ListCertificationsQuery) HasStatusFilter() bool {
return q.Status != "" || len(q.Statuses) > 0
}
// HasSearchFilter 检查是否有搜索过滤条件
func (q *ListCertificationsQuery) HasSearchFilter() bool {
return q.CompanyName != "" || q.LegalPersonName != "" || q.SearchKeyword != ""
}
// GetSearchFields 获取搜索字段映射
func (q *ListCertificationsQuery) GetSearchFields() map[string]string {
fields := make(map[string]string)
if q.CompanyName != "" {
fields["company_name"] = q.CompanyName
}
if q.LegalPersonName != "" {
fields["legal_person_name"] = q.LegalPersonName
}
if q.SearchKeyword != "" {
fields["keyword"] = q.SearchKeyword
}
return fields
}
// CertificationStatisticsQuery 认证统计查询
type CertificationStatisticsQuery struct {
// 时间范围
StartDate time.Time `json:"start_date" validate:"required"`
EndDate time.Time `json:"end_date" validate:"required"`
// 统计周期
Period string `json:"period" validate:"oneof=daily weekly monthly yearly"`
// 分组维度
GroupBy []string `json:"group_by,omitempty"` // status, failure_reason, user_type, date
// 过滤条件
UserIDs []string `json:"user_ids,omitempty"`
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
// 统计类型
IncludeProgressStats bool `json:"include_progress_stats,omitempty"`
IncludeRetryStats bool `json:"include_retry_stats,omitempty"`
IncludeTimeStats bool `json:"include_time_stats,omitempty"`
}
// Validate 验证统计查询参数
func (q *CertificationStatisticsQuery) Validate() error {
if q.EndDate.Before(q.StartDate) {
return fmt.Errorf("结束时间不能早于开始时间")
}
// 检查时间范围是否合理不超过1年
if q.EndDate.Sub(q.StartDate) > 365*24*time.Hour {
return fmt.Errorf("查询时间范围不能超过1年")
}
return nil
}
// GetTimeRange 获取时间范围描述
func (q *CertificationStatisticsQuery) GetTimeRange() string {
return fmt.Sprintf("%s 到 %s",
q.StartDate.Format("2006-01-02"),
q.EndDate.Format("2006-01-02"))
}
// SearchCertificationsQuery 搜索认证查询
type SearchCertificationsQuery struct {
// 搜索关键词
Keyword string `json:"keyword" validate:"required,min=2"`
// 搜索字段
SearchFields []string `json:"search_fields,omitempty"` // company_name, legal_person_name, unified_social_code
// 过滤条件
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
UserID string `json:"user_id,omitempty"`
// 分页参数
Page int `json:"page" validate:"min=1"`
PageSize int `json:"page_size" validate:"min=1,max=50"`
// 排序参数
SortBy string `json:"sort_by"`
SortOrder string `json:"sort_order"`
// 搜索选项
ExactMatch bool `json:"exact_match,omitempty"` // 是否精确匹配
IgnoreCase bool `json:"ignore_case,omitempty"` // 是否忽略大小写
}
// DefaultValues 设置搜索查询默认值
func (q *SearchCertificationsQuery) DefaultValues() {
if q.Page <= 0 {
q.Page = 1
}
if q.PageSize <= 0 {
q.PageSize = 10
}
if q.SortBy == "" {
q.SortBy = "created_at"
}
if q.SortOrder == "" {
q.SortOrder = "desc"
}
if len(q.SearchFields) == 0 {
q.SearchFields = []string{"company_name", "legal_person_name"}
}
// 默认忽略大小写
q.IgnoreCase = true
}
// GetLimit 获取查询限制数量
func (q *SearchCertificationsQuery) GetLimit() int {
return q.PageSize
}
// GetSearchPattern 获取搜索模式
func (q *SearchCertificationsQuery) GetSearchPattern() string {
if q.ExactMatch {
return q.Keyword
}
// 模糊搜索,添加通配符
return "%" + q.Keyword + "%"
}
// UserCertificationsQuery 用户认证查询
type UserCertificationsQuery struct {
UserID string `json:"user_id" validate:"required"`
// 状态过滤
Status enums.CertificationStatus `json:"status,omitempty"`
IncludeCompleted bool `json:"include_completed,omitempty"`
IncludeFailed bool `json:"include_failed,omitempty"`
// 时间过滤
After *time.Time `json:"after,omitempty"`
Before *time.Time `json:"before,omitempty"`
// 分页
Page int `json:"page"`
PageSize int `json:"page_size"`
// 排序
SortBy string `json:"sort_by"`
SortOrder string `json:"sort_order"`
}
// DefaultValues 设置用户认证查询默认值
func (q *UserCertificationsQuery) DefaultValues() {
if q.Page <= 0 {
q.Page = 1
}
if q.PageSize <= 0 {
q.PageSize = 10
}
if q.SortBy == "" {
q.SortBy = "created_at"
}
if q.SortOrder == "" {
q.SortOrder = "desc"
}
}
// ShouldIncludeStatus 检查是否应该包含指定状态
func (q *UserCertificationsQuery) ShouldIncludeStatus(status enums.CertificationStatus) bool {
// 如果指定了特定状态,只返回该状态
if q.Status != "" {
return status == q.Status
}
// 根据包含选项决定
if enums.IsFinalStatus(status) && !q.IncludeCompleted {
return false
}
if enums.IsFailureStatus(status) && !q.IncludeFailed {
return false
}
return true
}

View File

@@ -0,0 +1,384 @@
package services
import (
"context"
"fmt"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
"tyapi-server/internal/domains/certification/services/state_machine"
"go.uber.org/zap"
)
// CertificationAggregateService 认证聚合服务接口
// 负责认证聚合根的生命周期管理和状态转换协调
type CertificationAggregateService interface {
// 聚合根管理
CreateCertification(ctx context.Context, userID string) (*entities.Certification, error)
LoadCertification(ctx context.Context, certificationID string) (*entities.Certification, error)
SaveCertification(ctx context.Context, cert *entities.Certification) error
// 状态转换管理
TransitionState(ctx context.Context, certificationID string, targetStatus enums.CertificationStatus, actor enums.ActorType, actorID string, reason string, metadata map[string]interface{}) (*state_machine.StateTransitionResult, error)
ValidateStateTransition(ctx context.Context, certificationID string, targetStatus enums.CertificationStatus, actor enums.ActorType) error
// 业务规则验证
ValidateBusinessRules(ctx context.Context, cert *entities.Certification) error
CheckInvariance(ctx context.Context, cert *entities.Certification) error
// 查询方法
GetStateInfo(status enums.CertificationStatus) *state_machine.StateConfig
GetValidTransitions(ctx context.Context, certificationID string, actor enums.ActorType) ([]*state_machine.StateTransitionRule, error)
}
// CertificationAggregateServiceImpl 认证聚合服务实现
type CertificationAggregateServiceImpl struct {
commandRepo repositories.CertificationCommandRepository
queryRepo repositories.CertificationQueryRepository
stateMachine *state_machine.CertificationStateMachine
logger *zap.Logger
}
// NewCertificationAggregateService 创建认证聚合服务
func NewCertificationAggregateService(
commandRepo repositories.CertificationCommandRepository,
queryRepo repositories.CertificationQueryRepository,
stateMachine *state_machine.CertificationStateMachine,
logger *zap.Logger,
) CertificationAggregateService {
return &CertificationAggregateServiceImpl{
commandRepo: commandRepo,
queryRepo: queryRepo,
stateMachine: stateMachine,
logger: logger,
}
}
// ================ 聚合根管理 ================
// CreateCertification 创建认证申请
func (s *CertificationAggregateServiceImpl) CreateCertification(ctx context.Context, userID string) (*entities.Certification, error) {
s.logger.Info("创建认证申请", zap.String("user_id", userID))
// 1. 检查用户是否已有认证申请
existingCert, err := s.queryRepo.GetByUserID(ctx, userID)
if err == nil && existingCert != nil {
// 检查现有认证的状态
if !existingCert.IsFinalStatus() {
return nil, fmt.Errorf("用户已有进行中的认证申请,请先完成或取消现有申请")
}
s.logger.Info("用户已有完成的认证申请,允许创建新申请",
zap.String("user_id", userID),
zap.String("existing_cert_id", existingCert.ID),
zap.String("existing_status", string(existingCert.Status)))
}
// 2. 创建新的认证聚合根
cert, err := entities.NewCertification(userID)
if err != nil {
s.logger.Error("创建认证实体失败", zap.Error(err), zap.String("user_id", userID))
return nil, fmt.Errorf("创建认证实体失败: %w", err)
}
// 3. 验证业务规则
if err := s.ValidateBusinessRules(ctx, cert); err != nil {
s.logger.Error("认证业务规则验证失败", zap.Error(err))
return nil, fmt.Errorf("业务规则验证失败: %w", err)
}
// 4. 保存聚合根
if err := s.SaveCertification(ctx, cert); err != nil {
s.logger.Error("保存认证申请失败", zap.Error(err))
return nil, fmt.Errorf("保存认证申请失败: %w", err)
}
s.logger.Info("认证申请创建成功",
zap.String("user_id", userID),
zap.String("certification_id", cert.ID))
return cert, nil
}
// LoadCertification 加载认证聚合根
func (s *CertificationAggregateServiceImpl) LoadCertification(ctx context.Context, certificationID string) (*entities.Certification, error) {
s.logger.Debug("加载认证聚合根", zap.String("certification_id", certificationID))
// 从查询仓储加载
cert, err := s.queryRepo.GetByID(ctx, certificationID)
if err != nil {
s.logger.Error("加载认证聚合根失败", zap.Error(err), zap.String("certification_id", certificationID))
return nil, fmt.Errorf("认证申请不存在: %w", err)
}
// 验证聚合根完整性
if err := s.CheckInvariance(ctx, cert); err != nil {
s.logger.Error("认证聚合根完整性验证失败", zap.Error(err))
return nil, fmt.Errorf("认证数据完整性验证失败: %w", err)
}
return cert, nil
}
// SaveCertification 保存认证聚合根
func (s *CertificationAggregateServiceImpl) SaveCertification(ctx context.Context, cert *entities.Certification) error {
s.logger.Debug("保存认证聚合根", zap.String("certification_id", cert.ID))
// 1. 验证业务规则
if err := s.ValidateBusinessRules(ctx, cert); err != nil {
return fmt.Errorf("业务规则验证失败: %w", err)
}
// 2. 检查聚合根是否存在
exists, err := s.queryRepo.Exists(ctx, cert.ID)
if err != nil {
return fmt.Errorf("检查认证存在性失败: %w", err)
}
// 3. 保存到命令仓储
if exists {
err = s.commandRepo.Update(ctx, *cert)
if err != nil {
s.logger.Error("更新认证聚合根失败", zap.Error(err))
return fmt.Errorf("更新认证失败: %w", err)
}
} else {
err = s.commandRepo.Create(ctx, *cert)
if err != nil {
s.logger.Error("创建认证聚合根失败", zap.Error(err))
return fmt.Errorf("创建认证失败: %w", err)
}
}
s.logger.Debug("认证聚合根保存成功", zap.String("certification_id", cert.ID))
return nil
}
// ================ 状态转换管理 ================
// TransitionState 执行状态转换
func (s *CertificationAggregateServiceImpl) TransitionState(
ctx context.Context,
certificationID string,
targetStatus enums.CertificationStatus,
actor enums.ActorType,
actorID string,
reason string,
metadata map[string]interface{},
) (*state_machine.StateTransitionResult, error) {
s.logger.Info("执行状态转换",
zap.String("certification_id", certificationID),
zap.String("target_status", string(targetStatus)),
zap.String("actor", string(actor)),
zap.String("actor_id", actorID))
// 构建状态转换请求
req := &state_machine.StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: targetStatus,
Actor: actor,
ActorID: actorID,
Reason: reason,
Context: metadata,
AllowRollback: true,
}
// 执行状态转换
result, err := s.stateMachine.ExecuteTransition(ctx, req)
if err != nil {
s.logger.Error("状态转换执行失败",
zap.String("certification_id", certificationID),
zap.Error(err))
return result, err
}
s.logger.Info("状态转换执行成功",
zap.String("certification_id", certificationID),
zap.String("from_status", string(result.OldStatus)),
zap.String("to_status", string(result.NewStatus)))
return result, nil
}
// ValidateStateTransition 验证状态转换
func (s *CertificationAggregateServiceImpl) ValidateStateTransition(
ctx context.Context,
certificationID string,
targetStatus enums.CertificationStatus,
actor enums.ActorType,
) error {
// 加载认证聚合根
cert, err := s.LoadCertification(ctx, certificationID)
if err != nil {
return err
}
// 检查是否可以转换
canTransition, message := s.stateMachine.CanTransition(cert, targetStatus, actor)
if !canTransition {
return fmt.Errorf("状态转换验证失败: %s", message)
}
return nil
}
// ================ 业务规则验证 ================
// ValidateBusinessRules 验证业务规则
func (s *CertificationAggregateServiceImpl) ValidateBusinessRules(ctx context.Context, cert *entities.Certification) error {
s.logger.Debug("验证认证业务规则", zap.String("certification_id", cert.ID))
// 1. 实体内部业务规则验证
if err := cert.ValidateBusinessRules(); err != nil {
return fmt.Errorf("实体业务规则验证失败: %w", err)
}
// 2. 跨聚合根业务规则验证
if err := s.validateCrossAggregateRules(ctx, cert); err != nil {
return fmt.Errorf("跨聚合根业务规则验证失败: %w", err)
}
// 3. 领域级业务规则验证
if err := s.validateDomainRules(ctx, cert); err != nil {
return fmt.Errorf("领域业务规则验证失败: %w", err)
}
return nil
}
// CheckInvariance 检查聚合根不变量
func (s *CertificationAggregateServiceImpl) CheckInvariance(ctx context.Context, cert *entities.Certification) error {
s.logger.Debug("检查认证聚合根不变量", zap.String("certification_id", cert.ID))
// 1. 基础不变量检查
if cert.ID == "" {
return fmt.Errorf("认证ID不能为空")
}
if cert.UserID == "" {
return fmt.Errorf("用户ID不能为空")
}
if !enums.IsValidStatus(cert.Status) {
return fmt.Errorf("无效的认证状态: %s", cert.Status)
}
// 2. 状态相关不变量检查
if err := s.validateStatusInvariance(cert); err != nil {
return err
}
// 3. 时间戳不变量检查
if err := s.validateTimestampInvariance(cert); err != nil {
return err
}
return nil
}
// ================ 查询方法 ================
// GetStateInfo 获取状态信息
func (s *CertificationAggregateServiceImpl) GetStateInfo(status enums.CertificationStatus) *state_machine.StateConfig {
return s.stateMachine.GetStateInfo(status)
}
// GetValidTransitions 获取有效的状态转换
func (s *CertificationAggregateServiceImpl) GetValidTransitions(
ctx context.Context,
certificationID string,
actor enums.ActorType,
) ([]*state_machine.StateTransitionRule, error) {
// 加载认证聚合根
cert, err := s.LoadCertification(ctx, certificationID)
if err != nil {
return nil, err
}
// 获取有效转换
transitions := s.stateMachine.GetValidTransitions(cert, actor)
return transitions, nil
}
// ================ 私有方法 ================
// validateCrossAggregateRules 验证跨聚合根业务规则
func (s *CertificationAggregateServiceImpl) validateCrossAggregateRules(ctx context.Context, cert *entities.Certification) error {
// TODO: 实现跨聚合根业务规则验证
// 例如:检查用户是否有权限申请认证、检查企业信息是否已被其他用户使用等
return nil
}
// validateDomainRules 验证领域级业务规则
func (s *CertificationAggregateServiceImpl) validateDomainRules(ctx context.Context, cert *entities.Certification) error {
// TODO: 实现领域级业务规则验证
// 例如:检查认证流程是否符合法规要求、检查时间窗口限制等
return nil
}
// validateStatusInvariance 验证状态相关不变量
func (s *CertificationAggregateServiceImpl) validateStatusInvariance(cert *entities.Certification) error {
switch cert.Status {
case enums.StatusEnterpriseVerified:
if cert.AuthFlowID == "" {
return fmt.Errorf("企业认证状态下必须有认证流程ID")
}
if cert.EnterpriseVerifiedAt == nil {
return fmt.Errorf("企业认证状态下必须有认证完成时间")
}
case enums.StatusContractApplied:
if cert.AuthFlowID == "" {
return fmt.Errorf("合同申请状态下必须有企业认证流程ID")
}
if cert.ContractAppliedAt == nil {
return fmt.Errorf("合同申请状态下必须有合同申请时间")
}
case enums.StatusContractSigned:
if cert.ContractFileID == "" || cert.EsignFlowID == "" {
return fmt.Errorf("合同签署状态下必须有完整的合同信息")
}
if cert.ContractSignedAt == nil {
return fmt.Errorf("合同签署状态下必须有签署完成时间")
}
}
// 失败状态检查
if enums.IsFailureStatus(cert.Status) {
if cert.FailureReason == "" {
return fmt.Errorf("失败状态下必须有失败原因")
}
if !enums.IsValidFailureReason(cert.FailureReason) {
return fmt.Errorf("无效的失败原因: %s", cert.FailureReason)
}
}
return nil
}
// validateTimestampInvariance 验证时间戳不变量
func (s *CertificationAggregateServiceImpl) validateTimestampInvariance(cert *entities.Certification) error {
// 检查时间戳的逻辑顺序
if cert.InfoSubmittedAt != nil && cert.EnterpriseVerifiedAt != nil {
if cert.InfoSubmittedAt.After(*cert.EnterpriseVerifiedAt) {
return fmt.Errorf("企业信息提交时间不能晚于企业认证时间")
}
}
if cert.EnterpriseVerifiedAt != nil && cert.ContractAppliedAt != nil {
if cert.EnterpriseVerifiedAt.After(*cert.ContractAppliedAt) {
return fmt.Errorf("企业认证时间不能晚于合同申请时间")
}
}
if cert.ContractAppliedAt != nil && cert.ContractSignedAt != nil {
if cert.ContractAppliedAt.After(*cert.ContractSignedAt) {
return fmt.Errorf("合同申请时间不能晚于合同签署时间")
}
}
return nil
}

View File

@@ -1,126 +0,0 @@
package services
import (
"context"
"fmt"
"go.uber.org/zap"
cert_entities "tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/repositories"
user_entities "tyapi-server/internal/domains/user/entities"
"tyapi-server/internal/shared/esign"
)
// CertificationEsignService 负责与e签宝相关的认证业务逻辑
type CertificationEsignService struct {
certRepo repositories.CertificationRepository
esignClient *esign.Client
esignContractGenerateRecordRepo repositories.EsignContractGenerateRecordRepository
esignContractSignRecordRepo repositories.EsignContractSignRecordRepository
logger *zap.Logger
}
// NewCertificationEsignService 创建CertificationEsignService实例
func NewCertificationEsignService(
certRepo repositories.CertificationRepository,
esignClient *esign.Client,
esignContractGenerateRecordRepo repositories.EsignContractGenerateRecordRepository,
logger *zap.Logger,
) *CertificationEsignService {
return &CertificationEsignService{
certRepo: certRepo,
esignClient: esignClient,
esignContractGenerateRecordRepo: esignContractGenerateRecordRepo,
logger: logger,
}
}
// FillTemplate 生成合同文件e签宝模板填充
func (s *CertificationEsignService) FillTemplate(ctx context.Context, certification *cert_entities.Certification, components map[string]string) (*esign.FillTemplate, error) {
resp, err := s.esignClient.FillTemplate(components)
record := &cert_entities.EsignContractGenerateRecord{
CertificationID: certification.ID,
UserID: certification.UserID,
}
if err != nil {
s.logger.Error("生成合同文件失败", zap.Any("components", components), zap.Error(err))
record.Status = "failed"
} else {
record.TemplateID = resp.TemplateID
record.ContractName = resp.FileName
record.ContractFileID = resp.FileID
record.ContractURL = resp.FileDownloadUrl
record.Status = "success"
record.FillTime = &resp.FillTime
}
if _, createErr := s.esignContractGenerateRecordRepo.Create(ctx, *record); createErr != nil {
s.logger.Error("创建合同生成记录失败", zap.Error(createErr))
if err == nil {
return nil, fmt.Errorf("创建合同生成记录失败: %w", createErr)
}
}
if err != nil {
return nil, fmt.Errorf("生成合同文件失败: %w", err)
}
certification.ContractURL = resp.FileDownloadUrl
certification.ContractFileID = resp.FileID
err = s.certRepo.Update(ctx, *certification)
if err != nil {
s.logger.Error("更新认证申请失败", zap.Error(err))
return nil, fmt.Errorf("更新认证申请失败: %w", err)
}
s.logger.Info("生成合同文件成功", zap.String("template_id", resp.TemplateID), zap.String("file_id", resp.FileID))
return resp, nil
}
// 发起签署
func (s *CertificationEsignService) InitiateSign(ctx context.Context, certification *cert_entities.Certification, enterpriseInfo *user_entities.EnterpriseInfo) (*cert_entities.EsignContractSignRecord, error) {
// 发起签署流程
flowID, err := s.esignClient.CreateSignFlow(&esign.CreateSignFlowRequest{
FileID: certification.ContractFileID,
SignerAccount: enterpriseInfo.UnifiedSocialCode,
SignerName: enterpriseInfo.CompanyName,
TransactorPhone: enterpriseInfo.LegalPersonPhone,
TransactorName: enterpriseInfo.LegalPersonName,
TransactorIDCardNum: enterpriseInfo.LegalPersonID,
})
if err != nil {
s.logger.Error("获取签署链接失败",
zap.String("user_id", enterpriseInfo.UserID),
zap.Error(err),
)
return nil, fmt.Errorf("获取签署链接失败: %w", err)
}
signURL, shortURL, err := s.esignClient.GetSignURL(flowID, enterpriseInfo.UnifiedSocialCode, enterpriseInfo.CompanyName)
if err != nil {
s.logger.Error("获取签署链接失败", zap.Error(err))
return nil, fmt.Errorf("获取签署链接失败: %w", err)
}
esignContractSignRecord := cert_entities.NewEsignContractSignRecord(
certification.ID,
enterpriseInfo.UserID,
flowID,
certification.ContractFileID,
enterpriseInfo.UnifiedSocialCode,
enterpriseInfo.LegalPersonPhone,
enterpriseInfo.LegalPersonID,
signURL,
shortURL,
)
signContractSignRecord, err := s.esignContractSignRecordRepo.Create(ctx, *esignContractSignRecord)
if err != nil {
s.logger.Error("创建签署记录失败", zap.Error(err))
return nil, fmt.Errorf("创建签署记录失败: %w", err)
}
certification.EsignFlowID = signContractSignRecord.EsignFlowID
certification.ContractSignURL = signContractSignRecord.SignShortURL // 记录的是短链接
err = s.certRepo.Update(ctx, *certification)
if err != nil {
s.logger.Error("更新认证申请失败", zap.Error(err))
return nil, fmt.Errorf("更新认证申请失败: %w", err)
}
return &signContractSignRecord, nil
}

View File

@@ -1,153 +0,0 @@
package services
import (
"context"
"fmt"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
esign_service "tyapi-server/internal/shared/esign"
"go.uber.org/zap"
)
// CertificationManagementService 认证管理领域服务
// 负责认证申请的生命周期管理,包括创建、状态转换、进度查询等
type CertificationManagementService struct {
certRepo repositories.CertificationRepository
esignService *esign_service.Client
stateMachine *CertificationStateMachine
logger *zap.Logger
}
// NewCertificationManagementService 创建认证管理领域服务
func NewCertificationManagementService(
certRepo repositories.CertificationRepository,
esignService *esign_service.Client,
stateMachine *CertificationStateMachine,
logger *zap.Logger,
) *CertificationManagementService {
return &CertificationManagementService{
certRepo: certRepo,
esignService: esignService,
stateMachine: stateMachine,
logger: logger,
}
}
// CreateCertification 创建认证申请
func (s *CertificationManagementService) CreateCertification(ctx context.Context, userID string) (*entities.Certification, error) {
// 检查用户是否已有认证申请
existingCert, err := s.certRepo.GetByUserID(ctx, userID)
if err == nil && existingCert != nil {
return nil, fmt.Errorf("用户已有认证申请")
}
certification := &entities.Certification{
UserID: userID,
Status: enums.StatusPending,
}
createdCert, err := s.certRepo.Create(ctx, *certification)
if err != nil {
s.logger.Error("创建认证申请失败", zap.Error(err))
return nil, fmt.Errorf("创建认证申请失败: %w", err)
}
certification = &createdCert
s.logger.Info("认证申请创建成功",
zap.String("certification_id", certification.ID),
zap.String("user_id", userID),
)
return certification, nil
}
// GetCertificationByUserID 根据用户ID获取认证申请
func (s *CertificationManagementService) GetCertificationByUserID(ctx context.Context, userID string) (*entities.Certification, error) {
return s.certRepo.GetByUserID(ctx, userID)
}
// GetCertificationByID 根据ID获取认证申请
func (s *CertificationManagementService) GetCertificationByID(ctx context.Context, certificationID string) (*entities.Certification, error) {
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
return nil, err
}
return &cert, nil
}
// GetCertificationByAuthFlowID 根据认证流程ID获取认证申请
func (s *CertificationManagementService) GetCertificationByAuthFlowID(ctx context.Context, authFlowID string) (*entities.Certification, error) {
cert, err := s.certRepo.GetByAuthFlowID(ctx, authFlowID)
if err != nil {
return nil, err
}
return &cert, nil
}
// 根据签署流程ID获取认证申请
func (s *CertificationManagementService) GetCertificationByEsignFlowID(ctx context.Context, esignFlowID string) (*entities.Certification, error) {
cert, err := s.certRepo.GetByEsignFlowID(ctx, esignFlowID)
if err != nil {
return nil, err
}
return &cert, nil
}
// GetCertificationProgress 获取认证进度信息
func (s *CertificationManagementService) GetCertificationProgress(ctx context.Context, certificationID string) (map[string]interface{}, error) {
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
return nil, fmt.Errorf("认证申请不存在: %w", err)
}
progress := map[string]interface{}{
"certification_id": cert.ID,
"user_id": cert.UserID,
"current_status": cert.Status,
"progress_percentage": cert.GetProgressPercentage(),
"is_user_action_required": cert.IsUserActionRequired(),
"next_valid_statuses": cert.GetNextValidStatuses(),
"created_at": cert.CreatedAt,
"updated_at": cert.UpdatedAt,
}
// 添加时间节点信息
if cert.InfoSubmittedAt != nil {
progress["info_submitted_at"] = cert.InfoSubmittedAt
}
if cert.EnterpriseVerifiedAt != nil {
progress["enterprise_verified_at"] = cert.EnterpriseVerifiedAt
}
if cert.ContractAppliedAt != nil {
progress["contract_applied_at"] = cert.ContractAppliedAt
}
if cert.ContractSignedAt != nil {
progress["contract_signed_at"] = cert.ContractSignedAt
}
if cert.CompletedAt != nil {
progress["completed_at"] = cert.CompletedAt
}
return progress, nil
}
// 通过e签宝检查是否有过认证
func (s *CertificationManagementService) CheckCertification(ctx context.Context, companyName string, unifiedSocialCode string) (bool, error) {
// 查询企业是否已经过认证
queryOrgIdentityInfo := &esign_service.QueryOrgIdentityRequest{
OrgName: companyName,
OrgIDCardNum: unifiedSocialCode,
OrgIDCardType: esign_service.OrgIDCardTypeUSCC,
}
queryOrgIdentityResponse, err := s.esignService.QueryOrgIdentityInfo(queryOrgIdentityInfo)
if err != nil {
return false, fmt.Errorf("查询机构认证信息失败: %w", err)
}
if queryOrgIdentityResponse.Data.RealnameStatus == 1 {
s.logger.Info("该企业已进行过认证", zap.String("company_name", companyName), zap.String("unified_social_code", unifiedSocialCode))
return true, nil
}
return false, nil
}

View File

@@ -0,0 +1,636 @@
package services
import (
"context"
"fmt"
"time"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/entities/value_objects"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/services/state_machine"
"go.uber.org/zap"
)
// WorkflowResult 工作流执行结果
type WorkflowResult struct {
Success bool `json:"success"`
CertificationID string `json:"certification_id"`
CurrentStatus enums.CertificationStatus `json:"current_status"`
Message string `json:"message"`
Data map[string]interface{} `json:"data,omitempty"`
StateTransition *state_machine.StateTransitionResult `json:"state_transition,omitempty"`
ExecutedAt time.Time `json:"executed_at"`
}
// SubmitEnterpriseInfoCommand 提交企业信息命令
type SubmitEnterpriseInfoCommand struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info"`
}
// ApplyContractCommand 申请合同命令
type ApplyContractCommand struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
}
// EsignCallbackCommand e签宝回调命令
type EsignCallbackCommand struct {
CertificationID string `json:"certification_id"`
CallbackType string `json:"callback_type"` // "auth_result" | "sign_result" | "flow_status"
CallbackData *state_machine.EsignCallbackData `json:"callback_data"`
}
// CertificationWorkflowOrchestrator 认证工作流编排器接口
// 负责编排认证业务流程,协调各个领域服务的协作
type CertificationWorkflowOrchestrator interface {
// 用户操作用例
SubmitEnterpriseInfo(ctx context.Context, cmd *SubmitEnterpriseInfoCommand) (*WorkflowResult, error)
ApplyContract(ctx context.Context, cmd *ApplyContractCommand) (*WorkflowResult, error)
// e签宝回调处理
HandleEnterpriseVerificationCallback(ctx context.Context, cmd *EsignCallbackCommand) (*WorkflowResult, error)
HandleContractSignCallback(ctx context.Context, cmd *EsignCallbackCommand) (*WorkflowResult, error)
// 异常处理
HandleFailure(ctx context.Context, certificationID string, failureType string, reason string) (*WorkflowResult, error)
RetryOperation(ctx context.Context, certificationID string, operation string) (*WorkflowResult, error)
// 查询操作
GetWorkflowStatus(ctx context.Context, certificationID string) (*WorkflowResult, error)
}
// CertificationWorkflowOrchestratorImpl 认证工作流编排器实现
type CertificationWorkflowOrchestratorImpl struct {
aggregateService CertificationAggregateService
callbackHandler *state_machine.EsignCallbackHandler
logger *zap.Logger
}
// NewCertificationWorkflowOrchestrator 创建认证工作流编排器
func NewCertificationWorkflowOrchestrator(
aggregateService CertificationAggregateService,
callbackHandler *state_machine.EsignCallbackHandler,
logger *zap.Logger,
) CertificationWorkflowOrchestrator {
return &CertificationWorkflowOrchestratorImpl{
aggregateService: aggregateService,
callbackHandler: callbackHandler,
logger: logger,
}
}
// ================ 用户操作用例 ================
// SubmitEnterpriseInfo 用户提交企业信息
func (o *CertificationWorkflowOrchestratorImpl) SubmitEnterpriseInfo(
ctx context.Context,
cmd *SubmitEnterpriseInfoCommand,
) (*WorkflowResult, error) {
o.logger.Info("开始处理企业信息提交",
zap.String("certification_id", cmd.CertificationID),
zap.String("user_id", cmd.UserID))
// 1. 验证命令完整性
if err := o.validateSubmitEnterpriseInfoCommand(cmd); err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("命令验证失败: %s", err.Error())), err
}
// 2. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 3. 验证业务前置条件
if err := o.validateEnterpriseInfoSubmissionPreconditions(cert, cmd.UserID); err != nil {
return o.createFailureResult(cmd.CertificationID, cert.Status, err.Error()), err
}
// 4. 执行状态转换
metadata := map[string]interface{}{
"enterprise_info": cmd.EnterpriseInfo,
"user_id": cmd.UserID,
}
result, err := o.aggregateService.TransitionState(
ctx,
cmd.CertificationID,
enums.StatusInfoSubmitted,
enums.ActorTypeUser,
cmd.UserID,
"用户提交企业信息",
metadata,
)
if err != nil {
o.logger.Error("企业信息提交状态转换失败", zap.Error(err))
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("状态转换失败: %s", err.Error())), err
}
// 5. 执行后续处理如调用e签宝API
if err := o.triggerEnterpriseVerification(ctx, cmd.CertificationID, cmd.EnterpriseInfo); err != nil {
o.logger.Warn("触发企业认证失败", zap.Error(err))
// 这里不返回错误因为状态已经成功转换e签宝调用失败可以通过重试机制处理
}
// 6. 构建成功结果
return o.createSuccessResult(cmd.CertificationID, enums.StatusInfoSubmitted, "企业信息提交成功", map[string]interface{}{
"enterprise_info": cmd.EnterpriseInfo,
"next_action": "等待企业认证结果",
}, result), nil
}
// ApplyContract 用户申请合同签署
func (o *CertificationWorkflowOrchestratorImpl) ApplyContract(
ctx context.Context,
cmd *ApplyContractCommand,
) (*WorkflowResult, error) {
o.logger.Info("开始处理合同申请",
zap.String("certification_id", cmd.CertificationID),
zap.String("user_id", cmd.UserID))
// 1. 验证命令完整性
if err := o.validateApplyContractCommand(cmd); err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("命令验证失败: %s", err.Error())), err
}
// 2. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 3. 验证业务前置条件
if err := o.validateContractApplicationPreconditions(cert, cmd.UserID); err != nil {
return o.createFailureResult(cmd.CertificationID, cert.Status, err.Error()), err
}
// 4. 执行状态转换
result, err := o.aggregateService.TransitionState(
ctx,
cmd.CertificationID,
enums.StatusContractApplied,
enums.ActorTypeUser,
cmd.UserID,
"用户申请合同签署",
map[string]interface{}{},
)
if err != nil {
o.logger.Error("合同申请状态转换失败", zap.Error(err))
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("状态转换失败: %s", err.Error())), err
}
// 5. 生成合同和签署链接
contractInfo, err := o.generateContractAndSignURL(ctx, cmd.CertificationID, cert)
if err != nil {
o.logger.Error("生成合同失败", zap.Error(err))
// 需要回滚状态
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("生成合同失败: %s", err.Error())), err
}
// 6. 构建成功结果
return o.createSuccessResult(cmd.CertificationID, enums.StatusContractApplied, "合同申请成功", map[string]interface{}{
"contract_sign_url": contractInfo.ContractSignURL,
"contract_url": contractInfo.ContractURL,
"next_action": "请在规定时间内完成合同签署",
}, result), nil
}
// ================ e签宝回调处理 ================
// HandleEnterpriseVerificationCallback 处理企业认证回调
func (o *CertificationWorkflowOrchestratorImpl) HandleEnterpriseVerificationCallback(
ctx context.Context,
cmd *EsignCallbackCommand,
) (*WorkflowResult, error) {
o.logger.Info("开始处理企业认证回调",
zap.String("certification_id", cmd.CertificationID),
zap.String("callback_type", cmd.CallbackType))
// 1. 验证回调数据
if err := o.callbackHandler.ValidateCallbackData(cmd.CallbackData); err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("回调数据验证失败: %s", err.Error())), err
}
// 2. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 3. 验证回调处理前置条件
if cert.Status != enums.StatusInfoSubmitted {
return o.createFailureResult(cmd.CertificationID, cert.Status,
fmt.Sprintf("当前状态 %s 不允许处理企业认证回调", enums.GetStatusName(cert.Status))),
fmt.Errorf("无效的状态转换")
}
// 4. 处理回调
err = o.callbackHandler.HandleCallback(ctx, cmd.CertificationID, cmd.CallbackData)
if err != nil {
o.logger.Error("处理企业认证回调失败", zap.Error(err))
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("回调处理失败: %s", err.Error())), err
}
// 5. 重新加载认证信息获取最新状态
updatedCert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, cert.Status, "加载更新后的认证信息失败"), err
}
// 6. 构建结果
message := "企业认证回调处理成功"
data := map[string]interface{}{
"auth_flow_id": cmd.CallbackData.FlowID,
"status": cmd.CallbackData.Status,
}
if updatedCert.Status == enums.StatusEnterpriseVerified {
message = "企业认证成功"
data["next_action"] = "可以申请合同签署"
} else if updatedCert.Status == enums.StatusInfoRejected {
message = "企业认证失败"
data["next_action"] = "请修正企业信息后重新提交"
data["failure_reason"] = enums.GetFailureReasonName(updatedCert.FailureReason)
data["failure_message"] = updatedCert.FailureMessage
}
return o.createSuccessResult(cmd.CertificationID, updatedCert.Status, message, data, nil), nil
}
// HandleContractSignCallback 处理合同签署回调
func (o *CertificationWorkflowOrchestratorImpl) HandleContractSignCallback(
ctx context.Context,
cmd *EsignCallbackCommand,
) (*WorkflowResult, error) {
o.logger.Info("开始处理合同签署回调",
zap.String("certification_id", cmd.CertificationID),
zap.String("callback_type", cmd.CallbackType))
// 1. 验证回调数据
if err := o.callbackHandler.ValidateCallbackData(cmd.CallbackData); err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("回调数据验证失败: %s", err.Error())), err
}
// 2. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 3. 验证回调处理前置条件
if cert.Status != enums.StatusContractApplied {
return o.createFailureResult(cmd.CertificationID, cert.Status,
fmt.Sprintf("当前状态 %s 不允许处理合同签署回调", enums.GetStatusName(cert.Status))),
fmt.Errorf("无效的状态转换")
}
// 4. 处理回调
err = o.callbackHandler.HandleCallback(ctx, cmd.CertificationID, cmd.CallbackData)
if err != nil {
o.logger.Error("处理合同签署回调失败", zap.Error(err))
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("回调处理失败: %s", err.Error())), err
}
// 5. 重新加载认证信息获取最新状态
updatedCert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, cert.Status, "加载更新后的认证信息失败"), err
}
// 6. 构建结果
message := "合同签署回调处理成功"
data := map[string]interface{}{
"esign_flow_id": cmd.CallbackData.FlowID,
"status": cmd.CallbackData.Status,
}
if updatedCert.Status == enums.StatusContractSigned {
message = "认证完成"
data["next_action"] = "认证流程已完成"
data["contract_url"] = updatedCert.ContractURL
} else if enums.IsFailureStatus(updatedCert.Status) {
message = "合同签署失败"
data["next_action"] = "可以重新申请合同签署"
data["failure_reason"] = enums.GetFailureReasonName(updatedCert.FailureReason)
data["failure_message"] = updatedCert.FailureMessage
}
return o.createSuccessResult(cmd.CertificationID, updatedCert.Status, message, data, nil), nil
}
// ================ 异常处理 ================
// HandleFailure 处理业务失败
func (o *CertificationWorkflowOrchestratorImpl) HandleFailure(
ctx context.Context,
certificationID string,
failureType string,
reason string,
) (*WorkflowResult, error) {
o.logger.Info("开始处理业务失败",
zap.String("certification_id", certificationID),
zap.String("failure_type", failureType),
zap.String("reason", reason))
// 1. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, certificationID)
if err != nil {
return o.createFailureResult(certificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 2. 根据失败类型执行相应处理
var targetStatus enums.CertificationStatus
var failureReason enums.FailureReason
switch failureType {
case "enterprise_verification_failed":
targetStatus = enums.StatusInfoRejected
failureReason = enums.FailureReasonEsignVerificationFailed
case "contract_sign_failed":
targetStatus = enums.StatusContractRejected
failureReason = enums.FailureReasonSignProcessFailed
case "contract_expired":
targetStatus = enums.StatusContractExpired
failureReason = enums.FailureReasonContractExpired
default:
return o.createFailureResult(certificationID, cert.Status, fmt.Sprintf("未知的失败类型: %s", failureType)),
fmt.Errorf("未知的失败类型")
}
// 3. 执行状态转换
metadata := map[string]interface{}{
"failure_reason": failureReason,
"failure_message": reason,
}
result, err := o.aggregateService.TransitionState(
ctx,
certificationID,
targetStatus,
enums.ActorTypeSystem,
"failure_handler",
fmt.Sprintf("系统处理失败: %s", reason),
metadata,
)
if err != nil {
return o.createFailureResult(certificationID, cert.Status, fmt.Sprintf("失败处理状态转换失败: %s", err.Error())), err
}
return o.createSuccessResult(certificationID, targetStatus, "失败处理完成", map[string]interface{}{
"failure_type": failureType,
"failure_reason": enums.GetFailureReasonName(failureReason),
"can_retry": enums.IsRetryable(failureReason),
}, result), nil
}
// RetryOperation 重试操作
func (o *CertificationWorkflowOrchestratorImpl) RetryOperation(
ctx context.Context,
certificationID string,
operation string,
) (*WorkflowResult, error) {
o.logger.Info("开始重试操作",
zap.String("certification_id", certificationID),
zap.String("operation", operation))
// 1. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, certificationID)
if err != nil {
return o.createFailureResult(certificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 2. 检查是否可以重试
if !enums.IsFailureStatus(cert.Status) {
return o.createFailureResult(certificationID, cert.Status, "当前状态不是失败状态,无需重试"),
fmt.Errorf("不需要重试")
}
if !enums.IsRetryable(cert.FailureReason) {
return o.createFailureResult(certificationID, cert.Status,
fmt.Sprintf("失败原因 %s 不支持重试", enums.GetFailureReasonName(cert.FailureReason))),
fmt.Errorf("不支持重试")
}
if cert.RetryCount >= 3 {
return o.createFailureResult(certificationID, cert.Status, "已达到最大重试次数限制"),
fmt.Errorf("超过重试限制")
}
// 3. 根据操作类型执行重试
var targetStatus enums.CertificationStatus
var reason string
switch operation {
case "enterprise_verification":
if cert.Status != enums.StatusInfoRejected {
return o.createFailureResult(certificationID, cert.Status, "当前状态不支持企业认证重试"),
fmt.Errorf("无效的重试操作")
}
targetStatus = enums.StatusInfoSubmitted
reason = "重新提交企业信息"
case "contract_application":
if cert.Status != enums.StatusContractRejected && cert.Status != enums.StatusContractExpired {
return o.createFailureResult(certificationID, cert.Status, "当前状态不支持合同申请重试"),
fmt.Errorf("无效的重试操作")
}
targetStatus = enums.StatusEnterpriseVerified
reason = "重置状态,准备重新申请合同"
default:
return o.createFailureResult(certificationID, cert.Status, fmt.Sprintf("不支持的重试操作: %s", operation)),
fmt.Errorf("不支持的重试操作")
}
// 4. 执行状态转换
result, err := o.aggregateService.TransitionState(
ctx,
certificationID,
targetStatus,
enums.ActorTypeSystem,
"retry_handler",
reason,
map[string]interface{}{},
)
if err != nil {
return o.createFailureResult(certificationID, cert.Status, fmt.Sprintf("重试状态转换失败: %s", err.Error())), err
}
return o.createSuccessResult(certificationID, targetStatus, "重试操作成功", map[string]interface{}{
"retry_operation": operation,
"retry_count": cert.RetryCount + 1,
"next_action": o.getNextActionForStatus(targetStatus),
}, result), nil
}
// ================ 查询操作 ================
// GetWorkflowStatus 获取工作流状态
func (o *CertificationWorkflowOrchestratorImpl) GetWorkflowStatus(
ctx context.Context,
certificationID string,
) (*WorkflowResult, error) {
// 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, certificationID)
if err != nil {
return o.createFailureResult(certificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 构建状态信息
data := map[string]interface{}{
"status": cert.Status,
"status_name": enums.GetStatusName(cert.Status),
"progress": cert.GetProgress(),
"is_final": cert.IsFinalStatus(),
"is_completed": cert.IsCompleted(),
"user_action_required": cert.IsUserActionRequired(),
"next_action": o.getNextActionForStatus(cert.Status),
"available_actions": cert.GetAvailableActions(),
}
// 添加失败信息(如果存在)
if enums.IsFailureStatus(cert.Status) {
data["failure_reason"] = enums.GetFailureReasonName(cert.FailureReason)
data["failure_message"] = cert.FailureMessage
data["can_retry"] = enums.IsRetryable(cert.FailureReason)
data["retry_count"] = cert.RetryCount
}
// 添加时间戳信息
if cert.InfoSubmittedAt != nil {
data["info_submitted_at"] = cert.InfoSubmittedAt
}
if cert.EnterpriseVerifiedAt != nil {
data["enterprise_verified_at"] = cert.EnterpriseVerifiedAt
}
if cert.ContractAppliedAt != nil {
data["contract_applied_at"] = cert.ContractAppliedAt
}
if cert.ContractSignedAt != nil {
data["contract_signed_at"] = cert.ContractSignedAt
}
return o.createSuccessResult(certificationID, cert.Status, "工作流状态查询成功", data, nil), nil
}
// ================ 辅助方法 ================
// validateSubmitEnterpriseInfoCommand 验证提交企业信息命令
func (o *CertificationWorkflowOrchestratorImpl) validateSubmitEnterpriseInfoCommand(cmd *SubmitEnterpriseInfoCommand) error {
if cmd.CertificationID == "" {
return fmt.Errorf("认证ID不能为空")
}
if cmd.UserID == "" {
return fmt.Errorf("用户ID不能为空")
}
if cmd.EnterpriseInfo == nil {
return fmt.Errorf("企业信息不能为空")
}
return cmd.EnterpriseInfo.Validate()
}
// validateApplyContractCommand 验证申请合同命令
func (o *CertificationWorkflowOrchestratorImpl) validateApplyContractCommand(cmd *ApplyContractCommand) error {
if cmd.CertificationID == "" {
return fmt.Errorf("认证ID不能为空")
}
if cmd.UserID == "" {
return fmt.Errorf("用户ID不能为空")
}
return nil
}
// validateEnterpriseInfoSubmissionPreconditions 验证企业信息提交前置条件
func (o *CertificationWorkflowOrchestratorImpl) validateEnterpriseInfoSubmissionPreconditions(cert *entities.Certification, userID string) error {
if cert.UserID != userID {
return fmt.Errorf("用户无权限操作此认证申请")
}
if cert.Status != enums.StatusPending && cert.Status != enums.StatusInfoRejected {
return fmt.Errorf("当前状态 %s 不允许提交企业信息", enums.GetStatusName(cert.Status))
}
return nil
}
// validateContractApplicationPreconditions 验证合同申请前置条件
func (o *CertificationWorkflowOrchestratorImpl) validateContractApplicationPreconditions(cert *entities.Certification, userID string) error {
if cert.UserID != userID {
return fmt.Errorf("用户无权限操作此认证申请")
}
if cert.Status != enums.StatusEnterpriseVerified {
return fmt.Errorf("必须先完成企业认证才能申请合同")
}
if cert.AuthFlowID == "" {
return fmt.Errorf("缺少企业认证流程ID")
}
return nil
}
// triggerEnterpriseVerification 触发企业认证
func (o *CertificationWorkflowOrchestratorImpl) triggerEnterpriseVerification(ctx context.Context, certificationID string, enterpriseInfo *value_objects.EnterpriseInfo) error {
// TODO: 调用e签宝API进行企业认证
o.logger.Info("触发企业认证",
zap.String("certification_id", certificationID),
zap.String("company_name", enterpriseInfo.CompanyName))
return nil
}
// generateContractAndSignURL 生成合同和签署链接
func (o *CertificationWorkflowOrchestratorImpl) generateContractAndSignURL(ctx context.Context, certificationID string, cert *entities.Certification) (*value_objects.ContractInfo, error) {
// TODO: 调用e签宝API生成合同和签署链接
o.logger.Info("生成合同和签署链接", zap.String("certification_id", certificationID))
// 临时返回模拟数据
contractInfo, err := value_objects.NewContractInfo(
"contract_file_"+certificationID,
"esign_flow_"+certificationID,
"https://example.com/contract/"+certificationID,
"https://example.com/sign/"+certificationID,
)
if err != nil {
return nil, err
}
return contractInfo, nil
}
// getNextActionForStatus 获取状态对应的下一步操作提示
func (o *CertificationWorkflowOrchestratorImpl) getNextActionForStatus(status enums.CertificationStatus) string {
return enums.GetUserActionHint(status)
}
// createSuccessResult 创建成功结果
func (o *CertificationWorkflowOrchestratorImpl) createSuccessResult(
certificationID string,
status enums.CertificationStatus,
message string,
data map[string]interface{},
stateTransition *state_machine.StateTransitionResult,
) *WorkflowResult {
return &WorkflowResult{
Success: true,
CertificationID: certificationID,
CurrentStatus: status,
Message: message,
Data: data,
StateTransition: stateTransition,
ExecutedAt: time.Now(),
}
}
// createFailureResult 创建失败结果
func (o *CertificationWorkflowOrchestratorImpl) createFailureResult(
certificationID string,
status enums.CertificationStatus,
message string,
) *WorkflowResult {
return &WorkflowResult{
Success: false,
CertificationID: certificationID,
CurrentStatus: status,
Message: message,
Data: map[string]interface{}{},
ExecutedAt: time.Now(),
}
}

View File

@@ -1,162 +0,0 @@
package services
import (
"context"
"fmt"
"go.uber.org/zap"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
)
// CertificationWorkflowService 认证工作流领域服务
// 负责认证流程的状态转换和业务逻辑处理
type CertificationWorkflowService struct {
certRepo repositories.CertificationRepository
stateMachine *CertificationStateMachine
logger *zap.Logger
}
// NewCertificationWorkflowService 创建认证工作流领域服务
func NewCertificationWorkflowService(
certRepo repositories.CertificationRepository,
stateMachine *CertificationStateMachine,
logger *zap.Logger,
) *CertificationWorkflowService {
return &CertificationWorkflowService{
certRepo: certRepo,
stateMachine: stateMachine,
logger: logger,
}
}
// SubmitEnterpriseInfo 提交企业信息
func (s *CertificationWorkflowService) SubmitEnterpriseInfo(ctx context.Context, certificationID string) error {
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
return fmt.Errorf("认证申请不存在: %w", err)
}
// 检查当前状态是否可以提交企业信息
if cert.Status != enums.StatusPending && cert.Status != enums.StatusInfoSubmitted {
return fmt.Errorf("当前状态不允许提交企业信息")
}
// 使用状态机转换状态
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusInfoSubmitted, true, false, nil); err != nil {
return fmt.Errorf("状态转换失败: %w", err)
}
s.logger.Info("企业信息提交成功",
zap.String("certification_id", certificationID),
zap.String("user_id", cert.UserID),
)
return nil
}
// CompleteEnterpriseVerification 完成企业认证
func (s *CertificationWorkflowService) CompleteEnterpriseVerification(ctx context.Context, certificationID string) error {
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
return fmt.Errorf("认证申请不存在: %w", err)
}
// 检查当前状态是否可以完成企业认证
if cert.Status != enums.StatusInfoSubmitted {
return fmt.Errorf("当前状态不允许完成企业认证")
}
// 使用状态机转换状态
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusEnterpriseVerified, true, false, nil); err != nil {
return fmt.Errorf("状态转换失败: %w", err)
}
s.logger.Info("企业认证完成",
zap.String("certification_id", certificationID),
zap.String("user_id", cert.UserID),
)
return nil
}
// ApplyContract 申请签署合同
func (s *CertificationWorkflowService) ApplyContract(ctx context.Context, certificationID string) error {
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
return fmt.Errorf("认证申请不存在: %w", err)
}
// 检查当前状态是否可以申请签署合同
if cert.Status != enums.StatusEnterpriseVerified {
return fmt.Errorf("当前状态不允许申请签署合同")
}
// 使用状态机转换状态
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractApplied, true, false, nil); err != nil {
return fmt.Errorf("状态转换失败: %w", err)
}
s.logger.Info("签署合同申请成功",
zap.String("certification_id", certificationID),
zap.String("user_id", cert.UserID),
)
return nil
}
// CompleteContractSign 完成合同签署
func (s *CertificationWorkflowService) CompleteContractSign(ctx context.Context, certificationID, contractURL string) error {
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
return fmt.Errorf("认证申请不存在: %w", err)
}
// 检查当前状态是否可以签署
if cert.Status != enums.StatusContractApplied {
return fmt.Errorf("当前状态不允许签署")
}
// 准备签署元数据
metadata := map[string]interface{}{
"contract_url": contractURL,
}
// 使用状态机转换状态
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractSigned, true, false, metadata); err != nil {
return fmt.Errorf("状态转换失败: %w", err)
}
s.logger.Info("合同签署完成",
zap.String("certification_id", certificationID),
zap.String("user_id", cert.UserID),
)
return nil
}
// CompleteCertification 完成认证
func (s *CertificationWorkflowService) CompleteCertification(ctx context.Context, certificationID string) error {
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
return fmt.Errorf("认证申请不存在: %w", err)
}
// 检查当前状态是否可以完成
if cert.Status != enums.StatusContractSigned {
return fmt.Errorf("当前状态不允许完成认证")
}
// 使用状态机转换状态
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusCompleted, false, false, nil); err != nil {
return fmt.Errorf("状态转换失败: %w", err)
}
s.logger.Info("认证完成",
zap.String("certification_id", certificationID),
zap.String("user_id", cert.UserID),
)
return nil
}

View File

@@ -1,180 +0,0 @@
package services
import (
"context"
"fmt"
"go.uber.org/zap"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/repositories"
)
// EnterpriseInfoSubmitRecordService 企业信息提交记录领域服务
// 负责企业信息提交记录的业务逻辑处理
type EnterpriseInfoSubmitRecordService struct {
enterpriseRecordRepo repositories.EnterpriseInfoSubmitRecordRepository
logger *zap.Logger
}
// NewEnterpriseInfoSubmitRecordService 创建企业信息提交记录领域服务
func NewEnterpriseInfoSubmitRecordService(
enterpriseRecordRepo repositories.EnterpriseInfoSubmitRecordRepository,
logger *zap.Logger,
) *EnterpriseInfoSubmitRecordService {
return &EnterpriseInfoSubmitRecordService{
enterpriseRecordRepo: enterpriseRecordRepo,
logger: logger,
}
}
// CreateEnterpriseInfoSubmitRecord 创建企业信息提交记录
func (s *EnterpriseInfoSubmitRecordService) CreateEnterpriseInfoSubmitRecord(
ctx context.Context,
userID string,
companyName string,
unifiedSocialCode string,
legalPersonName string,
legalPersonID string,
legalPersonPhone string,
) (*entities.EnterpriseInfoSubmitRecord, error) {
// 创建企业信息提交记录实体
record := entities.NewEnterpriseInfoSubmitRecord(
userID,
companyName,
unifiedSocialCode,
legalPersonName,
legalPersonID,
legalPersonPhone,
)
// 保存到仓储
createdRecord, err := s.enterpriseRecordRepo.Create(ctx, *record)
if err != nil {
s.logger.Error("创建企业信息提交记录失败",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("创建企业信息提交记录失败: %w", err)
}
s.logger.Info("企业信息提交记录创建成功",
zap.String("record_id", createdRecord.ID),
zap.String("user_id", userID),
zap.String("company_name", companyName))
return &createdRecord, nil
}
// GetLatestByUserID 根据用户ID获取最新的企业信息提交记录
func (s *EnterpriseInfoSubmitRecordService) GetLatestByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error) {
record, err := s.enterpriseRecordRepo.GetLatestByUserID(ctx, userID)
if err != nil {
s.logger.Error("获取企业信息提交记录失败",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("获取企业信息提交记录失败: %w", err)
}
return record, nil
}
// UpdateEnterpriseInfoSubmitRecord 更新企业信息提交记录
func (s *EnterpriseInfoSubmitRecordService) UpdateEnterpriseInfoSubmitRecord(ctx context.Context, record *entities.EnterpriseInfoSubmitRecord) error {
err := s.enterpriseRecordRepo.Update(ctx, *record)
if err != nil {
s.logger.Error("更新企业信息提交记录失败",
zap.String("record_id", record.ID),
zap.Error(err))
return fmt.Errorf("更新企业信息提交记录失败: %w", err)
}
s.logger.Info("企业信息提交记录更新成功",
zap.String("record_id", record.ID),
zap.String("status", record.Status))
return nil
}
// MarkAsVerified 标记企业信息为已验证
func (s *EnterpriseInfoSubmitRecordService) MarkAsVerified(ctx context.Context, recordID string) error {
record, err := s.enterpriseRecordRepo.GetByID(ctx, recordID)
if err != nil {
return fmt.Errorf("获取企业信息提交记录失败: %w", err)
}
record.MarkAsVerified()
err = s.enterpriseRecordRepo.Update(ctx, record)
if err != nil {
s.logger.Error("标记企业信息为已验证失败",
zap.String("record_id", recordID),
zap.Error(err))
return fmt.Errorf("标记企业信息为已验证失败: %w", err)
}
s.logger.Info("企业信息标记为已验证成功",
zap.String("record_id", recordID))
return nil
}
// UpdateVerificationStatus 更新企业信息验证状态
func (s *EnterpriseInfoSubmitRecordService) UpdateVerificationStatus(ctx context.Context, userID string, isVerified bool, reason string) error {
// 获取用户最新的企业信息提交记录
record, err := s.enterpriseRecordRepo.GetLatestByUserID(ctx, userID)
if err != nil {
return fmt.Errorf("获取企业信息提交记录失败: %w", err)
}
// 更新验证状态
if isVerified {
record.MarkAsVerified()
} else {
record.MarkAsFailed(reason)
}
// 保存更新
err = s.enterpriseRecordRepo.Update(ctx, *record)
if err != nil {
s.logger.Error("更新企业信息验证状态失败",
zap.String("user_id", userID),
zap.Bool("is_verified", isVerified),
zap.Error(err))
return fmt.Errorf("更新企业信息验证状态失败: %w", err)
}
s.logger.Info("企业信息验证状态更新成功",
zap.String("user_id", userID),
zap.Bool("is_verified", isVerified),
zap.String("reason", reason))
return nil
}
// DeleteEnterpriseInfoSubmitRecord 删除企业信息提交记录
func (s *EnterpriseInfoSubmitRecordService) DeleteEnterpriseInfoSubmitRecord(ctx context.Context, recordID string) error {
err := s.enterpriseRecordRepo.Delete(ctx, recordID)
if err != nil {
s.logger.Error("删除企业信息提交记录失败",
zap.String("record_id", recordID),
zap.Error(err))
return fmt.Errorf("删除企业信息提交记录失败: %w", err)
}
s.logger.Info("企业信息提交记录删除成功",
zap.String("record_id", recordID))
return nil
}
// GetByUserID 根据用户ID获取企业信息提交记录列表
func (s *EnterpriseInfoSubmitRecordService) GetByUserID(ctx context.Context, userID string) ([]*entities.EnterpriseInfoSubmitRecord, error) {
records, err := s.enterpriseRecordRepo.GetByUserID(ctx, userID)
if err != nil {
s.logger.Error("获取用户企业信息提交记录失败",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("获取用户企业信息提交记录失败: %w", err)
}
return records, nil
}

View File

@@ -97,18 +97,9 @@ func (manager *CertificationStateManager) initStateConfigs() {
IsAdminActionRequired: false,
TimestampField: "ContractSignedAt",
Description: "合同已签署",
NextValidStatuses: []enums.CertificationStatus{enums.StatusCompleted},
},
{
Status: enums.StatusCompleted,
Name: "认证完成",
ProgressPercentage: 100,
IsUserActionRequired: false,
IsAdminActionRequired: false,
TimestampField: "CompletedAt",
Description: "认证流程已完成",
NextValidStatuses: []enums.CertificationStatus{},
},
// 已完成状态已合并到StatusContractSigned中
}
// 转换配置
@@ -168,17 +159,7 @@ func (manager *CertificationStateManager) initStateConfigs() {
RequiresValidation: true,
Description: "用户签署合同",
},
// 完成认证
{
From: enums.StatusContractSigned,
To: enums.StatusCompleted,
Action: "complete",
ActionName: "完成认证",
AllowUser: false,
AllowAdmin: false,
RequiresValidation: false,
Description: "系统自动完成认证",
},
// 合同签署即为认证完成,无需额外状态转换
}
// 构建映射

View File

@@ -1,258 +0,0 @@
package services
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 {
stateManager *CertificationStateManager
certRepo repositories.CertificationRepository
logger *zap.Logger
}
// NewCertificationStateMachine 创建认证状态机
func NewCertificationStateMachine(
certRepo repositories.CertificationRepository,
logger *zap.Logger,
) *CertificationStateMachine {
return &CertificationStateMachine{
stateManager: NewCertificationStateManager(),
certRepo: certRepo,
logger: logger,
}
}
// CanTransition 检查是否可以转换到指定状态
func (sm *CertificationStateMachine) CanTransition(
from enums.CertificationStatus,
to enums.CertificationStatus,
isUser bool,
isAdmin bool,
) (bool, string) {
return sm.stateManager.CanTransition(from, to, isUser, isAdmin)
}
// TransitionTo 执行状态转换
func (sm *CertificationStateMachine) TransitionTo(
ctx context.Context,
certificationID string,
targetStatus enums.CertificationStatus,
isUser bool,
isAdmin bool,
metadata map[string]interface{},
) error {
// 获取当前认证记录
cert, err := sm.certRepo.GetByID(ctx, certificationID)
if err != nil {
return fmt.Errorf("获取认证记录失败: %w", err)
}
// 检查是否可以转换
canTransition, reason := sm.CanTransition(cert.Status, targetStatus, isUser, isAdmin)
if !canTransition {
return fmt.Errorf("状态转换失败: %s", reason)
}
// 更新状态和时间戳
oldStatus := cert.Status
cert.Status = targetStatus
sm.updateTimestamp(&cert, targetStatus)
// 更新其他字段
sm.updateCertificationFields(&cert, targetStatus, metadata)
// 保存到数据库
if err := sm.certRepo.Update(ctx, cert); err != nil {
return fmt.Errorf("保存状态转换失败: %w", err)
}
sm.logger.Info("认证状态转换成功",
zap.String("certification_id", certificationID),
zap.String("from_status", string(oldStatus)),
zap.String("to_status", string(targetStatus)),
zap.Bool("is_user", isUser),
zap.Bool("is_admin", isAdmin),
)
return nil
}
// updateTimestamp 更新对应的时间戳字段
func (sm *CertificationStateMachine) updateTimestamp(cert *entities.Certification, status enums.CertificationStatus) {
stateConfig := sm.stateManager.GetStateConfig(status)
if stateConfig == nil || stateConfig.TimestampField == "" {
return
}
now := time.Now()
switch stateConfig.TimestampField {
case "InfoSubmittedAt":
cert.InfoSubmittedAt = &now
case "EnterpriseVerifiedAt":
cert.EnterpriseVerifiedAt = &now
case "ContractAppliedAt":
cert.ContractAppliedAt = &now
case "ContractSignedAt":
cert.ContractSignedAt = &now
case "CompletedAt":
cert.CompletedAt = &now
}
}
// updateCertificationFields 根据状态更新认证记录的其他字段
func (sm *CertificationStateMachine) updateCertificationFields(
cert *entities.Certification,
status enums.CertificationStatus,
metadata map[string]interface{},
) {
switch status {
case enums.StatusContractSigned:
if contractURL, ok := metadata["contract_url"].(string); ok {
cert.ContractURL = contractURL
}
}
}
// GetValidNextStatuses 获取当前状态可以转换到的下一个状态列表
func (sm *CertificationStateMachine) GetValidNextStatuses(
currentStatus enums.CertificationStatus,
isUser bool,
isAdmin bool,
) []enums.CertificationStatus {
return sm.stateManager.GetNextValidStatuses(currentStatus)
}
// GetTransitionAction 获取状态转换对应的操作名称
func (sm *CertificationStateMachine) GetTransitionAction(
from enums.CertificationStatus,
to enums.CertificationStatus,
) string {
transitions := sm.stateManager.GetTransitionConfigs(from)
for _, transition := range transitions {
if transition.To == to {
return transition.Action
}
}
return ""
}
// GetTransitionActionName 获取状态转换对应的操作中文名称
func (sm *CertificationStateMachine) GetTransitionActionName(
from enums.CertificationStatus,
to enums.CertificationStatus,
) string {
transitions := sm.stateManager.GetTransitionConfigs(from)
for _, transition := range transitions {
if transition.To == to {
return transition.ActionName
}
}
return ""
}
// GetStateConfig 获取状态配置
func (sm *CertificationStateMachine) GetStateConfig(status enums.CertificationStatus) *StateConfig {
return sm.stateManager.GetStateConfig(status)
}
// GetProgressPercentage 获取进度百分比
func (sm *CertificationStateMachine) GetProgressPercentage(status enums.CertificationStatus) int {
return sm.stateManager.GetProgressPercentage(status)
}
// IsUserActionRequired 检查是否需要用户操作
func (sm *CertificationStateMachine) IsUserActionRequired(status enums.CertificationStatus) bool {
return sm.stateManager.IsUserActionRequired(status)
}
// IsAdminActionRequired 检查是否需要管理员操作
func (sm *CertificationStateMachine) IsAdminActionRequired(status enums.CertificationStatus) bool {
return sm.stateManager.IsAdminActionRequired(status)
}
// GetTransitionHistory 获取状态转换历史
func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, certificationID string) ([]map[string]interface{}, error) {
cert, err := sm.certRepo.GetByID(ctx, certificationID)
if err != nil {
return nil, fmt.Errorf("获取认证记录失败: %w", err)
}
history := []map[string]interface{}{}
// 添加创建时间
history = append(history, map[string]interface{}{
"status": "CREATED",
"timestamp": cert.CreatedAt,
"action": "create",
"performer": "system",
"metadata": map[string]interface{}{},
})
// 添加各个时间节点的状态转换
if cert.InfoSubmittedAt != nil {
history = append(history, map[string]interface{}{
"status": string(enums.StatusInfoSubmitted),
"timestamp": *cert.InfoSubmittedAt,
"action": "submit_info",
"performer": "user",
"metadata": map[string]interface{}{},
})
}
if cert.EnterpriseVerifiedAt != nil {
history = append(history, map[string]interface{}{
"status": string(enums.StatusEnterpriseVerified),
"timestamp": *cert.EnterpriseVerifiedAt,
"action": "enterprise_verify",
"performer": "user",
"metadata": map[string]interface{}{},
})
}
if cert.ContractAppliedAt != nil {
history = append(history, map[string]interface{}{
"status": string(enums.StatusContractApplied),
"timestamp": *cert.ContractAppliedAt,
"action": "apply_contract",
"performer": "user",
"metadata": map[string]interface{}{},
})
}
if cert.ContractSignedAt != nil {
metadata := map[string]interface{}{}
if cert.ContractURL != "" {
metadata["contract_url"] = cert.ContractURL
}
history = append(history, map[string]interface{}{
"status": string(enums.StatusContractSigned),
"timestamp": *cert.ContractSignedAt,
"action": "sign_contract",
"performer": "user",
"metadata": metadata,
})
}
if cert.CompletedAt != nil {
history = append(history, map[string]interface{}{
"status": string(enums.StatusCompleted),
"timestamp": *cert.CompletedAt,
"action": "complete",
"performer": "system",
"metadata": map[string]interface{}{},
})
}
return history, nil
}

View File

@@ -0,0 +1,455 @@
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)
}

View File

@@ -0,0 +1,391 @@
package state_machine
import (
"context"
"encoding/json"
"fmt"
"tyapi-server/internal/domains/certification/enums"
"go.uber.org/zap"
)
// EsignCallbackData e签宝回调数据结构
type EsignCallbackData struct {
// 基础信息
Action string `json:"action"` // 回调动作类型
FlowID string `json:"flow_id"` // 流程ID
AccountID string `json:"account_id"` // 账户ID
Status string `json:"status"` // 状态
Message string `json:"message"` // 消息
Timestamp int64 `json:"timestamp"` // 时间戳
// 扩展数据
Data map[string]interface{} `json:"data,omitempty"` // 扩展数据
OriginalData string `json:"original_data"` // 原始回调数据
}
// EsignCallbackHandler e签宝回调处理器
// 负责处理e签宝的异步回调将外部回调转换为内部状态转换
type EsignCallbackHandler struct {
stateMachine *CertificationStateMachine
logger *zap.Logger
}
// NewEsignCallbackHandler 创建e签宝回调处理器
func NewEsignCallbackHandler(
stateMachine *CertificationStateMachine,
logger *zap.Logger,
) *EsignCallbackHandler {
return &EsignCallbackHandler{
stateMachine: stateMachine,
logger: logger,
}
}
// HandleCallback 处理e签宝回调
func (h *EsignCallbackHandler) HandleCallback(
ctx context.Context,
certificationID string,
callbackData *EsignCallbackData,
) error {
h.logger.Info("接收到e签宝回调",
zap.String("certification_id", certificationID),
zap.String("action", callbackData.Action),
zap.String("flow_id", callbackData.FlowID),
zap.String("status", callbackData.Status))
// 根据动作类型分发处理
switch callbackData.Action {
case "auth_result":
return h.handleAuthResult(ctx, certificationID, callbackData)
case "sign_result":
return h.handleSignResult(ctx, certificationID, callbackData)
case "flow_status":
return h.handleFlowStatus(ctx, certificationID, callbackData)
default:
h.logger.Warn("未知的e签宝回调动作",
zap.String("certification_id", certificationID),
zap.String("action", callbackData.Action))
return fmt.Errorf("未知的回调动作: %s", callbackData.Action)
}
}
// handleAuthResult 处理企业认证结果回调
func (h *EsignCallbackHandler) handleAuthResult(
ctx context.Context,
certificationID string,
callbackData *EsignCallbackData,
) error {
h.logger.Info("处理企业认证结果回调",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("status", callbackData.Status))
switch callbackData.Status {
case "success", "verified", "completed":
// 认证成功
_, err := h.stateMachine.TransitionToEnterpriseVerified(
ctx,
certificationID,
callbackData.FlowID,
)
return err
case "failed", "rejected", "error":
// 认证失败
failureReason := h.parseAuthFailureReason(callbackData)
_, err := h.stateMachine.TransitionToInfoRejected(
ctx,
certificationID,
failureReason,
callbackData.Message,
)
return err
default:
h.logger.Warn("未知的企业认证状态",
zap.String("certification_id", certificationID),
zap.String("status", callbackData.Status))
return fmt.Errorf("未知的认证状态: %s", callbackData.Status)
}
}
// handleSignResult 处理合同签署结果回调
func (h *EsignCallbackHandler) handleSignResult(
ctx context.Context,
certificationID string,
callbackData *EsignCallbackData,
) error {
h.logger.Info("处理合同签署结果回调",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("status", callbackData.Status))
switch callbackData.Status {
case "signed", "completed", "success":
// 签署成功
contractURL := h.extractContractURL(callbackData)
_, err := h.stateMachine.TransitionToContractSigned(
ctx,
certificationID,
contractURL,
)
return err
case "rejected", "refused":
// 用户拒绝签署
_, err := h.stateMachine.TransitionToContractRejected(
ctx,
certificationID,
enums.FailureReasonContractRejectedByUser,
callbackData.Message,
)
return err
case "expired", "timeout":
// 签署超时
_, err := h.stateMachine.TransitionToContractExpired(
ctx,
certificationID,
fmt.Sprintf("合同签署超时: %s", callbackData.Message),
)
return err
case "failed", "error":
// 签署失败
failureReason := h.parseSignFailureReason(callbackData)
_, err := h.stateMachine.TransitionToContractRejected(
ctx,
certificationID,
failureReason,
callbackData.Message,
)
return err
default:
h.logger.Warn("未知的合同签署状态",
zap.String("certification_id", certificationID),
zap.String("status", callbackData.Status))
return fmt.Errorf("未知的签署状态: %s", callbackData.Status)
}
}
// handleFlowStatus 处理流程状态回调
func (h *EsignCallbackHandler) handleFlowStatus(
ctx context.Context,
certificationID string,
callbackData *EsignCallbackData,
) error {
h.logger.Info("处理流程状态回调",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("status", callbackData.Status))
// 流程状态回调主要用于监控和日志记录
// 实际的状态转换由具体的auth_result和sign_result处理
switch callbackData.Status {
case "started", "processing", "in_progress":
h.logger.Info("e签宝流程进行中",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID))
case "paused", "suspended":
h.logger.Warn("e签宝流程被暂停",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("message", callbackData.Message))
case "cancelled", "terminated":
h.logger.Warn("e签宝流程被取消",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("message", callbackData.Message))
default:
h.logger.Info("收到其他流程状态",
zap.String("certification_id", certificationID),
zap.String("status", callbackData.Status))
}
return nil
}
// parseAuthFailureReason 解析企业认证失败原因
func (h *EsignCallbackHandler) parseAuthFailureReason(callbackData *EsignCallbackData) enums.FailureReason {
// 根据e签宝返回的错误信息解析失败原因
message := callbackData.Message
// 检查扩展数据中的错误码
if errorCode, exists := callbackData.Data["error_code"]; exists {
switch errorCode {
case "ENTERPRISE_NOT_FOUND", "ORG_NOT_EXISTS":
return enums.FailureReasonEnterpriseNotExists
case "INFO_MISMATCH", "ORG_INFO_ERROR":
return enums.FailureReasonEnterpriseInfoMismatch
case "STATUS_ABNORMAL", "ORG_STATUS_ERROR":
return enums.FailureReasonEnterpriseStatusAbnormal
case "LEGAL_PERSON_ERROR", "LEGAL_REP_ERROR":
return enums.FailureReasonLegalPersonMismatch
case "DOCUMENT_INVALID", "ID_CARD_ERROR":
return enums.FailureReasonInvalidDocument
}
}
// 根据错误消息文本判断
if message != "" {
if h.containsKeywords(message, []string{"企业不存在", "机构不存在", "not found"}) {
return enums.FailureReasonEnterpriseNotExists
}
if h.containsKeywords(message, []string{"信息不匹配", "信息错误", "mismatch"}) {
return enums.FailureReasonEnterpriseInfoMismatch
}
if h.containsKeywords(message, []string{"状态异常", "status abnormal"}) {
return enums.FailureReasonEnterpriseStatusAbnormal
}
if h.containsKeywords(message, []string{"法定代表人", "legal person", "法人"}) {
return enums.FailureReasonLegalPersonMismatch
}
if h.containsKeywords(message, []string{"证件", "身份证", "document", "id card"}) {
return enums.FailureReasonInvalidDocument
}
}
// 默认返回e签宝验证失败
return enums.FailureReasonEsignVerificationFailed
}
// parseSignFailureReason 解析合同签署失败原因
func (h *EsignCallbackHandler) parseSignFailureReason(callbackData *EsignCallbackData) enums.FailureReason {
// 根据e签宝返回的错误信息解析失败原因
message := callbackData.Message
// 检查扩展数据中的错误码
if errorCode, exists := callbackData.Data["error_code"]; exists {
switch errorCode {
case "USER_REJECTED", "SIGN_REJECTED":
return enums.FailureReasonContractRejectedByUser
case "FLOW_EXPIRED", "SIGN_EXPIRED":
return enums.FailureReasonContractExpired
case "FLOW_ERROR", "SIGN_PROCESS_ERROR":
return enums.FailureReasonSignProcessFailed
case "ESIGN_ERROR", "SYSTEM_ERROR":
return enums.FailureReasonEsignFlowError
}
}
// 根据错误消息文本判断
if message != "" {
if h.containsKeywords(message, []string{"拒绝", "rejected", "refused"}) {
return enums.FailureReasonContractRejectedByUser
}
if h.containsKeywords(message, []string{"过期", "超时", "expired", "timeout"}) {
return enums.FailureReasonContractExpired
}
if h.containsKeywords(message, []string{"流程错误", "process error", "flow error"}) {
return enums.FailureReasonSignProcessFailed
}
}
// 默认返回e签宝流程错误
return enums.FailureReasonEsignFlowError
}
// extractContractURL 提取合同URL
func (h *EsignCallbackHandler) extractContractURL(callbackData *EsignCallbackData) string {
// 优先从扩展数据中获取
if contractURL, exists := callbackData.Data["contract_url"]; exists {
if url, ok := contractURL.(string); ok && url != "" {
return url
}
}
if downloadURL, exists := callbackData.Data["download_url"]; exists {
if url, ok := downloadURL.(string); ok && url != "" {
return url
}
}
if fileURL, exists := callbackData.Data["file_url"]; exists {
if url, ok := fileURL.(string); ok && url != "" {
return url
}
}
// 如果没有找到URL返回空字符串
h.logger.Warn("未能从回调数据中提取合同URL",
zap.Any("callback_data", callbackData.Data))
return ""
}
// containsKeywords 检查文本是否包含关键词
func (h *EsignCallbackHandler) containsKeywords(text string, keywords []string) bool {
for _, keyword := range keywords {
if len(text) >= len(keyword) {
for i := 0; i <= len(text)-len(keyword); i++ {
if text[i:i+len(keyword)] == keyword {
return true
}
}
}
}
return false
}
// ValidateCallbackData 验证回调数据
func (h *EsignCallbackHandler) ValidateCallbackData(callbackData *EsignCallbackData) error {
if callbackData == nil {
return fmt.Errorf("回调数据不能为空")
}
if callbackData.Action == "" {
return fmt.Errorf("回调动作不能为空")
}
if callbackData.FlowID == "" {
return fmt.Errorf("流程ID不能为空")
}
if callbackData.Status == "" {
return fmt.Errorf("状态不能为空")
}
return nil
}
// ParseCallbackData 解析原始回调数据
func (h *EsignCallbackHandler) ParseCallbackData(rawData string) (*EsignCallbackData, error) {
var callbackData EsignCallbackData
if err := json.Unmarshal([]byte(rawData), &callbackData); err != nil {
h.logger.Error("解析e签宝回调数据失败", zap.Error(err), zap.String("raw_data", rawData))
return nil, fmt.Errorf("解析回调数据失败: %w", err)
}
// 保存原始数据
callbackData.OriginalData = rawData
// 验证数据完整性
if err := h.ValidateCallbackData(&callbackData); err != nil {
return nil, fmt.Errorf("回调数据验证失败: %w", err)
}
return &callbackData, nil
}
// GetCallbackType 获取回调类型描述
func (h *EsignCallbackHandler) GetCallbackType(action string) string {
types := map[string]string{
"auth_result": "企业认证结果",
"sign_result": "合同签署结果",
"flow_status": "流程状态更新",
}
if typeName, exists := types[action]; exists {
return typeName
}
return "未知类型"
}

View File

@@ -0,0 +1,438 @@
package state_machine
import (
"fmt"
"tyapi-server/internal/domains/certification/enums"
)
// StateConfig 状态配置
type StateConfig struct {
Status enums.CertificationStatus `json:"status"`
Name string `json:"name"`
ProgressPercentage int `json:"progress_percentage"`
IsUserActionRequired bool `json:"is_user_action_required"`
IsSystemAction bool `json:"is_system_action"`
TimestampField string `json:"timestamp_field,omitempty"`
Description string `json:"description"`
NextValidStatuses []enums.CertificationStatus `json:"next_valid_statuses"`
AllowedActors []enums.ActorType `json:"allowed_actors"`
}
// StateTransitionRule 状态转换规则
type StateTransitionRule struct {
FromStatus enums.CertificationStatus `json:"from_status"`
ToStatus enums.CertificationStatus `json:"to_status"`
TransitionName string `json:"transition_name"`
AllowedActors []enums.ActorType `json:"allowed_actors"`
RequiresValidation bool `json:"requires_validation"`
Description string `json:"description"`
BusinessRules []string `json:"business_rules"`
}
// StateConfigManager 状态配置管理器
type StateConfigManager struct {
stateConfigs map[enums.CertificationStatus]*StateConfig
transitionRules map[string]*StateTransitionRule // key: "from_status->to_status"
actorPermissions map[enums.ActorType][]string // actor允许的操作
}
// NewStateConfigManager 创建状态配置管理器
func NewStateConfigManager() *StateConfigManager {
manager := &StateConfigManager{
stateConfigs: make(map[enums.CertificationStatus]*StateConfig),
transitionRules: make(map[string]*StateTransitionRule),
actorPermissions: make(map[enums.ActorType][]string),
}
manager.initializeStateConfigs()
manager.initializeTransitionRules()
manager.initializeActorPermissions()
return manager
}
// initializeStateConfigs 初始化状态配置
func (m *StateConfigManager) initializeStateConfigs() {
configs := []*StateConfig{
{
Status: enums.StatusPending,
Name: "待认证",
ProgressPercentage: 0,
IsUserActionRequired: true,
IsSystemAction: false,
Description: "等待用户提交企业信息",
NextValidStatuses: []enums.CertificationStatus{enums.StatusInfoSubmitted},
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
},
{
Status: enums.StatusInfoSubmitted,
Name: "已提交企业信息",
ProgressPercentage: 25,
IsUserActionRequired: false,
IsSystemAction: true,
TimestampField: "InfoSubmittedAt",
Description: "企业信息已提交等待e签宝验证",
NextValidStatuses: []enums.CertificationStatus{enums.StatusEnterpriseVerified, enums.StatusInfoRejected},
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
},
{
Status: enums.StatusEnterpriseVerified,
Name: "已企业认证",
ProgressPercentage: 50,
IsUserActionRequired: true,
IsSystemAction: false,
TimestampField: "EnterpriseVerifiedAt",
Description: "企业认证完成,用户可申请合同",
NextValidStatuses: []enums.CertificationStatus{enums.StatusContractApplied},
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
},
{
Status: enums.StatusContractApplied,
Name: "已申请签署合同",
ProgressPercentage: 75,
IsUserActionRequired: true,
IsSystemAction: true,
TimestampField: "ContractAppliedAt",
Description: "合同已生成,等待用户签署",
NextValidStatuses: []enums.CertificationStatus{enums.StatusContractSigned, enums.StatusContractRejected, enums.StatusContractExpired},
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
},
{
Status: enums.StatusContractSigned,
Name: "认证完成",
ProgressPercentage: 100,
IsUserActionRequired: false,
IsSystemAction: false,
TimestampField: "ContractSignedAt",
Description: "认证流程已完成",
NextValidStatuses: []enums.CertificationStatus{},
AllowedActors: []enums.ActorType{},
},
// 失败状态
{
Status: enums.StatusInfoRejected,
Name: "企业信息被拒绝",
ProgressPercentage: 25,
IsUserActionRequired: true,
IsSystemAction: false,
Description: "企业信息验证失败,需要重新提交",
NextValidStatuses: []enums.CertificationStatus{enums.StatusInfoSubmitted},
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
},
{
Status: enums.StatusContractRejected,
Name: "合同被拒签",
ProgressPercentage: 75,
IsUserActionRequired: true,
IsSystemAction: false,
Description: "用户拒绝签署合同",
NextValidStatuses: []enums.CertificationStatus{enums.StatusEnterpriseVerified},
AllowedActors: []enums.ActorType{enums.ActorTypeUser, enums.ActorTypeSystem},
},
{
Status: enums.StatusContractExpired,
Name: "合同签署超时",
ProgressPercentage: 75,
IsUserActionRequired: true,
IsSystemAction: false,
Description: "合同签署链接已过期",
NextValidStatuses: []enums.CertificationStatus{enums.StatusEnterpriseVerified},
AllowedActors: []enums.ActorType{enums.ActorTypeUser, enums.ActorTypeSystem},
},
}
for _, config := range configs {
m.stateConfigs[config.Status] = config
}
}
// initializeTransitionRules 初始化状态转换规则
func (m *StateConfigManager) initializeTransitionRules() {
rules := []*StateTransitionRule{
// 用户提交企业信息
{
FromStatus: enums.StatusPending,
ToStatus: enums.StatusInfoSubmitted,
TransitionName: "submit_enterprise_info",
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
RequiresValidation: true,
Description: "用户提交企业信息",
BusinessRules: []string{"enterprise_info_complete", "enterprise_info_valid"},
},
// e签宝企业认证成功
{
FromStatus: enums.StatusInfoSubmitted,
ToStatus: enums.StatusEnterpriseVerified,
TransitionName: "enterprise_verification_success",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "e签宝企业认证成功",
BusinessRules: []string{"auth_flow_id_exists"},
},
// e签宝企业认证失败
{
FromStatus: enums.StatusInfoSubmitted,
ToStatus: enums.StatusInfoRejected,
TransitionName: "enterprise_verification_failed",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "e签宝企业认证失败",
BusinessRules: []string{"failure_reason_provided"},
},
// 用户申请合同
{
FromStatus: enums.StatusEnterpriseVerified,
ToStatus: enums.StatusContractApplied,
TransitionName: "apply_contract",
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
RequiresValidation: true,
Description: "用户申请合同签署",
BusinessRules: []string{"enterprise_verified", "auth_flow_id_exists"},
},
// e签宝合同签署成功
{
FromStatus: enums.StatusContractApplied,
ToStatus: enums.StatusContractSigned,
TransitionName: "contract_sign_success",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "e签宝合同签署成功",
BusinessRules: []string{"contract_info_complete"},
},
// 合同签署失败
{
FromStatus: enums.StatusContractApplied,
ToStatus: enums.StatusContractRejected,
TransitionName: "contract_sign_rejected",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "用户拒绝签署合同",
BusinessRules: []string{"failure_reason_provided"},
},
// 合同签署超时
{
FromStatus: enums.StatusContractApplied,
ToStatus: enums.StatusContractExpired,
TransitionName: "contract_sign_expired",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "合同签署超时",
BusinessRules: []string{"failure_reason_provided"},
},
// 重新提交企业信息
{
FromStatus: enums.StatusInfoRejected,
ToStatus: enums.StatusInfoSubmitted,
TransitionName: "resubmit_enterprise_info",
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
RequiresValidation: true,
Description: "用户重新提交企业信息",
BusinessRules: []string{"enterprise_info_complete", "enterprise_info_valid", "retry_limit_check"},
},
// 从合同失败状态恢复
{
FromStatus: enums.StatusContractRejected,
ToStatus: enums.StatusEnterpriseVerified,
TransitionName: "reset_from_contract_rejected",
AllowedActors: []enums.ActorType{enums.ActorTypeSystem, enums.ActorTypeUser},
RequiresValidation: false,
Description: "从合同拒签状态恢复",
BusinessRules: []string{"retry_limit_check"},
},
{
FromStatus: enums.StatusContractExpired,
ToStatus: enums.StatusEnterpriseVerified,
TransitionName: "reset_from_contract_expired",
AllowedActors: []enums.ActorType{enums.ActorTypeSystem, enums.ActorTypeUser},
RequiresValidation: false,
Description: "从合同超时状态恢复",
BusinessRules: []string{"retry_limit_check"},
},
}
for _, rule := range rules {
key := string(rule.FromStatus) + "->" + string(rule.ToStatus)
m.transitionRules[key] = rule
}
}
// initializeActorPermissions 初始化操作者权限
func (m *StateConfigManager) initializeActorPermissions() {
m.actorPermissions = map[enums.ActorType][]string{
enums.ActorTypeUser: {
"submit_enterprise_info",
"apply_contract",
"view_certification",
"retry_from_failure",
},
enums.ActorTypeSystem: {
"auto_transition",
"system_recovery",
"timeout_handling",
"data_cleanup",
},
enums.ActorTypeEsign: {
"verification_callback",
"sign_callback",
"status_notification",
},
enums.ActorTypeAdmin: {
"manual_intervention",
"force_transition",
"view_all_certifications",
"system_configuration",
},
}
}
// GetStateConfig 获取状态配置
func (m *StateConfigManager) GetStateConfig(status enums.CertificationStatus) *StateConfig {
return m.stateConfigs[status]
}
// GetTransitionRule 获取状态转换规则
func (m *StateConfigManager) GetTransitionRule(fromStatus, toStatus enums.CertificationStatus) *StateTransitionRule {
key := string(fromStatus) + "->" + string(toStatus)
return m.transitionRules[key]
}
// CanTransition 检查是否可以执行状态转换
func (m *StateConfigManager) CanTransition(fromStatus, toStatus enums.CertificationStatus, actor enums.ActorType) (bool, string) {
// 获取转换规则
rule := m.GetTransitionRule(fromStatus, toStatus)
if rule == nil {
return false, "不支持的状态转换"
}
// 检查操作者权限
allowed := false
for _, allowedActor := range rule.AllowedActors {
if actor == allowedActor {
allowed = true
break
}
}
if !allowed {
return false, "操作者无权限执行此转换"
}
return true, ""
}
// GetAllowedTransitions 获取指定状态下允许的转换
func (m *StateConfigManager) GetAllowedTransitions(fromStatus enums.CertificationStatus, actor enums.ActorType) []*StateTransitionRule {
var allowedTransitions []*StateTransitionRule
config := m.GetStateConfig(fromStatus)
if config == nil {
return allowedTransitions
}
for _, toStatus := range config.NextValidStatuses {
if canTransition, _ := m.CanTransition(fromStatus, toStatus, actor); canTransition {
if rule := m.GetTransitionRule(fromStatus, toStatus); rule != nil {
allowedTransitions = append(allowedTransitions, rule)
}
}
}
return allowedTransitions
}
// GetActorPermissions 获取操作者权限
func (m *StateConfigManager) GetActorPermissions(actor enums.ActorType) []string {
if permissions, exists := m.actorPermissions[actor]; exists {
return permissions
}
return []string{}
}
// HasPermission 检查操作者是否有指定权限
func (m *StateConfigManager) HasPermission(actor enums.ActorType, permission string) bool {
permissions := m.GetActorPermissions(actor)
for _, p := range permissions {
if p == permission {
return true
}
}
return false
}
// ValidateBusinessRules 验证业务规则
func (m *StateConfigManager) ValidateBusinessRules(rule *StateTransitionRule, context map[string]interface{}) error {
for _, businessRule := range rule.BusinessRules {
if err := m.validateSingleBusinessRule(businessRule, context); err != nil {
return err
}
}
return nil
}
// validateSingleBusinessRule 验证单个业务规则
func (m *StateConfigManager) validateSingleBusinessRule(ruleName string, context map[string]interface{}) error {
switch ruleName {
case "enterprise_info_complete":
if enterpriseInfo, exists := context["enterprise_info"]; !exists || enterpriseInfo == nil {
return fmt.Errorf("企业信息不能为空")
}
case "enterprise_info_valid":
// 这里可以添加更复杂的企业信息验证逻辑
return nil
case "auth_flow_id_exists":
if authFlowID, exists := context["auth_flow_id"]; !exists || authFlowID == "" {
return fmt.Errorf("认证流程ID不能为空")
}
case "failure_reason_provided":
if reason, exists := context["failure_reason"]; !exists || reason == "" {
return fmt.Errorf("失败原因不能为空")
}
case "enterprise_verified":
if status, exists := context["current_status"]; !exists || status != string(enums.StatusEnterpriseVerified) {
return fmt.Errorf("企业必须先完成认证")
}
case "contract_info_complete":
if contractInfo, exists := context["contract_info"]; !exists || contractInfo == nil {
return fmt.Errorf("合同信息不能为空")
}
case "retry_limit_check":
if retryCount, exists := context["retry_count"]; exists {
if count, ok := retryCount.(int); ok && count >= 3 {
return fmt.Errorf("已达到最大重试次数限制")
}
}
}
return nil
}
// GetStateProgress 获取状态进度信息
func (m *StateConfigManager) GetStateProgress(status enums.CertificationStatus) int {
if config := m.GetStateConfig(status); config != nil {
return config.ProgressPercentage
}
return 0
}
// IsUserActionRequired 检查是否需要用户操作
func (m *StateConfigManager) IsUserActionRequired(status enums.CertificationStatus) bool {
if config := m.GetStateConfig(status); config != nil {
return config.IsUserActionRequired
}
return false
}
// IsSystemAction 检查是否为系统操作状态
func (m *StateConfigManager) IsSystemAction(status enums.CertificationStatus) bool {
if config := m.GetStateConfig(status); config != nil {
return config.IsSystemAction
}
return false
}
// GetTimestampField 获取状态对应的时间戳字段
func (m *StateConfigManager) GetTimestampField(status enums.CertificationStatus) string {
if config := m.GetStateConfig(status); config != nil {
return config.TimestampField
}
return ""
}

View File

@@ -0,0 +1,181 @@
package value_objects
import (
"fmt"
"regexp"
"strings"
)
// EnterpriseInfo 企业信息值对象
// 负责封装和验证企业认证相关的信息
type EnterpriseInfo struct {
CompanyName string `json:"company_name" validate:"required,min=2,max=100"`
UnifiedSocialCode string `json:"unified_social_code" validate:"required,len=18"`
LegalPersonName string `json:"legal_person_name" validate:"required,min=2,max=50"`
LegalPersonID string `json:"legal_person_id" validate:"required,len=18"`
LegalPersonPhone string `json:"legal_person_phone" validate:"required,mobile"`
}
// NewEnterpriseInfo 创建企业信息值对象
func NewEnterpriseInfo(
companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone string,
) (*EnterpriseInfo, error) {
// 清理输入数据
info := &EnterpriseInfo{
CompanyName: strings.TrimSpace(companyName),
UnifiedSocialCode: strings.TrimSpace(unifiedSocialCode),
LegalPersonName: strings.TrimSpace(legalPersonName),
LegalPersonID: strings.TrimSpace(legalPersonID),
LegalPersonPhone: strings.TrimSpace(legalPersonPhone),
}
// 验证数据
if err := info.Validate(); err != nil {
return nil, err
}
return info, nil
}
// Validate 验证企业信息
func (e *EnterpriseInfo) Validate() error {
// 验证公司名称
if e.CompanyName == "" {
return fmt.Errorf("公司名称不能为空")
}
if len(e.CompanyName) < 2 || len(e.CompanyName) > 100 {
return fmt.Errorf("公司名称长度应在2-100个字符之间")
}
// 验证统一社会信用代码
if err := e.validateUnifiedSocialCode(); err != nil {
return err
}
// 验证法人姓名
if e.LegalPersonName == "" {
return fmt.Errorf("法人姓名不能为空")
}
if len(e.LegalPersonName) < 2 || len(e.LegalPersonName) > 50 {
return fmt.Errorf("法人姓名长度应在2-50个字符之间")
}
// 验证法人身份证
if err := e.validateLegalPersonID(); err != nil {
return err
}
// 验证法人手机号
if err := e.validateLegalPersonPhone(); err != nil {
return err
}
return nil
}
// validateUnifiedSocialCode 验证统一社会信用代码
func (e *EnterpriseInfo) validateUnifiedSocialCode() error {
if e.UnifiedSocialCode == "" {
return fmt.Errorf("统一社会信用代码不能为空")
}
if len(e.UnifiedSocialCode) != 18 {
return fmt.Errorf("统一社会信用代码应为18位")
}
// 检查格式:应为数字和大写字母组合
matched, _ := regexp.MatchString(`^[0-9A-Z]{18}$`, e.UnifiedSocialCode)
if !matched {
return fmt.Errorf("统一社会信用代码格式不正确")
}
// TODO: 可以添加更严格的校验算法
return nil
}
// validateLegalPersonID 验证法人身份证号
func (e *EnterpriseInfo) validateLegalPersonID() error {
if e.LegalPersonID == "" {
return fmt.Errorf("法人身份证号不能为空")
}
if len(e.LegalPersonID) != 18 {
return fmt.Errorf("法人身份证号应为18位")
}
// 检查格式前17位为数字最后一位为数字或X
matched, _ := regexp.MatchString(`^\d{17}[\dX]$`, e.LegalPersonID)
if !matched {
return fmt.Errorf("法人身份证号格式不正确")
}
// TODO: 可以添加身份证校验算法
return nil
}
// validateLegalPersonPhone 验证法人手机号
func (e *EnterpriseInfo) validateLegalPersonPhone() error {
if e.LegalPersonPhone == "" {
return fmt.Errorf("法人手机号不能为空")
}
// 检查中国大陆手机号格式
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, e.LegalPersonPhone)
if !matched {
return fmt.Errorf("法人手机号格式不正确")
}
return nil
}
// ================ 业务方法 ================
// GetDisplayName 获取显示名称
func (e *EnterpriseInfo) GetDisplayName() string {
return fmt.Sprintf("%s%s", e.CompanyName, e.LegalPersonName)
}
// IsSame 判断是否为同一企业信息
func (e *EnterpriseInfo) IsSame(other *EnterpriseInfo) bool {
if other == nil {
return false
}
return e.UnifiedSocialCode == other.UnifiedSocialCode &&
e.LegalPersonID == other.LegalPersonID
}
// GetMaskedPhone 获取脱敏手机号
func (e *EnterpriseInfo) GetMaskedPhone() string {
if len(e.LegalPersonPhone) != 11 {
return e.LegalPersonPhone
}
return e.LegalPersonPhone[:3] + "****" + e.LegalPersonPhone[7:]
}
// GetMaskedIDNumber 获取脱敏身份证号
func (e *EnterpriseInfo) GetMaskedIDNumber() string {
if len(e.LegalPersonID) != 18 {
return e.LegalPersonID
}
return e.LegalPersonID[:6] + "******" + e.LegalPersonID[14:]
}
// ToMap 转换为Map格式
func (e *EnterpriseInfo) ToMap() map[string]string {
return map[string]string{
"company_name": e.CompanyName,
"unified_social_code": e.UnifiedSocialCode,
"legal_person_name": e.LegalPersonName,
"legal_person_id": e.LegalPersonID,
"legal_person_phone": e.LegalPersonPhone,
}
}
// String 字符串表示
func (e *EnterpriseInfo) String() string {
return fmt.Sprintf("企业信息[公司=%s, 法人=%s, 信用代码=%s]",
e.CompanyName, e.LegalPersonName, e.UnifiedSocialCode)
}