775 lines
25 KiB
Go
775 lines
25 KiB
Go
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"
|
||
}
|