Files
tyapi-server/internal/domains/certification/services/certification_service.go

775 lines
25 KiB
Go
Raw Normal View History

2025-07-11 21:05:58 +08:00
package services
import (
"context"
"fmt"
2025-07-13 16:36:20 +08:00
"mime"
"os"
"path/filepath"
"strings"
2025-07-11 21:05:58 +08:00
"time"
"go.uber.org/zap"
2025-07-13 16:36:20 +08:00
"tyapi-server/internal/application/certification/dto/responses"
2025-07-11 21:05:58 +08:00
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
2025-07-13 16:36:20 +08:00
sharedOCR "tyapi-server/internal/shared/ocr"
sharedStorage "tyapi-server/internal/shared/storage"
2025-07-11 21:05:58 +08:00
)
2025-07-13 16:36:20 +08:00
// CertificationService 认证领域服务
2025-07-11 21:05:58 +08:00
type CertificationService struct {
certRepo repositories.CertificationRepository
licenseRepo repositories.LicenseUploadRecordRepository
faceVerifyRepo repositories.FaceVerifyRecordRepository
contractRepo repositories.ContractRecordRepository
stateMachine *CertificationStateMachine
logger *zap.Logger
2025-07-13 16:36:20 +08:00
ocrService sharedOCR.OCRService
storageService sharedStorage.StorageService
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// NewCertificationService 创建认证领域服务
2025-07-11 21:05:58 +08:00
func NewCertificationService(
certRepo repositories.CertificationRepository,
licenseRepo repositories.LicenseUploadRecordRepository,
faceVerifyRepo repositories.FaceVerifyRecordRepository,
contractRepo repositories.ContractRecordRepository,
stateMachine *CertificationStateMachine,
logger *zap.Logger,
2025-07-13 16:36:20 +08:00
ocrService sharedOCR.OCRService,
storageService sharedStorage.StorageService,
2025-07-11 21:05:58 +08:00
) *CertificationService {
return &CertificationService{
certRepo: certRepo,
licenseRepo: licenseRepo,
faceVerifyRepo: faceVerifyRepo,
contractRepo: contractRepo,
stateMachine: stateMachine,
logger: logger,
2025-07-13 16:36:20 +08:00
ocrService: ocrService,
storageService: storageService,
2025-07-11 21:05:58 +08:00
}
}
// CreateCertification 创建认证申请
2025-07-13 16:36:20 +08:00
func (s *CertificationService) CreateCertification(ctx context.Context, userID string) (*entities.Certification, error) {
2025-07-11 21:05:58 +08:00
// 检查用户是否已有认证申请
existingCert, err := s.certRepo.GetByUserID(ctx, userID)
if err == nil && existingCert != nil {
2025-07-13 16:36:20 +08:00
return nil, fmt.Errorf("用户已有认证申请")
2025-07-11 21:05:58 +08:00
}
certification := &entities.Certification{
UserID: userID,
Status: enums.StatusPending,
}
2025-07-13 16:36:20 +08:00
createdCert, err := s.certRepo.Create(ctx, *certification)
if err != nil {
s.logger.Error("创建认证申请失败", zap.Error(err))
2025-07-11 21:05:58 +08:00
return nil, fmt.Errorf("创建认证申请失败: %w", err)
}
2025-07-13 16:36:20 +08:00
certification = &createdCert
2025-07-11 21:05:58 +08:00
s.logger.Info("认证申请创建成功",
zap.String("certification_id", certification.ID),
zap.String("user_id", userID),
)
2025-07-13 16:36:20 +08:00
return certification, nil
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 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),
2025-07-11 21:05:58 +08:00
)
2025-07-13 16:36:20 +08:00
return nil
}
// InitiateFaceVerify 发起人脸识别验证
func (s *CertificationService) InitiateFaceVerify(ctx context.Context, certificationID, realName, idCardNumber string) (*entities.FaceVerifyRecord, error) {
cert, err := s.certRepo.GetByID(ctx, certificationID)
2025-07-11 21:05:58 +08:00
if err != nil {
2025-07-13 16:36:20 +08:00
return nil, fmt.Errorf("认证申请不存在: %w", err)
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 检查当前状态是否可以发起人脸识别
if cert.Status != enums.StatusInfoSubmitted {
return nil, fmt.Errorf("当前状态不允许发起人脸识别")
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 创建人脸识别记录
faceVerifyRecord := &entities.FaceVerifyRecord{
CertificationID: certificationID,
UserID: cert.UserID,
RealName: realName,
IDCardNumber: idCardNumber,
Status: "PROCESSING",
ExpiresAt: time.Now().Add(30 * time.Minute), // 30分钟有效期
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
createdFaceRecord, err := s.faceVerifyRepo.Create(ctx, *faceVerifyRecord)
if err != nil {
s.logger.Error("创建人脸识别记录失败", zap.Error(err))
return nil, fmt.Errorf("创建人脸识别记录失败: %w", err)
}
faceVerifyRecord = &createdFaceRecord
2025-07-11 21:05:58 +08:00
2025-07-13 16:36:20 +08:00
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)
2025-07-11 21:05:58 +08:00
if err != nil {
2025-07-13 16:36:20 +08:00
return fmt.Errorf("人脸识别记录不存在: %w", err)
}
// 更新人脸识别记录状态
now := time.Now()
faceVerifyRecord.CompletedAt = &now
if isSuccess {
faceVerifyRecord.Status = "SUCCESS"
faceVerifyRecord.VerifyScore = 0.95 // 示例分数
2025-07-11 21:05:58 +08:00
} else {
2025-07-13 16:36:20 +08:00
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)
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 根据验证结果转换认证状态
var targetStatus enums.CertificationStatus
if isSuccess {
targetStatus = enums.StatusFaceVerified
} else {
targetStatus = enums.StatusFaceFailed
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
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
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 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("合同申请成功",
2025-07-11 21:05:58 +08:00
zap.String("certification_id", certificationID),
2025-07-13 16:36:20 +08:00
zap.String("contract_id", contractRecord.ID),
2025-07-11 21:05:58 +08:00
)
2025-07-13 16:36:20 +08:00
return nil
}
// ApproveContract 管理员审核合同
func (s *CertificationService) ApproveContract(ctx context.Context, certificationID, adminID, signingURL, approvalNotes string) error {
2025-07-11 21:05:58 +08:00
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
2025-07-13 16:36:20 +08:00
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,
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 使用状态机转换状态
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractApproved, false, true, metadata); err != nil {
return fmt.Errorf("状态转换失败: %w", err)
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
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)
2025-07-11 21:05:58 +08:00
if err != nil {
2025-07-13 16:36:20 +08:00
return fmt.Errorf("认证申请不存在: %w", err)
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 检查当前状态是否可以拒绝
if cert.Status != enums.StatusContractPending {
return fmt.Errorf("当前状态不允许拒绝")
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 准备拒绝元数据
metadata := map[string]interface{}{
"admin_id": adminID,
"reject_reason": rejectReason,
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 使用状态机转换状态
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusRejected, false, true, metadata); err != nil {
return fmt.Errorf("状态转换失败: %w", err)
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
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)
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 检查当前状态是否可以签署
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("合同签署完成",
2025-07-11 21:05:58 +08:00
zap.String("certification_id", certificationID),
)
2025-07-13 16:36:20 +08:00
return nil
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 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
}
2025-07-11 21:05:58 +08:00
2025-07-13 16:36:20 +08:00
// RetryFaceVerify 重试人脸识别
func (s *CertificationService) RetryFaceVerify(ctx context.Context, certificationID string) error {
cert, err := s.certRepo.GetByID(ctx, certificationID)
2025-07-11 21:05:58 +08:00
if err != nil {
2025-07-13 16:36:20 +08:00
return fmt.Errorf("认证申请不存在: %w", err)
}
// 检查是否可以重试
if !cert.CanRetryFaceVerify() {
return fmt.Errorf("当前状态不允许重试人脸识别")
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 使用状态机转换状态
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
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 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("认证流程重新开始",
2025-07-11 21:05:58 +08:00
zap.String("certification_id", certificationID),
)
2025-07-13 16:36:20 +08:00
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) {
2025-07-11 21:05:58 +08:00
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
2025-07-13 16:36:20 +08:00
return nil, err
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
return &cert, nil
}
2025-07-11 21:05:58 +08:00
2025-07-13 16:36:20 +08:00
// 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)
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
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,
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 添加时间节点信息
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
}
2025-07-11 21:05:58 +08:00
2025-07-13 16:36:20 +08:00
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)
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
s.logger.Info("OCR结果更新成功",
2025-07-11 21:05:58 +08:00
zap.String("certification_id", certificationID),
2025-07-13 16:36:20 +08:00
zap.String("ocr_request_id", ocrRequestID),
zap.Float64("confidence", confidence),
2025-07-11 21:05:58 +08:00
)
2025-07-13 16:36:20 +08:00
return nil
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 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
}
}
2025-07-11 21:05:58 +08:00
2025-07-13 16:36:20 +08:00
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)
2025-07-11 21:05:58 +08:00
if err != nil {
2025-07-13 16:36:20 +08:00
s.logger.Error("创建营业执照记录失败", zap.Error(err))
return nil, fmt.Errorf("创建营业执照记录失败: %w", err)
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
licenseRecord = &createdLicense
s.logger.Info("营业执照上传记录创建成功",
zap.String("license_id", licenseRecord.ID),
zap.String("user_id", userID),
zap.String("file_name", fileName),
)
return licenseRecord, nil
}
2025-07-11 21:05:58 +08:00
2025-07-13 16:36:20 +08:00
// 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)
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 调用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
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 将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
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
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("营业执照识别失败,请上传清晰的营业执照图片")
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 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)
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 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: "",
}
2025-07-11 21:05:58 +08:00
2025-07-13 16:36:20 +08:00
// 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),
)
2025-07-11 21:05:58 +08:00
2025-07-13 16:36:20 +08:00
return uploadRecord, ocrResult, nil
2025-07-11 21:05:58 +08:00
}
2025-07-13 16:36:20 +08:00
// 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"
2025-07-11 21:05:58 +08:00
}