This commit is contained in:
2025-07-20 20:53:26 +08:00
parent 83bf9aea7d
commit 8ad1d7288e
158 changed files with 18156 additions and 13188 deletions

View File

@@ -1,110 +0,0 @@
package dto
import (
"time"
"tyapi-server/internal/domains/certification/enums"
)
// CertificationCreateRequest 创建认证申请请求
type CertificationCreateRequest struct {
UserID string `json:"user_id" binding:"required"`
}
// CertificationCreateResponse 创建认证申请响应
type CertificationCreateResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Status enums.CertificationStatus `json:"status"`
}
// CertificationStatusResponse 认证状态响应
type CertificationStatusResponse 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"`
}
// SubmitEnterpriseInfoRequest 提交企业信息请求
type SubmitEnterpriseInfoRequest struct {
CompanyName string `json:"company_name" binding:"required"`
UnifiedSocialCode string `json:"unified_social_code" binding:"required"`
LegalPersonName string `json:"legal_person_name" binding:"required"`
LegalPersonID string `json:"legal_person_id" binding:"required"`
LicenseUploadRecordID string `json:"license_upload_record_id" binding:"required"`
}
// SubmitEnterpriseInfoResponse 提交企业信息响应
type SubmitEnterpriseInfoResponse struct {
ID string `json:"id"`
Status enums.CertificationStatus `json:"status"`
Enterprise *EnterpriseInfoResponse `json:"enterprise"`
}
// FaceVerifyRequest 人脸识别请求
type FaceVerifyRequest struct {
RealName string `json:"real_name" binding:"required"`
IDCardNumber string `json:"id_card_number" binding:"required"`
ReturnURL string `json:"return_url" binding:"required"`
}
// FaceVerifyResponse 人脸识别响应
type FaceVerifyResponse struct {
CertifyID string `json:"certify_id"`
VerifyURL string `json:"verify_url"`
ExpiresAt time.Time `json:"expires_at"`
}
// ApplyContractRequest 申请合同请求(无需额外参数)
type ApplyContractRequest struct{}
// ApplyContractResponse 申请合同响应
type ApplyContractResponse struct {
ID string `json:"id"`
Status enums.CertificationStatus `json:"status"`
ContractAppliedAt time.Time `json:"contract_applied_at"`
}
// SignContractRequest 签署合同请求
type SignContractRequest struct {
SignatureData string `json:"signature_data,omitempty"`
}
// SignContractResponse 签署合同响应
type SignContractResponse struct {
ID string `json:"id"`
Status enums.CertificationStatus `json:"status"`
ContractSignedAt time.Time `json:"contract_signed_at"`
}
// CertificationDetailResponse 认证详情响应
type CertificationDetailResponse struct {
*CertificationStatusResponse
// 详细记录
LicenseUploadRecord *LicenseUploadRecordResponse `json:"license_upload_record,omitempty"`
FaceVerifyRecords []FaceVerifyRecordResponse `json:"face_verify_records,omitempty"`
ContractRecords []ContractRecordResponse `json:"contract_records,omitempty"`
NotificationRecords []NotificationRecordResponse `json:"notification_records,omitempty"`
}

View File

