temp
This commit is contained in:
404
internal/domains/certification/services/certification_service.go
Normal file
404
internal/domains/certification/services/certification_service.go
Normal file
@@ -0,0 +1,404 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/certification/dto"
|
||||
"tyapi-server/internal/domains/certification/entities"
|
||||
"tyapi-server/internal/domains/certification/enums"
|
||||
"tyapi-server/internal/domains/certification/repositories"
|
||||
"tyapi-server/internal/shared/ocr"
|
||||
"tyapi-server/internal/shared/storage"
|
||||
)
|
||||
|
||||
// CertificationService 认证服务
|
||||
type CertificationService struct {
|
||||
certRepo repositories.CertificationRepository
|
||||
enterpriseRepo repositories.EnterpriseRepository
|
||||
licenseRepo repositories.LicenseUploadRecordRepository
|
||||
faceVerifyRepo repositories.FaceVerifyRecordRepository
|
||||
contractRepo repositories.ContractRecordRepository
|
||||
stateMachine *CertificationStateMachine
|
||||
storageService storage.StorageService
|
||||
ocrService ocr.OCRService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCertificationService 创建认证服务
|
||||
func NewCertificationService(
|
||||
certRepo repositories.CertificationRepository,
|
||||
enterpriseRepo repositories.EnterpriseRepository,
|
||||
licenseRepo repositories.LicenseUploadRecordRepository,
|
||||
faceVerifyRepo repositories.FaceVerifyRecordRepository,
|
||||
contractRepo repositories.ContractRecordRepository,
|
||||
stateMachine *CertificationStateMachine,
|
||||
storageService storage.StorageService,
|
||||
ocrService ocr.OCRService,
|
||||
logger *zap.Logger,
|
||||
) *CertificationService {
|
||||
return &CertificationService{
|
||||
certRepo: certRepo,
|
||||
enterpriseRepo: enterpriseRepo,
|
||||
licenseRepo: licenseRepo,
|
||||
faceVerifyRepo: faceVerifyRepo,
|
||||
contractRepo: contractRepo,
|
||||
stateMachine: stateMachine,
|
||||
storageService: storageService,
|
||||
ocrService: ocrService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCertification 创建认证申请
|
||||
func (s *CertificationService) CreateCertification(ctx context.Context, userID string) (*dto.CertificationCreateResponse, error) {
|
||||
s.logger.Info("创建认证申请", zap.String("user_id", userID))
|
||||
|
||||
// 检查用户是否已有认证申请
|
||||
existingCert, err := s.certRepo.GetByUserID(ctx, userID)
|
||||
if err == nil && existingCert != nil {
|
||||
// 如果已存在且不是最终状态,返回现有申请
|
||||
if !enums.IsFinalStatus(existingCert.Status) {
|
||||
return &dto.CertificationCreateResponse{
|
||||
ID: existingCert.ID,
|
||||
UserID: existingCert.UserID,
|
||||
Status: existingCert.Status,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新的认证申请
|
||||
certification := &entities.Certification{
|
||||
ID: uuid.New().String(),
|
||||
UserID: userID,
|
||||
Status: enums.StatusPending,
|
||||
}
|
||||
|
||||
if err := s.certRepo.Create(ctx, certification); err != nil {
|
||||
return nil, fmt.Errorf("创建认证申请失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("认证申请创建成功",
|
||||
zap.String("certification_id", certification.ID),
|
||||
zap.String("user_id", userID),
|
||||
)
|
||||
|
||||
return &dto.CertificationCreateResponse{
|
||||
ID: certification.ID,
|
||||
UserID: certification.UserID,
|
||||
Status: certification.Status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UploadLicense 上传营业执照并进行OCR识别
|
||||
func (s *CertificationService) UploadLicense(ctx context.Context, userID string, fileBytes []byte, fileName string) (*dto.UploadLicenseResponse, error) {
|
||||
s.logger.Info("上传营业执照",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("file_name", fileName),
|
||||
zap.Int("file_size", len(fileBytes)),
|
||||
)
|
||||
|
||||
// 1. 上传文件到存储服务
|
||||
uploadResult, err := s.storageService.UploadFile(ctx, fileBytes, fileName)
|
||||
if err != nil {
|
||||
s.logger.Error("文件上传失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("文件上传失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 创建上传记录
|
||||
uploadRecord := &entities.LicenseUploadRecord{
|
||||
ID: uuid.New().String(),
|
||||
UserID: userID,
|
||||
OriginalFileName: fileName,
|
||||
FileSize: int64(len(fileBytes)),
|
||||
FileType: uploadResult.MimeType,
|
||||
FileURL: uploadResult.URL,
|
||||
QiNiuKey: uploadResult.Key,
|
||||
OCRProcessed: false,
|
||||
OCRSuccess: false,
|
||||
}
|
||||
|
||||
if err := s.licenseRepo.Create(ctx, uploadRecord); err != nil {
|
||||
s.logger.Error("创建上传记录失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建上传记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 3. 尝试OCR识别
|
||||
var enterpriseInfo *dto.OCREnterpriseInfo
|
||||
var ocrError string
|
||||
|
||||
ocrResult, err := s.ocrService.RecognizeBusinessLicense(ctx, uploadResult.URL)
|
||||
if err != nil {
|
||||
s.logger.Warn("OCR识别失败", zap.Error(err))
|
||||
ocrError = err.Error()
|
||||
uploadRecord.OCRProcessed = true
|
||||
uploadRecord.OCRSuccess = false
|
||||
uploadRecord.OCRErrorMessage = ocrError
|
||||
} else {
|
||||
s.logger.Info("OCR识别成功",
|
||||
zap.String("company_name", ocrResult.CompanyName),
|
||||
zap.Float64("confidence", ocrResult.Confidence),
|
||||
)
|
||||
enterpriseInfo = ocrResult
|
||||
uploadRecord.OCRProcessed = true
|
||||
uploadRecord.OCRSuccess = true
|
||||
uploadRecord.OCRConfidence = ocrResult.Confidence
|
||||
// 存储OCR原始数据
|
||||
if rawData, err := s.serializeOCRResult(ocrResult); err == nil {
|
||||
uploadRecord.OCRRawData = rawData
|
||||
}
|
||||
}
|
||||
|
||||
// 更新上传记录
|
||||
if err := s.licenseRepo.Update(ctx, uploadRecord); err != nil {
|
||||
s.logger.Warn("更新上传记录失败", zap.Error(err))
|
||||
}
|
||||
|
||||
return &dto.UploadLicenseResponse{
|
||||
UploadRecordID: uploadRecord.ID,
|
||||
FileURL: uploadResult.URL,
|
||||
OCRProcessed: uploadRecord.OCRProcessed,
|
||||
OCRSuccess: uploadRecord.OCRSuccess,
|
||||
EnterpriseInfo: enterpriseInfo,
|
||||
OCRErrorMessage: ocrError,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SubmitEnterpriseInfo 提交企业信息
|
||||
func (s *CertificationService) SubmitEnterpriseInfo(ctx context.Context, certificationID string, req *dto.SubmitEnterpriseInfoRequest) (*dto.SubmitEnterpriseInfoResponse, error) {
|
||||
s.logger.Info("提交企业信息",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("company_name", req.CompanyName),
|
||||
)
|
||||
|
||||
// 1. 获取认证记录
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取认证记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 检查状态是否允许提交企业信息
|
||||
if !cert.CanTransitionTo(enums.StatusInfoSubmitted) {
|
||||
return nil, fmt.Errorf("当前状态不允许提交企业信息,当前状态: %s", enums.GetStatusName(cert.Status))
|
||||
}
|
||||
|
||||
// 3. 检查统一社会信用代码是否已存在
|
||||
exists, err := s.enterpriseRepo.ExistsByUnifiedSocialCode(ctx, req.UnifiedSocialCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("检查企业信息失败: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("该统一社会信用代码已被使用")
|
||||
}
|
||||
|
||||
// 4. 创建企业信息
|
||||
enterprise := &entities.Enterprise{
|
||||
ID: uuid.New().String(),
|
||||
CertificationID: certificationID,
|
||||
CompanyName: req.CompanyName,
|
||||
UnifiedSocialCode: req.UnifiedSocialCode,
|
||||
LegalPersonName: req.LegalPersonName,
|
||||
LegalPersonID: req.LegalPersonID,
|
||||
LicenseUploadRecordID: req.LicenseUploadRecordID,
|
||||
IsOCRVerified: false,
|
||||
IsFaceVerified: false,
|
||||
}
|
||||
|
||||
if err := s.enterpriseRepo.Create(ctx, enterprise); err != nil {
|
||||
return nil, fmt.Errorf("创建企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
// 5. 更新认证记录状态
|
||||
cert.EnterpriseID = &enterprise.ID
|
||||
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusInfoSubmitted, true, false, nil); err != nil {
|
||||
return nil, fmt.Errorf("更新认证状态失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("企业信息提交成功",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("enterprise_id", enterprise.ID),
|
||||
)
|
||||
|
||||
return &dto.SubmitEnterpriseInfoResponse{
|
||||
ID: certificationID,
|
||||
Status: enums.StatusInfoSubmitted,
|
||||
Enterprise: &dto.EnterpriseInfoResponse{
|
||||
ID: enterprise.ID,
|
||||
CertificationID: enterprise.CertificationID,
|
||||
CompanyName: enterprise.CompanyName,
|
||||
UnifiedSocialCode: enterprise.UnifiedSocialCode,
|
||||
LegalPersonName: enterprise.LegalPersonName,
|
||||
LegalPersonID: enterprise.LegalPersonID,
|
||||
LicenseUploadRecordID: enterprise.LicenseUploadRecordID,
|
||||
IsOCRVerified: enterprise.IsOCRVerified,
|
||||
IsFaceVerified: enterprise.IsFaceVerified,
|
||||
CreatedAt: enterprise.CreatedAt,
|
||||
UpdatedAt: enterprise.UpdatedAt,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCertificationStatus 获取认证状态
|
||||
func (s *CertificationService) GetCertificationStatus(ctx context.Context, userID string) (*dto.CertificationStatusResponse, error) {
|
||||
s.logger.Info("获取认证状态", zap.String("user_id", userID))
|
||||
|
||||
// 获取用户的认证记录
|
||||
cert, err := s.certRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取认证记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取企业信息
|
||||
var enterprise *dto.EnterpriseInfoResponse
|
||||
if cert.EnterpriseID != nil {
|
||||
ent, err := s.enterpriseRepo.GetByID(ctx, *cert.EnterpriseID)
|
||||
if err == nil {
|
||||
enterprise = &dto.EnterpriseInfoResponse{
|
||||
ID: ent.ID,
|
||||
CertificationID: ent.CertificationID,
|
||||
CompanyName: ent.CompanyName,
|
||||
UnifiedSocialCode: ent.UnifiedSocialCode,
|
||||
LegalPersonName: ent.LegalPersonName,
|
||||
LegalPersonID: ent.LegalPersonID,
|
||||
LicenseUploadRecordID: ent.LicenseUploadRecordID,
|
||||
IsOCRVerified: ent.IsOCRVerified,
|
||||
IsFaceVerified: ent.IsFaceVerified,
|
||||
CreatedAt: ent.CreatedAt,
|
||||
UpdatedAt: ent.UpdatedAt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &dto.CertificationStatusResponse{
|
||||
ID: cert.ID,
|
||||
UserID: cert.UserID,
|
||||
Status: cert.Status,
|
||||
StatusName: enums.GetStatusName(cert.Status),
|
||||
Progress: cert.GetProgressPercentage(),
|
||||
IsUserActionRequired: cert.IsUserActionRequired(),
|
||||
IsAdminActionRequired: cert.IsAdminActionRequired(),
|
||||
InfoSubmittedAt: cert.InfoSubmittedAt,
|
||||
FaceVerifiedAt: cert.FaceVerifiedAt,
|
||||
ContractAppliedAt: cert.ContractAppliedAt,
|
||||
ContractApprovedAt: cert.ContractApprovedAt,
|
||||
ContractSignedAt: cert.ContractSignedAt,
|
||||
CompletedAt: cert.CompletedAt,
|
||||
Enterprise: enterprise,
|
||||
ContractURL: cert.ContractURL,
|
||||
SigningURL: cert.SigningURL,
|
||||
RejectReason: cert.RejectReason,
|
||||
CreatedAt: cert.CreatedAt,
|
||||
UpdatedAt: cert.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// InitiateFaceVerify 初始化人脸识别
|
||||
func (s *CertificationService) InitiateFaceVerify(ctx context.Context, certificationID string, req *dto.FaceVerifyRequest) (*dto.FaceVerifyResponse, error) {
|
||||
s.logger.Info("初始化人脸识别",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("real_name", req.RealName),
|
||||
)
|
||||
|
||||
// 1. 获取认证记录
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取认证记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 检查状态
|
||||
if cert.Status != enums.StatusInfoSubmitted && cert.Status != enums.StatusFaceFailed {
|
||||
return nil, fmt.Errorf("当前状态不允许进行人脸识别,当前状态: %s", enums.GetStatusName(cert.Status))
|
||||
}
|
||||
|
||||
// 3. 创建人脸识别记录
|
||||
verifyRecord := &entities.FaceVerifyRecord{
|
||||
ID: uuid.New().String(),
|
||||
CertificationID: certificationID,
|
||||
UserID: cert.UserID,
|
||||
CertifyID: fmt.Sprintf("cert_%s_%d", certificationID, time.Now().Unix()),
|
||||
RealName: req.RealName,
|
||||
IDCardNumber: req.IDCardNumber,
|
||||
ReturnURL: req.ReturnURL,
|
||||
Status: "PROCESSING",
|
||||
ExpiresAt: time.Now().Add(24 * time.Hour), // 24小时过期
|
||||
}
|
||||
|
||||
// TODO: 实际调用阿里云人脸识别API
|
||||
// 这里是模拟实现
|
||||
verifyRecord.VerifyURL = fmt.Sprintf("https://face-verify.aliyun.com/verify?certifyId=%s", verifyRecord.CertifyID)
|
||||
|
||||
if err := s.faceVerifyRepo.Create(ctx, verifyRecord); err != nil {
|
||||
return nil, fmt.Errorf("创建人脸识别记录失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("人脸识别初始化成功",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("certify_id", verifyRecord.CertifyID),
|
||||
)
|
||||
|
||||
return &dto.FaceVerifyResponse{
|
||||
CertifyID: verifyRecord.CertifyID,
|
||||
VerifyURL: verifyRecord.VerifyURL,
|
||||
ExpiresAt: verifyRecord.ExpiresAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ApplyContract 申请电子合同
|
||||
func (s *CertificationService) ApplyContract(ctx context.Context, certificationID string) (*dto.ApplyContractResponse, error) {
|
||||
s.logger.Info("申请电子合同", zap.String("certification_id", certificationID))
|
||||
|
||||
// 1. 获取认证记录
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取认证记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 检查状态
|
||||
if !cert.CanTransitionTo(enums.StatusContractApplied) {
|
||||
return nil, fmt.Errorf("当前状态不允许申请合同,当前状态: %s", enums.GetStatusName(cert.Status))
|
||||
}
|
||||
|
||||
// 3. 转换状态
|
||||
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractApplied, true, false, nil); err != nil {
|
||||
return nil, fmt.Errorf("更新认证状态失败: %w", err)
|
||||
}
|
||||
|
||||
// 4. 自动转换到待审核状态
|
||||
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractPending, false, false, nil); err != nil {
|
||||
s.logger.Warn("自动转换到待审核状态失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 5. 创建合同记录
|
||||
contractRecord := &entities.ContractRecord{
|
||||
ID: uuid.New().String(),
|
||||
CertificationID: certificationID,
|
||||
UserID: cert.UserID,
|
||||
ContractType: "ENTERPRISE_CERTIFICATION",
|
||||
Status: "PENDING",
|
||||
}
|
||||
|
||||
if err := s.contractRepo.Create(ctx, contractRecord); err != nil {
|
||||
s.logger.Warn("创建合同记录失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// TODO: 发送通知给管理员
|
||||
|
||||
s.logger.Info("合同申请成功", zap.String("certification_id", certificationID))
|
||||
|
||||
return &dto.ApplyContractResponse{
|
||||
ID: certificationID,
|
||||
Status: enums.StatusContractPending,
|
||||
ContractAppliedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// serializeOCRResult 序列化OCR结果
|
||||
func (s *CertificationService) serializeOCRResult(result *dto.OCREnterpriseInfo) (string, error) {
|
||||
// 简单的JSON序列化
|
||||
return fmt.Sprintf(`{"company_name":"%s","unified_social_code":"%s","legal_person_name":"%s","legal_person_id":"%s","confidence":%f}`,
|
||||
result.CompanyName, result.UnifiedSocialCode, result.LegalPersonName, result.LegalPersonID, result.Confidence), nil
|
||||
}
|
||||
287
internal/domains/certification/services/state_machine.go
Normal file
287
internal/domains/certification/services/state_machine.go
Normal file
@@ -0,0 +1,287 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// StateTransition 状态转换规则
|
||||
type StateTransition struct {
|
||||
From enums.CertificationStatus
|
||||
To enums.CertificationStatus
|
||||
Action string
|
||||
AllowUser bool // 是否允许用户操作
|
||||
AllowAdmin bool // 是否允许管理员操作
|
||||
RequiresValidation bool // 是否需要额外验证
|
||||
}
|
||||
|
||||
// CertificationStateMachine 认证状态机
|
||||
type CertificationStateMachine struct {
|
||||
transitions map[enums.CertificationStatus][]StateTransition
|
||||
certRepo repositories.CertificationRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCertificationStateMachine 创建认证状态机
|
||||
func NewCertificationStateMachine(
|
||||
certRepo repositories.CertificationRepository,
|
||||
logger *zap.Logger,
|
||||
) *CertificationStateMachine {
|
||||
sm := &CertificationStateMachine{
|
||||
transitions: make(map[enums.CertificationStatus][]StateTransition),
|
||||
certRepo: certRepo,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
// 初始化状态转换规则
|
||||
sm.initializeTransitions()
|
||||
return sm
|
||||
}
|
||||
|
||||
// initializeTransitions 初始化状态转换规则
|
||||
func (sm *CertificationStateMachine) initializeTransitions() {
|
||||
transitions := []StateTransition{
|
||||
// 正常流程转换
|
||||
{enums.StatusPending, enums.StatusInfoSubmitted, "submit_info", true, false, true},
|
||||
{enums.StatusInfoSubmitted, enums.StatusFaceVerified, "face_verify", true, false, true},
|
||||
{enums.StatusFaceVerified, enums.StatusContractApplied, "apply_contract", true, false, false},
|
||||
{enums.StatusContractApplied, enums.StatusContractPending, "system_process", false, false, false},
|
||||
{enums.StatusContractPending, enums.StatusContractApproved, "admin_approve", false, true, true},
|
||||
{enums.StatusContractApproved, enums.StatusContractSigned, "user_sign", true, false, true},
|
||||
{enums.StatusContractSigned, enums.StatusCompleted, "system_complete", false, false, false},
|
||||
|
||||
// 失败和重试转换
|
||||
{enums.StatusInfoSubmitted, enums.StatusFaceFailed, "face_fail", false, false, false},
|
||||
{enums.StatusFaceFailed, enums.StatusFaceVerified, "retry_face", true, false, true},
|
||||
{enums.StatusContractPending, enums.StatusRejected, "admin_reject", false, true, true},
|
||||
{enums.StatusRejected, enums.StatusInfoSubmitted, "restart_process", true, false, false},
|
||||
{enums.StatusContractApproved, enums.StatusSignFailed, "sign_fail", false, false, false},
|
||||
{enums.StatusSignFailed, enums.StatusContractSigned, "retry_sign", true, false, true},
|
||||
}
|
||||
|
||||
// 构建状态转换映射
|
||||
for _, transition := range transitions {
|
||||
sm.transitions[transition.From] = append(sm.transitions[transition.From], transition)
|
||||
}
|
||||
}
|
||||
|
||||
// CanTransition 检查是否可以转换到指定状态
|
||||
func (sm *CertificationStateMachine) CanTransition(
|
||||
from enums.CertificationStatus,
|
||||
to enums.CertificationStatus,
|
||||
isUser bool,
|
||||
isAdmin bool,
|
||||
) (bool, string) {
|
||||
validTransitions, exists := sm.transitions[from]
|
||||
if !exists {
|
||||
return false, "当前状态不支持任何转换"
|
||||
}
|
||||
|
||||
for _, transition := range validTransitions {
|
||||
if transition.To == to {
|
||||
if isUser && !transition.AllowUser {
|
||||
return false, "用户不允许执行此操作"
|
||||
}
|
||||
if isAdmin && !transition.AllowAdmin {
|
||||
return false, "管理员不允许执行此操作"
|
||||
}
|
||||
if !isUser && !isAdmin && (transition.AllowUser || transition.AllowAdmin) {
|
||||
return false, "此操作需要用户或管理员权限"
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
}
|
||||
|
||||
return false, "不支持的状态转换"
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 执行状态转换前的验证
|
||||
if err := sm.validateTransition(ctx, cert, targetStatus, metadata); err != nil {
|
||||
return fmt.Errorf("状态转换验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 更新状态和时间戳
|
||||
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) {
|
||||
now := time.Now()
|
||||
|
||||
switch status {
|
||||
case enums.StatusInfoSubmitted:
|
||||
cert.InfoSubmittedAt = &now
|
||||
case enums.StatusFaceVerified:
|
||||
cert.FaceVerifiedAt = &now
|
||||
case enums.StatusContractApplied:
|
||||
cert.ContractAppliedAt = &now
|
||||
case enums.StatusContractApproved:
|
||||
cert.ContractApprovedAt = &now
|
||||
case enums.StatusContractSigned:
|
||||
cert.ContractSignedAt = &now
|
||||
case enums.StatusCompleted:
|
||||
cert.CompletedAt = &now
|
||||
}
|
||||
}
|
||||
|
||||
// updateCertificationFields 根据状态更新认证记录的其他字段
|
||||
func (sm *CertificationStateMachine) updateCertificationFields(
|
||||
cert *entities.Certification,
|
||||
status enums.CertificationStatus,
|
||||
metadata map[string]interface{},
|
||||
) {
|
||||
switch status {
|
||||
case enums.StatusContractApproved:
|
||||
if adminID, ok := metadata["admin_id"].(string); ok {
|
||||
cert.AdminID = &adminID
|
||||
}
|
||||
if approvalNotes, ok := metadata["approval_notes"].(string); ok {
|
||||
cert.ApprovalNotes = approvalNotes
|
||||
}
|
||||
if signingURL, ok := metadata["signing_url"].(string); ok {
|
||||
cert.SigningURL = signingURL
|
||||
}
|
||||
|
||||
case enums.StatusRejected:
|
||||
if adminID, ok := metadata["admin_id"].(string); ok {
|
||||
cert.AdminID = &adminID
|
||||
}
|
||||
if rejectReason, ok := metadata["reject_reason"].(string); ok {
|
||||
cert.RejectReason = rejectReason
|
||||
}
|
||||
|
||||
case enums.StatusContractSigned:
|
||||
if contractURL, ok := metadata["contract_url"].(string); ok {
|
||||
cert.ContractURL = contractURL
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// validateTransition 验证状态转换的有效性
|
||||
func (sm *CertificationStateMachine) validateTransition(
|
||||
ctx context.Context,
|
||||
cert *entities.Certification,
|
||||
targetStatus enums.CertificationStatus,
|
||||
metadata map[string]interface{},
|
||||
) error {
|
||||
switch targetStatus {
|
||||
case enums.StatusInfoSubmitted:
|
||||
// 验证企业信息是否完整
|
||||
if cert.EnterpriseID == nil {
|
||||
return fmt.Errorf("企业信息未提交")
|
||||
}
|
||||
|
||||
case enums.StatusFaceVerified:
|
||||
// 验证人脸识别是否成功
|
||||
// 这里可以添加人脸识别结果的验证逻辑
|
||||
|
||||
case enums.StatusContractApproved:
|
||||
// 验证管理员审核信息
|
||||
if metadata["signing_url"] == nil || metadata["signing_url"].(string) == "" {
|
||||
return fmt.Errorf("缺少合同签署链接")
|
||||
}
|
||||
|
||||
case enums.StatusRejected:
|
||||
// 验证拒绝原因
|
||||
if metadata["reject_reason"] == nil || metadata["reject_reason"].(string) == "" {
|
||||
return fmt.Errorf("缺少拒绝原因")
|
||||
}
|
||||
|
||||
case enums.StatusContractSigned:
|
||||
// 验证合同签署信息
|
||||
if cert.SigningURL == "" {
|
||||
return fmt.Errorf("缺少合同签署链接")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValidNextStatuses 获取当前状态可以转换到的下一个状态列表
|
||||
func (sm *CertificationStateMachine) GetValidNextStatuses(
|
||||
currentStatus enums.CertificationStatus,
|
||||
isUser bool,
|
||||
isAdmin bool,
|
||||
) []enums.CertificationStatus {
|
||||
var validStatuses []enums.CertificationStatus
|
||||
|
||||
transitions, exists := sm.transitions[currentStatus]
|
||||
if !exists {
|
||||
return validStatuses
|
||||
}
|
||||
|
||||
for _, transition := range transitions {
|
||||
if (isUser && transition.AllowUser) || (isAdmin && transition.AllowAdmin) {
|
||||
validStatuses = append(validStatuses, transition.To)
|
||||
}
|
||||
}
|
||||
|
||||
return validStatuses
|
||||
}
|
||||
|
||||
// GetTransitionAction 获取状态转换对应的操作名称
|
||||
func (sm *CertificationStateMachine) GetTransitionAction(
|
||||
from enums.CertificationStatus,
|
||||
to enums.CertificationStatus,
|
||||
) string {
|
||||
transitions, exists := sm.transitions[from]
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, transition := range transitions {
|
||||
if transition.To == to {
|
||||
return transition.Action
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user