基础架构
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package certification
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tyapi-server/internal/application/certification/dto/commands"
|
||||
"tyapi-server/internal/application/certification/dto/queries"
|
||||
"tyapi-server/internal/application/certification/dto/responses"
|
||||
)
|
||||
|
||||
// CertificationApplicationService 认证应用服务接口
|
||||
type CertificationApplicationService interface {
|
||||
// 认证申请管理
|
||||
CreateCertification(ctx context.Context, cmd *commands.CreateCertificationCommand) (*responses.CertificationResponse, error)
|
||||
GetCertificationStatus(ctx context.Context, query *queries.GetCertificationStatusQuery) (*responses.CertificationResponse, error)
|
||||
GetCertificationDetails(ctx context.Context, query *queries.GetCertificationDetailsQuery) (*responses.CertificationResponse, error)
|
||||
GetCertificationProgress(ctx context.Context, userID string) (map[string]interface{}, error)
|
||||
|
||||
// 企业信息管理
|
||||
CreateEnterpriseInfo(ctx context.Context, cmd *commands.CreateEnterpriseInfoCommand) (*responses.EnterpriseInfoResponse, error)
|
||||
SubmitEnterpriseInfo(ctx context.Context, cmd *commands.SubmitEnterpriseInfoCommand) (*responses.EnterpriseInfoResponse, error)
|
||||
|
||||
// 营业执照上传
|
||||
UploadLicense(ctx context.Context, cmd *commands.UploadLicenseCommand) (*responses.UploadLicenseResponse, error)
|
||||
GetLicenseOCRResult(ctx context.Context, recordID string) (*responses.UploadLicenseResponse, error)
|
||||
|
||||
// UploadBusinessLicense 上传营业执照并同步OCR识别
|
||||
UploadBusinessLicense(ctx context.Context, userID string, fileBytes []byte, fileName string) (*responses.UploadLicenseResponse, error)
|
||||
|
||||
// 人脸识别
|
||||
InitiateFaceVerify(ctx context.Context, cmd *commands.InitiateFaceVerifyCommand) (*responses.FaceVerifyResponse, error)
|
||||
CompleteFaceVerify(ctx context.Context, faceVerifyID string, isSuccess bool) error
|
||||
RetryFaceVerify(ctx context.Context, userID string) (*responses.FaceVerifyResponse, error)
|
||||
|
||||
// 合同管理
|
||||
ApplyContract(ctx context.Context, userID string) (*responses.CertificationResponse, error)
|
||||
ApproveContract(ctx context.Context, certificationID, adminID, signingURL, approvalNotes string) error
|
||||
RejectContract(ctx context.Context, certificationID, adminID, rejectReason string) error
|
||||
CompleteContractSign(ctx context.Context, certificationID, contractURL string) error
|
||||
RetryContractSign(ctx context.Context, userID string) (*responses.CertificationResponse, error)
|
||||
|
||||
// 认证完成
|
||||
CompleteCertification(ctx context.Context, certificationID string) error
|
||||
|
||||
// 重试和重启
|
||||
RetryStep(ctx context.Context, cmd *commands.RetryStepCommand) error
|
||||
RestartCertification(ctx context.Context, certificationID string) error
|
||||
}
|
||||
@@ -0,0 +1,608 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package commands
|
||||
|
||||
// CreateCertificationCommand 创建认证申请命令
|
||||
// 用于用户发起企业认证流程的初始请求
|
||||
type CreateCertificationCommand struct {
|
||||
UserID string `json:"user_id" binding:"required" comment:"用户唯一标识,从JWT token获取"`
|
||||
}
|
||||
|
||||
// UploadLicenseCommand 上传营业执照命令
|
||||
// 用于处理营业执照文件上传的业务逻辑
|
||||
type UploadLicenseCommand struct {
|
||||
UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"`
|
||||
FileBytes []byte `json:"-" comment:"营业执照文件的二进制内容,从multipart/form-data获取"`
|
||||
FileName string `json:"-" comment:"营业执照文件的原始文件名,从multipart/form-data获取"`
|
||||
FileSize int64 `json:"-" comment:"营业执照文件的大小(字节),从multipart/form-data获取"`
|
||||
}
|
||||
|
||||
// SubmitEnterpriseInfoCommand 提交企业信息命令
|
||||
// 用于用户提交企业四要素信息,完成企业信息验证
|
||||
type SubmitEnterpriseInfoCommand struct {
|
||||
UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"`
|
||||
CertificationID string `json:"-" comment:"认证申请唯一标识,从URL路径获取,不在JSON中暴露"`
|
||||
CompanyName string `json:"company_name" binding:"required" comment:"企业名称,如:北京科技有限公司"`
|
||||
UnifiedSocialCode string `json:"unified_social_code" binding:"required" comment:"统一社会信用代码,18位企业唯一标识,如:91110000123456789X"`
|
||||
LegalPersonName string `json:"legal_person_name" binding:"required" comment:"法定代表人姓名,如:张三"`
|
||||
LegalPersonID string `json:"legal_person_id" binding:"required" comment:"法定代表人身份证号码,18位,如:110101199001011234"`
|
||||
LicenseUploadRecordID string `json:"license_upload_record_id" binding:"required" comment:"营业执照上传记录唯一标识,关联已上传的营业执照文件"`
|
||||
}
|
||||
|
||||
// InitiateFaceVerifyCommand 初始化人脸识别命令
|
||||
// 用于发起人脸识别验证流程,验证法定代表人身份
|
||||
type InitiateFaceVerifyCommand struct {
|
||||
CertificationID string `json:"-" comment:"认证申请唯一标识,从URL路径获取,不在JSON中暴露"`
|
||||
RealName string `json:"real_name" binding:"required" comment:"真实姓名,必须与营业执照上的法定代表人姓名一致"`
|
||||
IDCardNumber string `json:"id_card_number" binding:"required" comment:"身份证号码,18位,用于人脸识别身份验证"`
|
||||
ReturnURL string `json:"return_url" binding:"required" comment:"人脸识别完成后的回调地址,用于跳转回应用"`
|
||||
}
|
||||
|
||||
// ApplyContractCommand 申请合同命令
|
||||
// 用于用户申请电子合同,进入合同签署流程
|
||||
type ApplyContractCommand struct {
|
||||
CertificationID string `json:"-" comment:"认证申请唯一标识,从URL路径获取,不在JSON中暴露"`
|
||||
}
|
||||
|
||||
// RetryStepCommand 重试认证步骤命令
|
||||
// 用于用户重试失败的认证步骤,如人脸识别失败后的重试
|
||||
type RetryStepCommand struct {
|
||||
UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"`
|
||||
CertificationID string `json:"-" comment:"认证申请唯一标识,从URL路径获取,不在JSON中暴露"`
|
||||
Step string `json:"step" binding:"required" comment:"重试的步骤名称,如:face_verify(人脸识别)、contract_sign(合同签署)"`
|
||||
}
|
||||
|
||||
// CreateEnterpriseInfoCommand 创建企业信息命令
|
||||
// 用于创建企业基本信息,通常在企业认证流程中使用
|
||||
// @Description 创建企业信息请求参数
|
||||
type CreateEnterpriseInfoCommand struct {
|
||||
UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"`
|
||||
CompanyName string `json:"company_name" binding:"required" example:"示例企业有限公司" comment:"企业名称,如:示例企业有限公司"`
|
||||
UnifiedSocialCode string `json:"unified_social_code" binding:"required" example:"91110000123456789X" comment:"统一社会信用代码,18位企业唯一标识,如:91110000123456789X"`
|
||||
LegalPersonName string `json:"legal_person_name" binding:"required" example:"张三" comment:"法定代表人姓名,如:张三"`
|
||||
LegalPersonID string `json:"legal_person_id" binding:"required" example:"110101199001011234" comment:"法定代表人身份证号码,18位,如:110101199001011234"`
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package queries
|
||||
|
||||
// GetCertificationStatusQuery 获取认证状态查询
|
||||
// 用于查询用户当前认证申请的进度状态
|
||||
type GetCertificationStatusQuery struct {
|
||||
UserID string `json:"user_id" binding:"required" comment:"用户唯一标识,用于查询该用户的认证申请状态"`
|
||||
}
|
||||
|
||||
// GetCertificationDetailsQuery 获取认证详情查询
|
||||
// 用于查询用户认证申请的详细信息,包括所有相关记录
|
||||
type GetCertificationDetailsQuery struct {
|
||||
UserID string `json:"user_id" binding:"required" comment:"用户唯一标识,用于查询该用户的认证申请详细信息"`
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/certification/enums"
|
||||
)
|
||||
|
||||
// CertificationResponse 认证响应
|
||||
type CertificationResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
StatusName string `json:"status_name"`
|
||||
Progress int `json:"progress"`
|
||||
IsUserActionRequired bool `json:"is_user_action_required"`
|
||||
IsAdminActionRequired bool `json:"is_admin_action_required"`
|
||||
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty"`
|
||||
FaceVerifiedAt *time.Time `json:"face_verified_at,omitempty"`
|
||||
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty"`
|
||||
ContractApprovedAt *time.Time `json:"contract_approved_at,omitempty"`
|
||||
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||
Enterprise *EnterpriseInfoResponse `json:"enterprise,omitempty"`
|
||||
ContractURL string `json:"contract_url,omitempty"`
|
||||
SigningURL string `json:"signing_url,omitempty"`
|
||||
RejectReason string `json:"reject_reason,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// EnterpriseInfoResponse 企业信息响应
|
||||
type EnterpriseInfoResponse struct {
|
||||
ID string `json:"id"`
|
||||
CertificationID string `json:"certification_id"`
|
||||
CompanyName string `json:"company_name"`
|
||||
UnifiedSocialCode string `json:"unified_social_code"`
|
||||
LegalPersonName string `json:"legal_person_name"`
|
||||
LegalPersonID string `json:"legal_person_id"`
|
||||
LicenseUploadRecordID string `json:"license_upload_record_id"`
|
||||
IsOCRVerified bool `json:"is_ocr_verified"`
|
||||
IsFaceVerified bool `json:"is_face_verified"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// UploadLicenseResponse 上传营业执照响应
|
||||
type UploadLicenseResponse struct {
|
||||
UploadRecordID string `json:"upload_record_id"`
|
||||
FileURL string `json:"file_url"`
|
||||
OCRProcessed bool `json:"ocr_processed"`
|
||||
OCRSuccess bool `json:"ocr_success"`
|
||||
// OCR识别结果(如果成功)
|
||||
EnterpriseName string `json:"enterprise_name,omitempty"`
|
||||
CreditCode string `json:"credit_code,omitempty"`
|
||||
LegalPerson string `json:"legal_person,omitempty"`
|
||||
OCRConfidence float64 `json:"ocr_confidence,omitempty"`
|
||||
OCRErrorMessage string `json:"ocr_error_message,omitempty"`
|
||||
}
|
||||
|
||||
// FaceVerifyResponse 人脸识别响应
|
||||
type FaceVerifyResponse struct {
|
||||
CertifyID string `json:"certify_id"`
|
||||
VerifyURL string `json:"verify_url"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package responses
|
||||
|
||||
import "time"
|
||||
|
||||
// BusinessLicenseResult 营业执照识别结果
|
||||
type BusinessLicenseResult struct {
|
||||
CompanyName string `json:"company_name"` // 企业名称
|
||||
UnifiedSocialCode string `json:"unified_social_code"` // 统一社会信用代码
|
||||
LegalPersonName string `json:"legal_person_name"` // 法定代表人姓名
|
||||
LegalPersonID string `json:"legal_person_id"` // 法定代表人身份证号
|
||||
RegisteredCapital string `json:"registered_capital"` // 注册资本
|
||||
BusinessScope string `json:"business_scope"` // 经营范围
|
||||
Address string `json:"address"` // 企业地址
|
||||
IssueDate string `json:"issue_date"` // 发证日期
|
||||
ValidPeriod string `json:"valid_period"` // 有效期
|
||||
Confidence float64 `json:"confidence"` // 识别置信度
|
||||
ProcessedAt time.Time `json:"processed_at"` // 处理时间
|
||||
}
|
||||
|
||||
// IDCardResult 身份证识别结果
|
||||
type IDCardResult struct {
|
||||
Name string `json:"name"` // 姓名
|
||||
IDCardNumber string `json:"id_card_number"` // 身份证号
|
||||
Gender string `json:"gender"` // 性别
|
||||
Nation string `json:"nation"` // 民族
|
||||
Birthday string `json:"birthday"` // 出生日期
|
||||
Address string `json:"address"` // 住址
|
||||
IssuingAgency string `json:"issuing_agency"` // 签发机关
|
||||
ValidPeriod string `json:"valid_period"` // 有效期限
|
||||
Side string `json:"side"` // 身份证面(front/back)
|
||||
Confidence float64 `json:"confidence"` // 识别置信度
|
||||
ProcessedAt time.Time `json:"processed_at"` // 处理时间
|
||||
}
|
||||
|
||||
// GeneralTextResult 通用文字识别结果
|
||||
type GeneralTextResult struct {
|
||||
Words []TextLine `json:"words"` // 识别的文字行
|
||||
Confidence float64 `json:"confidence"` // 整体置信度
|
||||
ProcessedAt time.Time `json:"processed_at"` // 处理时间
|
||||
}
|
||||
|
||||
// TextLine 文字行
|
||||
type TextLine struct {
|
||||
Text string `json:"text"` // 文字内容
|
||||
Confidence float64 `json:"confidence"` // 置信度
|
||||
Position Position `json:"position"` // 位置信息
|
||||
}
|
||||
|
||||
// Position 位置信息
|
||||
type Position struct {
|
||||
X int `json:"x"` // X坐标
|
||||
Y int `json:"y"` // Y坐标
|
||||
Width int `json:"width"` // 宽度
|
||||
Height int `json:"height"` // 高度
|
||||
}
|
||||
Reference in New Issue
Block a user