temp
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
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
|
||||
}
|
||||
@@ -1,774 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/application/certification/dto/responses"
|
||||
"tyapi-server/internal/domains/certification/entities"
|
||||
"tyapi-server/internal/domains/certification/enums"
|
||||
"tyapi-server/internal/domains/certification/repositories"
|
||||
sharedOCR "tyapi-server/internal/shared/ocr"
|
||||
sharedStorage "tyapi-server/internal/shared/storage"
|
||||
)
|
||||
|
||||
// CertificationService 认证领域服务
|
||||
type CertificationService struct {
|
||||
certRepo repositories.CertificationRepository
|
||||
licenseRepo repositories.LicenseUploadRecordRepository
|
||||
faceVerifyRepo repositories.FaceVerifyRecordRepository
|
||||
contractRepo repositories.ContractRecordRepository
|
||||
stateMachine *CertificationStateMachine
|
||||
logger *zap.Logger
|
||||
ocrService sharedOCR.OCRService
|
||||
storageService sharedStorage.StorageService
|
||||
}
|
||||
|
||||
// NewCertificationService 创建认证领域服务
|
||||
func NewCertificationService(
|
||||
certRepo repositories.CertificationRepository,
|
||||
licenseRepo repositories.LicenseUploadRecordRepository,
|
||||
faceVerifyRepo repositories.FaceVerifyRecordRepository,
|
||||
contractRepo repositories.ContractRecordRepository,
|
||||
stateMachine *CertificationStateMachine,
|
||||
logger *zap.Logger,
|
||||
ocrService sharedOCR.OCRService,
|
||||
storageService sharedStorage.StorageService,
|
||||
) *CertificationService {
|
||||
return &CertificationService{
|
||||
certRepo: certRepo,
|
||||
licenseRepo: licenseRepo,
|
||||
faceVerifyRepo: faceVerifyRepo,
|
||||
contractRepo: contractRepo,
|
||||
stateMachine: stateMachine,
|
||||
logger: logger,
|
||||
ocrService: ocrService,
|
||||
storageService: storageService,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCertification 创建认证申请
|
||||
func (s *CertificationService) 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
|
||||
}
|
||||
|
||||
// SubmitEnterpriseInfo 提交企业信息
|
||||
func (s *CertificationService) 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 {
|
||||
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
|
||||
}
|
||||
|
||||
// InitiateFaceVerify 发起人脸识别验证
|
||||
func (s *CertificationService) InitiateFaceVerify(ctx context.Context, certificationID, realName, idCardNumber string) (*entities.FaceVerifyRecord, error) {
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查当前状态是否可以发起人脸识别
|
||||
if cert.Status != enums.StatusInfoSubmitted {
|
||||
return nil, fmt.Errorf("当前状态不允许发起人脸识别")
|
||||
}
|
||||
|
||||
// 创建人脸识别记录
|
||||
faceVerifyRecord := &entities.FaceVerifyRecord{
|
||||
CertificationID: certificationID,
|
||||
UserID: cert.UserID,
|
||||
RealName: realName,
|
||||
IDCardNumber: idCardNumber,
|
||||
Status: "PROCESSING",
|
||||
ExpiresAt: time.Now().Add(30 * time.Minute), // 30分钟有效期
|
||||
}
|
||||
|
||||
createdFaceRecord, err := s.faceVerifyRepo.Create(ctx, *faceVerifyRecord)
|
||||
if err != nil {
|
||||
s.logger.Error("创建人脸识别记录失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建人脸识别记录失败: %w", err)
|
||||
}
|
||||
faceVerifyRecord = &createdFaceRecord
|
||||
|
||||
s.logger.Info("人脸识别验证发起成功",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("face_verify_id", faceVerifyRecord.ID),
|
||||
)
|
||||
|
||||
return faceVerifyRecord, nil
|
||||
}
|
||||
|
||||
// CompleteFaceVerify 完成人脸识别验证
|
||||
func (s *CertificationService) CompleteFaceVerify(ctx context.Context, faceVerifyID string, isSuccess bool) error {
|
||||
faceVerifyRecord, err := s.faceVerifyRepo.GetByID(ctx, faceVerifyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("人脸识别记录不存在: %w", err)
|
||||
}
|
||||
|
||||
// 更新人脸识别记录状态
|
||||
now := time.Now()
|
||||
faceVerifyRecord.CompletedAt = &now
|
||||
|
||||
if isSuccess {
|
||||
faceVerifyRecord.Status = "SUCCESS"
|
||||
faceVerifyRecord.VerifyScore = 0.95 // 示例分数
|
||||
} else {
|
||||
faceVerifyRecord.Status = "FAIL"
|
||||
faceVerifyRecord.ResultMessage = "人脸识别验证失败"
|
||||
}
|
||||
|
||||
if err := s.faceVerifyRepo.Update(ctx, faceVerifyRecord); err != nil {
|
||||
s.logger.Error("更新人脸识别记录失败", zap.Error(err))
|
||||
return fmt.Errorf("更新人脸识别记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 根据验证结果转换认证状态
|
||||
var targetStatus enums.CertificationStatus
|
||||
if isSuccess {
|
||||
targetStatus = enums.StatusFaceVerified
|
||||
} else {
|
||||
targetStatus = enums.StatusFaceFailed
|
||||
}
|
||||
|
||||
if err := s.stateMachine.TransitionTo(ctx, faceVerifyRecord.CertificationID, targetStatus, false, false, nil); err != nil {
|
||||
return fmt.Errorf("状态转换失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("人脸识别验证完成",
|
||||
zap.String("face_verify_id", faceVerifyID),
|
||||
zap.Bool("is_success", isSuccess),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyContract 申请合同
|
||||
func (s *CertificationService) 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.StatusFaceVerified {
|
||||
return fmt.Errorf("当前状态不允许申请合同")
|
||||
}
|
||||
|
||||
// 使用状态机转换状态
|
||||
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractApplied, true, false, nil); err != nil {
|
||||
return fmt.Errorf("状态转换失败: %w", err)
|
||||
}
|
||||
|
||||
// 创建合同记录
|
||||
contractRecord := &entities.ContractRecord{
|
||||
CertificationID: certificationID,
|
||||
UserID: cert.UserID,
|
||||
Status: "PENDING",
|
||||
ContractType: "ENTERPRISE_CERTIFICATION",
|
||||
}
|
||||
|
||||
createdContract, err := s.contractRepo.Create(ctx, *contractRecord)
|
||||
if err != nil {
|
||||
s.logger.Error("创建合同记录失败", zap.Error(err))
|
||||
return fmt.Errorf("创建合同记录失败: %w", err)
|
||||
}
|
||||
contractRecord = &createdContract
|
||||
|
||||
// 自动转换到待审核状态
|
||||
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractPending, false, false, nil); err != nil {
|
||||
return fmt.Errorf("状态转换失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("合同申请成功",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("contract_id", contractRecord.ID),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApproveContract 管理员审核合同
|
||||
func (s *CertificationService) ApproveContract(ctx context.Context, certificationID, adminID, signingURL, approvalNotes string) error {
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查当前状态是否可以审核
|
||||
if cert.Status != enums.StatusContractPending {
|
||||
return fmt.Errorf("当前状态不允许审核")
|
||||
}
|
||||
|
||||
// 准备审核元数据
|
||||
metadata := map[string]interface{}{
|
||||
"admin_id": adminID,
|
||||
"signing_url": signingURL,
|
||||
"approval_notes": approvalNotes,
|
||||
}
|
||||
|
||||
// 使用状态机转换状态
|
||||
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractApproved, false, true, metadata); err != nil {
|
||||
return fmt.Errorf("状态转换失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("合同审核通过",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("admin_id", adminID),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RejectContract 管理员拒绝合同
|
||||
func (s *CertificationService) RejectContract(ctx context.Context, certificationID, adminID, rejectReason string) error {
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查当前状态是否可以拒绝
|
||||
if cert.Status != enums.StatusContractPending {
|
||||
return fmt.Errorf("当前状态不允许拒绝")
|
||||
}
|
||||
|
||||
// 准备拒绝元数据
|
||||
metadata := map[string]interface{}{
|
||||
"admin_id": adminID,
|
||||
"reject_reason": rejectReason,
|
||||
}
|
||||
|
||||
// 使用状态机转换状态
|
||||
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusRejected, false, true, metadata); err != nil {
|
||||
return fmt.Errorf("状态转换失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("合同审核拒绝",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("admin_id", adminID),
|
||||
zap.String("reject_reason", rejectReason),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompleteContractSign 完成合同签署
|
||||
func (s *CertificationService) 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.StatusContractApproved {
|
||||
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),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompleteCertification 完成认证
|
||||
func (s *CertificationService) 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
|
||||
}
|
||||
|
||||
// RetryFaceVerify 重试人脸识别
|
||||
func (s *CertificationService) RetryFaceVerify(ctx context.Context, certificationID string) error {
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查是否可以重试
|
||||
if !cert.CanRetryFaceVerify() {
|
||||
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),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestartCertification 重新开始认证流程
|
||||
func (s *CertificationService) RestartCertification(ctx context.Context, certificationID string) error {
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查是否可以重新开始
|
||||
if !cert.CanRestart() {
|
||||
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),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCertificationByUserID 根据用户ID获取认证申请
|
||||
func (s *CertificationService) GetCertificationByUserID(ctx context.Context, userID string) (*entities.Certification, error) {
|
||||
return s.certRepo.GetByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
// GetCertificationByID 根据ID获取认证申请
|
||||
func (s *CertificationService) 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
|
||||
}
|
||||
|
||||
// GetFaceVerifyRecords 获取人脸识别记录
|
||||
func (s *CertificationService) GetFaceVerifyRecords(ctx context.Context, certificationID string) ([]*entities.FaceVerifyRecord, error) {
|
||||
return s.faceVerifyRepo.GetByCertificationID(ctx, certificationID)
|
||||
}
|
||||
|
||||
// GetContractRecords 获取合同记录
|
||||
func (s *CertificationService) GetContractRecords(ctx context.Context, certificationID string) ([]*entities.ContractRecord, error) {
|
||||
return s.contractRepo.GetByCertificationID(ctx, certificationID)
|
||||
}
|
||||
|
||||
// GetCertificationProgress 获取认证进度信息
|
||||
func (s *CertificationService) 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(),
|
||||
"is_admin_action_required": cert.IsAdminActionRequired(),
|
||||
"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.FaceVerifiedAt != nil {
|
||||
progress["face_verified_at"] = cert.FaceVerifiedAt
|
||||
}
|
||||
if cert.ContractAppliedAt != nil {
|
||||
progress["contract_applied_at"] = cert.ContractAppliedAt
|
||||
}
|
||||
if cert.ContractApprovedAt != nil {
|
||||
progress["contract_approved_at"] = cert.ContractApprovedAt
|
||||
}
|
||||
if cert.ContractSignedAt != nil {
|
||||
progress["contract_signed_at"] = cert.ContractSignedAt
|
||||
}
|
||||
if cert.CompletedAt != nil {
|
||||
progress["completed_at"] = cert.CompletedAt
|
||||
}
|
||||
|
||||
return progress, nil
|
||||
}
|
||||
|
||||
// GetCertificationWithDetails 获取认证申请详细信息(包含关联记录)
|
||||
func (s *CertificationService) GetCertificationWithDetails(ctx context.Context, certificationID string) (*entities.Certification, error) {
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 获取人脸识别记录
|
||||
faceRecords, err := s.faceVerifyRepo.GetByCertificationID(ctx, certificationID)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取人脸识别记录失败", zap.Error(err))
|
||||
} else {
|
||||
// 将指针切片转换为值切片
|
||||
for _, record := range faceRecords {
|
||||
cert.FaceVerifyRecords = append(cert.FaceVerifyRecords, *record)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取合同记录
|
||||
contractRecords, err := s.contractRepo.GetByCertificationID(ctx, certificationID)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取合同记录失败", zap.Error(err))
|
||||
} else {
|
||||
// 将指针切片转换为值切片
|
||||
for _, record := range contractRecords {
|
||||
cert.ContractRecords = append(cert.ContractRecords, *record)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取营业执照上传记录
|
||||
licenseRecord, err := s.licenseRepo.GetByCertificationID(ctx, certificationID)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取营业执照记录失败", zap.Error(err))
|
||||
} else {
|
||||
cert.LicenseUploadRecord = licenseRecord
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// UpdateOCRResult 更新OCR识别结果
|
||||
func (s *CertificationService) UpdateOCRResult(ctx context.Context, certificationID, ocrRequestID string, confidence float64) error {
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 更新OCR信息
|
||||
cert.OCRRequestID = ocrRequestID
|
||||
cert.OCRConfidence = confidence
|
||||
|
||||
if err := s.certRepo.Update(ctx, cert); err != nil {
|
||||
s.logger.Error("更新OCR结果失败", zap.Error(err))
|
||||
return fmt.Errorf("更新OCR结果失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("OCR结果更新成功",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("ocr_request_id", ocrRequestID),
|
||||
zap.Float64("confidence", confidence),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateLicenseUpload 验证营业执照上传
|
||||
func (s *CertificationService) ValidateLicenseUpload(ctx context.Context, userID, fileName string, fileSize int64) error {
|
||||
// 检查文件类型
|
||||
allowedExts := []string{".jpg", ".jpeg", ".png", ".pdf"}
|
||||
ext := strings.ToLower(filepath.Ext(fileName))
|
||||
|
||||
isAllowed := false
|
||||
for _, allowedExt := range allowedExts {
|
||||
if ext == allowedExt {
|
||||
isAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isAllowed {
|
||||
return fmt.Errorf("文件格式不支持,仅支持 JPG、PNG、PDF 格式")
|
||||
}
|
||||
|
||||
// 检查文件大小(限制为10MB)
|
||||
const maxFileSize = 10 * 1024 * 1024 // 10MB
|
||||
if fileSize > maxFileSize {
|
||||
return fmt.Errorf("文件大小不能超过10MB")
|
||||
}
|
||||
|
||||
// 检查用户是否已有上传记录(可选,根据业务需求决定)
|
||||
// existingRecords, err := s.licenseRepo.GetByUserID(ctx, userID, 1, 1)
|
||||
// if err == nil && len(existingRecords) > 0 {
|
||||
// return fmt.Errorf("用户已有营业执照上传记录")
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateLicenseUploadRecord 创建营业执照上传记录
|
||||
func (s *CertificationService) CreateLicenseUploadRecord(ctx context.Context, userID, fileName string, fileSize int64, uploadResult *sharedStorage.UploadResult) (*entities.LicenseUploadRecord, error) {
|
||||
// 获取文件MIME类型
|
||||
fileType := mime.TypeByExtension(filepath.Ext(fileName))
|
||||
if fileType == "" {
|
||||
fileType = "application/octet-stream"
|
||||
}
|
||||
|
||||
// 创建营业执照上传记录
|
||||
licenseRecord := &entities.LicenseUploadRecord{
|
||||
UserID: userID,
|
||||
OriginalFileName: fileName,
|
||||
FileURL: uploadResult.URL,
|
||||
FileSize: fileSize,
|
||||
FileType: fileType,
|
||||
QiNiuKey: uploadResult.Key,
|
||||
OCRProcessed: false,
|
||||
OCRSuccess: false,
|
||||
}
|
||||
|
||||
createdLicense, err := s.licenseRepo.Create(ctx, *licenseRecord)
|
||||
if err != nil {
|
||||
s.logger.Error("创建营业执照记录失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建营业执照记录失败: %w", err)
|
||||
}
|
||||
licenseRecord = &createdLicense
|
||||
|
||||
s.logger.Info("营业执照上传记录创建成功",
|
||||
zap.String("license_id", licenseRecord.ID),
|
||||
zap.String("user_id", userID),
|
||||
zap.String("file_name", fileName),
|
||||
)
|
||||
|
||||
return licenseRecord, nil
|
||||
}
|
||||
|
||||
// ProcessOCRAsync 异步处理OCR识别
|
||||
func (s *CertificationService) ProcessOCRAsync(ctx context.Context, licenseID string, fileBytes []byte) error {
|
||||
// 创建临时文件
|
||||
tempFile, err := os.CreateTemp("", "license-upload-*.jpg")
|
||||
if err != nil {
|
||||
s.logger.Error("创建临时文件失败", zap.Error(err))
|
||||
return fmt.Errorf("创建临时文件失败: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
tempFile.Close()
|
||||
os.Remove(tempFile.Name()) // 清理临时文件
|
||||
}()
|
||||
|
||||
// 将文件内容写入临时文件
|
||||
if _, err := tempFile.Write(fileBytes); err != nil {
|
||||
s.logger.Error("写入临时文件失败", zap.Error(err))
|
||||
return fmt.Errorf("写入临时文件失败: %w", err)
|
||||
}
|
||||
|
||||
// 调用OCR服务识别营业执照
|
||||
ocrResult, err := s.ocrService.RecognizeBusinessLicense(ctx, fileBytes)
|
||||
if err != nil {
|
||||
s.logger.Error("OCR识别失败", zap.Error(err), zap.String("license_id", licenseID))
|
||||
// 更新OCR处理状态为失败
|
||||
if updateErr := s.updateOCRStatus(ctx, licenseID, false, "", 0, err.Error()); updateErr != nil {
|
||||
s.logger.Error("更新OCR失败状态失败", zap.Error(updateErr))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 将OCR结果转换为JSON字符串
|
||||
ocrRawData := fmt.Sprintf(`{"company_name":"%s","unified_social_code":"%s","legal_person_name":"%s","confidence":%.2f}`,
|
||||
ocrResult.CompanyName, ocrResult.UnifiedSocialCode, ocrResult.LegalPersonName, ocrResult.Confidence)
|
||||
|
||||
// 更新OCR处理状态
|
||||
success := ocrResult.Confidence >= 0.8 // 置信度大于0.8认为成功
|
||||
if err := s.updateOCRStatus(ctx, licenseID, true, ocrRawData, ocrResult.Confidence, ""); err != nil {
|
||||
s.logger.Error("更新OCR结果失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Info("OCR识别完成",
|
||||
zap.String("license_id", licenseID),
|
||||
zap.Bool("success", success),
|
||||
zap.Float64("confidence", ocrResult.Confidence),
|
||||
zap.String("company_name", ocrResult.CompanyName),
|
||||
zap.String("legal_person", ocrResult.LegalPersonName),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateOCRStatus 更新OCR处理状态
|
||||
func (s *CertificationService) updateOCRStatus(ctx context.Context, licenseID string, processed bool, ocrRawData string, confidence float64, errorMessage string) error {
|
||||
licenseRecord, err := s.licenseRepo.GetByID(ctx, licenseID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取营业执照记录失败: %w", err)
|
||||
}
|
||||
|
||||
licenseRecord.OCRProcessed = processed
|
||||
if processed {
|
||||
licenseRecord.OCRSuccess = confidence >= 0.8
|
||||
licenseRecord.OCRRawData = ocrRawData
|
||||
licenseRecord.OCRConfidence = confidence
|
||||
} else {
|
||||
licenseRecord.OCRSuccess = false
|
||||
licenseRecord.OCRErrorMessage = errorMessage
|
||||
}
|
||||
|
||||
if err := s.licenseRepo.Update(ctx, licenseRecord); err != nil {
|
||||
return fmt.Errorf("更新OCR结果失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateLicenseFile 验证营业执照文件
|
||||
func (s *CertificationService) validateLicenseFile(fileBytes []byte, fileName string) error {
|
||||
// 检查文件大小(最大10MB)
|
||||
if len(fileBytes) > 10*1024*1024 {
|
||||
return fmt.Errorf("文件大小超过限制,最大支持10MB")
|
||||
}
|
||||
|
||||
// 检查文件类型
|
||||
ext := filepath.Ext(fileName)
|
||||
allowedExts := []string{".jpg", ".jpeg", ".png", ".bmp"}
|
||||
isAllowed := false
|
||||
for _, allowedExt := range allowedExts {
|
||||
if ext == allowedExt {
|
||||
isAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isAllowed {
|
||||
return fmt.Errorf("不支持的文件格式,仅支持jpg、jpeg、png、bmp格式")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UploadBusinessLicense 上传营业执照,先OCR识别,成功后再上传到七牛
|
||||
func (s *CertificationService) UploadBusinessLicense(ctx context.Context, userID string, fileBytes []byte, fileName string) (*entities.LicenseUploadRecord, *responses.BusinessLicenseResult, error) {
|
||||
s.logger.Info("开始上传营业执照",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("file_name", fileName),
|
||||
zap.Int("file_size", len(fileBytes)),
|
||||
)
|
||||
|
||||
// 1. 验证文件
|
||||
if err := s.validateLicenseFile(fileBytes, fileName); err != nil {
|
||||
return nil, nil, fmt.Errorf("文件验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 先OCR识别
|
||||
s.logger.Info("开始OCR识别营业执照")
|
||||
ocrResult, err := s.ocrService.RecognizeBusinessLicense(ctx, fileBytes)
|
||||
if err != nil {
|
||||
s.logger.Error("OCR识别失败", zap.Error(err))
|
||||
return nil, nil, fmt.Errorf("营业执照识别失败,请上传清晰的营业执照图片")
|
||||
}
|
||||
if ocrResult.CompanyName == "" || ocrResult.LegalPersonName == "" {
|
||||
s.logger.Warn("OCR识别未提取到关键信息")
|
||||
return nil, nil, fmt.Errorf("营业执照识别失败,请上传清晰的营业执照图片")
|
||||
}
|
||||
|
||||
// 3. 识别成功后上传到七牛
|
||||
uploadResult, err := s.storageService.UploadFile(ctx, fileBytes, fileName)
|
||||
if err != nil {
|
||||
s.logger.Error("文件上传失败", zap.Error(err))
|
||||
return nil, nil, fmt.Errorf("文件上传失败: %w", err)
|
||||
}
|
||||
|
||||
// 4. 创建上传记录
|
||||
uploadRecord := &entities.LicenseUploadRecord{
|
||||
UserID: userID,
|
||||
OriginalFileName: fileName,
|
||||
FileURL: uploadResult.URL,
|
||||
QiNiuKey: uploadResult.Key,
|
||||
FileSize: uploadResult.Size,
|
||||
FileType: uploadResult.MimeType,
|
||||
OCRProcessed: true,
|
||||
OCRSuccess: true,
|
||||
OCRConfidence: ocrResult.Confidence,
|
||||
OCRRawData: "", // 可存储OCR原始数据
|
||||
OCRErrorMessage: "",
|
||||
}
|
||||
|
||||
// 5. 保存上传记录并获取新创建的记录
|
||||
createdRecord, err := s.licenseRepo.Create(ctx, *uploadRecord)
|
||||
if err != nil {
|
||||
s.logger.Error("保存上传记录失败", zap.Error(err))
|
||||
return nil, nil, fmt.Errorf("保存上传记录失败: %w", err)
|
||||
}
|
||||
uploadRecord.ID = createdRecord.ID
|
||||
s.logger.Info("营业执照上传和OCR识别完成",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("file_url", uploadResult.URL),
|
||||
zap.Bool("ocr_success", uploadRecord.OCRSuccess),
|
||||
)
|
||||
|
||||
return uploadRecord, ocrResult, nil
|
||||
}
|
||||
|
||||
// getOCRStatus 根据OCR结果确定状态
|
||||
func (s *CertificationService) getOCRStatus(result *responses.BusinessLicenseResult) string {
|
||||
if result.CompanyName == "" && result.LegalPersonName == "" {
|
||||
return "failed"
|
||||
}
|
||||
if result.CompanyName != "" && result.LegalPersonName != "" {
|
||||
return "success"
|
||||
}
|
||||
return "partial"
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
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
|
||||
}
|
||||
256
internal/domains/certification/services/state_config.go
Normal file
256
internal/domains/certification/services/state_config.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"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"`
|
||||
IsAdminActionRequired bool `json:"is_admin_action_required"`
|
||||
TimestampField string `json:"timestamp_field,omitempty"`
|
||||
Description string `json:"description"`
|
||||
NextValidStatuses []enums.CertificationStatus `json:"next_valid_statuses"`
|
||||
}
|
||||
|
||||
// TransitionConfig 状态转换配置
|
||||
type TransitionConfig struct {
|
||||
From enums.CertificationStatus `json:"from"`
|
||||
To enums.CertificationStatus `json:"to"`
|
||||
Action string `json:"action"`
|
||||
ActionName string `json:"action_name"`
|
||||
AllowUser bool `json:"allow_user"`
|
||||
AllowAdmin bool `json:"allow_admin"`
|
||||
RequiresValidation bool `json:"requires_validation"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// CertificationStateManager 认证状态管理器
|
||||
type CertificationStateManager struct {
|
||||
stateMap map[enums.CertificationStatus]*StateConfig
|
||||
transitionMap map[enums.CertificationStatus][]*TransitionConfig
|
||||
}
|
||||
|
||||
// NewCertificationStateManager 创建认证状态管理器
|
||||
func NewCertificationStateManager() *CertificationStateManager {
|
||||
manager := &CertificationStateManager{
|
||||
stateMap: make(map[enums.CertificationStatus]*StateConfig),
|
||||
transitionMap: make(map[enums.CertificationStatus][]*TransitionConfig),
|
||||
}
|
||||
|
||||
// 初始化状态配置
|
||||
manager.initStateConfigs()
|
||||
return manager
|
||||
}
|
||||
|
||||
// initStateConfigs 初始化状态配置
|
||||
func (manager *CertificationStateManager) initStateConfigs() {
|
||||
// 状态配置
|
||||
states := []*StateConfig{
|
||||
{
|
||||
Status: enums.StatusPending,
|
||||
Name: "待认证",
|
||||
ProgressPercentage: 0,
|
||||
IsUserActionRequired: true,
|
||||
IsAdminActionRequired: false,
|
||||
Description: "等待用户提交企业信息",
|
||||
NextValidStatuses: []enums.CertificationStatus{enums.StatusInfoSubmitted},
|
||||
},
|
||||
{
|
||||
Status: enums.StatusInfoSubmitted,
|
||||
Name: "已提交企业信息",
|
||||
ProgressPercentage: 20,
|
||||
IsUserActionRequired: true,
|
||||
IsAdminActionRequired: false,
|
||||
TimestampField: "InfoSubmittedAt",
|
||||
Description: "用户已提交企业信息",
|
||||
NextValidStatuses: []enums.CertificationStatus{enums.StatusEnterpriseVerified, enums.StatusInfoSubmitted}, // 可以重新提交
|
||||
},
|
||||
{
|
||||
Status: enums.StatusEnterpriseVerified,
|
||||
Name: "已企业认证",
|
||||
ProgressPercentage: 40,
|
||||
IsUserActionRequired: true,
|
||||
IsAdminActionRequired: false,
|
||||
TimestampField: "EnterpriseVerifiedAt",
|
||||
Description: "企业认证已完成",
|
||||
NextValidStatuses: []enums.CertificationStatus{enums.StatusContractApplied},
|
||||
},
|
||||
{
|
||||
Status: enums.StatusContractApplied,
|
||||
Name: "已申请合同",
|
||||
ProgressPercentage: 60,
|
||||
IsUserActionRequired: true,
|
||||
IsAdminActionRequired: false,
|
||||
TimestampField: "ContractAppliedAt",
|
||||
Description: "合同已申请",
|
||||
NextValidStatuses: []enums.CertificationStatus{enums.StatusContractSigned},
|
||||
},
|
||||
{
|
||||
Status: enums.StatusContractSigned,
|
||||
Name: "已签署合同",
|
||||
ProgressPercentage: 80,
|
||||
IsUserActionRequired: false,
|
||||
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{},
|
||||
},
|
||||
}
|
||||
|
||||
// 转换配置
|
||||
transitions := []*TransitionConfig{
|
||||
// 提交企业信息
|
||||
{
|
||||
From: enums.StatusPending,
|
||||
To: enums.StatusInfoSubmitted,
|
||||
Action: "submit_info",
|
||||
ActionName: "提交企业信息",
|
||||
AllowUser: true,
|
||||
AllowAdmin: false,
|
||||
RequiresValidation: true,
|
||||
Description: "用户提交企业信息",
|
||||
},
|
||||
// 重新提交企业信息
|
||||
{
|
||||
From: enums.StatusInfoSubmitted,
|
||||
To: enums.StatusInfoSubmitted,
|
||||
Action: "resubmit_info",
|
||||
ActionName: "重新提交企业信息",
|
||||
AllowUser: true,
|
||||
AllowAdmin: false,
|
||||
RequiresValidation: true,
|
||||
Description: "用户重新提交企业信息",
|
||||
},
|
||||
// 企业认证
|
||||
{
|
||||
From: enums.StatusInfoSubmitted,
|
||||
To: enums.StatusEnterpriseVerified,
|
||||
Action: "enterprise_verify",
|
||||
ActionName: "企业认证",
|
||||
AllowUser: true,
|
||||
AllowAdmin: false,
|
||||
RequiresValidation: true,
|
||||
Description: "用户完成企业认证",
|
||||
},
|
||||
// 申请合同
|
||||
{
|
||||
From: enums.StatusEnterpriseVerified,
|
||||
To: enums.StatusContractApplied,
|
||||
Action: "apply_contract",
|
||||
ActionName: "申请合同",
|
||||
AllowUser: true,
|
||||
AllowAdmin: false,
|
||||
RequiresValidation: false,
|
||||
Description: "用户申请合同",
|
||||
},
|
||||
// 签署合同
|
||||
{
|
||||
From: enums.StatusContractApplied,
|
||||
To: enums.StatusContractSigned,
|
||||
Action: "sign_contract",
|
||||
ActionName: "签署合同",
|
||||
AllowUser: true,
|
||||
AllowAdmin: false,
|
||||
RequiresValidation: true,
|
||||
Description: "用户签署合同",
|
||||
},
|
||||
// 完成认证
|
||||
{
|
||||
From: enums.StatusContractSigned,
|
||||
To: enums.StatusCompleted,
|
||||
Action: "complete",
|
||||
ActionName: "完成认证",
|
||||
AllowUser: false,
|
||||
AllowAdmin: false,
|
||||
RequiresValidation: false,
|
||||
Description: "系统自动完成认证",
|
||||
},
|
||||
}
|
||||
|
||||
// 构建映射
|
||||
for _, state := range states {
|
||||
manager.stateMap[state.Status] = state
|
||||
}
|
||||
|
||||
for _, transition := range transitions {
|
||||
manager.transitionMap[transition.From] = append(manager.transitionMap[transition.From], transition)
|
||||
}
|
||||
}
|
||||
|
||||
// GetStateConfig 获取状态配置
|
||||
func (manager *CertificationStateManager) GetStateConfig(status enums.CertificationStatus) *StateConfig {
|
||||
return manager.stateMap[status]
|
||||
}
|
||||
|
||||
// GetTransitionConfigs 获取状态转换配置
|
||||
func (manager *CertificationStateManager) GetTransitionConfigs(from enums.CertificationStatus) []*TransitionConfig {
|
||||
return manager.transitionMap[from]
|
||||
}
|
||||
|
||||
// CanTransition 检查是否可以转换
|
||||
func (manager *CertificationStateManager) CanTransition(from enums.CertificationStatus, to enums.CertificationStatus, isUser bool, isAdmin bool) (bool, string) {
|
||||
transitions := manager.GetTransitionConfigs(from)
|
||||
|
||||
for _, transition := range transitions {
|
||||
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, "不支持的状态转换"
|
||||
}
|
||||
|
||||
// GetProgressPercentage 获取进度百分比
|
||||
func (manager *CertificationStateManager) GetProgressPercentage(status enums.CertificationStatus) int {
|
||||
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
|
||||
return stateConfig.ProgressPercentage
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// IsUserActionRequired 检查是否需要用户操作
|
||||
func (manager *CertificationStateManager) IsUserActionRequired(status enums.CertificationStatus) bool {
|
||||
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
|
||||
return stateConfig.IsUserActionRequired
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAdminActionRequired 检查是否需要管理员操作
|
||||
func (manager *CertificationStateManager) IsAdminActionRequired(status enums.CertificationStatus) bool {
|
||||
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
|
||||
return stateConfig.IsAdminActionRequired
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetNextValidStatuses 获取下一个有效状态
|
||||
func (manager *CertificationStateManager) GetNextValidStatuses(status enums.CertificationStatus) []enums.CertificationStatus {
|
||||
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
|
||||
return stateConfig.NextValidStatuses
|
||||
}
|
||||
return []enums.CertificationStatus{}
|
||||
}
|
||||
@@ -12,21 +12,11 @@ import (
|
||||
"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
|
||||
stateManager *CertificationStateManager
|
||||
certRepo repositories.CertificationRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCertificationStateMachine 创建认证状态机
|
||||
@@ -34,41 +24,10 @@ 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)
|
||||
return &CertificationStateMachine{
|
||||
stateManager: NewCertificationStateManager(),
|
||||
certRepo: certRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,27 +38,7 @@ func (sm *CertificationStateMachine) CanTransition(
|
||||
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, "不支持的状态转换"
|
||||
return sm.stateManager.CanTransition(from, to, isUser, isAdmin)
|
||||
}
|
||||
|
||||
// TransitionTo 执行状态转换
|
||||
@@ -123,11 +62,6 @@ func (sm *CertificationStateMachine) TransitionTo(
|
||||
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
|
||||
@@ -154,20 +88,23 @@ func (sm *CertificationStateMachine) TransitionTo(
|
||||
|
||||
// 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 status {
|
||||
case enums.StatusInfoSubmitted:
|
||||
switch stateConfig.TimestampField {
|
||||
case "InfoSubmittedAt":
|
||||
cert.InfoSubmittedAt = &now
|
||||
case enums.StatusFaceVerified:
|
||||
cert.FaceVerifiedAt = &now
|
||||
case enums.StatusContractApplied:
|
||||
case "EnterpriseVerifiedAt":
|
||||
cert.EnterpriseVerifiedAt = &now
|
||||
case "ContractAppliedAt":
|
||||
cert.ContractAppliedAt = &now
|
||||
case enums.StatusContractApproved:
|
||||
cert.ContractApprovedAt = &now
|
||||
case enums.StatusContractSigned:
|
||||
case "ContractSignedAt":
|
||||
cert.ContractSignedAt = &now
|
||||
case enums.StatusCompleted:
|
||||
case "CompletedAt":
|
||||
cert.CompletedAt = &now
|
||||
}
|
||||
}
|
||||
@@ -179,25 +116,6 @@ func (sm *CertificationStateMachine) updateCertificationFields(
|
||||
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
|
||||
@@ -205,66 +123,13 @@ func (sm *CertificationStateMachine) updateCertificationFields(
|
||||
}
|
||||
}
|
||||
|
||||
// validateTransition 验证状态转换的有效性
|
||||
func (sm *CertificationStateMachine) validateTransition(
|
||||
ctx context.Context,
|
||||
cert *entities.Certification,
|
||||
targetStatus enums.CertificationStatus,
|
||||
metadata map[string]interface{},
|
||||
) error {
|
||||
switch targetStatus {
|
||||
case enums.StatusInfoSubmitted:
|
||||
// 验证企业信息是否完整
|
||||
// 这里应该检查用户是否有企业信息,通过用户域的企业服务验证
|
||||
// 暂时跳过验证,由应用服务层协调
|
||||
break
|
||||
|
||||
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
|
||||
return sm.stateManager.GetNextValidStatuses(currentStatus)
|
||||
}
|
||||
|
||||
// GetTransitionAction 获取状态转换对应的操作名称
|
||||
@@ -272,20 +137,49 @@ func (sm *CertificationStateMachine) GetTransitionAction(
|
||||
from enums.CertificationStatus,
|
||||
to enums.CertificationStatus,
|
||||
) string {
|
||||
transitions, exists := sm.transitions[from]
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -315,12 +209,12 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
|
||||
})
|
||||
}
|
||||
|
||||
if cert.FaceVerifiedAt != nil {
|
||||
if cert.EnterpriseVerifiedAt != nil {
|
||||
history = append(history, map[string]interface{}{
|
||||
"status": string(enums.StatusFaceVerified),
|
||||
"timestamp": *cert.FaceVerifiedAt,
|
||||
"action": "face_verify",
|
||||
"performer": "system",
|
||||
"status": string(enums.StatusEnterpriseVerified),
|
||||
"timestamp": *cert.EnterpriseVerifiedAt,
|
||||
"action": "enterprise_verify",
|
||||
"performer": "user",
|
||||
"metadata": map[string]interface{}{},
|
||||
})
|
||||
}
|
||||
@@ -335,27 +229,6 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
|
||||
})
|
||||
}
|
||||
|
||||
if cert.ContractApprovedAt != nil {
|
||||
metadata := map[string]interface{}{}
|
||||
if cert.AdminID != nil {
|
||||
metadata["admin_id"] = *cert.AdminID
|
||||
}
|
||||
if cert.ApprovalNotes != "" {
|
||||
metadata["approval_notes"] = cert.ApprovalNotes
|
||||
}
|
||||
if cert.SigningURL != "" {
|
||||
metadata["signing_url"] = cert.SigningURL
|
||||
}
|
||||
|
||||
history = append(history, map[string]interface{}{
|
||||
"status": string(enums.StatusContractApproved),
|
||||
"timestamp": *cert.ContractApprovedAt,
|
||||
"action": "admin_approve",
|
||||
"performer": "admin",
|
||||
"metadata": metadata,
|
||||
})
|
||||
}
|
||||
|
||||
if cert.ContractSignedAt != nil {
|
||||
metadata := map[string]interface{}{}
|
||||
if cert.ContractURL != "" {
|
||||
@@ -365,7 +238,7 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
|
||||
history = append(history, map[string]interface{}{
|
||||
"status": string(enums.StatusContractSigned),
|
||||
"timestamp": *cert.ContractSignedAt,
|
||||
"action": "user_sign",
|
||||
"action": "sign_contract",
|
||||
"performer": "user",
|
||||
"metadata": metadata,
|
||||
})
|
||||
@@ -375,7 +248,7 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
|
||||
history = append(history, map[string]interface{}{
|
||||
"status": string(enums.StatusCompleted),
|
||||
"timestamp": *cert.CompletedAt,
|
||||
"action": "system_complete",
|
||||
"action": "complete",
|
||||
"performer": "system",
|
||||
"metadata": map[string]interface{}{},
|
||||
})
|
||||
@@ -383,66 +256,3 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
|
||||
|
||||
return history, nil
|
||||
}
|
||||
|
||||
// ValidateCertificationFlow 验证认证流程的完整性
|
||||
func (sm *CertificationStateMachine) ValidateCertificationFlow(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)
|
||||
}
|
||||
|
||||
validation := map[string]interface{}{
|
||||
"certification_id": certificationID,
|
||||
"current_status": cert.Status,
|
||||
"is_valid": true,
|
||||
"issues": []string{},
|
||||
"warnings": []string{},
|
||||
}
|
||||
|
||||
// 检查必要的时间节点
|
||||
if cert.Status != enums.StatusPending {
|
||||
if cert.InfoSubmittedAt == nil {
|
||||
validation["is_valid"] = false
|
||||
validation["issues"] = append(validation["issues"].([]string), "缺少企业信息提交时间")
|
||||
}
|
||||
}
|
||||
|
||||
if cert.Status == enums.StatusFaceVerified || cert.Status == enums.StatusContractApplied ||
|
||||
cert.Status == enums.StatusContractPending || cert.Status == enums.StatusContractApproved ||
|
||||
cert.Status == enums.StatusContractSigned || cert.Status == enums.StatusCompleted {
|
||||
if cert.FaceVerifiedAt == nil {
|
||||
validation["is_valid"] = false
|
||||
validation["issues"] = append(validation["issues"].([]string), "缺少人脸识别完成时间")
|
||||
}
|
||||
}
|
||||
|
||||
if cert.Status == enums.StatusContractApproved || cert.Status == enums.StatusContractSigned ||
|
||||
cert.Status == enums.StatusCompleted {
|
||||
if cert.ContractApprovedAt == nil {
|
||||
validation["is_valid"] = false
|
||||
validation["issues"] = append(validation["issues"].([]string), "缺少合同审核时间")
|
||||
}
|
||||
if cert.SigningURL == "" {
|
||||
validation["warnings"] = append(validation["warnings"].([]string), "缺少合同签署链接")
|
||||
}
|
||||
}
|
||||
|
||||
if cert.Status == enums.StatusContractSigned || cert.Status == enums.StatusCompleted {
|
||||
if cert.ContractSignedAt == nil {
|
||||
validation["is_valid"] = false
|
||||
validation["issues"] = append(validation["issues"].([]string), "缺少合同签署时间")
|
||||
}
|
||||
if cert.ContractURL == "" {
|
||||
validation["warnings"] = append(validation["warnings"].([]string), "缺少合同文件链接")
|
||||
}
|
||||
}
|
||||
|
||||
if cert.Status == enums.StatusCompleted {
|
||||
if cert.CompletedAt == nil {
|
||||
validation["is_valid"] = false
|
||||
validation["issues"] = append(validation["issues"].([]string), "缺少认证完成时间")
|
||||
}
|
||||
}
|
||||
|
||||
return validation, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user