package certification import ( "context" "fmt" "time" "go.uber.org/zap" "tyapi-server/internal/application/certification/dto/commands" "tyapi-server/internal/application/certification/dto/queries" "tyapi-server/internal/application/certification/dto/responses" "tyapi-server/internal/domains/certification/entities" "tyapi-server/internal/domains/certification/repositories" "tyapi-server/internal/domains/certification/services" user_entities "tyapi-server/internal/domains/user/entities" user_repositories "tyapi-server/internal/domains/user/repositories" "tyapi-server/internal/shared/ocr" "tyapi-server/internal/shared/storage" ) // CertificationApplicationServiceImpl 认证应用服务实现 type CertificationApplicationServiceImpl struct { certRepo repositories.CertificationRepository licenseRepo repositories.LicenseUploadRecordRepository faceVerifyRepo repositories.FaceVerifyRecordRepository contractRepo repositories.ContractRecordRepository certService *services.CertificationService stateMachine *services.CertificationStateMachine storageService storage.StorageService ocrService ocr.OCRService enterpriseInfoRepo user_repositories.EnterpriseInfoRepository logger *zap.Logger } // NewCertificationApplicationService 创建认证应用服务 func NewCertificationApplicationService( certRepo repositories.CertificationRepository, licenseRepo repositories.LicenseUploadRecordRepository, faceVerifyRepo repositories.FaceVerifyRecordRepository, contractRepo repositories.ContractRecordRepository, certService *services.CertificationService, stateMachine *services.CertificationStateMachine, storageService storage.StorageService, ocrService ocr.OCRService, enterpriseInfoRepo user_repositories.EnterpriseInfoRepository, logger *zap.Logger, ) CertificationApplicationService { return &CertificationApplicationServiceImpl{ certRepo: certRepo, licenseRepo: licenseRepo, faceVerifyRepo: faceVerifyRepo, contractRepo: contractRepo, certService: certService, stateMachine: stateMachine, storageService: storageService, ocrService: ocrService, enterpriseInfoRepo: enterpriseInfoRepo, logger: logger, } } // CreateCertification 创建认证申请 func (s *CertificationApplicationServiceImpl) CreateCertification(ctx context.Context, cmd *commands.CreateCertificationCommand) (*responses.CertificationResponse, error) { // 使用领域服务创建认证申请 certification, err := s.certService.CreateCertification(ctx, cmd.UserID) if err != nil { return nil, err } // 构建响应 response := &responses.CertificationResponse{ ID: certification.ID, UserID: certification.UserID, Status: certification.Status, StatusName: string(certification.Status), Progress: certification.GetProgressPercentage(), IsUserActionRequired: certification.IsUserActionRequired(), IsAdminActionRequired: certification.IsAdminActionRequired(), InfoSubmittedAt: certification.InfoSubmittedAt, FaceVerifiedAt: certification.FaceVerifiedAt, ContractAppliedAt: certification.ContractAppliedAt, ContractApprovedAt: certification.ContractApprovedAt, ContractSignedAt: certification.ContractSignedAt, CompletedAt: certification.CompletedAt, ContractURL: certification.ContractURL, SigningURL: certification.SigningURL, RejectReason: certification.RejectReason, CreatedAt: certification.CreatedAt, UpdatedAt: certification.UpdatedAt, } s.logger.Info("认证申请创建成功", zap.String("certification_id", certification.ID), zap.String("user_id", cmd.UserID), ) return response, nil } // CreateEnterpriseInfo 创建企业信息 func (s *CertificationApplicationServiceImpl) CreateEnterpriseInfo(ctx context.Context, cmd *commands.CreateEnterpriseInfoCommand) (*responses.EnterpriseInfoResponse, error) { // 检查用户是否已有企业信息 existingInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, cmd.UserID) if err == nil && existingInfo != nil { return nil, fmt.Errorf("用户已有企业信息") } // 检查统一社会信用代码是否已存在 exists, err := s.enterpriseInfoRepo.CheckUnifiedSocialCodeExists(ctx, cmd.UnifiedSocialCode, "") if err != nil { return nil, fmt.Errorf("检查企业信息失败: %w", err) } if exists { return nil, fmt.Errorf("统一社会信用代码已存在") } // 创建企业信息 enterpriseInfo := &user_entities.EnterpriseInfo{ UserID: cmd.UserID, CompanyName: cmd.CompanyName, UnifiedSocialCode: cmd.UnifiedSocialCode, LegalPersonName: cmd.LegalPersonName, LegalPersonID: cmd.LegalPersonID, } createdEnterpriseInfo, err := s.enterpriseInfoRepo.Create(ctx, *enterpriseInfo) if err != nil { s.logger.Error("创建企业信息失败", zap.Error(err)) return nil, fmt.Errorf("创建企业信息失败: %w", err) } s.logger.Info("企业信息创建成功", zap.String("user_id", cmd.UserID), zap.String("enterprise_id", enterpriseInfo.ID), ) return &responses.EnterpriseInfoResponse{ ID: createdEnterpriseInfo.ID, CompanyName: enterpriseInfo.CompanyName, UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode, LegalPersonName: enterpriseInfo.LegalPersonName, LegalPersonID: enterpriseInfo.LegalPersonID, IsOCRVerified: enterpriseInfo.IsOCRVerified, IsFaceVerified: enterpriseInfo.IsFaceVerified, CreatedAt: enterpriseInfo.CreatedAt, UpdatedAt: enterpriseInfo.UpdatedAt, }, nil } // UploadLicense 上传营业执照 func (s *CertificationApplicationServiceImpl) UploadLicense(ctx context.Context, cmd *commands.UploadLicenseCommand) (*responses.UploadLicenseResponse, error) { // 1. 业务规则验证 - 调用领域服务 if err := s.certService.ValidateLicenseUpload(ctx, cmd.UserID, cmd.FileName, cmd.FileSize); err != nil { return nil, err } // 2. 上传文件到存储服务 uploadResult, err := s.storageService.UploadFile(ctx, cmd.FileBytes, cmd.FileName) if err != nil { s.logger.Error("上传营业执照失败", zap.Error(err)) return nil, fmt.Errorf("上传营业执照失败: %w", err) } // 3. 创建营业执照上传记录 - 调用领域服务 licenseRecord, err := s.certService.CreateLicenseUploadRecord(ctx, cmd.UserID, cmd.FileName, cmd.FileSize, uploadResult) if err != nil { s.logger.Error("创建营业执照记录失败", zap.Error(err)) return nil, fmt.Errorf("创建营业执照记录失败: %w", err) } // 4. 异步处理OCR识别 - 使用任务队列或后台任务 go s.processOCRAsync(ctx, licenseRecord.ID, cmd.FileBytes) s.logger.Info("营业执照上传成功", zap.String("user_id", cmd.UserID), zap.String("license_id", licenseRecord.ID), zap.String("file_url", uploadResult.URL), ) // 5. 构建响应 response := &responses.UploadLicenseResponse{ UploadRecordID: licenseRecord.ID, FileURL: uploadResult.URL, OCRProcessed: false, OCRSuccess: false, } // 6. 如果OCR处理很快完成,尝试获取结果 // 这里可以添加一个简单的轮询机制,或者使用WebSocket推送结果 // 暂时返回基础信息,前端可以通过查询接口获取OCR结果 return response, nil } // UploadBusinessLicense 上传营业执照并同步OCR识别 func (s *CertificationApplicationServiceImpl) UploadBusinessLicense(ctx context.Context, userID string, fileBytes []byte, fileName string) (*responses.UploadLicenseResponse, error) { s.logger.Info("开始处理营业执照上传", zap.String("user_id", userID), zap.String("file_name", fileName), ) // 调用领域服务进行上传和OCR识别 uploadRecord, ocrResult, err := s.certService.UploadBusinessLicense(ctx, userID, fileBytes, fileName) if err != nil { s.logger.Error("营业执照上传失败", zap.Error(err)) return nil, err } // 构建响应 response := &responses.UploadLicenseResponse{ UploadRecordID: uploadRecord.ID, FileURL: uploadRecord.FileURL, OCRProcessed: uploadRecord.OCRProcessed, OCRSuccess: uploadRecord.OCRSuccess, OCRConfidence: uploadRecord.OCRConfidence, OCRErrorMessage: uploadRecord.OCRErrorMessage, } // 如果OCR成功,添加识别结果 if ocrResult != nil && uploadRecord.OCRSuccess { response.EnterpriseName = ocrResult.CompanyName response.CreditCode = ocrResult.UnifiedSocialCode response.LegalPerson = ocrResult.LegalPersonName } s.logger.Info("营业执照上传完成", zap.String("user_id", userID), zap.String("upload_record_id", uploadRecord.ID), zap.Bool("ocr_success", uploadRecord.OCRSuccess), ) return response, nil } // SubmitEnterpriseInfo 提交企业信息 func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(ctx context.Context, cmd *commands.SubmitEnterpriseInfoCommand) (*responses.EnterpriseInfoResponse, error) { // 根据用户ID获取认证申请 certification, err := s.certRepo.GetByUserID(ctx, cmd.UserID) if err != nil { return nil, fmt.Errorf("用户尚未创建认证申请: %w", err) } // 设置认证ID cmd.CertificationID = certification.ID // 调用领域服务提交企业信息 if err := s.certService.SubmitEnterpriseInfo(ctx, certification.ID); err != nil { return nil, err } // 创建企业信息 enterpriseInfo := &user_entities.EnterpriseInfo{ UserID: cmd.UserID, CompanyName: cmd.CompanyName, UnifiedSocialCode: cmd.UnifiedSocialCode, LegalPersonName: cmd.LegalPersonName, LegalPersonID: cmd.LegalPersonID, } *enterpriseInfo, err = s.enterpriseInfoRepo.Create(ctx, *enterpriseInfo) if err != nil { s.logger.Error("创建企业信息失败", zap.Error(err)) return nil, fmt.Errorf("创建企业信息失败: %w", err) } s.logger.Info("企业信息提交成功", zap.String("user_id", cmd.UserID), zap.String("certification_id", certification.ID), zap.String("enterprise_id", enterpriseInfo.ID), ) return &responses.EnterpriseInfoResponse{ ID: enterpriseInfo.ID, CompanyName: enterpriseInfo.CompanyName, UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode, LegalPersonName: enterpriseInfo.LegalPersonName, LegalPersonID: enterpriseInfo.LegalPersonID, IsOCRVerified: enterpriseInfo.IsOCRVerified, IsFaceVerified: enterpriseInfo.IsFaceVerified, CreatedAt: enterpriseInfo.CreatedAt, UpdatedAt: enterpriseInfo.UpdatedAt, }, nil } // InitiateFaceVerify 发起人脸识别验证 func (s *CertificationApplicationServiceImpl) InitiateFaceVerify(ctx context.Context, cmd *commands.InitiateFaceVerifyCommand) (*responses.FaceVerifyResponse, error) { // 根据用户ID获取认证申请 - 这里需要从Handler传入用户ID // 由于cmd中没有UserID字段,我们需要修改Handler的调用方式 // 暂时使用certificationID来获取认证申请 certification, err := s.certRepo.GetByID(ctx, cmd.CertificationID) if err != nil { return nil, fmt.Errorf("认证申请不存在: %w", err) } // 调用领域服务发起人脸识别 faceVerifyRecord, err := s.certService.InitiateFaceVerify(ctx, certification.ID, cmd.RealName, cmd.IDCardNumber) if err != nil { return nil, err } // 构建验证URL(这里应该根据实际的人脸识别服务生成) verifyURL := fmt.Sprintf("/api/certification/face-verify/%s?return_url=%s", faceVerifyRecord.ID, cmd.ReturnURL) s.logger.Info("人脸识别验证发起成功", zap.String("certification_id", certification.ID), zap.String("face_verify_id", faceVerifyRecord.ID), ) return &responses.FaceVerifyResponse{ CertifyID: faceVerifyRecord.ID, VerifyURL: verifyURL, ExpiresAt: faceVerifyRecord.ExpiresAt, }, nil } // ApplyContract 申请合同 func (s *CertificationApplicationServiceImpl) ApplyContract(ctx context.Context, userID string) (*responses.CertificationResponse, error) { // 根据用户ID获取认证申请 certification, err := s.certRepo.GetByUserID(ctx, userID) if err != nil { return nil, fmt.Errorf("用户尚未创建认证申请: %w", err) } // 调用领域服务申请合同 if err := s.certService.ApplyContract(ctx, certification.ID); err != nil { return nil, err } // 重新获取更新后的认证申请 updatedCertification, err := s.certRepo.GetByID(ctx, certification.ID) if err != nil { return nil, err } s.logger.Info("合同申请成功", zap.String("user_id", userID), zap.String("certification_id", certification.ID), ) return s.buildCertificationResponse(&updatedCertification), nil } // GetCertificationStatus 获取认证状态 func (s *CertificationApplicationServiceImpl) GetCertificationStatus(ctx context.Context, query *queries.GetCertificationStatusQuery) (*responses.CertificationResponse, error) { // 根据用户ID获取认证申请 certification, err := s.certRepo.GetByUserID(ctx, query.UserID) if err != nil { // 如果用户没有认证申请,返回一个表示未开始的状态 if err.Error() == "认证申请不存在" || err.Error() == "record not found" { return &responses.CertificationResponse{ ID: "", UserID: query.UserID, Status: "not_started", StatusName: "未开始认证", Progress: 0, IsUserActionRequired: true, IsAdminActionRequired: false, InfoSubmittedAt: nil, FaceVerifiedAt: nil, ContractAppliedAt: nil, ContractApprovedAt: nil, ContractSignedAt: nil, CompletedAt: nil, ContractURL: "", SigningURL: "", RejectReason: "", CreatedAt: time.Time{}, UpdatedAt: time.Time{}, }, nil } return nil, err } // 构建响应 response := s.buildCertificationResponse(certification) return response, nil } // GetCertificationDetails 获取认证详情 func (s *CertificationApplicationServiceImpl) GetCertificationDetails(ctx context.Context, query *queries.GetCertificationDetailsQuery) (*responses.CertificationResponse, error) { // 根据用户ID获取认证申请 certification, err := s.certRepo.GetByUserID(ctx, query.UserID) if err != nil { // 如果用户没有认证申请,返回错误 if err.Error() == "认证申请不存在" || err.Error() == "record not found" { return nil, fmt.Errorf("用户尚未创建认证申请") } return nil, err } // 获取认证申请详细信息 certificationWithDetails, err := s.certService.GetCertificationWithDetails(ctx, certification.ID) if err != nil { return nil, err } // 构建响应 response := s.buildCertificationResponse(certificationWithDetails) // 添加企业信息 if certification.UserID != "" { enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, certification.UserID) if err == nil && enterpriseInfo != nil { response.Enterprise = &responses.EnterpriseInfoResponse{ ID: enterpriseInfo.ID, CompanyName: enterpriseInfo.CompanyName, UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode, LegalPersonName: enterpriseInfo.LegalPersonName, LegalPersonID: enterpriseInfo.LegalPersonID, IsOCRVerified: enterpriseInfo.IsOCRVerified, IsFaceVerified: enterpriseInfo.IsFaceVerified, CreatedAt: enterpriseInfo.CreatedAt, UpdatedAt: enterpriseInfo.UpdatedAt, } } } return response, nil } // CompleteFaceVerify 完成人脸识别验证 func (s *CertificationApplicationServiceImpl) CompleteFaceVerify(ctx context.Context, faceVerifyID string, isSuccess bool) error { return s.certService.CompleteFaceVerify(ctx, faceVerifyID, isSuccess) } // ApproveContract 管理员审核合同 func (s *CertificationApplicationServiceImpl) ApproveContract(ctx context.Context, certificationID, adminID, signingURL, approvalNotes string) error { return s.certService.ApproveContract(ctx, certificationID, adminID, signingURL, approvalNotes) } // RejectContract 管理员拒绝合同 func (s *CertificationApplicationServiceImpl) RejectContract(ctx context.Context, certificationID, adminID, rejectReason string) error { return s.certService.RejectContract(ctx, certificationID, adminID, rejectReason) } // CompleteContractSign 完成合同签署 func (s *CertificationApplicationServiceImpl) CompleteContractSign(ctx context.Context, certificationID, contractURL string) error { return s.certService.CompleteContractSign(ctx, certificationID, contractURL) } // CompleteCertification 完成认证 func (s *CertificationApplicationServiceImpl) CompleteCertification(ctx context.Context, certificationID string) error { return s.certService.CompleteCertification(ctx, certificationID) } // RetryStep 重试认证步骤 func (s *CertificationApplicationServiceImpl) RetryStep(ctx context.Context, cmd *commands.RetryStepCommand) error { switch cmd.Step { case "face_verify": return s.certService.RetryFaceVerify(ctx, cmd.CertificationID) case "restart": return s.certService.RestartCertification(ctx, cmd.CertificationID) default: return fmt.Errorf("不支持的重试步骤: %s", cmd.Step) } } // GetCertificationProgress 获取认证进度 func (s *CertificationApplicationServiceImpl) GetCertificationProgress(ctx context.Context, userID string) (map[string]interface{}, error) { // 根据用户ID获取认证申请 certification, err := s.certRepo.GetByUserID(ctx, userID) if err != nil { // 如果用户没有认证申请,返回未开始状态 if err.Error() == "认证申请不存在" || err.Error() == "record not found" { return map[string]interface{}{ "certification_id": "", "user_id": userID, "current_status": "not_started", "status_name": "未开始认证", "progress_percentage": 0, "is_user_action_required": true, "is_admin_action_required": false, "next_valid_statuses": []string{"pending"}, "message": "用户尚未开始认证流程", "created_at": nil, "updated_at": nil, }, nil } return nil, err } // 获取认证进度 return s.certService.GetCertificationProgress(ctx, certification.ID) } // RetryFaceVerify 重试人脸识别 func (s *CertificationApplicationServiceImpl) RetryFaceVerify(ctx context.Context, userID string) (*responses.FaceVerifyResponse, error) { // 根据用户ID获取认证申请 certification, err := s.certRepo.GetByUserID(ctx, userID) if err != nil { return nil, fmt.Errorf("用户尚未创建认证申请: %w", err) } // 调用领域服务重试人脸识别 if err := s.certService.RetryFaceVerify(ctx, certification.ID); err != nil { return nil, err } // 重新发起人脸识别 faceVerifyRecord, err := s.certService.InitiateFaceVerify(ctx, certification.ID, "", "") if err != nil { return nil, err } // 构建验证URL verifyURL := fmt.Sprintf("/api/certification/face-verify/%s", faceVerifyRecord.ID) return &responses.FaceVerifyResponse{ CertifyID: faceVerifyRecord.ID, VerifyURL: verifyURL, ExpiresAt: faceVerifyRecord.ExpiresAt, }, nil } // RetryContractSign 重试合同签署 func (s *CertificationApplicationServiceImpl) RetryContractSign(ctx context.Context, userID string) (*responses.CertificationResponse, error) { // 根据用户ID获取认证申请 certification, err := s.certRepo.GetByUserID(ctx, userID) if err != nil { return nil, fmt.Errorf("用户尚未创建认证申请: %w", err) } // 重新获取更新后的认证申请 updatedCertification, err := s.certRepo.GetByID(ctx, certification.ID) if err != nil { return nil, err } s.logger.Info("合同签署重试准备完成", zap.String("user_id", userID), zap.String("certification_id", certification.ID), ) return s.buildCertificationResponse(&updatedCertification), nil } // RestartCertification 重新开始认证 func (s *CertificationApplicationServiceImpl) RestartCertification(ctx context.Context, certificationID string) error { return s.certService.RestartCertification(ctx, certificationID) } // buildCertificationResponse 构建认证响应 func (s *CertificationApplicationServiceImpl) buildCertificationResponse(certification *entities.Certification) *responses.CertificationResponse { return &responses.CertificationResponse{ ID: certification.ID, UserID: certification.UserID, Status: certification.Status, StatusName: string(certification.Status), Progress: certification.GetProgressPercentage(), IsUserActionRequired: certification.IsUserActionRequired(), IsAdminActionRequired: certification.IsAdminActionRequired(), InfoSubmittedAt: certification.InfoSubmittedAt, FaceVerifiedAt: certification.FaceVerifiedAt, ContractAppliedAt: certification.ContractAppliedAt, ContractApprovedAt: certification.ContractApprovedAt, ContractSignedAt: certification.ContractSignedAt, CompletedAt: certification.CompletedAt, ContractURL: certification.ContractURL, SigningURL: certification.SigningURL, RejectReason: certification.RejectReason, CreatedAt: certification.CreatedAt, UpdatedAt: certification.UpdatedAt, } } // processOCRAsync 异步处理OCR识别 func (s *CertificationApplicationServiceImpl) processOCRAsync(ctx context.Context, licenseID string, fileBytes []byte) { // 调用领域服务处理OCR识别 if err := s.certService.ProcessOCRAsync(ctx, licenseID, fileBytes); err != nil { s.logger.Error("OCR处理失败", zap.String("license_id", licenseID), zap.Error(err), ) } } // GetLicenseOCRResult 获取营业执照OCR识别结果 func (s *CertificationApplicationServiceImpl) GetLicenseOCRResult(ctx context.Context, recordID string) (*responses.UploadLicenseResponse, error) { // 获取营业执照上传记录 licenseRecord, err := s.licenseRepo.GetByID(ctx, recordID) if err != nil { s.logger.Error("获取营业执照记录失败", zap.Error(err)) return nil, fmt.Errorf("获取营业执照记录失败: %w", err) } // 构建响应 response := &responses.UploadLicenseResponse{ UploadRecordID: licenseRecord.ID, FileURL: licenseRecord.FileURL, OCRProcessed: licenseRecord.OCRProcessed, OCRSuccess: licenseRecord.OCRSuccess, OCRConfidence: licenseRecord.OCRConfidence, OCRErrorMessage: licenseRecord.OCRErrorMessage, } // 如果OCR成功,解析OCR结果 if licenseRecord.OCRSuccess && licenseRecord.OCRRawData != "" { // 这里可以解析OCR原始数据,提取企业信息 // 简化处理,直接返回原始数据中的关键信息 // 实际项目中可以使用JSON解析 response.EnterpriseName = "已识别" // 从OCR数据中提取 response.CreditCode = "已识别" // 从OCR数据中提取 response.LegalPerson = "已识别" // 从OCR数据中提取 } return response, nil }