@@ -1,108 +0,0 @@
package dto
import "time"
// 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"`
OCRRawData string `json:"ocr_raw_data,omitempty"`
OCRConfidence float64 `json:"ocr_confidence,omitempty"`
IsOCRVerified bool `json:"is_ocr_verified"`
IsFaceVerified bool `json:"is_face_verified"`
VerificationData string `json:"verification_data,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// LicenseUploadRecordResponse 营业执照上传记录响应
type LicenseUploadRecordResponse struct {
ID string `json:"id"`
CertificationID *string `json:"certification_id,omitempty"`
UserID string `json:"user_id"`
OriginalFileName string `json:"original_file_name"`
FileSize int64 `json:"file_size"`
FileType string `json:"file_type"`
FileURL string `json:"file_url"`
QiNiuKey string `json:"qiniu_key"`
OCRProcessed bool `json:"ocr_processed"`
OCRSuccess bool `json:"ocr_success"`
OCRConfidence float64 `json:"ocr_confidence,omitempty"`
OCRRawData string `json:"ocr_raw_data,omitempty"`
OCRErrorMessage string `json:"ocr_error_message,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// FaceVerifyRecordResponse 人脸识别记录响应
type FaceVerifyRecordResponse struct {
ID string `json:"id"`
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
CertifyID string `json:"certify_id"`
VerifyURL string `json:"verify_url,omitempty"`
ReturnURL string `json:"return_url,omitempty"`
RealName string `json:"real_name"`
IDCardNumber string `json:"id_card_number"`
Status string `json:"status"`
StatusName string `json:"status_name"`
ResultCode string `json:"result_code,omitempty"`
ResultMessage string `json:"result_message,omitempty"`
VerifyScore float64 `json:"verify_score,omitempty"`
InitiatedAt time.Time `json:"initiated_at"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
ExpiresAt time.Time `json:"expires_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// ContractRecordResponse 合同记录响应
type ContractRecordResponse struct {
ID string `json:"id"`
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
AdminID *string `json:"admin_id,omitempty"`
ContractType string `json:"contract_type"`
ContractURL string `json:"contract_url,omitempty"`
SigningURL string `json:"signing_url,omitempty"`
SignatureData string `json:"signature_data,omitempty"`
SignedAt *time.Time `json:"signed_at,omitempty"`
ClientIP string `json:"client_ip,omitempty"`
UserAgent string `json:"user_agent,omitempty"`
Status string `json:"status"`
StatusName string `json:"status_name"`
ApprovalNotes string `json:"approval_notes,omitempty"`
RejectReason string `json:"reject_reason,omitempty"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// NotificationRecordResponse 通知记录响应
type NotificationRecordResponse struct {
ID string `json:"id"`
CertificationID *string `json:"certification_id,omitempty"`
UserID *string `json:"user_id,omitempty"`
NotificationType string `json:"notification_type"`
NotificationTypeName string `json:"notification_type_name"`
NotificationScene string `json:"notification_scene"`
NotificationSceneName string `json:"notification_scene_name"`
Recipient string `json:"recipient"`
Title string `json:"title,omitempty"`
Content string `json:"content"`
TemplateID string `json:"template_id,omitempty"`
TemplateParams string `json:"template_params,omitempty"`
Status string `json:"status"`
StatusName string `json:"status_name"`
ErrorMessage string `json:"error_message,omitempty"`
SentAt *time.Time `json:"sent_at,omitempty"`
RetryCount int `json:"retry_count"`
MaxRetryCount int `json:"max_retry_count"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

View File

@@ -1,77 +0,0 @@
package dto
// BusinessLicenseResult 营业执照识别结果
type BusinessLicenseResult struct {
CompanyName string `json:"company_name"` // 公司名称
LegalRepresentative string `json:"legal_representative"` // 法定代表人
RegisteredCapital string `json:"registered_capital"` // 注册资本
RegisteredAddress string `json:"registered_address"` // 注册地址
RegistrationNumber string `json:"registration_number"` // 统一社会信用代码
BusinessScope string `json:"business_scope"` // 经营范围
RegistrationDate string `json:"registration_date"` // 成立日期
ValidDate string `json:"valid_date"` // 营业期限
Confidence float64 `json:"confidence"` // 识别置信度
Words []string `json:"words"` // 识别的所有文字
}
// IDCardResult 身份证识别结果
type IDCardResult struct {
Side string `json:"side"` // 身份证面front/back
Name string `json:"name"` // 姓名(正面)
Sex string `json:"sex"` // 性别(正面)
Nation string `json:"nation"` // 民族(正面)
BirthDate string `json:"birth_date"` // 出生日期(正面)
Address string `json:"address"` // 住址(正面)
IDNumber string `json:"id_number"` // 身份证号码(正面)
IssuingAuthority string `json:"issuing_authority"` // 签发机关(背面)
ValidDate string `json:"valid_date"` // 有效期限(背面)
Confidence float64 `json:"confidence"` // 识别置信度
Words []string `json:"words"` // 识别的所有文字
}
// GeneralTextResult 通用文字识别结果
type GeneralTextResult struct {
Words []string `json:"words"` // 识别的文字列表
Confidence float64 `json:"confidence"` // 识别置信度
}
// OCREnterpriseInfo OCR识别的企业信息
type OCREnterpriseInfo struct {
CompanyName string `json:"company_name"` // 企业名称
UnifiedSocialCode string `json:"unified_social_code"` // 统一社会信用代码
LegalPersonName string `json:"legal_person_name"` // 法人姓名
LegalPersonID string `json:"legal_person_id"` // 法人身份证号
Confidence float64 `json:"confidence"` // 识别置信度
}
// LicenseProcessResult 营业执照处理结果
type LicenseProcessResult struct {
LicenseURL string `json:"license_url"` // 营业执照文件URL
EnterpriseInfo *OCREnterpriseInfo `json:"enterprise_info"` // OCR识别的企业信息
OCRSuccess bool `json:"ocr_success"` // OCR是否成功
OCRError string `json:"ocr_error,omitempty"` // OCR错误信息
}
// UploadLicenseRequest 上传营业执照请求
type UploadLicenseRequest struct {
// 文件通过multipart/form-data上传这里定义验证规则
}
// UploadLicenseResponse 上传营业执照响应
type UploadLicenseResponse struct {
UploadRecordID string `json:"upload_record_id"` // 上传记录ID
FileURL string `json:"file_url"` // 文件URL
OCRProcessed bool `json:"ocr_processed"` // OCR是否已处理
OCRSuccess bool `json:"ocr_success"` // OCR是否成功
EnterpriseInfo *OCREnterpriseInfo `json:"enterprise_info"` // OCR识别的企业信息如果成功
OCRErrorMessage string `json:"ocr_error_message,omitempty"` // OCR错误信息如果失败
}
// UploadResult 上传结果
type UploadResult struct {
Key string `json:"key"` // 文件key
URL string `json:"url"` // 文件访问URL
MimeType string `json:"mime_type"` // MIME类型
Size int64 `json:"size"` // 文件大小
Hash string `json:"hash"` // 文件哈希值
}

View File

@@ -12,7 +12,6 @@ import (
// Certification 认证申请实体
// 这是企业认证流程的核心实体,负责管理整个认证申请的生命周期
// 包含认证状态、时间节点、审核信息、合同信息等核心数据
type Certification struct {
// 基础信息
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"认证申请唯一标识"`
@@ -20,36 +19,25 @@ type Certification struct {
Status enums.CertificationStatus `gorm:"type:varchar(50);not null;index" json:"status" comment:"当前认证状态"`
// 流程节点时间戳 - 记录每个关键步骤的完成时间
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
FaceVerifiedAt *time.Time `json:"face_verified_at,omitempty" comment:"人脸识别完成时间"`
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty" comment:"合同申请时间"`
ContractApprovedAt *time.Time `json:"contract_approved_at,omitempty" comment:"合同审核通过时间"`
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty" comment:"合同签署完成时间"`
CompletedAt *time.Time `json:"completed_at,omitempty" comment:"认证完成时间"`
// 审核信息 - 管理员审核相关数据
AdminID *string `gorm:"type:varchar(36)" json:"admin_id,omitempty" comment:"审核管理员ID"`
ApprovalNotes string `gorm:"type:text" json:"approval_notes,omitempty" comment:"审核备注信息"`
RejectReason string `gorm:"type:text" json:"reject_reason,omitempty" comment:"拒绝原因说明"`
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty" comment:"企业认证完成时间"`
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty" comment:"合同申请时间"`
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty" comment:"合同签署完成时间"`
CompletedAt *time.Time `json:"completed_at,omitempty" comment:"认证完成时间"`
// 合同信息 - 电子合同相关链接
ContractURL string `gorm:"type:varchar(500)" json:"contract_url,omitempty" comment:"合同文件访问链接"`
SigningURL string `gorm:"type:varchar(500)" json:"signing_url,omitempty" comment:"电子签署链接"`
ContractFileID string `gorm:"type:varchar(500)" json:"contract_file_id,omitempty" comment:"合同文件ID"`
EsignFlowID string `gorm:"type:varchar(500)" json:"esign_flow_id,omitempty" comment:"签署流程ID"`
ContractURL string `gorm:"type:varchar(500)" json:"contract_url,omitempty" comment:"合同文件访问链接"`
ContractSignURL string `gorm:"type:varchar(500)" json:"contract_sign_url,omitempty" comment:"合同签署链接"`
// OCR识别信息 - 营业执照OCR识别结果
OCRRequestID string `gorm:"type:varchar(100)" json:"ocr_request_id,omitempty" comment:"OCR识别请求ID"`
OCRConfidence float64 `gorm:"type:decimal(5,2)" json:"ocr_confidence,omitempty" comment:"OCR识别置信度(0-1)"`
// 认证信息
AuthFlowID string `gorm:"type:varchar(500)" json:"auth_flow_id,omitempty" comment:"认证流程ID"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
// 关联关系 - 与其他实体的关联
LicenseUploadRecord *LicenseUploadRecord `gorm:"foreignKey:CertificationID" json:"license_upload_record,omitempty" comment:"关联的营业执照上传记录"`
FaceVerifyRecords []FaceVerifyRecord `gorm:"foreignKey:CertificationID" json:"face_verify_records,omitempty" comment:"关联的人脸识别记录列表"`
ContractRecords []ContractRecord `gorm:"foreignKey:CertificationID" json:"contract_records,omitempty" comment:"关联的合同记录列表"`
NotificationRecords []NotificationRecord `gorm:"foreignKey:CertificationID" json:"notification_records,omitempty" comment:"关联的通知记录列表"`
}
// TableName 指定数据库表名
@@ -66,122 +54,94 @@ func (c *Certification) BeforeCreate(tx *gorm.DB) error {
}
// IsStatusChangeable 检查状态是否可以变更
// 只有非最终状态(完成/拒绝)的认证申请才能进行状态变更
func (c *Certification) IsStatusChangeable() bool {
return !enums.IsFinalStatus(c.Status)
}
// CanRetryFaceVerify 检查是否可以重试人脸识别
// 只有人脸识别失败状态的申请才能重试
func (c *Certification) CanRetryFaceVerify() bool {
return c.Status == enums.StatusFaceFailed
// GetStatusName 获取状态名称
func (c *Certification) GetStatusName() string {
return enums.GetStatusName(c.Status)
}
// CanRetrySign 检查是否可以重试签署
// 只有签署失败状态的申请才能重试
func (c *Certification) CanRetrySign() bool {
return c.Status == enums.StatusSignFailed
// IsFinalStatus 判断是否为最终状态
func (c *Certification) IsFinalStatus() bool {
return enums.IsFinalStatus(c.Status)
}
// CanRestart 检查是否可以重新开始流程
// 只有被拒绝的申请才能重新开始认证流程
func (c *Certification) CanRestart() bool {
return c.Status == enums.StatusRejected
// GetStatusCategory 获取状态分类
func (c *Certification) GetStatusCategory() string {
return enums.GetStatusCategory(c.Status)
}
// GetNextValidStatuses 获取当前状态可以转换到的下一个状态列表
// 根据状态机规则,返回所有合法的下一个状态
func (c *Certification) GetNextValidStatuses() []enums.CertificationStatus {
switch c.Status {
case enums.StatusPending:
return []enums.CertificationStatus{enums.StatusInfoSubmitted}
case enums.StatusInfoSubmitted:
return []enums.CertificationStatus{enums.StatusFaceVerified, enums.StatusFaceFailed}
case enums.StatusFaceVerified:
return []enums.CertificationStatus{enums.StatusContractApplied}
case enums.StatusContractApplied:
return []enums.CertificationStatus{enums.StatusContractPending}
case enums.StatusContractPending:
return []enums.CertificationStatus{enums.StatusContractApproved, enums.StatusRejected}
case enums.StatusContractApproved:
return []enums.CertificationStatus{enums.StatusContractSigned, enums.StatusSignFailed}
case enums.StatusContractSigned:
return []enums.CertificationStatus{enums.StatusCompleted}
case enums.StatusFaceFailed:
return []enums.CertificationStatus{enums.StatusFaceVerified}
case enums.StatusSignFailed:
return []enums.CertificationStatus{enums.StatusContractSigned}
case enums.StatusRejected:
return []enums.CertificationStatus{enums.StatusInfoSubmitted}
default:
return []enums.CertificationStatus{}
}
// GetStatusPriority 获取状态优先级
func (c *Certification) GetStatusPriority() int {
return enums.GetStatusPriority(c.Status)
}
// CanTransitionTo 检查是否可以转换到指定状态
// 验证状态转换的合法性,确保状态机规则得到遵守
func (c *Certification) CanTransitionTo(targetStatus enums.CertificationStatus) bool {
validStatuses := c.GetNextValidStatuses()
for _, status := range validStatuses {
if status == targetStatus {
return true
}
}
return false
}
// GetProgressPercentage 获取认证进度百分比
// 根据当前状态计算认证流程的完成进度,用于前端进度条显示
// GetProgressPercentage 获取进度百分比
func (c *Certification) GetProgressPercentage() int {
switch c.Status {
case enums.StatusPending:
return 0
case enums.StatusInfoSubmitted:
return 12
case enums.StatusFaceVerified:
return 25
case enums.StatusContractApplied:
return 37
case enums.StatusContractPending:
return 50
case enums.StatusContractApproved:
return 75
case enums.StatusContractSigned:
return 87
case enums.StatusCompleted:
return 100
case enums.StatusFaceFailed, enums.StatusSignFailed:
return c.GetProgressPercentage() // 失败状态保持原进度
case enums.StatusRejected:
return 0
default:
return 0
progressMap := map[enums.CertificationStatus]int{
enums.StatusPending: 0,
enums.StatusInfoSubmitted: 20,
enums.StatusEnterpriseVerified: 40,
enums.StatusContractApplied: 60,
enums.StatusContractSigned: 80,
enums.StatusCompleted: 100,
}
if progress, exists := progressMap[c.Status]; exists {
return progress
}
return 0
}
// IsUserActionRequired 检查是否需要用户操作
// 判断当前状态是否需要用户进行下一步操作,用于前端提示
func (c *Certification) IsUserActionRequired() bool {
userActionStatuses := []enums.CertificationStatus{
enums.StatusPending,
enums.StatusInfoSubmitted,
enums.StatusFaceVerified,
enums.StatusContractApproved,
enums.StatusFaceFailed,
enums.StatusSignFailed,
enums.StatusRejected,
userActionRequired := map[enums.CertificationStatus]bool{
enums.StatusPending: true,
enums.StatusInfoSubmitted: true,
enums.StatusEnterpriseVerified: true,
enums.StatusContractApplied: true,
enums.StatusContractSigned: false,
enums.StatusCompleted: false,
}
for _, status := range userActionStatuses {
if c.Status == status {
return true
}
if required, exists := userActionRequired[c.Status]; exists {
return required
}
return false
}
// IsAdminActionRequired 检查是否需要管理员操作
// 判断当前状态是否需要管理员审核,用于后台管理界面
func (c *Certification) IsAdminActionRequired() bool {
return c.Status == enums.StatusContractPending
// GetNextValidStatuses 获取下一个有效状态
func (c *Certification) GetNextValidStatuses() []enums.CertificationStatus {
nextStatusMap := map[enums.CertificationStatus][]enums.CertificationStatus{
enums.StatusPending: {enums.StatusInfoSubmitted},
enums.StatusInfoSubmitted: {enums.StatusEnterpriseVerified, enums.StatusInfoSubmitted}, // 可以重新提交
enums.StatusEnterpriseVerified: {enums.StatusContractApplied},
enums.StatusContractApplied: {enums.StatusContractSigned},
enums.StatusContractSigned: {enums.StatusCompleted},
enums.StatusCompleted: {},
}
if nextStatuses, exists := nextStatusMap[c.Status]; exists {
return nextStatuses
}
return []enums.CertificationStatus{}
}
// CanTransitionTo 检查是否可以转换到指定状态
func (c *Certification) CanTransitionTo(targetStatus enums.CertificationStatus, isUser bool) (bool, string) {
nextStatuses := c.GetNextValidStatuses()
for _, nextStatus := range nextStatuses {
if nextStatus == targetStatus {
// 检查权限
if isUser && !c.IsUserActionRequired() {
return false, "当前状态不需要用户操作"
}
return true, ""
}
}
return false, "不支持的状态转换"
}

View File

@@ -1,107 +0,0 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// ContractRecord 合同记录实体
// 记录电子合同的详细信息,包括合同生成、审核、签署的完整流程
// 支持合同状态跟踪、签署信息记录、审核流程管理等功能
type ContractRecord struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"合同记录唯一标识"`
CertificationID string `gorm:"type:varchar(36);not null;index" json:"certification_id" comment:"关联的认证申请ID"`
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"合同申请人ID"`
AdminID *string `gorm:"type:varchar(36);index" json:"admin_id,omitempty" comment:"审核管理员ID"`
// 合同信息 - 电子合同的基本信息
ContractType string `gorm:"type:varchar(50);not null" json:"contract_type" comment:"合同类型(ENTERPRISE_CERTIFICATION)"`
ContractURL string `gorm:"type:varchar(500)" json:"contract_url,omitempty" comment:"合同文件访问链接"`
SigningURL string `gorm:"type:varchar(500)" json:"signing_url,omitempty" comment:"电子签署链接"`
// 签署信息 - 记录用户签署的详细信息
SignatureData string `gorm:"type:text" json:"signature_data,omitempty" comment:"签署数据(JSON格式)"`
SignedAt *time.Time `json:"signed_at,omitempty" comment:"签署完成时间"`
ClientIP string `gorm:"type:varchar(50)" json:"client_ip,omitempty" comment:"签署客户端IP"`
UserAgent string `gorm:"type:varchar(500)" json:"user_agent,omitempty" comment:"签署客户端信息"`
// 状态信息 - 合同的生命周期状态
Status string `gorm:"type:varchar(50);not null;index" json:"status" comment:"合同状态(PENDING/APPROVED/SIGNED/EXPIRED)"`
ApprovalNotes string `gorm:"type:text" json:"approval_notes,omitempty" comment:"审核备注信息"`
RejectReason string `gorm:"type:text" json:"reject_reason,omitempty" comment:"拒绝原因说明"`
ExpiresAt *time.Time `json:"expires_at,omitempty" comment:"合同过期时间"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
// 关联关系
Certification *Certification `gorm:"foreignKey:CertificationID" json:"certification,omitempty" comment:"关联的认证申请"`
}
// TableName 指定数据库表名
func (ContractRecord) TableName() string {
return "contract_records"
}
// IsPending 检查合同是否待审核
// 判断合同是否处于等待管理员审核的状态
func (c *ContractRecord) IsPending() bool {
return c.Status == "PENDING"
}
// IsApproved 检查合同是否已审核通过
// 判断合同是否已通过管理员审核,可以进入签署阶段
func (c *ContractRecord) IsApproved() bool {
return c.Status == "APPROVED"
}
// IsSigned 检查合同是否已签署
// 判断合同是否已完成电子签署,认证流程即将完成
func (c *ContractRecord) IsSigned() bool {
return c.Status == "SIGNED"
}
// IsExpired 检查合同是否已过期
// 判断合同是否已超过有效期,过期后需要重新申请
func (c *ContractRecord) IsExpired() bool {
if c.ExpiresAt == nil {
return false
}
return time.Now().After(*c.ExpiresAt)
}
// HasSigningURL 检查是否有签署链接
// 判断是否已生成电子签署链接,用于前端判断是否显示签署按钮
func (c *ContractRecord) HasSigningURL() bool {
return c.SigningURL != ""
}
// GetStatusName 获取状态的中文名称
// 将英文状态码转换为中文显示名称,用于前端展示和用户理解
func (c *ContractRecord) GetStatusName() string {
statusNames := map[string]string{
"PENDING": "待审核",
"APPROVED": "已审核",
"SIGNED": "已签署",
"EXPIRED": "已过期",
"REJECTED": "已拒绝",
}
if name, exists := statusNames[c.Status]; exists {
return name
}
return c.Status
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (c *ContractRecord) BeforeCreate(tx *gorm.DB) error {
if c.ID == "" {
c.ID = uuid.New().String()
}
return nil
}

View File

@@ -0,0 +1,83 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// EnterpriseInfoSubmitRecord 企业信息提交记录
type EnterpriseInfoSubmitRecord struct {
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
// 企业信息
CompanyName string `json:"company_name" gorm:"type:varchar(200);not null"`
UnifiedSocialCode string `json:"unified_social_code" gorm:"type:varchar(50);not null;index"`
LegalPersonName string `json:"legal_person_name" gorm:"type:varchar(50);not null"`
LegalPersonID string `json:"legal_person_id" gorm:"type:varchar(50);not null"`
LegalPersonPhone string `json:"legal_person_phone" gorm:"type:varchar(50);not null"`
// 提交状态
Status string `json:"status" gorm:"type:varchar(20);not null;default:'submitted'"` // submitted, verified, failed
SubmitAt time.Time `json:"submit_at" gorm:"not null"`
VerifiedAt *time.Time `json:"verified_at"`
FailedAt *time.Time `json:"failed_at"`
FailureReason string `json:"failure_reason" gorm:"type:text"`
// 系统字段
CreatedAt time.Time `json:"created_at" gorm:"not null"`
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
}
// TableName 指定表名
func (EnterpriseInfoSubmitRecord) TableName() string {
return "enterprise_info_submit_records"
}
// NewEnterpriseInfoSubmitRecord 创建新的企业信息提交记录
func NewEnterpriseInfoSubmitRecord(
userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone string,
) *EnterpriseInfoSubmitRecord {
return &EnterpriseInfoSubmitRecord{
ID: uuid.New().String(),
UserID: userID,
CompanyName: companyName,
UnifiedSocialCode: unifiedSocialCode,
LegalPersonName: legalPersonName,
LegalPersonID: legalPersonID,
LegalPersonPhone: legalPersonPhone,
Status: "submitted",
SubmitAt: time.Now(),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
}
// MarkAsVerified 标记为已验证
func (r *EnterpriseInfoSubmitRecord) MarkAsVerified() {
now := time.Now()
r.Status = "verified"
r.VerifiedAt = &now
r.UpdatedAt = now
}
// MarkAsFailed 标记为验证失败
func (r *EnterpriseInfoSubmitRecord) MarkAsFailed(reason string) {
now := time.Now()
r.Status = "failed"
r.FailedAt = &now
r.FailureReason = reason
r.UpdatedAt = now
}
// IsVerified 检查是否已验证
func (r *EnterpriseInfoSubmitRecord) IsVerified() bool {
return r.Status == "verified"
}
// IsFailed 检查是否验证失败
func (r *EnterpriseInfoSubmitRecord) IsFailed() bool {
return r.Status == "failed"
}

View File

@@ -0,0 +1,35 @@
package entities
import (
"time"
"gorm.io/gorm"
)
// EsignContractGenerateRecord e签宝生成合同文件记录
type EsignContractGenerateRecord struct {
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
CertificationID string `json:"certification_id" gorm:"type:varchar(36);not null;index"`
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
// e签宝相关
TemplateID string `json:"template_id" gorm:"type:varchar(100);index"` // 模板ID
ContractFileID string `json:"contract_file_id" gorm:"type:varchar(100);index"` // 合同文件ID
ContractURL string `json:"contract_url" gorm:"type:varchar(500)"` // 合同文件URL
ContractName string `json:"contract_name" gorm:"type:varchar(200)"` // 合同名称
// 生成状态
Status string `json:"status" gorm:"type:varchar(20);not null"` // success, failed
FillTime *time.Time `json:"fill_time"` // 填写时间
// 系统字段
CreatedAt time.Time `json:"created_at" gorm:"not null"`
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
}
// TableName 指定表名
func (EsignContractGenerateRecord) TableName() string {
return "esign_contract_generate_records"
}

View File

@@ -0,0 +1,147 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// EsignContractSignRecord e签宝签署合同记录
type EsignContractSignRecord struct {
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
CertificationID string `json:"certification_id" gorm:"type:varchar(36);not null;index"`
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
EnterpriseInfoID string `json:"enterprise_info_id" gorm:"type:varchar(36);not null;index"` // 企业信息ID
// e签宝相关
EsignFlowID string `json:"esign_flow_id" gorm:"type:varchar(100);index"` // e签宝流程ID
ContractFileID string `json:"contract_file_id" gorm:"type:varchar(100);index"` // 合同文件ID
SignURL string `json:"sign_url" gorm:"type:varchar(500)"` // 签署链接
SignShortURL string `json:"sign_short_url" gorm:"type:varchar(500)"` // 签署短链接
SignedFileURL string `json:"signed_file_url" gorm:"type:varchar(500)"` // 已签署文件URL
// 签署状态
Status string `json:"status" gorm:"type:varchar(20);not null;default:'pending'"` // pending, signing, success, failed, expired
RequestAt time.Time `json:"request_at" gorm:"not null"` // 申请签署时间
SignedAt *time.Time `json:"signed_at"` // 签署完成时间
ExpiredAt *time.Time `json:"expired_at"` // 签署链接过期时间
FailedAt *time.Time `json:"failed_at"` // 失败时间
FailureReason string `json:"failure_reason" gorm:"type:text"` // 失败原因
// 签署人信息
SignerName string `json:"signer_name" gorm:"type:varchar(50)"` // 签署人姓名
SignerPhone string `json:"signer_phone" gorm:"type:varchar(20)"` // 签署人手机号
SignerIDCard string `json:"signer_id_card" gorm:"type:varchar(20)"` // 签署人身份证号
// 系统字段
CreatedAt time.Time `json:"created_at" gorm:"not null"`
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
}
// TableName 指定表名
func (EsignContractSignRecord) TableName() string {
return "esign_contract_sign_records"
}
// NewEsignContractSignRecord 创建新的e签宝签署合同记录
func NewEsignContractSignRecord(
certificationID, userID, esignFlowID, contractFileID, signerName, signerPhone, signerIDCard, signURL, signShortURL string,
) *EsignContractSignRecord {
// 设置签署链接过期时间为7天后
expiredAt := time.Now().AddDate(0, 0, 7)
return &EsignContractSignRecord{
ID: uuid.New().String(),
CertificationID: certificationID,
UserID: userID,
EsignFlowID: esignFlowID,
ContractFileID: contractFileID,
SignURL: signURL,
SignShortURL: signShortURL,
SignerName: signerName,
SignerPhone: signerPhone,
SignerIDCard: signerIDCard,
Status: "pending",
RequestAt: time.Now(),
ExpiredAt: &expiredAt,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
}
// MarkAsSigning 标记为签署中
func (r *EsignContractSignRecord) MarkAsSigning() {
r.Status = "signing"
r.UpdatedAt = time.Now()
}
// MarkAsSuccess 标记为签署成功
func (r *EsignContractSignRecord) MarkAsSuccess(signedFileURL string) {
now := time.Now()
r.Status = "success"
r.SignedFileURL = signedFileURL
r.SignedAt = &now
r.UpdatedAt = now
}
// MarkAsFailed 标记为签署失败
func (r *EsignContractSignRecord) MarkAsFailed(reason string) {
now := time.Now()
r.Status = "failed"
r.FailedAt = &now
r.FailureReason = reason
r.UpdatedAt = now
}
// MarkAsExpired 标记为已过期
func (r *EsignContractSignRecord) MarkAsExpired() {
now := time.Now()
r.Status = "expired"
r.ExpiredAt = &now
r.UpdatedAt = now
}
// SetSignURL 设置签署链接
func (r *EsignContractSignRecord) SetSignURL(signURL string) {
r.SignURL = signURL
r.UpdatedAt = time.Now()
}
// IsSuccess 检查是否签署成功
func (r *EsignContractSignRecord) IsSuccess() bool {
return r.Status == "success"
}
// IsFailed 检查是否签署失败
func (r *EsignContractSignRecord) IsFailed() bool {
return r.Status == "failed"
}
// IsExpired 检查是否已过期
func (r *EsignContractSignRecord) IsExpired() bool {
return r.Status == "expired" || (r.ExpiredAt != nil && time.Now().After(*r.ExpiredAt))
}
// IsPending 检查是否待处理
func (r *EsignContractSignRecord) IsPending() bool {
return r.Status == "pending"
}
// IsSigning 检查是否签署中
func (r *EsignContractSignRecord) IsSigning() bool {
return r.Status == "signing"
}
// GetRemainingTime 获取剩余签署时间
func (r *EsignContractSignRecord) GetRemainingTime() time.Duration {
if r.ExpiredAt == nil {
return 0
}
remaining := time.Until(*r.ExpiredAt)
if remaining < 0 {
return 0
}
return remaining
}

View File

@@ -1,98 +0,0 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// FaceVerifyRecord 人脸识别记录实体
// 记录用户进行人脸识别验证的详细信息,包括验证状态、结果和身份信息
// 支持多次验证尝试,每次验证都会生成独立的记录,便于追踪和重试
type FaceVerifyRecord struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"人脸识别记录唯一标识"`
CertificationID string `gorm:"type:varchar(36);not null;index" json:"certification_id" comment:"关联的认证申请ID"`
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"进行验证的用户ID"`
// 阿里云人脸识别信息 - 第三方服务的相关数据
CertifyID string `gorm:"type:varchar(100);not null;index" json:"certify_id" comment:"阿里云人脸识别任务ID"`
VerifyURL string `gorm:"type:varchar(500)" json:"verify_url,omitempty" comment:"人脸识别验证页面URL"`
ReturnURL string `gorm:"type:varchar(500)" json:"return_url,omitempty" comment:"验证完成后的回调URL"`
// 身份信息 - 用于人脸识别的身份验证数据
RealName string `gorm:"type:varchar(100);not null" json:"real_name" comment:"真实姓名"`
IDCardNumber string `gorm:"type:varchar(50);not null" json:"id_card_number" comment:"身份证号码"`
// 验证结果 - 记录验证的详细结果信息
Status string `gorm:"type:varchar(50);not null;index" json:"status" comment:"验证状态(PROCESSING/SUCCESS/FAIL)"`
ResultCode string `gorm:"type:varchar(50)" json:"result_code,omitempty" comment:"结果代码"`
ResultMessage string `gorm:"type:varchar(500)" json:"result_message,omitempty" comment:"结果描述信息"`
VerifyScore float64 `gorm:"type:decimal(5,2)" json:"verify_score,omitempty" comment:"验证分数(0-1)"`
// 时间信息 - 验证流程的时间节点
InitiatedAt time.Time `gorm:"autoCreateTime" json:"initiated_at" comment:"验证发起时间"`
CompletedAt *time.Time `json:"completed_at,omitempty" comment:"验证完成时间"`
ExpiresAt time.Time `gorm:"not null" json:"expires_at" comment:"验证链接过期时间"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
// 关联关系
Certification *Certification `gorm:"foreignKey:CertificationID" json:"certification,omitempty" comment:"关联的认证申请"`
}
// TableName 指定数据库表名
func (FaceVerifyRecord) TableName() string {
return "face_verify_records"
}
// IsSuccess 检查人脸识别是否成功
// 判断验证状态是否为成功状态
func (f *FaceVerifyRecord) IsSuccess() bool {
return f.Status == "SUCCESS"
}
// IsProcessing 检查是否正在处理中
// 判断验证是否正在进行中,等待用户完成验证
func (f *FaceVerifyRecord) IsProcessing() bool {
return f.Status == "PROCESSING"
}
// IsFailed 检查是否失败
// 判断验证是否失败,包括超时、验证不通过等情况
func (f *FaceVerifyRecord) IsFailed() bool {
return f.Status == "FAIL"
}
// IsExpired 检查是否已过期
// 判断验证链接是否已超过有效期,过期后需要重新发起验证
func (f *FaceVerifyRecord) IsExpired() bool {
return time.Now().After(f.ExpiresAt)
}
// GetStatusName 获取状态的中文名称
// 将英文状态码转换为中文显示名称,用于前端展示
func (f *FaceVerifyRecord) GetStatusName() string {
statusNames := map[string]string{
"PROCESSING": "处理中",
"SUCCESS": "成功",
"FAIL": "失败",
}
if name, exists := statusNames[f.Status]; exists {
return name
}
return f.Status
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (f *FaceVerifyRecord) BeforeCreate(tx *gorm.DB) error {
if f.ID == "" {
f.ID = uuid.New().String()
}
return nil
}

View File

@@ -1,79 +0,0 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// LicenseUploadRecord 营业执照上传记录实体
// 记录用户上传营业执照文件的详细信息包括文件元数据和OCR处理结果
// 支持多种文件格式自动进行OCR识别为后续企业信息验证提供数据支持
type LicenseUploadRecord struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"上传记录唯一标识"`
CertificationID *string `gorm:"type:varchar(36);index" json:"certification_id,omitempty" comment:"关联的认证申请ID(可为空,表示独立上传)"`
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"上传用户ID"`
// 文件信息 - 存储文件的元数据信息
OriginalFileName string `gorm:"type:varchar(255);not null" json:"original_file_name" comment:"原始文件名"`
FileSize int64 `gorm:"not null" json:"file_size" comment:"文件大小(字节)"`
FileType string `gorm:"type:varchar(50);not null" json:"file_type" comment:"文件MIME类型"`
FileURL string `gorm:"type:varchar(500);not null" json:"file_url" comment:"文件访问URL"`
QiNiuKey string `gorm:"type:varchar(255);not null;index" json:"qiniu_key" comment:"七牛云存储的Key"`
// OCR处理结果 - 记录OCR识别的详细结果
OCRProcessed bool `gorm:"default:false" json:"ocr_processed" comment:"是否已进行OCR处理"`
OCRSuccess bool `gorm:"default:false" json:"ocr_success" comment:"OCR识别是否成功"`
OCRConfidence float64 `gorm:"type:decimal(5,2)" json:"ocr_confidence,omitempty" comment:"OCR识别置信度(0-1)"`
OCRRawData string `gorm:"type:text" json:"ocr_raw_data,omitempty" comment:"OCR原始返回数据(JSON格式)"`
OCRErrorMessage string `gorm:"type:varchar(500)" json:"ocr_error_message,omitempty" comment:"OCR处理错误信息"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
// 关联关系
Certification *Certification `gorm:"foreignKey:CertificationID" json:"certification,omitempty" comment:"关联的认证申请"`
}
// TableName 指定数据库表名
func (LicenseUploadRecord) TableName() string {
return "license_upload_records"
}
// IsOCRSuccess 检查OCR是否成功
// 判断OCR处理已完成且识别成功
func (l *LicenseUploadRecord) IsOCRSuccess() bool {
return l.OCRProcessed && l.OCRSuccess
}
// GetFileExtension 获取文件扩展名
// 从原始文件名中提取文件扩展名,用于文件类型判断
func (l *LicenseUploadRecord) GetFileExtension() string {
// 从OriginalFileName提取扩展名的逻辑
// 这里简化处理实际使用时可以用path.Ext()
return l.FileType
}
// IsValidForOCR 检查文件是否适合OCR处理
// 验证文件类型是否支持OCR识别目前支持JPEG、PNG格式
func (l *LicenseUploadRecord) IsValidForOCR() bool {
validTypes := []string{"image/jpeg", "image/png", "image/jpg"}
for _, validType := range validTypes {
if l.FileType == validType {
return true
}
}
return false
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (l *LicenseUploadRecord) BeforeCreate(tx *gorm.DB) error {
if l.ID == "" {
l.ID = uuid.New().String()
}
return nil
}

View File

@@ -1,136 +0,0 @@
package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
// NotificationRecord 通知记录实体
// 记录系统发送的所有通知信息,包括短信、企业微信、邮件等多种通知渠道
// 支持通知状态跟踪、重试机制、模板化消息等功能,确保通知的可靠送达
type NotificationRecord struct {
// 基础标识
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"通知记录唯一标识"`
CertificationID *string `gorm:"type:varchar(36);index" json:"certification_id,omitempty" comment:"关联的认证申请ID(可为空)"`
UserID *string `gorm:"type:varchar(36);index" json:"user_id,omitempty" comment:"接收用户ID(可为空)"`
// 通知类型和渠道 - 定义通知的发送方式和业务场景
NotificationType string `gorm:"type:varchar(50);not null;index" json:"notification_type" comment:"通知类型(SMS/WECHAT_WORK/EMAIL)"`
NotificationScene string `gorm:"type:varchar(50);not null;index" json:"notification_scene" comment:"通知场景(ADMIN_NEW_APPLICATION/USER_CONTRACT_READY等)"`
// 接收方信息 - 通知的目标接收者
Recipient string `gorm:"type:varchar(255);not null" json:"recipient" comment:"接收方标识(手机号/邮箱/用户ID)"`
// 消息内容 - 通知的具体内容信息
Title string `gorm:"type:varchar(255)" json:"title,omitempty" comment:"通知标题"`
Content string `gorm:"type:text;not null" json:"content" comment:"通知内容"`
TemplateID string `gorm:"type:varchar(100)" json:"template_id,omitempty" comment:"消息模板ID"`
TemplateParams string `gorm:"type:text" json:"template_params,omitempty" comment:"模板参数(JSON格式)"`
// 发送状态 - 记录通知的发送过程和结果
Status string `gorm:"type:varchar(50);not null;index" json:"status" comment:"发送状态(PENDING/SENT/FAILED)"`
ErrorMessage string `gorm:"type:varchar(500)" json:"error_message,omitempty" comment:"发送失败的错误信息"`
SentAt *time.Time `json:"sent_at,omitempty" comment:"发送成功时间"`
RetryCount int `gorm:"default:0" json:"retry_count" comment:"当前重试次数"`
MaxRetryCount int `gorm:"default:3" json:"max_retry_count" comment:"最大重试次数"`
// 时间戳字段
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
// 关联关系
Certification *Certification `gorm:"foreignKey:CertificationID" json:"certification,omitempty" comment:"关联的认证申请"`
}
// TableName 指定数据库表名
func (NotificationRecord) TableName() string {
return "notification_records"
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (n *NotificationRecord) BeforeCreate(tx *gorm.DB) error {
if n.ID == "" {
n.ID = uuid.New().String()
}
return nil
}
// IsPending 检查通知是否待发送
// 判断通知是否处于等待发送的状态
func (n *NotificationRecord) IsPending() bool {
return n.Status == "PENDING"
}
// IsSent 检查通知是否已发送
// 判断通知是否已成功发送到接收方
func (n *NotificationRecord) IsSent() bool {
return n.Status == "SENT"
}
// IsFailed 检查通知是否发送失败
// 判断通知是否发送失败,包括网络错误、接收方无效等情况
func (n *NotificationRecord) IsFailed() bool {
return n.Status == "FAILED"
}
// CanRetry 检查是否可以重试
// 判断失败的通知是否还可以进行重试发送
func (n *NotificationRecord) CanRetry() bool {
return n.IsFailed() && n.RetryCount < n.MaxRetryCount
}
// IncrementRetryCount 增加重试次数
// 在重试发送时增加重试计数器
func (n *NotificationRecord) IncrementRetryCount() {
n.RetryCount++
}
// GetStatusName 获取状态的中文名称
// 将英文状态码转换为中文显示名称,用于前端展示
func (n *NotificationRecord) GetStatusName() string {
statusNames := map[string]string{
"PENDING": "待发送",
"SENT": "已发送",
"FAILED": "发送失败",
}
if name, exists := statusNames[n.Status]; exists {
return name
}
return n.Status
}
// GetNotificationTypeName 获取通知类型的中文名称
// 将通知类型转换为中文显示名称,便于用户理解
func (n *NotificationRecord) GetNotificationTypeName() string {
typeNames := map[string]string{
"SMS": "短信",
"WECHAT_WORK": "企业微信",
"EMAIL": "邮件",
}
if name, exists := typeNames[n.NotificationType]; exists {
return name
}
return n.NotificationType
}
// GetNotificationSceneName 获取通知场景的中文名称
// 将通知场景转换为中文显示名称,便于业务人员理解通知的触发原因
func (n *NotificationRecord) GetNotificationSceneName() string {
sceneNames := map[string]string{
"ADMIN_NEW_APPLICATION": "管理员新申请通知",
"USER_CONTRACT_READY": "用户合同就绪通知",
"USER_CERTIFICATION_COMPLETED": "用户认证完成通知",
"USER_FACE_VERIFY_FAILED": "用户人脸识别失败通知",
"USER_CONTRACT_REJECTED": "用户合同被拒绝通知",
}
if name, exists := sceneNames[n.NotificationScene]; exists {
return name
}
return n.NotificationScene
}

View File

@@ -5,29 +5,19 @@ type CertificationStatus string
const (
// 主流程状态
StatusNotStarted CertificationStatus = "not_started" // 未开始认证
StatusPending CertificationStatus = "pending" // 待开始
StatusInfoSubmitted CertificationStatus = "info_submitted" // 企业信息已提交
StatusFaceVerified CertificationStatus = "face_verified" // 人脸识别完成
StatusContractApplied CertificationStatus = "contract_applied" // 已申请合同
StatusContractPending CertificationStatus = "contract_pending" // 合同待审核
StatusContractApproved CertificationStatus = "contract_approved" // 合同已审核(有链接)
StatusContractSigned CertificationStatus = "contract_signed" // 合同已签署
StatusPending CertificationStatus = "pending" // 认证
StatusInfoSubmitted CertificationStatus = "info_submitted" // 已提交企业信息
StatusEnterpriseVerified CertificationStatus = "enterprise_verified" // 企业认证
StatusContractApplied CertificationStatus = "contract_applied" // 已申请签署合同
StatusContractSigned CertificationStatus = "contract_signed" // 已签署合同
StatusCompleted CertificationStatus = "completed" // 认证完成
// 失败和重试状态
StatusFaceFailed CertificationStatus = "face_failed" // 人脸识别失败
StatusSignFailed CertificationStatus = "sign_failed" // 签署失败
StatusRejected CertificationStatus = "rejected" // 已拒绝
)
// IsValidStatus 检查状态是否有效
func IsValidStatus(status CertificationStatus) bool {
validStatuses := []CertificationStatus{
StatusNotStarted, StatusPending, StatusInfoSubmitted, StatusFaceVerified,
StatusContractApplied, StatusContractPending, StatusContractApproved,
StatusContractSigned, StatusCompleted, StatusFaceFailed,
StatusSignFailed, StatusRejected,
StatusPending, StatusInfoSubmitted, StatusEnterpriseVerified,
StatusContractApplied, StatusContractSigned, StatusCompleted,
}
for _, validStatus := range validStatuses {
@@ -41,18 +31,12 @@ func IsValidStatus(status CertificationStatus) bool {
// GetStatusName 获取状态的中文名称
func GetStatusName(status CertificationStatus) string {
statusNames := map[CertificationStatus]string{
StatusNotStarted: "未开始认证",
StatusPending: "待开始",
StatusInfoSubmitted: "企业信息已提交",
StatusFaceVerified: "人脸识别完成",
StatusContractApplied: "已申请合同",
StatusContractPending: "合同待审核",
StatusContractApproved: "合同已审核",
StatusContractSigned: "合同已签署",
StatusCompleted: "认证完成",
StatusFaceFailed: "人脸识别失败",
StatusSignFailed: "签署失败",
StatusRejected: "已拒绝",
StatusPending: "待认证",
StatusInfoSubmitted: "已提交企业信息",
StatusEnterpriseVerified: "企业认证",
StatusContractApplied: "已申请合同",
StatusContractSigned: "已签署合同",
StatusCompleted: "认证完成",
}
if name, exists := statusNames[status]; exists {
@@ -63,28 +47,36 @@ func GetStatusName(status CertificationStatus) string {
// IsFinalStatus 判断是否为最终状态
func IsFinalStatus(status CertificationStatus) bool {
finalStatuses := []CertificationStatus{
StatusCompleted, StatusRejected,
}
for _, finalStatus := range finalStatuses {
if status == finalStatus {
return true
}
}
return false
return status == StatusCompleted
}
// IsFailedStatus 判断是否为失败状态
func IsFailedStatus(status CertificationStatus) bool {
failedStatuses := []CertificationStatus{
StatusFaceFailed, StatusSignFailed, StatusRejected,
// GetStatusCategory 获取状态分类
func GetStatusCategory(status CertificationStatus) string {
switch status {
case StatusPending:
return "initial"
case StatusInfoSubmitted, StatusEnterpriseVerified, StatusContractApplied, StatusContractSigned:
return "processing"
case StatusCompleted:
return "completed"
default:
return "unknown"
}
}
// GetStatusPriority 获取状态优先级(用于排序)
func GetStatusPriority(status CertificationStatus) int {
priorities := map[CertificationStatus]int{
StatusPending: 0,
StatusInfoSubmitted: 1,
StatusEnterpriseVerified: 2,
StatusContractApplied: 3,
StatusContractSigned: 4,
StatusCompleted: 5,
}
for _, failedStatus := range failedStatuses {
if status == failedStatus {
return true
}
if priority, exists := priorities[status]; exists {
return priority
}
return false
return 999
}

View File

@@ -5,25 +5,18 @@ import (
"time"
"tyapi-server/internal/domains/certification/entities"
"github.com/google/uuid"
)
// 认证事件类型常量
// 事件类型常量
const (
EventTypeCertificationCreated = "certification.created"
EventTypeCertificationSubmitted = "certification.submitted"
EventTypeLicenseUploaded = "certification.license.uploaded"
EventTypeOCRCompleted = "certification.ocr.completed"
EventTypeEnterpriseInfoConfirmed = "certification.enterprise.confirmed"
EventTypeFaceVerifyInitiated = "certification.face_verify.initiated"
EventTypeFaceVerifyCompleted = "certification.face_verify.completed"
EventTypeContractRequested = "certification.contract.requested"
EventTypeContractGenerated = "certification.contract.generated"
EventTypeContractSigned = "certification.contract.signed"
EventTypeCertificationApproved = "certification.approved"
EventTypeCertificationRejected = "certification.rejected"
EventTypeWalletCreated = "certification.wallet.created"
EventTypeCertificationCompleted = "certification.completed"
EventTypeCertificationFailed = "certification.failed"
EventTypeCertificationCreated = "certification.created"
EventTypeEnterpriseInfoSubmitted = "enterprise.info.submitted"
EventTypeEnterpriseVerified = "enterprise.verified"
EventTypeContractApplied = "contract.applied"
EventTypeContractSigned = "contract.signed"
EventTypeCertificationCompleted = "certification.completed"
)
// BaseCertificationEvent 认证事件基础结构
@@ -39,7 +32,7 @@ type BaseCertificationEvent struct {
Payload interface{} `json:"payload"`
}
// 实现 Event 接口
// 实现 DomainEvent 接口
func (e *BaseCertificationEvent) GetID() string { return e.ID }
func (e *BaseCertificationEvent) GetType() string { return e.Type }
func (e *BaseCertificationEvent) GetVersion() string { return e.Version }
@@ -62,15 +55,15 @@ func NewBaseCertificationEvent(eventType, aggregateID string, payload interface{
Type: eventType,
Version: "1.0",
Timestamp: time.Now(),
Source: "certification-domain",
Source: "certification-service",
AggregateID: aggregateID,
AggregateType: "certification",
AggregateType: "Certification",
Metadata: make(map[string]interface{}),
Payload: payload,
}
}
// CertificationCreatedEvent 认证创建事件
// CertificationCreatedEvent 认证申请创建事件
type CertificationCreatedEvent struct {
*BaseCertificationEvent
Data struct {
@@ -80,7 +73,7 @@ type CertificationCreatedEvent struct {
} `json:"data"`
}
// NewCertificationCreatedEvent 创建认证创建事件
// NewCertificationCreatedEvent 创建认证申请创建事件
func NewCertificationCreatedEvent(certification *entities.Certification) *CertificationCreatedEvent {
event := &CertificationCreatedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
@@ -96,8 +89,8 @@ func NewCertificationCreatedEvent(certification *entities.Certification) *Certif
return event
}
// CertificationSubmittedEvent 认证提交事件
type CertificationSubmittedEvent struct {
// EnterpriseInfoSubmittedEvent 企业信息提交事件
type EnterpriseInfoSubmittedEvent struct {
*BaseCertificationEvent
Data struct {
CertificationID string `json:"certification_id"`
@@ -106,11 +99,11 @@ type CertificationSubmittedEvent struct {
} `json:"data"`
}
// NewCertificationSubmittedEvent 创建认证提交事件
func NewCertificationSubmittedEvent(certification *entities.Certification) *CertificationSubmittedEvent {
event := &CertificationSubmittedEvent{
// NewEnterpriseInfoSubmittedEvent 创建企业信息提交事件
func NewEnterpriseInfoSubmittedEvent(certification *entities.Certification) *EnterpriseInfoSubmittedEvent {
event := &EnterpriseInfoSubmittedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
EventTypeCertificationSubmitted,
EventTypeEnterpriseInfoSubmitted,
certification.ID,
nil,
),
@@ -122,158 +115,8 @@ func NewCertificationSubmittedEvent(certification *entities.Certification) *Cert
return event
}
// LicenseUploadedEvent 营业执照上传事件
type LicenseUploadedEvent struct {
*BaseCertificationEvent
Data struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
FileURL string `json:"file_url"`
FileName string `json:"file_name"`
FileSize int64 `json:"file_size"`
Status string `json:"status"`
} `json:"data"`
}
// NewLicenseUploadedEvent 创建营业执照上传事件
func NewLicenseUploadedEvent(certification *entities.Certification, record *entities.LicenseUploadRecord) *LicenseUploadedEvent {
event := &LicenseUploadedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
EventTypeLicenseUploaded,
certification.ID,
nil,
),
}
event.Data.CertificationID = certification.ID
event.Data.UserID = certification.UserID
event.Data.FileURL = record.FileURL
event.Data.FileName = record.OriginalFileName
event.Data.FileSize = record.FileSize
event.Data.Status = string(certification.Status)
event.Payload = event.Data
return event
}
// OCRCompletedEvent OCR识别完成事件
type OCRCompletedEvent struct {
*BaseCertificationEvent
Data struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
OCRResult map[string]interface{} `json:"ocr_result"`
Confidence float64 `json:"confidence"`
Status string `json:"status"`
} `json:"data"`
}
// NewOCRCompletedEvent 创建OCR识别完成事件
func NewOCRCompletedEvent(certification *entities.Certification, ocrResult map[string]interface{}, confidence float64) *OCRCompletedEvent {
event := &OCRCompletedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
EventTypeOCRCompleted,
certification.ID,
nil,
),
}
event.Data.CertificationID = certification.ID
event.Data.UserID = certification.UserID
event.Data.OCRResult = ocrResult
event.Data.Confidence = confidence
event.Data.Status = string(certification.Status)
event.Payload = event.Data
return event
}
// EnterpriseInfoConfirmedEvent 企业信息确认事件
type EnterpriseInfoConfirmedEvent struct {
*BaseCertificationEvent
Data struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
EnterpriseInfo map[string]interface{} `json:"enterprise_info"`
Status string `json:"status"`
} `json:"data"`
}
// NewEnterpriseInfoConfirmedEvent 创建企业信息确认事件
func NewEnterpriseInfoConfirmedEvent(certification *entities.Certification, enterpriseInfo map[string]interface{}) *EnterpriseInfoConfirmedEvent {
event := &EnterpriseInfoConfirmedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
EventTypeEnterpriseInfoConfirmed,
certification.ID,
nil,
),
}
event.Data.CertificationID = certification.ID
event.Data.UserID = certification.UserID
event.Data.EnterpriseInfo = enterpriseInfo
event.Data.Status = string(certification.Status)
event.Payload = event.Data
return event
}
// FaceVerifyInitiatedEvent 人脸识别初始化事件
type FaceVerifyInitiatedEvent struct {
*BaseCertificationEvent
Data struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
VerifyToken string `json:"verify_token"`
Status string `json:"status"`
} `json:"data"`
}
// NewFaceVerifyInitiatedEvent 创建人脸识别初始化事件
func NewFaceVerifyInitiatedEvent(certification *entities.Certification, verifyToken string) *FaceVerifyInitiatedEvent {
event := &FaceVerifyInitiatedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
EventTypeFaceVerifyInitiated,
certification.ID,
nil,
),
}
event.Data.CertificationID = certification.ID
event.Data.UserID = certification.UserID
event.Data.VerifyToken = verifyToken
event.Data.Status = string(certification.Status)
event.Payload = event.Data
return event
}
// FaceVerifyCompletedEvent 人脸识别完成事件
type FaceVerifyCompletedEvent struct {
*BaseCertificationEvent
Data struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
VerifyToken string `json:"verify_token"`
Success bool `json:"success"`
Score float64 `json:"score"`
Status string `json:"status"`
} `json:"data"`
}
// NewFaceVerifyCompletedEvent 创建人脸识别完成事件
func NewFaceVerifyCompletedEvent(certification *entities.Certification, record *entities.FaceVerifyRecord) *FaceVerifyCompletedEvent {
event := &FaceVerifyCompletedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
EventTypeFaceVerifyCompleted,
certification.ID,
nil,
),
}
event.Data.CertificationID = certification.ID
event.Data.UserID = certification.UserID
event.Data.VerifyToken = record.CertifyID
event.Data.Success = record.IsSuccess()
event.Data.Score = record.VerifyScore
event.Data.Status = string(certification.Status)
event.Payload = event.Data
return event
}
// ContractRequestedEvent 合同申请事件
type ContractRequestedEvent struct {
// EnterpriseVerifiedEvent 企业认证完成事件
type EnterpriseVerifiedEvent struct {
*BaseCertificationEvent
Data struct {
CertificationID string `json:"certification_id"`
@@ -282,11 +125,11 @@ type ContractRequestedEvent struct {
} `json:"data"`
}
// NewContractRequestedEvent 创建合同申请事件
func NewContractRequestedEvent(certification *entities.Certification) *ContractRequestedEvent {
event := &ContractRequestedEvent{
// NewEnterpriseVerifiedEvent 创建企业认证完成事件
func NewEnterpriseVerifiedEvent(certification *entities.Certification) *EnterpriseVerifiedEvent {
event := &EnterpriseVerifiedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
EventTypeContractRequested,
EventTypeEnterpriseVerified,
certification.ID,
nil,
),
@@ -298,31 +141,27 @@ func NewContractRequestedEvent(certification *entities.Certification) *ContractR
return event
}
// ContractGeneratedEvent 合同生成事件
type ContractGeneratedEvent struct {
// ContractAppliedEvent 合同申请事件
type ContractAppliedEvent struct {
*BaseCertificationEvent
Data struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
ContractURL string `json:"contract_url"`
ContractID string `json:"contract_id"`
Status string `json:"status"`
} `json:"data"`
}
// NewContractGeneratedEvent 创建合同生成事件
func NewContractGeneratedEvent(certification *entities.Certification, record *entities.ContractRecord) *ContractGeneratedEvent {
event := &ContractGeneratedEvent{
// NewContractAppliedEvent 创建合同申请事件
func NewContractAppliedEvent(certification *entities.Certification) *ContractAppliedEvent {
event := &ContractAppliedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
EventTypeContractGenerated,
EventTypeContractApplied,
certification.ID,
nil,
),
}
event.Data.CertificationID = certification.ID
event.Data.UserID = certification.UserID
event.Data.ContractURL = record.ContractURL
event.Data.ContractID = record.ID
event.Data.Status = string(certification.Status)
event.Payload = event.Data
return event
@@ -334,14 +173,13 @@ type ContractSignedEvent struct {
Data struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
ContractID string `json:"contract_id"`
SignedAt string `json:"signed_at"`
ContractURL string `json:"contract_url"`
Status string `json:"status"`
} `json:"data"`
}
// NewContractSignedEvent 创建合同签署事件
func NewContractSignedEvent(certification *entities.Certification, record *entities.ContractRecord) *ContractSignedEvent {
func NewContractSignedEvent(certification *entities.Certification, contractURL string) *ContractSignedEvent {
event := &ContractSignedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
EventTypeContractSigned,
@@ -351,100 +189,7 @@ func NewContractSignedEvent(certification *entities.Certification, record *entit
}
event.Data.CertificationID = certification.ID
event.Data.UserID = certification.UserID
event.Data.ContractID = record.ID
event.Data.SignedAt = record.SignedAt.Format(time.RFC3339)
event.Data.Status = string(certification.Status)
event.Payload = event.Data
return event
}
// CertificationApprovedEvent 认证审核通过事件
type CertificationApprovedEvent struct {
*BaseCertificationEvent
Data struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
AdminID string `json:"admin_id"`
ApprovedAt string `json:"approved_at"`
Status string `json:"status"`
} `json:"data"`
}
// NewCertificationApprovedEvent 创建认证审核通过事件
func NewCertificationApprovedEvent(certification *entities.Certification, adminID string) *CertificationApprovedEvent {
event := &CertificationApprovedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
EventTypeCertificationApproved,
certification.ID,
nil,
),
}
event.Data.CertificationID = certification.ID
event.Data.UserID = certification.UserID
event.Data.AdminID = adminID
event.Data.ApprovedAt = time.Now().Format(time.RFC3339)
event.Data.Status = string(certification.Status)
event.Payload = event.Data
return event
}
// CertificationRejectedEvent 认证审核拒绝事件
type CertificationRejectedEvent struct {
*BaseCertificationEvent
Data struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
AdminID string `json:"admin_id"`
RejectReason string `json:"reject_reason"`
RejectedAt string `json:"rejected_at"`
Status string `json:"status"`
} `json:"data"`
}
// NewCertificationRejectedEvent 创建认证审核拒绝事件
func NewCertificationRejectedEvent(certification *entities.Certification, adminID, rejectReason string) *CertificationRejectedEvent {
event := &CertificationRejectedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
EventTypeCertificationRejected,
certification.ID,
nil,
),
}
event.Data.CertificationID = certification.ID
event.Data.UserID = certification.UserID
event.Data.AdminID = adminID
event.Data.RejectReason = rejectReason
event.Data.RejectedAt = time.Now().Format(time.RFC3339)
event.Data.Status = string(certification.Status)
event.Payload = event.Data
return event
}
// WalletCreatedEvent 钱包创建事件
type WalletCreatedEvent struct {
*BaseCertificationEvent
Data struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
WalletID string `json:"wallet_id"`
AccessID string `json:"access_id"`
Status string `json:"status"`
} `json:"data"`
}
// NewWalletCreatedEvent 创建钱包创建事件
func NewWalletCreatedEvent(certification *entities.Certification, walletID, accessID string) *WalletCreatedEvent {
event := &WalletCreatedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
EventTypeWalletCreated,
certification.ID,
nil,
),
}
event.Data.CertificationID = certification.ID
event.Data.UserID = certification.UserID
event.Data.WalletID = walletID
event.Data.AccessID = accessID
event.Data.ContractURL = contractURL
event.Data.Status = string(certification.Status)
event.Payload = event.Data
return event
@@ -456,14 +201,13 @@ type CertificationCompletedEvent struct {
Data struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
WalletID string `json:"wallet_id"`
CompletedAt string `json:"completed_at"`
Status string `json:"status"`
} `json:"data"`
}
// NewCertificationCompletedEvent 创建认证完成事件
func NewCertificationCompletedEvent(certification *entities.Certification, walletID string) *CertificationCompletedEvent {
func NewCertificationCompletedEvent(certification *entities.Certification) *CertificationCompletedEvent {
event := &CertificationCompletedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
EventTypeCertificationCompleted,
@@ -473,54 +217,13 @@ func NewCertificationCompletedEvent(certification *entities.Certification, walle
}
event.Data.CertificationID = certification.ID
event.Data.UserID = certification.UserID
event.Data.WalletID = walletID
event.Data.CompletedAt = time.Now().Format(time.RFC3339)
event.Data.Status = string(certification.Status)
event.Payload = event.Data
return event
}
// CertificationFailedEvent 认证失败事件
type CertificationFailedEvent struct {
*BaseCertificationEvent
Data struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
FailedAt string `json:"failed_at"`
FailureReason string `json:"failure_reason"`
Status string `json:"status"`
} `json:"data"`
}
// NewCertificationFailedEvent 创建认证失败事件
func NewCertificationFailedEvent(certification *entities.Certification, failureReason string) *CertificationFailedEvent {
event := &CertificationFailedEvent{
BaseCertificationEvent: NewBaseCertificationEvent(
EventTypeCertificationFailed,
certification.ID,
nil,
),
}
event.Data.CertificationID = certification.ID
event.Data.UserID = certification.UserID
event.Data.FailedAt = time.Now().Format(time.RFC3339)
event.Data.FailureReason = failureReason
event.Data.Status = string(certification.Status)
event.Payload = event.Data
return event
}
// generateEventID 生成事件ID
// 工具函数
func generateEventID() string {
return time.Now().Format("20060102150405") + "-" + generateRandomString(8)
}
// generateRandomString 生成随机字符串
func generateRandomString(length int) string {
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
b := make([]byte, length)
for i := range b {
b[i] = charset[time.Now().UnixNano()%int64(len(charset))]
}
return string(b)
return uuid.New().String()
}

View File

@@ -29,20 +29,11 @@ func NewCertificationEventHandler(logger *zap.Logger, notification notification.
name: "certification-event-handler",
eventTypes: []string{
EventTypeCertificationCreated,
EventTypeCertificationSubmitted,
EventTypeLicenseUploaded,
EventTypeOCRCompleted,
EventTypeEnterpriseInfoConfirmed,
EventTypeFaceVerifyInitiated,
EventTypeFaceVerifyCompleted,
EventTypeContractRequested,
EventTypeContractGenerated,
EventTypeEnterpriseInfoSubmitted,
EventTypeEnterpriseVerified,
EventTypeContractApplied,
EventTypeContractSigned,
EventTypeCertificationApproved,
EventTypeCertificationRejected,
EventTypeWalletCreated,
EventTypeCertificationCompleted,
EventTypeCertificationFailed,
},
isAsync: true,
}
@@ -84,34 +75,16 @@ func (h *CertificationEventHandler) Handle(ctx context.Context, event interfaces
switch event.GetType() {
case EventTypeCertificationCreated:
return h.handleCertificationCreated(ctx, event)
case EventTypeCertificationSubmitted:
return h.handleCertificationSubmitted(ctx, event)
case EventTypeLicenseUploaded:
return h.handleLicenseUploaded(ctx, event)
case EventTypeOCRCompleted:
return h.handleOCRCompleted(ctx, event)
case EventTypeEnterpriseInfoConfirmed:
return h.handleEnterpriseInfoConfirmed(ctx, event)
case EventTypeFaceVerifyInitiated:
return h.handleFaceVerifyInitiated(ctx, event)
case EventTypeFaceVerifyCompleted:
return h.handleFaceVerifyCompleted(ctx, event)
case EventTypeContractRequested:
return h.handleContractRequested(ctx, event)
case EventTypeContractGenerated:
return h.handleContractGenerated(ctx, event)
case EventTypeEnterpriseInfoSubmitted:
return h.handleEnterpriseInfoSubmitted(ctx, event)
case EventTypeEnterpriseVerified:
return h.handleEnterpriseVerified(ctx, event)
case EventTypeContractApplied:
return h.handleContractApplied(ctx, event)
case EventTypeContractSigned:
return h.handleContractSigned(ctx, event)
case EventTypeCertificationApproved:
return h.handleCertificationApproved(ctx, event)
case EventTypeCertificationRejected:
return h.handleCertificationRejected(ctx, event)
case EventTypeWalletCreated:
return h.handleWalletCreated(ctx, event)
case EventTypeCertificationCompleted:
return h.handleCertificationCompleted(ctx, event)
case EventTypeCertificationFailed:
return h.handleCertificationFailed(ctx, event)
default:
h.logger.Warn("未知的事件类型", zap.String("event_type", event.GetType()))
return nil
@@ -133,126 +106,49 @@ func (h *CertificationEventHandler) handleCertificationCreated(ctx context.Conte
return h.sendUserNotification(ctx, event, "认证申请创建成功", message)
}
// handleCertificationSubmitted 处理认证提交事件
func (h *CertificationEventHandler) handleCertificationSubmitted(ctx context.Context, event interfaces.Event) error {
h.logger.Info("认证申请已提交",
zap.String("certification_id", event.GetAggregateID()),
zap.String("user_id", h.extractUserID(event)),
)
// 发送通知给管理员
adminMessage := fmt.Sprintf("📋 新的企业认证申请待审核\n\n认证ID: %s\n用户ID: %s\n提交时间: %s\n\n请及时处理审核。",
event.GetAggregateID(),
h.extractUserID(event),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
return h.sendAdminNotification(ctx, event, "新认证申请待审核", adminMessage)
}
// handleLicenseUploaded 处理营业执照上传事件
func (h *CertificationEventHandler) handleLicenseUploaded(ctx context.Context, event interfaces.Event) error {
h.logger.Info("营业执照已上传",
// handleEnterpriseInfoSubmitted 处理企业信息提交事件
func (h *CertificationEventHandler) handleEnterpriseInfoSubmitted(ctx context.Context, event interfaces.Event) error {
h.logger.Info("企业信息已提交",
zap.String("certification_id", event.GetAggregateID()),
zap.String("user_id", h.extractUserID(event)),
)
// 发送通知给用户
message := fmt.Sprintf("📄 营业执照上传成功!\n\n认证ID: %s\n上传时间: %s\n\n系统正在识别营业执照信息,请稍候...",
message := fmt.Sprintf("✅ 企业信息提交成功!\n\n认证ID: %s\n提交时间: %s\n\n系统正在验证企业信息,请稍候...",
event.GetAggregateID(),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
return h.sendUserNotification(ctx, event, "营业执照上传成功", message)
return h.sendUserNotification(ctx, event, "企业信息提交成功", message)
}
// handleOCRCompleted 处理OCR识别完成事件
func (h *CertificationEventHandler) handleOCRCompleted(ctx context.Context, event interfaces.Event) error {
h.logger.Info("OCR识别已完成",
// handleEnterpriseVerified 处理企业认证完成事件
func (h *CertificationEventHandler) handleEnterpriseVerified(ctx context.Context, event interfaces.Event) error {
h.logger.Info("企业认证已完成",
zap.String("certification_id", event.GetAggregateID()),
zap.String("user_id", h.extractUserID(event)),
)
// 发送通知给用户
message := fmt.Sprintf("✅ OCR识别完成!\n\n认证ID: %s\n识别时间: %s\n\n请确认企业信息是否正确,如有问题请及时联系客服。",
message := fmt.Sprintf("✅ 企业认证完成!\n\n认证ID: %s\n完成时间: %s\n\n下一步:请申请电子合同。",
event.GetAggregateID(),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
return h.sendUserNotification(ctx, event, "OCR识别完成", message)
return h.sendUserNotification(ctx, event, "企业认证完成", message)
}
// handleEnterpriseInfoConfirmed 处理企业信息确认事件
func (h *CertificationEventHandler) handleEnterpriseInfoConfirmed(ctx context.Context, event interfaces.Event) error {
h.logger.Info("企业信息已确认",
zap.String("certification_id", event.GetAggregateID()),
zap.String("user_id", h.extractUserID(event)),
)
// 发送通知给用户
message := fmt.Sprintf("✅ 企业信息确认成功!\n\n认证ID: %s\n确认时间: %s\n\n下一步请完成人脸识别验证。",
event.GetAggregateID(),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
return h.sendUserNotification(ctx, event, "企业信息确认成功", message)
}
// handleFaceVerifyInitiated 处理人脸识别初始化事件
func (h *CertificationEventHandler) handleFaceVerifyInitiated(ctx context.Context, event interfaces.Event) error {
h.logger.Info("人脸识别已初始化",
zap.String("certification_id", event.GetAggregateID()),
zap.String("user_id", h.extractUserID(event)),
)
// 发送通知给用户
message := fmt.Sprintf("👤 人脸识别验证已开始!\n\n认证ID: %s\n开始时间: %s\n\n请按照指引完成人脸识别验证。",
event.GetAggregateID(),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
return h.sendUserNotification(ctx, event, "人脸识别验证开始", message)
}
// handleFaceVerifyCompleted 处理人脸识别完成事件
func (h *CertificationEventHandler) handleFaceVerifyCompleted(ctx context.Context, event interfaces.Event) error {
h.logger.Info("人脸识别已完成",
zap.String("certification_id", event.GetAggregateID()),
zap.String("user_id", h.extractUserID(event)),
)
// 发送通知给用户
message := fmt.Sprintf("✅ 人脸识别验证完成!\n\n认证ID: %s\n完成时间: %s\n\n下一步系统将为您申请电子合同。",
event.GetAggregateID(),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
return h.sendUserNotification(ctx, event, "人脸识别验证完成", message)
}
// handleContractRequested 处理合同申请事件
func (h *CertificationEventHandler) handleContractRequested(ctx context.Context, event interfaces.Event) error {
// handleContractApplied 处理合同申请事件
func (h *CertificationEventHandler) handleContractApplied(ctx context.Context, event interfaces.Event) error {
h.logger.Info("电子合同申请已提交",
zap.String("certification_id", event.GetAggregateID()),
zap.String("user_id", h.extractUserID(event)),
)
// 发送通知给管理员
adminMessage := fmt.Sprintf("📋 新的电子合同申请待审核\n\n认证ID: %s\n用户ID: %s\n申请时间: %s\n\n请及时处理合同审核。",
event.GetAggregateID(),
h.extractUserID(event),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
return h.sendAdminNotification(ctx, event, "新合同申请待审核", adminMessage)
}
// handleContractGenerated 处理合同生成事件
func (h *CertificationEventHandler) handleContractGenerated(ctx context.Context, event interfaces.Event) error {
h.logger.Info("电子合同已生成",
zap.String("certification_id", event.GetAggregateID()),
zap.String("user_id", h.extractUserID(event)),
)
// 发送通知给用户
message := fmt.Sprintf("📄 电子合同已生成\n\n认证ID: %s\n生成时间: %s\n\n请及时签署电子合同以完成认证流程。",
message := fmt.Sprintf("📋 电子合同申请已提交\n\n认证ID: %s\n申请时间: %s\n\n系统正在生成电子合同,请稍候...",
event.GetAggregateID(),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
return h.sendUserNotification(ctx, event, "电子合同已生成", message)
return h.sendUserNotification(ctx, event, "合同申请已提交", message)
}
// handleContractSigned 处理合同签署事件
@@ -263,56 +159,11 @@ func (h *CertificationEventHandler) handleContractSigned(ctx context.Context, ev
)
// 发送通知给用户
message := fmt.Sprintf("✅ 电子合同签署成\n\n认证ID: %s\n签署时间: %s\n\n您的企业认证申请已进入最终审核阶段。",
message := fmt.Sprintf("✅ 电子合同签署成!\n\n认证ID: %s\n签署时间: %s\n\n恭喜!您的企业认证已完成。",
event.GetAggregateID(),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
return h.sendUserNotification(ctx, event, "电子合同签署成", message)
}
// handleCertificationApproved 处理认证审核通过事件
func (h *CertificationEventHandler) handleCertificationApproved(ctx context.Context, event interfaces.Event) error {
h.logger.Info("认证申请已审核通过",
zap.String("certification_id", event.GetAggregateID()),
zap.String("user_id", h.extractUserID(event)),
)
// 发送通知给用户
message := fmt.Sprintf("🎉 恭喜!您的企业认证申请已审核通过!\n\n认证ID: %s\n审核时间: %s\n\n系统正在为您创建钱包和访问密钥...",
event.GetAggregateID(),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
return h.sendUserNotification(ctx, event, "认证申请审核通过", message)
}
// handleCertificationRejected 处理认证审核拒绝事件
func (h *CertificationEventHandler) handleCertificationRejected(ctx context.Context, event interfaces.Event) error {
h.logger.Info("认证申请已被拒绝",
zap.String("certification_id", event.GetAggregateID()),
zap.String("user_id", h.extractUserID(event)),
)
// 发送通知给用户
message := fmt.Sprintf("❌ 很抱歉,您的企业认证申请未通过审核\n\n认证ID: %s\n拒绝时间: %s\n\n请根据拒绝原因修改后重新提交申请。",
event.GetAggregateID(),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
return h.sendUserNotification(ctx, event, "认证申请审核未通过", message)
}
// handleWalletCreated 处理钱包创建事件
func (h *CertificationEventHandler) handleWalletCreated(ctx context.Context, event interfaces.Event) error {
h.logger.Info("钱包已创建",
zap.String("certification_id", event.GetAggregateID()),
zap.String("user_id", h.extractUserID(event)),
)
// 发送通知给用户
message := fmt.Sprintf("💰 钱包创建成功!\n\n认证ID: %s\n创建时间: %s\n\n您的企业钱包已激活可以开始使用相关服务。",
event.GetAggregateID(),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
return h.sendUserNotification(ctx, event, "钱包创建成功", message)
return h.sendUserNotification(ctx, event, "合同签署成", message)
}
// handleCertificationCompleted 处理认证完成事件
@@ -323,44 +174,26 @@ func (h *CertificationEventHandler) handleCertificationCompleted(ctx context.Con
)
// 发送通知给用户
message := fmt.Sprintf("🎉 恭喜!您的企业认证已全部完成!\n\n认证ID: %s\n完成时间: %s\n\n您现在可以享受完整的企业级服务功能。",
message := fmt.Sprintf("🎉 恭喜!您的企业认证已完成!\n\n认证ID: %s\n完成时间: %s\n\n您现在可以享受企业用户的所有权益。",
event.GetAggregateID(),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
return h.sendUserNotification(ctx, event, "企业认证完成", message)
}
// handleCertificationFailed 处理认证失败事件
func (h *CertificationEventHandler) handleCertificationFailed(ctx context.Context, event interfaces.Event) error {
h.logger.Error("企业认证失败",
zap.String("certification_id", event.GetAggregateID()),
zap.String("user_id", h.extractUserID(event)),
)
// 发送通知给用户
message := fmt.Sprintf("❌ 企业认证流程遇到问题\n\n认证ID: %s\n失败时间: %s\n\n请联系客服获取帮助。",
event.GetAggregateID(),
event.GetTimestamp().Format("2006-01-02 15:04:05"))
return h.sendUserNotification(ctx, event, "企业认证失败", message)
}
// sendUserNotification 发送用户通知
func (h *CertificationEventHandler) sendUserNotification(ctx context.Context, event interfaces.Event, title, message string) error {
url := fmt.Sprintf("https://example.com/certification/%s", event.GetAggregateID())
btnText := "查看详情"
if err := h.notification.SendCardMessage(ctx, title, message, url, btnText); err != nil {
h.logger.Error("发送用户通知失败",
zap.String("event_type", event.GetType()),
zap.String("event_id", event.GetID()),
zap.Error(err),
)
return err
userID := h.extractUserID(event)
if userID == "" {
h.logger.Warn("无法提取用户ID跳过通知发送")
return nil
}
h.logger.Info("用户通知发送成功",
zap.String("event_type", event.GetType()),
zap.String("event_id", event.GetID()),
// 这里可以调用通知服务发送消息
h.logger.Info("发送用户通知",
zap.String("user_id", userID),
zap.String("title", title),
zap.String("message", message),
)
return nil
@@ -368,20 +201,10 @@ func (h *CertificationEventHandler) sendUserNotification(ctx context.Context, ev
// sendAdminNotification 发送管理员通知
func (h *CertificationEventHandler) sendAdminNotification(ctx context.Context, event interfaces.Event, title, message string) error {
url := fmt.Sprintf("https://admin.example.com/certification/%s", event.GetAggregateID())
btnText := "立即处理"
if err := h.notification.SendCardMessage(ctx, title, message, url, btnText); err != nil {
h.logger.Error("发送管理员通知失败",
zap.String("event_type", event.GetType()),
zap.String("event_id", event.GetID()),
zap.Error(err),
)
return err
}
h.logger.Info("管理员通知发送成功",
zap.String("event_type", event.GetType()),
zap.String("event_id", event.GetID()),
// 这里可以调用通知服务发送管理员消息
h.logger.Info("发送管理员通知",
zap.String("title", title),
zap.String("message", message),
)
return nil
@@ -389,29 +212,58 @@ func (h *CertificationEventHandler) sendAdminNotification(ctx context.Context, e
// extractUserID 从事件中提取用户ID
func (h *CertificationEventHandler) extractUserID(event interfaces.Event) string {
if payload, ok := event.GetPayload().(map[string]interface{}); ok {
if userID, exists := payload["user_id"]; exists {
if id, ok := userID.(string); ok {
return id
payload := event.GetPayload()
if payload == nil {
return ""
}
// 尝试从payload中提取user_id
if data, ok := payload.(map[string]interface{}); ok {
if userID, exists := data["user_id"]; exists {
if str, ok := userID.(string); ok {
return str
}
}
}
// 尝试从事件数据中提取
if eventData, ok := event.(*BaseCertificationEvent); ok {
if data, ok := eventData.Payload.(map[string]interface{}); ok {
if userID, exists := data["user_id"]; exists {
if id, ok := userID.(string); ok {
return id
// 尝试从JSON中解析
if data, ok := payload.(map[string]interface{}); ok {
if dataField, exists := data["data"]; exists {
if dataMap, ok := dataField.(map[string]interface{}); ok {
if userID, exists := dataMap["user_id"]; exists {
if str, ok := userID.(string); ok {
return str
}
}
}
}
}
return "unknown"
// 尝试从JSON字符串解析
if jsonData, err := json.Marshal(payload); err == nil {
var data map[string]interface{}
if err := json.Unmarshal(jsonData, &data); err == nil {
if userID, exists := data["user_id"]; exists {
if str, ok := userID.(string); ok {
return str
}
}
if dataField, exists := data["data"]; exists {
if dataMap, ok := dataField.(map[string]interface{}); ok {
if userID, exists := dataMap["user_id"]; exists {
if str, ok := userID.(string); ok {
return str
}
}
}
}
}
}
return ""
}
// LoggingEventHandler 日志记录事件处理器
// LoggingEventHandler 日志事件处理器
type LoggingEventHandler struct {
logger *zap.Logger
name string
@@ -419,29 +271,20 @@ type LoggingEventHandler struct {
isAsync bool
}
// NewLoggingEventHandler 创建日志记录事件处理器
// NewLoggingEventHandler 创建日志事件处理器
func NewLoggingEventHandler(logger *zap.Logger) *LoggingEventHandler {
return &LoggingEventHandler{
logger: logger,
name: "logging-event-handler",
eventTypes: []string{
EventTypeCertificationCreated,
EventTypeCertificationSubmitted,
EventTypeLicenseUploaded,
EventTypeOCRCompleted,
EventTypeEnterpriseInfoConfirmed,
EventTypeFaceVerifyInitiated,
EventTypeFaceVerifyCompleted,
EventTypeContractRequested,
EventTypeContractGenerated,
EventTypeEnterpriseInfoSubmitted,
EventTypeEnterpriseVerified,
EventTypeContractApplied,
EventTypeContractSigned,
EventTypeCertificationApproved,
EventTypeCertificationRejected,
EventTypeWalletCreated,
EventTypeCertificationCompleted,
EventTypeCertificationFailed,
},
isAsync: false, // 同步处理,确保日志及时记录
isAsync: false,
}
}
@@ -463,27 +306,21 @@ func (l *LoggingEventHandler) IsAsync() bool {
// GetRetryConfig 获取重试配置
func (l *LoggingEventHandler) GetRetryConfig() interfaces.RetryConfig {
return interfaces.RetryConfig{
MaxRetries: 1,
RetryDelay: 1 * time.Second,
MaxRetries: 0,
RetryDelay: 0,
BackoffFactor: 1.0,
MaxDelay: 1 * time.Second,
MaxDelay: 0,
}
}
// Handle 处理事件
func (l *LoggingEventHandler) Handle(ctx context.Context, event interfaces.Event) error {
// 记录结构化日志
eventData, _ := json.Marshal(event.GetPayload())
l.logger.Info("认证事件记录",
zap.String("event_id", event.GetID()),
l.logger.Info("认证事件日志",
zap.String("event_type", event.GetType()),
zap.String("event_id", event.GetID()),
zap.String("aggregate_id", event.GetAggregateID()),
zap.String("aggregate_type", event.GetAggregateType()),
zap.Time("timestamp", event.GetTimestamp()),
zap.String("source", event.GetSource()),
zap.String("payload", string(eventData)),
zap.Any("payload", event.GetPayload()),
)
return nil
}

View File

@@ -12,7 +12,6 @@ type CertificationStats struct {
TotalCertifications int64
PendingCertifications int64
CompletedCertifications int64
RejectedCertifications int64
TodaySubmissions int64
}
@@ -24,75 +23,69 @@ type CertificationRepository interface {
GetByUserID(ctx context.Context, userID string) (*entities.Certification, error)
GetByStatus(ctx context.Context, status string) ([]*entities.Certification, error)
GetPendingCertifications(ctx context.Context) ([]*entities.Certification, error)
GetByAuthFlowID(ctx context.Context, authFlowID string) (entities.Certification, error)
GetByEsignFlowID(ctx context.Context, esignFlowID string) (entities.Certification, error)
// 复杂查询 - 使用查询参数
ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) ([]*entities.Certification, int64, error)
// 业务操作
UpdateStatus(ctx context.Context, certificationID string, status string, adminID *string, notes string) error
UpdateStatus(ctx context.Context, certificationID string, status string) error
// 统计信息
GetStats(ctx context.Context) (*CertificationStats, error)
GetStatsByDateRange(ctx context.Context, startDate, endDate string) (*CertificationStats, error)
}
// FaceVerifyRecordRepository 人脸识别记录仓储接口
type FaceVerifyRecordRepository interface {
interfaces.Repository[entities.FaceVerifyRecord]
// EnterpriseInfoSubmitRecordRepository 企业信息提交记录仓储接口
type EnterpriseInfoSubmitRecordRepository interface {
interfaces.Repository[entities.EnterpriseInfoSubmitRecord]
// 基础查询 - 直接使用实体
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.FaceVerifyRecord, error)
GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.FaceVerifyRecord, error)
// 基础查询
GetByUserID(ctx context.Context, userID string) ([]*entities.EnterpriseInfoSubmitRecord, error)
GetLatestByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error)
// 复杂查询 - 使用查询参数
ListRecords(ctx context.Context, query *queries.ListFaceVerifyRecordsQuery) ([]*entities.FaceVerifyRecord, int64, error)
// 统计信息
GetSuccessRate(ctx context.Context, days int) (float64, error)
}
// ContractRecordRepository 合同记录仓储接口
type ContractRecordRepository interface {
interfaces.Repository[entities.ContractRecord]
// 基础查询 - 直接使用实体
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.ContractRecord, error)
GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.ContractRecord, error)
// 复杂查询 - 使用查询参数
ListRecords(ctx context.Context, query *queries.ListContractRecordsQuery) ([]*entities.ContractRecord, int64, error)
// 复杂查询
ListRecords(ctx context.Context, query *queries.ListEnterpriseInfoSubmitRecordsQuery) ([]*entities.EnterpriseInfoSubmitRecord, int64, error)
// 业务操作
UpdateContractStatus(ctx context.Context, recordID string, status string, adminID *string, notes string) error
UpdateStatus(ctx context.Context, recordID string, status string, reason string) error
}
// LicenseUploadRecordRepository 营业执照上传记录仓储接口
type LicenseUploadRecordRepository interface {
interfaces.Repository[entities.LicenseUploadRecord]
// EsignContractGenerateRecordRepository e签宝生成合同记录仓储接口
type EsignContractGenerateRecordRepository interface {
interfaces.Repository[entities.EsignContractGenerateRecord]
// 基础查询 - 直接使用实体
GetByCertificationID(ctx context.Context, certificationID string) (*entities.LicenseUploadRecord, error)
// 基础查询
GetByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractGenerateRecord, error)
GetByUserID(ctx context.Context, userID string) ([]*entities.EsignContractGenerateRecord, error)
GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractGenerateRecord, error)
// 复杂查询 - 使用查询参数
ListRecords(ctx context.Context, query *queries.ListLicenseUploadRecordsQuery) ([]*entities.LicenseUploadRecord, int64, error)
// 复杂查询
ListRecords(ctx context.Context, query *queries.ListEsignContractGenerateRecordsQuery) ([]*entities.EsignContractGenerateRecord, int64, error)
// 业务操作
UpdateOCRResult(ctx context.Context, recordID string, ocrResult string, confidence float64) error
UpdateStatus(ctx context.Context, recordID string, status string, reason string) error
UpdateSuccessInfo(ctx context.Context, recordID, esignFlowID, contractFileID, contractURL string) error
IncrementRetry(ctx context.Context, recordID string) error
}
// NotificationRecordRepository 通知记录仓储接口
type NotificationRecordRepository interface {
interfaces.Repository[entities.NotificationRecord]
// EsignContractSignRecordRepository e签宝签署合同记录仓储接口
type EsignContractSignRecordRepository interface {
interfaces.Repository[entities.EsignContractSignRecord]
// 基础查询 - 直接使用实体
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.NotificationRecord, error)
GetUnreadByUserID(ctx context.Context, userID string) ([]*entities.NotificationRecord, error)
// 基础查询
GetByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractSignRecord, error)
GetByUserID(ctx context.Context, userID string) ([]*entities.EsignContractSignRecord, error)
GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractSignRecord, error)
GetByGenerateRecordID(ctx context.Context, generateRecordID string) (*entities.EsignContractSignRecord, error)
// 复杂查询 - 使用查询参数
ListRecords(ctx context.Context, query *queries.ListNotificationRecordsQuery) ([]*entities.NotificationRecord, int64, error)
// 复杂查询
ListRecords(ctx context.Context, query *queries.ListEsignContractSignRecordsQuery) ([]*entities.EsignContractSignRecord, int64, error)
// 批量操作
BatchCreate(ctx context.Context, records []entities.NotificationRecord) error
MarkAsRead(ctx context.Context, recordIDs []string) error
MarkAllAsReadByUser(ctx context.Context, userID string) error
// 业务操作
UpdateStatus(ctx context.Context, recordID string, status string, reason string) error
UpdateSuccessInfo(ctx context.Context, recordID, signedFileURL string) error
SetSignURL(ctx context.Context, recordID, signURL string) error
IncrementRetry(ctx context.Context, recordID string) error
MarkExpiredRecords(ctx context.Context) error
}

View File

@@ -8,7 +8,6 @@ type ListCertificationsQuery struct {
PageSize int `json:"page_size"`
UserID string `json:"user_id"`
Status enums.CertificationStatus `json:"status"`
AdminID string `json:"admin_id"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
EnterpriseName string `json:"enterprise_name"`
@@ -26,47 +25,38 @@ type ListEnterprisesQuery struct {
EndDate string `json:"end_date"`
}
// ListFaceVerifyRecordsQuery 人脸识别记录列表查询参数
type ListFaceVerifyRecordsQuery struct {
// ListEnterpriseInfoSubmitRecordsQuery 企业信息提交记录列表查询参数
type ListEnterpriseInfoSubmitRecordsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
Status string `json:"status"`
CompanyName string `json:"company_name"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
}
// ListContractRecordsQuery 合同记录列表查询参数
type ListContractRecordsQuery struct {
// ListEsignContractGenerateRecordsQuery e签宝生成合同记录列表查询参数
type ListEsignContractGenerateRecordsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
Status string `json:"status"`
ContractType string `json:"contract_type"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
}
// ListLicenseUploadRecordsQuery 营业执照上传记录列表查询参数
type ListLicenseUploadRecordsQuery struct {
// ListEsignContractSignRecordsQuery e签宝签署合同记录列表查询参数
type ListEsignContractSignRecordsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
Status string `json:"status"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
}
// ListNotificationRecordsQuery 通知记录列表查询参数
type ListNotificationRecordsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
Type string `json:"type"`
IsRead *bool `json:"is_read"`
SignerName string `json:"signer_name"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
}

View File

@@ -0,0 +1,126 @@
package services
import (
"context"
"fmt"
"go.uber.org/zap"
cert_entities "tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/repositories"
user_entities "tyapi-server/internal/domains/user/entities"
"tyapi-server/internal/shared/esign"
)
// CertificationEsignService 负责与e签宝相关的认证业务逻辑
type CertificationEsignService struct {
certRepo repositories.CertificationRepository
esignClient *esign.Client
esignContractGenerateRecordRepo repositories.EsignContractGenerateRecordRepository
esignContractSignRecordRepo repositories.EsignContractSignRecordRepository
logger *zap.Logger
}
// NewCertificationEsignService 创建CertificationEsignService实例
func NewCertificationEsignService(
certRepo repositories.CertificationRepository,
esignClient *esign.Client,
esignContractGenerateRecordRepo repositories.EsignContractGenerateRecordRepository,
logger *zap.Logger,
) *CertificationEsignService {
return &CertificationEsignService{
certRepo: certRepo,
esignClient: esignClient,
esignContractGenerateRecordRepo: esignContractGenerateRecordRepo,
logger: logger,
}
}
// FillTemplate 生成合同文件e签宝模板填充
func (s *CertificationEsignService) FillTemplate(ctx context.Context, certification *cert_entities.Certification, components map[string]string) (*esign.FillTemplate, error) {
resp, err := s.esignClient.FillTemplate(components)
record := &cert_entities.EsignContractGenerateRecord{
CertificationID: certification.ID,
UserID: certification.UserID,
}
if err != nil {
s.logger.Error("生成合同文件失败", zap.Any("components", components), zap.Error(err))
record.Status = "failed"
} else {
record.TemplateID = resp.TemplateID
record.ContractName = resp.FileName
record.ContractFileID = resp.FileID
record.ContractURL = resp.FileDownloadUrl
record.Status = "success"
record.FillTime = &resp.FillTime
}
if _, createErr := s.esignContractGenerateRecordRepo.Create(ctx, *record); createErr != nil {
s.logger.Error("创建合同生成记录失败", zap.Error(createErr))
if err == nil {
return nil, fmt.Errorf("创建合同生成记录失败: %w", createErr)
}
}
if err != nil {
return nil, fmt.Errorf("生成合同文件失败: %w", err)
}
certification.ContractURL = resp.FileDownloadUrl
certification.ContractFileID = resp.FileID
err = s.certRepo.Update(ctx, *certification)
if err != nil {
s.logger.Error("更新认证申请失败", zap.Error(err))
return nil, fmt.Errorf("更新认证申请失败: %w", err)
}
s.logger.Info("生成合同文件成功", zap.String("template_id", resp.TemplateID), zap.String("file_id", resp.FileID))
return resp, nil
}
// 发起签署
func (s *CertificationEsignService) InitiateSign(ctx context.Context, certification *cert_entities.Certification, enterpriseInfo *user_entities.EnterpriseInfo) (*cert_entities.EsignContractSignRecord, error) {
// 发起签署流程
flowID, err := s.esignClient.CreateSignFlow(&esign.CreateSignFlowRequest{
FileID: certification.ContractFileID,
SignerAccount: enterpriseInfo.UnifiedSocialCode,
SignerName: enterpriseInfo.CompanyName,
TransactorPhone: enterpriseInfo.LegalPersonPhone,
TransactorName: enterpriseInfo.LegalPersonName,
TransactorIDCardNum: enterpriseInfo.LegalPersonID,
})
if err != nil {
s.logger.Error("获取签署链接失败",
zap.String("user_id", enterpriseInfo.UserID),
zap.Error(err),
)
return nil, fmt.Errorf("获取签署链接失败: %w", err)
}
signURL, shortURL, err := s.esignClient.GetSignURL(flowID, enterpriseInfo.UnifiedSocialCode, enterpriseInfo.CompanyName)
if err != nil {
s.logger.Error("获取签署链接失败", zap.Error(err))
return nil, fmt.Errorf("获取签署链接失败: %w", err)
}
esignContractSignRecord := cert_entities.NewEsignContractSignRecord(
certification.ID,
enterpriseInfo.UserID,
flowID,
certification.ContractFileID,
enterpriseInfo.UnifiedSocialCode,
enterpriseInfo.LegalPersonPhone,
enterpriseInfo.LegalPersonID,
signURL,
shortURL,
)
signContractSignRecord, err := s.esignContractSignRecordRepo.Create(ctx, *esignContractSignRecord)
if err != nil {
s.logger.Error("创建签署记录失败", zap.Error(err))
return nil, fmt.Errorf("创建签署记录失败: %w", err)
}
certification.EsignFlowID = signContractSignRecord.EsignFlowID
certification.ContractSignURL = signContractSignRecord.SignShortURL // 记录的是短链接
err = s.certRepo.Update(ctx, *certification)
if err != nil {
s.logger.Error("更新认证申请失败", zap.Error(err))
return nil, fmt.Errorf("更新认证申请失败: %w", err)
}
return &signContractSignRecord, nil
}

View File

@@ -0,0 +1,153 @@
package services
import (
"context"
"fmt"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
esign_service "tyapi-server/internal/shared/esign"
"go.uber.org/zap"
)
// CertificationManagementService 认证管理领域服务
// 负责认证申请的生命周期管理,包括创建、状态转换、进度查询等
type CertificationManagementService struct {
certRepo repositories.CertificationRepository
esignService *esign_service.Client
stateMachine *CertificationStateMachine
logger *zap.Logger
}
// NewCertificationManagementService 创建认证管理领域服务
func NewCertificationManagementService(
certRepo repositories.CertificationRepository,
esignService *esign_service.Client,
stateMachine *CertificationStateMachine,
logger *zap.Logger,
) *CertificationManagementService {
return &CertificationManagementService{
certRepo: certRepo,
esignService: esignService,
stateMachine: stateMachine,
logger: logger,
}
}
// CreateCertification 创建认证申请
func (s *CertificationManagementService) 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
}
// GetCertificationByUserID 根据用户ID获取认证申请
func (s *CertificationManagementService) GetCertificationByUserID(ctx context.Context, userID string) (*entities.Certification, error) {
return s.certRepo.GetByUserID(ctx, userID)
}
// GetCertificationByID 根据ID获取认证申请
func (s *CertificationManagementService) 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
}
// GetCertificationByAuthFlowID 根据认证流程ID获取认证申请
func (s *CertificationManagementService) GetCertificationByAuthFlowID(ctx context.Context, authFlowID string) (*entities.Certification, error) {
cert, err := s.certRepo.GetByAuthFlowID(ctx, authFlowID)
if err != nil {
return nil, err
}
return &cert, nil
}
// 根据签署流程ID获取认证申请
func (s *CertificationManagementService) GetCertificationByEsignFlowID(ctx context.Context, esignFlowID string) (*entities.Certification, error) {
cert, err := s.certRepo.GetByEsignFlowID(ctx, esignFlowID)
if err != nil {
return nil, err
}
return &cert, nil
}
// GetCertificationProgress 获取认证进度信息
func (s *CertificationManagementService) 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(),
"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.EnterpriseVerifiedAt != nil {
progress["enterprise_verified_at"] = cert.EnterpriseVerifiedAt
}
if cert.ContractAppliedAt != nil {
progress["contract_applied_at"] = cert.ContractAppliedAt
}
if cert.ContractSignedAt != nil {
progress["contract_signed_at"] = cert.ContractSignedAt
}
if cert.CompletedAt != nil {
progress["completed_at"] = cert.CompletedAt
}
return progress, nil
}
// 通过e签宝检查是否有过认证
func (s *CertificationManagementService) CheckCertification(ctx context.Context, companyName string, unifiedSocialCode string) (bool, error) {
// 查询企业是否已经过认证
queryOrgIdentityInfo := &esign_service.QueryOrgIdentityRequest{
OrgName: companyName,
OrgIDCardNum: unifiedSocialCode,
OrgIDCardType: esign_service.OrgIDCardTypeUSCC,
}
queryOrgIdentityResponse, err := s.esignService.QueryOrgIdentityInfo(queryOrgIdentityInfo)
if err != nil {
return false, fmt.Errorf("查询机构认证信息失败: %w", err)
}
if queryOrgIdentityResponse.Data.RealnameStatus == 1 {
s.logger.Info("该企业已进行过认证", zap.String("company_name", companyName), zap.String("unified_social_code", unifiedSocialCode))
return true, nil
}
return false, nil
}

View File

@@ -1,774 +0,0 @@
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"
}

View File

@@ -0,0 +1,162 @@
package services
import (
"context"
"fmt"
"go.uber.org/zap"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
)
// CertificationWorkflowService 认证工作流领域服务
// 负责认证流程的状态转换和业务逻辑处理
type CertificationWorkflowService struct {
certRepo repositories.CertificationRepository
stateMachine *CertificationStateMachine
logger *zap.Logger
}
// NewCertificationWorkflowService 创建认证工作流领域服务
func NewCertificationWorkflowService(
certRepo repositories.CertificationRepository,
stateMachine *CertificationStateMachine,
logger *zap.Logger,
) *CertificationWorkflowService {
return &CertificationWorkflowService{
certRepo: certRepo,
stateMachine: stateMachine,
logger: logger,
}
}
// SubmitEnterpriseInfo 提交企业信息
func (s *CertificationWorkflowService) 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 && cert.Status != enums.StatusInfoSubmitted {
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
}
// CompleteEnterpriseVerification 完成企业认证
func (s *CertificationWorkflowService) CompleteEnterpriseVerification(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.StatusInfoSubmitted {
return fmt.Errorf("当前状态不允许完成企业认证")
}
// 使用状态机转换状态
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusEnterpriseVerified, 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
}
// ApplyContract 申请签署合同
func (s *CertificationWorkflowService) 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.StatusEnterpriseVerified {
return fmt.Errorf("当前状态不允许申请签署合同")
}
// 使用状态机转换状态
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractApplied, 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
}
// CompleteContractSign 完成合同签署
func (s *CertificationWorkflowService) 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.StatusContractApplied {
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),
zap.String("user_id", cert.UserID),
)
return nil
}
// CompleteCertification 完成认证
func (s *CertificationWorkflowService) 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
}

