405 lines
14 KiB
Go
405 lines
14 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"go.uber.org/zap"
|
|
|
|
"tyapi-server/internal/domains/certification/dto"
|
|
"tyapi-server/internal/domains/certification/entities"
|
|
"tyapi-server/internal/domains/certification/enums"
|
|
"tyapi-server/internal/domains/certification/repositories"
|
|
"tyapi-server/internal/shared/ocr"
|
|
"tyapi-server/internal/shared/storage"
|
|
)
|
|
|
|
// CertificationService 认证服务
|
|
type CertificationService struct {
|
|
certRepo repositories.CertificationRepository
|
|
enterpriseRepo repositories.EnterpriseRepository
|
|
licenseRepo repositories.LicenseUploadRecordRepository
|
|
faceVerifyRepo repositories.FaceVerifyRecordRepository
|
|
contractRepo repositories.ContractRecordRepository
|
|
stateMachine *CertificationStateMachine
|
|
storageService storage.StorageService
|
|
ocrService ocr.OCRService
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewCertificationService 创建认证服务
|
|
func NewCertificationService(
|
|
certRepo repositories.CertificationRepository,
|
|
enterpriseRepo repositories.EnterpriseRepository,
|
|
licenseRepo repositories.LicenseUploadRecordRepository,
|
|
faceVerifyRepo repositories.FaceVerifyRecordRepository,
|
|
contractRepo repositories.ContractRecordRepository,
|
|
stateMachine *CertificationStateMachine,
|
|
storageService storage.StorageService,
|
|
ocrService ocr.OCRService,
|
|
logger *zap.Logger,
|
|
) *CertificationService {
|
|
return &CertificationService{
|
|
certRepo: certRepo,
|
|
enterpriseRepo: enterpriseRepo,
|
|
licenseRepo: licenseRepo,
|
|
faceVerifyRepo: faceVerifyRepo,
|
|
contractRepo: contractRepo,
|
|
stateMachine: stateMachine,
|
|
storageService: storageService,
|
|
ocrService: ocrService,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// CreateCertification 创建认证申请
|
|
func (s *CertificationService) CreateCertification(ctx context.Context, userID string) (*dto.CertificationCreateResponse, error) {
|
|
s.logger.Info("创建认证申请", zap.String("user_id", userID))
|
|
|
|
// 检查用户是否已有认证申请
|
|
existingCert, err := s.certRepo.GetByUserID(ctx, userID)
|
|
if err == nil && existingCert != nil {
|
|
// 如果已存在且不是最终状态,返回现有申请
|
|
if !enums.IsFinalStatus(existingCert.Status) {
|
|
return &dto.CertificationCreateResponse{
|
|
ID: existingCert.ID,
|
|
UserID: existingCert.UserID,
|
|
Status: existingCert.Status,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
// 创建新的认证申请
|
|
certification := &entities.Certification{
|
|
ID: uuid.New().String(),
|
|
UserID: userID,
|
|
Status: enums.StatusPending,
|
|
}
|
|
|
|
if err := s.certRepo.Create(ctx, certification); err != nil {
|
|
return nil, fmt.Errorf("创建认证申请失败: %w", err)
|
|
}
|
|
|
|
s.logger.Info("认证申请创建成功",
|
|
zap.String("certification_id", certification.ID),
|
|
zap.String("user_id", userID),
|
|
)
|
|
|
|
return &dto.CertificationCreateResponse{
|
|
ID: certification.ID,
|
|
UserID: certification.UserID,
|
|
Status: certification.Status,
|
|
}, nil
|
|
}
|
|
|
|
// UploadLicense 上传营业执照并进行OCR识别
|
|
func (s *CertificationService) UploadLicense(ctx context.Context, userID string, fileBytes []byte, fileName string) (*dto.UploadLicenseResponse, error) {
|
|
s.logger.Info("上传营业执照",
|
|
zap.String("user_id", userID),
|
|
zap.String("file_name", fileName),
|
|
zap.Int("file_size", len(fileBytes)),
|
|
)
|
|
|
|
// 1. 上传文件到存储服务
|
|
uploadResult, err := s.storageService.UploadFile(ctx, fileBytes, fileName)
|
|
if err != nil {
|
|
s.logger.Error("文件上传失败", zap.Error(err))
|
|
return nil, fmt.Errorf("文件上传失败: %w", err)
|
|
}
|
|
|
|
// 2. 创建上传记录
|
|
uploadRecord := &entities.LicenseUploadRecord{
|
|
ID: uuid.New().String(),
|
|
UserID: userID,
|
|
OriginalFileName: fileName,
|
|
FileSize: int64(len(fileBytes)),
|
|
FileType: uploadResult.MimeType,
|
|
FileURL: uploadResult.URL,
|
|
QiNiuKey: uploadResult.Key,
|
|
OCRProcessed: false,
|
|
OCRSuccess: false,
|
|
}
|
|
|
|
if err := s.licenseRepo.Create(ctx, uploadRecord); err != nil {
|
|
s.logger.Error("创建上传记录失败", zap.Error(err))
|
|
return nil, fmt.Errorf("创建上传记录失败: %w", err)
|
|
}
|
|
|
|
// 3. 尝试OCR识别
|
|
var enterpriseInfo *dto.OCREnterpriseInfo
|
|
var ocrError string
|
|
|
|
ocrResult, err := s.ocrService.RecognizeBusinessLicense(ctx, uploadResult.URL)
|
|
if err != nil {
|
|
s.logger.Warn("OCR识别失败", zap.Error(err))
|
|
ocrError = err.Error()
|
|
uploadRecord.OCRProcessed = true
|
|
uploadRecord.OCRSuccess = false
|
|
uploadRecord.OCRErrorMessage = ocrError
|
|
} else {
|
|
s.logger.Info("OCR识别成功",
|
|
zap.String("company_name", ocrResult.CompanyName),
|
|
zap.Float64("confidence", ocrResult.Confidence),
|
|
)
|
|
enterpriseInfo = ocrResult
|
|
uploadRecord.OCRProcessed = true
|
|
uploadRecord.OCRSuccess = true
|
|
uploadRecord.OCRConfidence = ocrResult.Confidence
|
|
// 存储OCR原始数据
|
|
if rawData, err := s.serializeOCRResult(ocrResult); err == nil {
|
|
uploadRecord.OCRRawData = rawData
|
|
}
|
|
}
|
|
|
|
// 更新上传记录
|
|
if err := s.licenseRepo.Update(ctx, uploadRecord); err != nil {
|
|
s.logger.Warn("更新上传记录失败", zap.Error(err))
|
|
}
|
|
|
|
return &dto.UploadLicenseResponse{
|
|
UploadRecordID: uploadRecord.ID,
|
|
FileURL: uploadResult.URL,
|
|
OCRProcessed: uploadRecord.OCRProcessed,
|
|
OCRSuccess: uploadRecord.OCRSuccess,
|
|
EnterpriseInfo: enterpriseInfo,
|
|
OCRErrorMessage: ocrError,
|
|
}, nil
|
|
}
|
|
|
|
// SubmitEnterpriseInfo 提交企业信息
|
|
func (s *CertificationService) SubmitEnterpriseInfo(ctx context.Context, certificationID string, req *dto.SubmitEnterpriseInfoRequest) (*dto.SubmitEnterpriseInfoResponse, error) {
|
|
s.logger.Info("提交企业信息",
|
|
zap.String("certification_id", certificationID),
|
|
zap.String("company_name", req.CompanyName),
|
|
)
|
|
|
|
// 1. 获取认证记录
|
|
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("获取认证记录失败: %w", err)
|
|
}
|
|
|
|
// 2. 检查状态是否允许提交企业信息
|
|
if !cert.CanTransitionTo(enums.StatusInfoSubmitted) {
|
|
return nil, fmt.Errorf("当前状态不允许提交企业信息,当前状态: %s", enums.GetStatusName(cert.Status))
|
|
}
|
|
|
|
// 3. 检查统一社会信用代码是否已存在
|
|
exists, err := s.enterpriseRepo.ExistsByUnifiedSocialCode(ctx, req.UnifiedSocialCode)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("检查企业信息失败: %w", err)
|
|
}
|
|
if exists {
|
|
return nil, fmt.Errorf("该统一社会信用代码已被使用")
|
|
}
|
|
|
|
// 4. 创建企业信息
|
|
enterprise := &entities.Enterprise{
|
|
ID: uuid.New().String(),
|
|
CertificationID: certificationID,
|
|
CompanyName: req.CompanyName,
|
|
UnifiedSocialCode: req.UnifiedSocialCode,
|
|
LegalPersonName: req.LegalPersonName,
|
|
LegalPersonID: req.LegalPersonID,
|
|
LicenseUploadRecordID: req.LicenseUploadRecordID,
|
|
IsOCRVerified: false,
|
|
IsFaceVerified: false,
|
|
}
|
|
|
|
if err := s.enterpriseRepo.Create(ctx, enterprise); err != nil {
|
|
return nil, fmt.Errorf("创建企业信息失败: %w", err)
|
|
}
|
|
|
|
// 5. 更新认证记录状态
|
|
cert.EnterpriseID = &enterprise.ID
|
|
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusInfoSubmitted, true, false, nil); err != nil {
|
|
return nil, fmt.Errorf("更新认证状态失败: %w", err)
|
|
}
|
|
|
|
s.logger.Info("企业信息提交成功",
|
|
zap.String("certification_id", certificationID),
|
|
zap.String("enterprise_id", enterprise.ID),
|
|
)
|
|
|
|
return &dto.SubmitEnterpriseInfoResponse{
|
|
ID: certificationID,
|
|
Status: enums.StatusInfoSubmitted,
|
|
Enterprise: &dto.EnterpriseInfoResponse{
|
|
ID: enterprise.ID,
|
|
CertificationID: enterprise.CertificationID,
|
|
CompanyName: enterprise.CompanyName,
|
|
UnifiedSocialCode: enterprise.UnifiedSocialCode,
|
|
LegalPersonName: enterprise.LegalPersonName,
|
|
LegalPersonID: enterprise.LegalPersonID,
|
|
LicenseUploadRecordID: enterprise.LicenseUploadRecordID,
|
|
IsOCRVerified: enterprise.IsOCRVerified,
|
|
IsFaceVerified: enterprise.IsFaceVerified,
|
|
CreatedAt: enterprise.CreatedAt,
|
|
UpdatedAt: enterprise.UpdatedAt,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
// GetCertificationStatus 获取认证状态
|
|
func (s *CertificationService) GetCertificationStatus(ctx context.Context, userID string) (*dto.CertificationStatusResponse, error) {
|
|
s.logger.Info("获取认证状态", zap.String("user_id", userID))
|
|
|
|
// 获取用户的认证记录
|
|
cert, err := s.certRepo.GetByUserID(ctx, userID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("获取认证记录失败: %w", err)
|
|
}
|
|
|
|
// 获取企业信息
|
|
var enterprise *dto.EnterpriseInfoResponse
|
|
if cert.EnterpriseID != nil {
|
|
ent, err := s.enterpriseRepo.GetByID(ctx, *cert.EnterpriseID)
|
|
if err == nil {
|
|
enterprise = &dto.EnterpriseInfoResponse{
|
|
ID: ent.ID,
|
|
CertificationID: ent.CertificationID,
|
|
CompanyName: ent.CompanyName,
|
|
UnifiedSocialCode: ent.UnifiedSocialCode,
|
|
LegalPersonName: ent.LegalPersonName,
|
|
LegalPersonID: ent.LegalPersonID,
|
|
LicenseUploadRecordID: ent.LicenseUploadRecordID,
|
|
IsOCRVerified: ent.IsOCRVerified,
|
|
IsFaceVerified: ent.IsFaceVerified,
|
|
CreatedAt: ent.CreatedAt,
|
|
UpdatedAt: ent.UpdatedAt,
|
|
}
|
|
}
|
|
}
|
|
|
|
return &dto.CertificationStatusResponse{
|
|
ID: cert.ID,
|
|
UserID: cert.UserID,
|
|
Status: cert.Status,
|
|
StatusName: enums.GetStatusName(cert.Status),
|
|
Progress: cert.GetProgressPercentage(),
|
|
IsUserActionRequired: cert.IsUserActionRequired(),
|
|
IsAdminActionRequired: cert.IsAdminActionRequired(),
|
|
InfoSubmittedAt: cert.InfoSubmittedAt,
|
|
FaceVerifiedAt: cert.FaceVerifiedAt,
|
|
ContractAppliedAt: cert.ContractAppliedAt,
|
|
ContractApprovedAt: cert.ContractApprovedAt,
|
|
ContractSignedAt: cert.ContractSignedAt,
|
|
CompletedAt: cert.CompletedAt,
|
|
Enterprise: enterprise,
|
|
ContractURL: cert.ContractURL,
|
|
SigningURL: cert.SigningURL,
|
|
RejectReason: cert.RejectReason,
|
|
CreatedAt: cert.CreatedAt,
|
|
UpdatedAt: cert.UpdatedAt,
|
|
}, nil
|
|
}
|
|
|
|
// InitiateFaceVerify 初始化人脸识别
|
|
func (s *CertificationService) InitiateFaceVerify(ctx context.Context, certificationID string, req *dto.FaceVerifyRequest) (*dto.FaceVerifyResponse, error) {
|
|
s.logger.Info("初始化人脸识别",
|
|
zap.String("certification_id", certificationID),
|
|
zap.String("real_name", req.RealName),
|
|
)
|
|
|
|
// 1. 获取认证记录
|
|
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("获取认证记录失败: %w", err)
|
|
}
|
|
|
|
// 2. 检查状态
|
|
if cert.Status != enums.StatusInfoSubmitted && cert.Status != enums.StatusFaceFailed {
|
|
return nil, fmt.Errorf("当前状态不允许进行人脸识别,当前状态: %s", enums.GetStatusName(cert.Status))
|
|
}
|
|
|
|
// 3. 创建人脸识别记录
|
|
verifyRecord := &entities.FaceVerifyRecord{
|
|
ID: uuid.New().String(),
|
|
CertificationID: certificationID,
|
|
UserID: cert.UserID,
|
|
CertifyID: fmt.Sprintf("cert_%s_%d", certificationID, time.Now().Unix()),
|
|
RealName: req.RealName,
|
|
IDCardNumber: req.IDCardNumber,
|
|
ReturnURL: req.ReturnURL,
|
|
Status: "PROCESSING",
|
|
ExpiresAt: time.Now().Add(24 * time.Hour), // 24小时过期
|
|
}
|
|
|
|
// TODO: 实际调用阿里云人脸识别API
|
|
// 这里是模拟实现
|
|
verifyRecord.VerifyURL = fmt.Sprintf("https://face-verify.aliyun.com/verify?certifyId=%s", verifyRecord.CertifyID)
|
|
|
|
if err := s.faceVerifyRepo.Create(ctx, verifyRecord); err != nil {
|
|
return nil, fmt.Errorf("创建人脸识别记录失败: %w", err)
|
|
}
|
|
|
|
s.logger.Info("人脸识别初始化成功",
|
|
zap.String("certification_id", certificationID),
|
|
zap.String("certify_id", verifyRecord.CertifyID),
|
|
)
|
|
|
|
return &dto.FaceVerifyResponse{
|
|
CertifyID: verifyRecord.CertifyID,
|
|
VerifyURL: verifyRecord.VerifyURL,
|
|
ExpiresAt: verifyRecord.ExpiresAt,
|
|
}, nil
|
|
}
|
|
|
|
// ApplyContract 申请电子合同
|
|
func (s *CertificationService) ApplyContract(ctx context.Context, certificationID string) (*dto.ApplyContractResponse, error) {
|
|
s.logger.Info("申请电子合同", zap.String("certification_id", certificationID))
|
|
|
|
// 1. 获取认证记录
|
|
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("获取认证记录失败: %w", err)
|
|
}
|
|
|
|
// 2. 检查状态
|
|
if !cert.CanTransitionTo(enums.StatusContractApplied) {
|
|
return nil, fmt.Errorf("当前状态不允许申请合同,当前状态: %s", enums.GetStatusName(cert.Status))
|
|
}
|
|
|
|
// 3. 转换状态
|
|
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractApplied, true, false, nil); err != nil {
|
|
return nil, fmt.Errorf("更新认证状态失败: %w", err)
|
|
}
|
|
|
|
// 4. 自动转换到待审核状态
|
|
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractPending, false, false, nil); err != nil {
|
|
s.logger.Warn("自动转换到待审核状态失败", zap.Error(err))
|
|
}
|
|
|
|
// 5. 创建合同记录
|
|
contractRecord := &entities.ContractRecord{
|
|
ID: uuid.New().String(),
|
|
CertificationID: certificationID,
|
|
UserID: cert.UserID,
|
|
ContractType: "ENTERPRISE_CERTIFICATION",
|
|
Status: "PENDING",
|
|
}
|
|
|
|
if err := s.contractRepo.Create(ctx, contractRecord); err != nil {
|
|
s.logger.Warn("创建合同记录失败", zap.Error(err))
|
|
}
|
|
|
|
// TODO: 发送通知给管理员
|
|
|
|
s.logger.Info("合同申请成功", zap.String("certification_id", certificationID))
|
|
|
|
return &dto.ApplyContractResponse{
|
|
ID: certificationID,
|
|
Status: enums.StatusContractPending,
|
|
ContractAppliedAt: time.Now(),
|
|
}, nil
|
|
}
|
|
|
|
// serializeOCRResult 序列化OCR结果
|
|
func (s *CertificationService) serializeOCRResult(result *dto.OCREnterpriseInfo) (string, error) {
|
|
// 简单的JSON序列化
|
|
return fmt.Sprintf(`{"company_name":"%s","unified_social_code":"%s","legal_person_name":"%s","legal_person_id":"%s","confidence":%f}`,
|
|
result.CompanyName, result.UnifiedSocialCode, result.LegalPersonName, result.LegalPersonID, result.Confidence), nil
|
|
}
|