View File

@@ -0,0 +1,180 @@
package services
import (
"context"
"fmt"
"go.uber.org/zap"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/repositories"
)
// EnterpriseInfoSubmitRecordService 企业信息提交记录领域服务
// 负责企业信息提交记录的业务逻辑处理
type EnterpriseInfoSubmitRecordService struct {
enterpriseRecordRepo repositories.EnterpriseInfoSubmitRecordRepository
logger *zap.Logger
}
// NewEnterpriseInfoSubmitRecordService 创建企业信息提交记录领域服务
func NewEnterpriseInfoSubmitRecordService(
enterpriseRecordRepo repositories.EnterpriseInfoSubmitRecordRepository,
logger *zap.Logger,
) *EnterpriseInfoSubmitRecordService {
return &EnterpriseInfoSubmitRecordService{
enterpriseRecordRepo: enterpriseRecordRepo,
logger: logger,
}
}
// CreateEnterpriseInfoSubmitRecord 创建企业信息提交记录
func (s *EnterpriseInfoSubmitRecordService) CreateEnterpriseInfoSubmitRecord(
ctx context.Context,
userID string,
companyName string,
unifiedSocialCode string,
legalPersonName string,
legalPersonID string,
legalPersonPhone string,
) (*entities.EnterpriseInfoSubmitRecord, error) {
// 创建企业信息提交记录实体
record := entities.NewEnterpriseInfoSubmitRecord(
userID,
companyName,
unifiedSocialCode,
legalPersonName,
legalPersonID,
legalPersonPhone,
)
// 保存到仓储
createdRecord, err := s.enterpriseRecordRepo.Create(ctx, *record)
if err != nil {
s.logger.Error("创建企业信息提交记录失败",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("创建企业信息提交记录失败: %w", err)
}
s.logger.Info("企业信息提交记录创建成功",
zap.String("record_id", createdRecord.ID),
zap.String("user_id", userID),
zap.String("company_name", companyName))
return &createdRecord, nil
}
// GetLatestByUserID 根据用户ID获取最新的企业信息提交记录
func (s *EnterpriseInfoSubmitRecordService) GetLatestByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error) {
record, err := s.enterpriseRecordRepo.GetLatestByUserID(ctx, userID)
if err != nil {
s.logger.Error("获取企业信息提交记录失败",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("获取企业信息提交记录失败: %w", err)
}
return record, nil
}
// UpdateEnterpriseInfoSubmitRecord 更新企业信息提交记录
func (s *EnterpriseInfoSubmitRecordService) UpdateEnterpriseInfoSubmitRecord(ctx context.Context, record *entities.EnterpriseInfoSubmitRecord) error {
err := s.enterpriseRecordRepo.Update(ctx, *record)
if err != nil {
s.logger.Error("更新企业信息提交记录失败",
zap.String("record_id", record.ID),
zap.Error(err))
return fmt.Errorf("更新企业信息提交记录失败: %w", err)
}
s.logger.Info("企业信息提交记录更新成功",
zap.String("record_id", record.ID),
zap.String("status", record.Status))
return nil
}
// MarkAsVerified 标记企业信息为已验证
func (s *EnterpriseInfoSubmitRecordService) MarkAsVerified(ctx context.Context, recordID string) error {
record, err := s.enterpriseRecordRepo.GetByID(ctx, recordID)
if err != nil {
return fmt.Errorf("获取企业信息提交记录失败: %w", err)
}
record.MarkAsVerified()
err = s.enterpriseRecordRepo.Update(ctx, record)
if err != nil {
s.logger.Error("标记企业信息为已验证失败",
zap.String("record_id", recordID),
zap.Error(err))
return fmt.Errorf("标记企业信息为已验证失败: %w", err)
}
s.logger.Info("企业信息标记为已验证成功",
zap.String("record_id", recordID))
return nil
}
// UpdateVerificationStatus 更新企业信息验证状态
func (s *EnterpriseInfoSubmitRecordService) UpdateVerificationStatus(ctx context.Context, userID string, isVerified bool, reason string) error {
// 获取用户最新的企业信息提交记录
record, err := s.enterpriseRecordRepo.GetLatestByUserID(ctx, userID)
if err != nil {
return fmt.Errorf("获取企业信息提交记录失败: %w", err)
}
// 更新验证状态
if isVerified {
record.MarkAsVerified()
} else {
record.MarkAsFailed(reason)
}
// 保存更新
err = s.enterpriseRecordRepo.Update(ctx, *record)
if err != nil {
s.logger.Error("更新企业信息验证状态失败",
zap.String("user_id", userID),
zap.Bool("is_verified", isVerified),
zap.Error(err))
return fmt.Errorf("更新企业信息验证状态失败: %w", err)
}
s.logger.Info("企业信息验证状态更新成功",
zap.String("user_id", userID),
zap.Bool("is_verified", isVerified),
zap.String("reason", reason))
return nil
}
// DeleteEnterpriseInfoSubmitRecord 删除企业信息提交记录
func (s *EnterpriseInfoSubmitRecordService) DeleteEnterpriseInfoSubmitRecord(ctx context.Context, recordID string) error {
err := s.enterpriseRecordRepo.Delete(ctx, recordID)
if err != nil {
s.logger.Error("删除企业信息提交记录失败",
zap.String("record_id", recordID),
zap.Error(err))
return fmt.Errorf("删除企业信息提交记录失败: %w", err)
}
s.logger.Info("企业信息提交记录删除成功",
zap.String("record_id", recordID))
return nil
}
// GetByUserID 根据用户ID获取企业信息提交记录列表
func (s *EnterpriseInfoSubmitRecordService) GetByUserID(ctx context.Context, userID string) ([]*entities.EnterpriseInfoSubmitRecord, error) {
records, err := s.enterpriseRecordRepo.GetByUserID(ctx, userID)
if err != nil {
s.logger.Error("获取用户企业信息提交记录失败",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("获取用户企业信息提交记录失败: %w", err)
}
return records, nil
}

View File

@@ -0,0 +1,256 @@
package services
import (
"tyapi-server/internal/domains/certification/enums"
)
// StateConfig 状态配置
type StateConfig struct {
Status enums.CertificationStatus `json:"status"`
Name string `json:"name"`
ProgressPercentage int `json:"progress_percentage"`
IsUserActionRequired bool `json:"is_user_action_required"`
IsAdminActionRequired bool `json:"is_admin_action_required"`
TimestampField string `json:"timestamp_field,omitempty"`
Description string `json:"description"`
NextValidStatuses []enums.CertificationStatus `json:"next_valid_statuses"`
}
// TransitionConfig 状态转换配置
type TransitionConfig struct {
From enums.CertificationStatus `json:"from"`
To enums.CertificationStatus `json:"to"`
Action string `json:"action"`
ActionName string `json:"action_name"`
AllowUser bool `json:"allow_user"`
AllowAdmin bool `json:"allow_admin"`
RequiresValidation bool `json:"requires_validation"`
Description string `json:"description"`
}
// CertificationStateManager 认证状态管理器
type CertificationStateManager struct {
stateMap map[enums.CertificationStatus]*StateConfig
transitionMap map[enums.CertificationStatus][]*TransitionConfig
}
// NewCertificationStateManager 创建认证状态管理器
func NewCertificationStateManager() *CertificationStateManager {
manager := &CertificationStateManager{
stateMap: make(map[enums.CertificationStatus]*StateConfig),
transitionMap: make(map[enums.CertificationStatus][]*TransitionConfig),
}
// 初始化状态配置
manager.initStateConfigs()
return manager
}
// initStateConfigs 初始化状态配置
func (manager *CertificationStateManager) initStateConfigs() {
// 状态配置
states := []*StateConfig{
{
Status: enums.StatusPending,
Name: "待认证",
ProgressPercentage: 0,
IsUserActionRequired: true,
IsAdminActionRequired: false,
Description: "等待用户提交企业信息",
NextValidStatuses: []enums.CertificationStatus{enums.StatusInfoSubmitted},
},
{
Status: enums.StatusInfoSubmitted,
Name: "已提交企业信息",
ProgressPercentage: 20,
IsUserActionRequired: true,
IsAdminActionRequired: false,
TimestampField: "InfoSubmittedAt",
Description: "用户已提交企业信息",
NextValidStatuses: []enums.CertificationStatus{enums.StatusEnterpriseVerified, enums.StatusInfoSubmitted}, // 可以重新提交
},
{
Status: enums.StatusEnterpriseVerified,
Name: "已企业认证",
ProgressPercentage: 40,
IsUserActionRequired: true,
IsAdminActionRequired: false,
TimestampField: "EnterpriseVerifiedAt",
Description: "企业认证已完成",
NextValidStatuses: []enums.CertificationStatus{enums.StatusContractApplied},
},
{
Status: enums.StatusContractApplied,
Name: "已申请合同",
ProgressPercentage: 60,
IsUserActionRequired: true,
IsAdminActionRequired: false,
TimestampField: "ContractAppliedAt",
Description: "合同已申请",
NextValidStatuses: []enums.CertificationStatus{enums.StatusContractSigned},
},
{
Status: enums.StatusContractSigned,
Name: "已签署合同",
ProgressPercentage: 80,
IsUserActionRequired: false,
IsAdminActionRequired: false,
TimestampField: "ContractSignedAt",
Description: "合同已签署",
NextValidStatuses: []enums.CertificationStatus{enums.StatusCompleted},
},
{
Status: enums.StatusCompleted,
Name: "认证完成",
ProgressPercentage: 100,
IsUserActionRequired: false,
IsAdminActionRequired: false,
TimestampField: "CompletedAt",
Description: "认证流程已完成",
NextValidStatuses: []enums.CertificationStatus{},
},
}
// 转换配置
transitions := []*TransitionConfig{
// 提交企业信息
{
From: enums.StatusPending,
To: enums.StatusInfoSubmitted,
Action: "submit_info",
ActionName: "提交企业信息",
AllowUser: true,
AllowAdmin: false,
RequiresValidation: true,
Description: "用户提交企业信息",
},
// 重新提交企业信息
{
From: enums.StatusInfoSubmitted,
To: enums.StatusInfoSubmitted,
Action: "resubmit_info",
ActionName: "重新提交企业信息",
AllowUser: true,
AllowAdmin: false,
RequiresValidation: true,
Description: "用户重新提交企业信息",
},
// 企业认证
{
From: enums.StatusInfoSubmitted,
To: enums.StatusEnterpriseVerified,
Action: "enterprise_verify",
ActionName: "企业认证",
AllowUser: true,
AllowAdmin: false,
RequiresValidation: true,
Description: "用户完成企业认证",
},
// 申请合同
{
From: enums.StatusEnterpriseVerified,
To: enums.StatusContractApplied,
Action: "apply_contract",
ActionName: "申请合同",
AllowUser: true,
AllowAdmin: false,
RequiresValidation: false,
Description: "用户申请合同",
},
// 签署合同
{
From: enums.StatusContractApplied,
To: enums.StatusContractSigned,
Action: "sign_contract",
ActionName: "签署合同",
AllowUser: true,
AllowAdmin: false,
RequiresValidation: true,
Description: "用户签署合同",
},
// 完成认证
{
From: enums.StatusContractSigned,
To: enums.StatusCompleted,
Action: "complete",
ActionName: "完成认证",
AllowUser: false,
AllowAdmin: false,
RequiresValidation: false,
Description: "系统自动完成认证",
},
}
// 构建映射
for _, state := range states {
manager.stateMap[state.Status] = state
}
for _, transition := range transitions {
manager.transitionMap[transition.From] = append(manager.transitionMap[transition.From], transition)
}
}
// GetStateConfig 获取状态配置
func (manager *CertificationStateManager) GetStateConfig(status enums.CertificationStatus) *StateConfig {
return manager.stateMap[status]
}
// GetTransitionConfigs 获取状态转换配置
func (manager *CertificationStateManager) GetTransitionConfigs(from enums.CertificationStatus) []*TransitionConfig {
return manager.transitionMap[from]
}
// CanTransition 检查是否可以转换
func (manager *CertificationStateManager) CanTransition(from enums.CertificationStatus, to enums.CertificationStatus, isUser bool, isAdmin bool) (bool, string) {
transitions := manager.GetTransitionConfigs(from)
for _, transition := range transitions {
if transition.To == to {
if isUser && !transition.AllowUser {
return false, "用户不允许执行此操作"
}
if isAdmin && !transition.AllowAdmin {
return false, "管理员不允许执行此操作"
}
if !isUser && !isAdmin && (transition.AllowUser || transition.AllowAdmin) {
return false, "此操作需要用户或管理员权限"
}
return true, ""
}
}
return false, "不支持的状态转换"
}
// GetProgressPercentage 获取进度百分比
func (manager *CertificationStateManager) GetProgressPercentage(status enums.CertificationStatus) int {
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
return stateConfig.ProgressPercentage
}
return 0
}
// IsUserActionRequired 检查是否需要用户操作
func (manager *CertificationStateManager) IsUserActionRequired(status enums.CertificationStatus) bool {
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
return stateConfig.IsUserActionRequired
}
return false
}
// IsAdminActionRequired 检查是否需要管理员操作
func (manager *CertificationStateManager) IsAdminActionRequired(status enums.CertificationStatus) bool {
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
return stateConfig.IsAdminActionRequired
}
return false
}
// GetNextValidStatuses 获取下一个有效状态
func (manager *CertificationStateManager) GetNextValidStatuses(status enums.CertificationStatus) []enums.CertificationStatus {
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
return stateConfig.NextValidStatuses
}
return []enums.CertificationStatus{}
}

View File

@@ -12,21 +12,11 @@ import (
"go.uber.org/zap"
)
// StateTransition 状态转换规则
type StateTransition struct {
From enums.CertificationStatus
To enums.CertificationStatus
Action string
AllowUser bool // 是否允许用户操作
AllowAdmin bool // 是否允许管理员操作
RequiresValidation bool // 是否需要额外验证
}
// CertificationStateMachine 认证状态机
type CertificationStateMachine struct {
transitions map[enums.CertificationStatus][]StateTransition
certRepo repositories.CertificationRepository
logger *zap.Logger
stateManager *CertificationStateManager
certRepo repositories.CertificationRepository
logger *zap.Logger
}
// NewCertificationStateMachine 创建认证状态机
@@ -34,41 +24,10 @@ func NewCertificationStateMachine(
certRepo repositories.CertificationRepository,
logger *zap.Logger,
) *CertificationStateMachine {
sm := &CertificationStateMachine{
transitions: make(map[enums.CertificationStatus][]StateTransition),
certRepo: certRepo,
logger: logger,
}
// 初始化状态转换规则
sm.initializeTransitions()
return sm
}
// initializeTransitions 初始化状态转换规则
func (sm *CertificationStateMachine) initializeTransitions() {
transitions := []StateTransition{
// 正常流程转换
{enums.StatusPending, enums.StatusInfoSubmitted, "submit_info", true, false, true},
{enums.StatusInfoSubmitted, enums.StatusFaceVerified, "face_verify", true, false, true},
{enums.StatusFaceVerified, enums.StatusContractApplied, "apply_contract", true, false, false},
{enums.StatusContractApplied, enums.StatusContractPending, "system_process", false, false, false},
{enums.StatusContractPending, enums.StatusContractApproved, "admin_approve", false, true, true},
{enums.StatusContractApproved, enums.StatusContractSigned, "user_sign", true, false, true},
{enums.StatusContractSigned, enums.StatusCompleted, "system_complete", false, false, false},
// 失败和重试转换
{enums.StatusInfoSubmitted, enums.StatusFaceFailed, "face_fail", false, false, false},
{enums.StatusFaceFailed, enums.StatusFaceVerified, "retry_face", true, false, true},
{enums.StatusContractPending, enums.StatusRejected, "admin_reject", false, true, true},
{enums.StatusRejected, enums.StatusInfoSubmitted, "restart_process", true, false, false},
{enums.StatusContractApproved, enums.StatusSignFailed, "sign_fail", false, false, false},
{enums.StatusSignFailed, enums.StatusContractSigned, "retry_sign", true, false, true},
}
// 构建状态转换映射
for _, transition := range transitions {
sm.transitions[transition.From] = append(sm.transitions[transition.From], transition)
return &CertificationStateMachine{
stateManager: NewCertificationStateManager(),
certRepo: certRepo,
logger: logger,
}
}
@@ -79,27 +38,7 @@ func (sm *CertificationStateMachine) CanTransition(
isUser bool,
isAdmin bool,
) (bool, string) {
validTransitions, exists := sm.transitions[from]
if !exists {
return false, "当前状态不支持任何转换"
}
for _, transition := range validTransitions {
if transition.To == to {
if isUser && !transition.AllowUser {
return false, "用户不允许执行此操作"
}
if isAdmin && !transition.AllowAdmin {
return false, "管理员不允许执行此操作"
}
if !isUser && !isAdmin && (transition.AllowUser || transition.AllowAdmin) {
return false, "此操作需要用户或管理员权限"
}
return true, ""
}
}
return false, "不支持的状态转换"
return sm.stateManager.CanTransition(from, to, isUser, isAdmin)
}
// TransitionTo 执行状态转换
@@ -123,11 +62,6 @@ func (sm *CertificationStateMachine) TransitionTo(
return fmt.Errorf("状态转换失败: %s", reason)
}
// 执行状态转换前的验证
if err := sm.validateTransition(ctx, &cert, targetStatus, metadata); err != nil {
return fmt.Errorf("状态转换验证失败: %w", err)
}
// 更新状态和时间戳
oldStatus := cert.Status
cert.Status = targetStatus
@@ -154,20 +88,23 @@ func (sm *CertificationStateMachine) TransitionTo(
// updateTimestamp 更新对应的时间戳字段
func (sm *CertificationStateMachine) updateTimestamp(cert *entities.Certification, status enums.CertificationStatus) {
stateConfig := sm.stateManager.GetStateConfig(status)
if stateConfig == nil || stateConfig.TimestampField == "" {
return
}
now := time.Now()
switch status {
case enums.StatusInfoSubmitted:
switch stateConfig.TimestampField {
case "InfoSubmittedAt":
cert.InfoSubmittedAt = &now
case enums.StatusFaceVerified:
cert.FaceVerifiedAt = &now
case enums.StatusContractApplied:
case "EnterpriseVerifiedAt":
cert.EnterpriseVerifiedAt = &now
case "ContractAppliedAt":
cert.ContractAppliedAt = &now
case enums.StatusContractApproved:
cert.ContractApprovedAt = &now
case enums.StatusContractSigned:
case "ContractSignedAt":
cert.ContractSignedAt = &now
case enums.StatusCompleted:
case "CompletedAt":
cert.CompletedAt = &now
}
}
@@ -179,25 +116,6 @@ func (sm *CertificationStateMachine) updateCertificationFields(
metadata map[string]interface{},
) {
switch status {
case enums.StatusContractApproved:
if adminID, ok := metadata["admin_id"].(string); ok {
cert.AdminID = &adminID
}
if approvalNotes, ok := metadata["approval_notes"].(string); ok {
cert.ApprovalNotes = approvalNotes
}
if signingURL, ok := metadata["signing_url"].(string); ok {
cert.SigningURL = signingURL
}
case enums.StatusRejected:
if adminID, ok := metadata["admin_id"].(string); ok {
cert.AdminID = &adminID
}
if rejectReason, ok := metadata["reject_reason"].(string); ok {
cert.RejectReason = rejectReason
}
case enums.StatusContractSigned:
if contractURL, ok := metadata["contract_url"].(string); ok {
cert.ContractURL = contractURL
@@ -205,66 +123,13 @@ func (sm *CertificationStateMachine) updateCertificationFields(
}
}
// validateTransition 验证状态转换的有效性
func (sm *CertificationStateMachine) validateTransition(
ctx context.Context,
cert *entities.Certification,
targetStatus enums.CertificationStatus,
metadata map[string]interface{},
) error {
switch targetStatus {
case enums.StatusInfoSubmitted:
// 验证企业信息是否完整
// 这里应该检查用户是否有企业信息,通过用户域的企业服务验证
// 暂时跳过验证,由应用服务层协调
break
case enums.StatusFaceVerified:
// 验证人脸识别是否成功
// 这里可以添加人脸识别结果的验证逻辑
case enums.StatusContractApproved:
// 验证管理员审核信息
if metadata["signing_url"] == nil || metadata["signing_url"].(string) == "" {
return fmt.Errorf("缺少合同签署链接")
}
case enums.StatusRejected:
// 验证拒绝原因
if metadata["reject_reason"] == nil || metadata["reject_reason"].(string) == "" {
return fmt.Errorf("缺少拒绝原因")
}
case enums.StatusContractSigned:
// 验证合同签署信息
if cert.SigningURL == "" {
return fmt.Errorf("缺少合同签署链接")
}
}
return nil
}
// GetValidNextStatuses 获取当前状态可以转换到的下一个状态列表
func (sm *CertificationStateMachine) GetValidNextStatuses(
currentStatus enums.CertificationStatus,
isUser bool,
isAdmin bool,
) []enums.CertificationStatus {
var validStatuses []enums.CertificationStatus
transitions, exists := sm.transitions[currentStatus]
if !exists {
return validStatuses
}
for _, transition := range transitions {
if (isUser && transition.AllowUser) || (isAdmin && transition.AllowAdmin) {
validStatuses = append(validStatuses, transition.To)
}
}
return validStatuses
return sm.stateManager.GetNextValidStatuses(currentStatus)
}
// GetTransitionAction 获取状态转换对应的操作名称
@@ -272,20 +137,49 @@ func (sm *CertificationStateMachine) GetTransitionAction(
from enums.CertificationStatus,
to enums.CertificationStatus,
) string {
transitions, exists := sm.transitions[from]
if !exists {
return ""
}
transitions := sm.stateManager.GetTransitionConfigs(from)
for _, transition := range transitions {
if transition.To == to {
return transition.Action
}
}
return ""
}
// GetTransitionActionName 获取状态转换对应的操作中文名称
func (sm *CertificationStateMachine) GetTransitionActionName(
from enums.CertificationStatus,
to enums.CertificationStatus,
) string {
transitions := sm.stateManager.GetTransitionConfigs(from)
for _, transition := range transitions {
if transition.To == to {
return transition.ActionName
}
}
return ""
}
// GetStateConfig 获取状态配置
func (sm *CertificationStateMachine) GetStateConfig(status enums.CertificationStatus) *StateConfig {
return sm.stateManager.GetStateConfig(status)
}
// GetProgressPercentage 获取进度百分比
func (sm *CertificationStateMachine) GetProgressPercentage(status enums.CertificationStatus) int {
return sm.stateManager.GetProgressPercentage(status)
}
// IsUserActionRequired 检查是否需要用户操作
func (sm *CertificationStateMachine) IsUserActionRequired(status enums.CertificationStatus) bool {
return sm.stateManager.IsUserActionRequired(status)
}
// IsAdminActionRequired 检查是否需要管理员操作
func (sm *CertificationStateMachine) IsAdminActionRequired(status enums.CertificationStatus) bool {
return sm.stateManager.IsAdminActionRequired(status)
}
// GetTransitionHistory 获取状态转换历史
func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, certificationID string) ([]map[string]interface{}, error) {
cert, err := sm.certRepo.GetByID(ctx, certificationID)
@@ -315,12 +209,12 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
})
}
if cert.FaceVerifiedAt != nil {
if cert.EnterpriseVerifiedAt != nil {
history = append(history, map[string]interface{}{
"status": string(enums.StatusFaceVerified),
"timestamp": *cert.FaceVerifiedAt,
"action": "face_verify",
"performer": "system",
"status": string(enums.StatusEnterpriseVerified),
"timestamp": *cert.EnterpriseVerifiedAt,
"action": "enterprise_verify",
"performer": "user",
"metadata": map[string]interface{}{},
})
}
@@ -335,27 +229,6 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
})
}
if cert.ContractApprovedAt != nil {
metadata := map[string]interface{}{}
if cert.AdminID != nil {
metadata["admin_id"] = *cert.AdminID
}
if cert.ApprovalNotes != "" {
metadata["approval_notes"] = cert.ApprovalNotes
}
if cert.SigningURL != "" {
metadata["signing_url"] = cert.SigningURL
}
history = append(history, map[string]interface{}{
"status": string(enums.StatusContractApproved),
"timestamp": *cert.ContractApprovedAt,
"action": "admin_approve",
"performer": "admin",
"metadata": metadata,
})
}
if cert.ContractSignedAt != nil {
metadata := map[string]interface{}{}
if cert.ContractURL != "" {
@@ -365,7 +238,7 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
history = append(history, map[string]interface{}{
"status": string(enums.StatusContractSigned),
"timestamp": *cert.ContractSignedAt,
"action": "user_sign",
"action": "sign_contract",
"performer": "user",
"metadata": metadata,
})
@@ -375,7 +248,7 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
history = append(history, map[string]interface{}{
"status": string(enums.StatusCompleted),
"timestamp": *cert.CompletedAt,
"action": "system_complete",
"action": "complete",
"performer": "system",
"metadata": map[string]interface{}{},
})
@@ -383,66 +256,3 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
return history, nil
}
// ValidateCertificationFlow 验证认证流程的完整性
func (sm *CertificationStateMachine) ValidateCertificationFlow(ctx context.Context, certificationID string) (map[string]interface{}, error) {
cert, err := sm.certRepo.GetByID(ctx, certificationID)
if err != nil {
return nil, fmt.Errorf("获取认证记录失败: %w", err)
}
validation := map[string]interface{}{
"certification_id": certificationID,
"current_status": cert.Status,
"is_valid": true,
"issues": []string{},
"warnings": []string{},
}
// 检查必要的时间节点
if cert.Status != enums.StatusPending {
if cert.InfoSubmittedAt == nil {
validation["is_valid"] = false
validation["issues"] = append(validation["issues"].([]string), "缺少企业信息提交时间")
}
}
if cert.Status == enums.StatusFaceVerified || cert.Status == enums.StatusContractApplied ||
cert.Status == enums.StatusContractPending || cert.Status == enums.StatusContractApproved ||
cert.Status == enums.StatusContractSigned || cert.Status == enums.StatusCompleted {
if cert.FaceVerifiedAt == nil {
validation["is_valid"] = false
validation["issues"] = append(validation["issues"].([]string), "缺少人脸识别完成时间")
}
}
if cert.Status == enums.StatusContractApproved || cert.Status == enums.StatusContractSigned ||
cert.Status == enums.StatusCompleted {
if cert.ContractApprovedAt == nil {
validation["is_valid"] = false
validation["issues"] = append(validation["issues"].([]string), "缺少合同审核时间")
}
if cert.SigningURL == "" {
validation["warnings"] = append(validation["warnings"].([]string), "缺少合同签署链接")
}
}
if cert.Status == enums.StatusContractSigned || cert.Status == enums.StatusCompleted {
if cert.ContractSignedAt == nil {
validation["is_valid"] = false
validation["issues"] = append(validation["issues"].([]string), "缺少合同签署时间")
}
if cert.ContractURL == "" {
validation["warnings"] = append(validation["warnings"].([]string), "缺少合同文件链接")
}
}
if cert.Status == enums.StatusCompleted {
if cert.CompletedAt == nil {
validation["is_valid"] = false
validation["issues"] = append(validation["issues"].([]string), "缺少认证完成时间")
}
}
return validation, nil
}