基础架构

This commit is contained in:
2025-07-13 16:36:20 +08:00
parent e3d64e7485
commit 807004f78d
128 changed files with 17232 additions and 11396 deletions

View File

@@ -5,6 +5,8 @@ import (
"tyapi-server/internal/domains/certification/enums"
"github.com/google/uuid"
"gorm.io/gorm"
)
@@ -13,10 +15,9 @@ import (
// 包含认证状态、时间节点、审核信息、合同信息等核心数据
type Certification struct {
// 基础信息
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"认证申请唯一标识"`
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"申请用户ID"`
EnterpriseID *string `gorm:"type:varchar(36);index" json:"enterprise_id" comment:"关联的企业信息ID"`
Status enums.CertificationStatus `gorm:"type:varchar(50);not null;index" json:"status" comment:"当前认证状态"`
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"认证申请唯一标识"`
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"申请用户ID"`
Status enums.CertificationStatus `gorm:"type:varchar(50);not null;index" json:"status" comment:"当前认证状态"`
// 流程节点时间戳 - 记录每个关键步骤的完成时间
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
@@ -45,7 +46,6 @@ type Certification struct {
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
// 关联关系 - 与其他实体的关联
Enterprise *Enterprise `gorm:"foreignKey:EnterpriseID" json:"enterprise,omitempty" 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:"关联的合同记录列表"`
@@ -57,6 +57,14 @@ func (Certification) TableName() string {
return "certifications"
}
// BeforeCreate GORM钩子创建前自动生成UUID
func (c *Certification) BeforeCreate(tx *gorm.DB) error {
if c.ID == "" {
c.ID = uuid.New().String()
}
return nil
}
// IsStatusChangeable 检查状态是否可以变更
// 只有非最终状态(完成/拒绝)的认证申请才能进行状态变更
func (c *Certification) IsStatusChangeable() bool {

View File

@@ -3,6 +3,7 @@ package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
@@ -96,3 +97,11 @@ func (c *ContractRecord) GetStatusName() string {
}
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

@@ -1,66 +0,0 @@
package entities
import (
"time"
"gorm.io/gorm"
)
// Enterprise 企业信息实体
// 存储企业认证的核心信息,包括企业四要素和验证状态
// 与认证申请是一对一关系,每个认证申请对应一个企业信息
type Enterprise 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"`
// 企业四要素 - 企业认证的核心信息
CompanyName string `gorm:"type:varchar(255);not null" json:"company_name" comment:"企业名称"`
UnifiedSocialCode string `gorm:"type:varchar(50);not null;index" json:"unified_social_code" comment:"统一社会信用代码"`
LegalPersonName string `gorm:"type:varchar(100);not null" json:"legal_person_name" comment:"法定代表人姓名"`
LegalPersonID string `gorm:"type:varchar(50);not null" json:"legal_person_id" comment:"法定代表人身份证号"`
// 关联的营业执照上传记录
LicenseUploadRecordID string `gorm:"type:varchar(36);not null;index" json:"license_upload_record_id" comment:"关联的营业执照上传记录ID"`
// OCR识别结果 - 从营业执照中自动识别的信息
OCRRawData string `gorm:"type:text" json:"ocr_raw_data,omitempty" comment:"OCR原始返回数据(JSON格式)"`
OCRConfidence float64 `gorm:"type:decimal(5,2)" json:"ocr_confidence,omitempty" comment:"OCR识别置信度(0-1)"`
// 验证状态 - 各环节的验证结果
IsOCRVerified bool `gorm:"default:false" json:"is_ocr_verified" comment:"OCR验证是否通过"`
IsFaceVerified bool `gorm:"default:false" json:"is_face_verified" comment:"人脸识别是否通过"`
VerificationData string `gorm:"type:text" json:"verification_data,omitempty" comment:"验证数据(JSON格式)"`
// 时间戳字段
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:"关联的认证申请"`
LicenseUploadRecord *LicenseUploadRecord `gorm:"foreignKey:LicenseUploadRecordID" json:"license_upload_record,omitempty" comment:"关联的营业执照上传记录"`
}
// TableName 指定数据库表名
func (Enterprise) TableName() string {
return "enterprises"
}
// IsComplete 检查企业四要素是否完整
// 验证企业名称、统一社会信用代码、法定代表人姓名、身份证号是否都已填写
func (e *Enterprise) IsComplete() bool {
return e.CompanyName != "" &&
e.UnifiedSocialCode != "" &&
e.LegalPersonName != "" &&
e.LegalPersonID != ""
}
// Validate 验证企业信息是否有效
// 这里可以添加企业信息的业务验证逻辑
// 比如统一社会信用代码格式验证、身份证号格式验证等
func (e *Enterprise) Validate() error {
// 这里可以添加企业信息的业务验证逻辑
// 比如统一社会信用代码格式验证、身份证号格式验证等
return nil
}

View File

@@ -3,6 +3,7 @@ package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
@@ -87,3 +88,11 @@ func (f *FaceVerifyRecord) GetStatusName() string {
}
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

@@ -3,6 +3,7 @@ package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
@@ -68,3 +69,11 @@ func (l *LicenseUploadRecord) IsValidForOCR() bool {
}
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

@@ -3,6 +3,7 @@ package entities
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
@@ -49,6 +50,14 @@ 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 {

View File

@@ -5,6 +5,7 @@ type CertificationStatus string
const (
// 主流程状态
StatusNotStarted CertificationStatus = "not_started" // 未开始认证
StatusPending CertificationStatus = "pending" // 待开始
StatusInfoSubmitted CertificationStatus = "info_submitted" // 企业信息已提交
StatusFaceVerified CertificationStatus = "face_verified" // 人脸识别完成
@@ -23,7 +24,7 @@ const (
// IsValidStatus 检查状态是否有效
func IsValidStatus(status CertificationStatus) bool {
validStatuses := []CertificationStatus{
StatusPending, StatusInfoSubmitted, StatusFaceVerified,
StatusNotStarted, StatusPending, StatusInfoSubmitted, StatusFaceVerified,
StatusContractApplied, StatusContractPending, StatusContractApproved,
StatusContractSigned, StatusCompleted, StatusFaceFailed,
StatusSignFailed, StatusRejected,
@@ -40,6 +41,7 @@ func IsValidStatus(status CertificationStatus) bool {
// GetStatusName 获取状态的中文名称
func GetStatusName(status CertificationStatus) string {
statusNames := map[CertificationStatus]string{
StatusNotStarted: "未开始认证",
StatusPending: "待开始",
StatusInfoSubmitted: "企业信息已提交",
StatusFaceVerified: "人脸识别完成",

View File

@@ -8,8 +8,8 @@ import (
"go.uber.org/zap"
"tyapi-server/internal/infrastructure/external/notification"
"tyapi-server/internal/shared/interfaces"
"tyapi-server/internal/shared/notification"
)
// CertificationEventHandler 认证事件处理器

View File

@@ -1,536 +0,0 @@
package handlers
import (
"io"
"path/filepath"
"strconv"
"strings"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"tyapi-server/internal/domains/certification/dto"
"tyapi-server/internal/domains/certification/services"
"tyapi-server/internal/shared/interfaces"
)
// CertificationHandler 认证处理器
type CertificationHandler struct {
certificationService *services.CertificationService
response interfaces.ResponseBuilder
logger *zap.Logger
}
// NewCertificationHandler 创建认证处理器
func NewCertificationHandler(
certificationService *services.CertificationService,
response interfaces.ResponseBuilder,
logger *zap.Logger,
) *CertificationHandler {
return &CertificationHandler{
certificationService: certificationService,
response: response,
logger: logger,
}
}
// CreateCertification 创建认证申请
// @Summary 创建认证申请
// @Description 用户创建企业认证申请
// @Tags 认证
// @Accept json
// @Produce json
// @Success 200 {object} dto.CertificationCreateResponse
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/certification/create [post]
func (h *CertificationHandler) CreateCertification(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.response.Unauthorized(c, "用户未认证")
return
}
result, err := h.certificationService.CreateCertification(c.Request.Context(), userID)
if err != nil {
h.logger.Error("创建认证申请失败",
zap.String("user_id", userID),
zap.Error(err),
)
h.response.InternalError(c, "创建认证申请失败")
return
}
h.response.Success(c, result, "认证申请创建成功")
}
// UploadLicense 上传营业执照
// @Summary 上传营业执照
// @Description 上传营业执照文件并进行OCR识别
// @Tags 认证
// @Accept multipart/form-data
// @Produce json
// @Param file formData file true "营业执照文件"
// @Success 200 {object} dto.UploadLicenseResponse
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/certification/upload-license [post]
func (h *CertificationHandler) UploadLicense(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.response.Unauthorized(c, "用户未认证")
return
}
// 获取上传的文件
file, header, err := c.Request.FormFile("file")
if err != nil {
h.logger.Error("获取上传文件失败", zap.Error(err))
h.response.BadRequest(c, "请选择要上传的文件")
return
}
defer file.Close()
// 检查文件类型
fileName := header.Filename
ext := strings.ToLower(filepath.Ext(fileName))
allowedExts := []string{".jpg", ".jpeg", ".png", ".pdf"}
isAllowed := false
for _, allowedExt := range allowedExts {
if ext == allowedExt {
isAllowed = true
break
}
}
if !isAllowed {
h.response.BadRequest(c, "文件格式不支持,仅支持 JPG、PNG、PDF 格式")
return
}
// 检查文件大小限制为10MB
const maxFileSize = 10 * 1024 * 1024 // 10MB
if header.Size > maxFileSize {
h.response.BadRequest(c, "文件大小不能超过10MB")
return
}
// 读取文件内容
fileBytes, err := io.ReadAll(file)
if err != nil {
h.logger.Error("读取文件内容失败", zap.Error(err))
h.response.InternalError(c, "文件读取失败")
return
}
// 调用服务上传文件
result, err := h.certificationService.UploadLicense(c.Request.Context(), userID, fileBytes, fileName)
if err != nil {
h.logger.Error("上传营业执照失败",
zap.String("user_id", userID),
zap.String("file_name", fileName),
zap.Error(err),
)
h.response.InternalError(c, "上传失败,请稍后重试")
return
}
h.response.Success(c, result, "营业执照上传成功")
}
// SubmitEnterpriseInfo 提交企业信息
// @Summary 提交企业信息
// @Description 确认并提交企业四要素信息
// @Tags 认证
// @Accept json
// @Produce json
// @Param id path string true "认证申请ID"
// @Param request body dto.SubmitEnterpriseInfoRequest true "企业信息"
// @Success 200 {object} dto.SubmitEnterpriseInfoResponse
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/certification/{id}/submit-info [put]
func (h *CertificationHandler) SubmitEnterpriseInfo(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.response.Unauthorized(c, "用户未认证")
return
}
certificationID := c.Param("id")
if certificationID == "" {
h.response.BadRequest(c, "认证申请ID不能为空")
return
}
var req dto.SubmitEnterpriseInfoRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("参数绑定失败", zap.Error(err))
h.response.BadRequest(c, "请求参数格式错误")
return
}
// 验证企业信息
if req.CompanyName == "" {
h.response.BadRequest(c, "企业名称不能为空")
return
}
if req.UnifiedSocialCode == "" {
h.response.BadRequest(c, "统一社会信用代码不能为空")
return
}
if req.LegalPersonName == "" {
h.response.BadRequest(c, "法定代表人姓名不能为空")
return
}
if req.LegalPersonID == "" {
h.response.BadRequest(c, "法定代表人身份证号不能为空")
return
}
if req.LicenseUploadRecordID == "" {
h.response.BadRequest(c, "营业执照上传记录ID不能为空")
return
}
result, err := h.certificationService.SubmitEnterpriseInfo(c.Request.Context(), certificationID, &req)
if err != nil {
h.logger.Error("提交企业信息失败",
zap.String("certification_id", certificationID),
zap.String("user_id", userID),
zap.Error(err),
)
if strings.Contains(err.Error(), "已被使用") || strings.Contains(err.Error(), "不允许") {
h.response.BadRequest(c, err.Error())
} else {
h.response.InternalError(c, "提交失败,请稍后重试")
}
return
}
h.response.Success(c, result, "企业信息提交成功")
}
// InitiateFaceVerify 初始化人脸识别
// @Summary 初始化人脸识别
// @Description 开始人脸识别认证流程
// @Tags 认证
// @Accept json
// @Produce json
// @Param id path string true "认证申请ID"
// @Param request body dto.FaceVerifyRequest true "人脸识别请求"
// @Success 200 {object} dto.FaceVerifyResponse
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/certification/{id}/face-verify [post]
func (h *CertificationHandler) InitiateFaceVerify(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.response.Unauthorized(c, "用户未认证")
return
}
certificationID := c.Param("id")
if certificationID == "" {
h.response.BadRequest(c, "认证申请ID不能为空")
return
}
var req dto.FaceVerifyRequest
if err := c.ShouldBindJSON(&req); err != nil {
h.logger.Error("参数绑定失败", zap.Error(err))
h.response.BadRequest(c, "请求参数格式错误")
return
}
// 验证请求参数
if req.RealName == "" {
h.response.BadRequest(c, "真实姓名不能为空")
return
}
if req.IDCardNumber == "" {
h.response.BadRequest(c, "身份证号不能为空")
return
}
result, err := h.certificationService.InitiateFaceVerify(c.Request.Context(), certificationID, &req)
if err != nil {
h.logger.Error("初始化人脸识别失败",
zap.String("certification_id", certificationID),
zap.String("user_id", userID),
zap.Error(err),
)
if strings.Contains(err.Error(), "不允许") {
h.response.BadRequest(c, err.Error())
} else {
h.response.InternalError(c, "初始化失败,请稍后重试")
}
return
}
h.response.Success(c, result, "人脸识别初始化成功")
}
// ApplyContract 申请电子合同
// @Summary 申请电子合同
// @Description 申请生成企业认证电子合同
// @Tags 认证
// @Accept json
// @Produce json
// @Param id path string true "认证申请ID"
// @Success 200 {object} dto.ApplyContractResponse
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/certification/{id}/apply-contract [post]
func (h *CertificationHandler) ApplyContract(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.response.Unauthorized(c, "用户未认证")
return
}
certificationID := c.Param("id")
if certificationID == "" {
h.response.BadRequest(c, "认证申请ID不能为空")
return
}
result, err := h.certificationService.ApplyContract(c.Request.Context(), certificationID)
if err != nil {
h.logger.Error("申请电子合同失败",
zap.String("certification_id", certificationID),
zap.String("user_id", userID),
zap.Error(err),
)
if strings.Contains(err.Error(), "不允许") {
h.response.BadRequest(c, err.Error())
} else {
h.response.InternalError(c, "申请失败,请稍后重试")
}
return
}
h.response.Success(c, result, "合同申请提交成功,请等待管理员审核")
}
// GetCertificationStatus 获取认证状态
// @Summary 获取认证状态
// @Description 查询当前用户的认证申请状态和进度
// @Tags 认证
// @Accept json
// @Produce json
// @Success 200 {object} dto.CertificationStatusResponse
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/certification/status [get]
func (h *CertificationHandler) GetCertificationStatus(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.response.Unauthorized(c, "用户未认证")
return
}
result, err := h.certificationService.GetCertificationStatus(c.Request.Context(), userID)
if err != nil {
h.logger.Error("获取认证状态失败",
zap.String("user_id", userID),
zap.Error(err),
)
if strings.Contains(err.Error(), "不存在") {
h.response.NotFound(c, "未找到认证申请记录")
} else {
h.response.InternalError(c, "查询失败,请稍后重试")
}
return
}
h.response.Success(c, result, "查询成功")
}
// GetCertificationDetails 获取认证详情
// @Summary 获取认证详情
// @Description 获取指定认证申请的详细信息
// @Tags 认证
// @Accept json
// @Produce json
// @Param id path string true "认证申请ID"
// @Success 200 {object} dto.CertificationStatusResponse
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/certification/{id} [get]
func (h *CertificationHandler) GetCertificationDetails(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.response.Unauthorized(c, "用户未认证")
return
}
certificationID := c.Param("id")
if certificationID == "" {
h.response.BadRequest(c, "认证申请ID不能为空")
return
}
// 通过用户ID获取状态来确保用户只能查看自己的认证记录
result, err := h.certificationService.GetCertificationStatus(c.Request.Context(), userID)
if err != nil {
h.logger.Error("获取认证详情失败",
zap.String("certification_id", certificationID),
zap.String("user_id", userID),
zap.Error(err),
)
if strings.Contains(err.Error(), "不存在") {
h.response.NotFound(c, "未找到认证申请记录")
} else {
h.response.InternalError(c, "查询失败,请稍后重试")
}
return
}
// 检查是否是用户自己的认证记录
if result.ID != certificationID {
h.response.Forbidden(c, "无权访问此认证记录")
return
}
h.response.Success(c, result, "查询成功")
}
// RetryStep 重试认证步骤
// @Summary 重试认证步骤
// @Description 重试失败的认证步骤(如人脸识别失败、签署失败等)
// @Tags 认证
// @Accept json
// @Produce json
// @Param id path string true "认证申请ID"
// @Param step query string true "重试步骤face_verify, sign_contract"
// @Success 200 {object} interfaces.APIResponse
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/certification/{id}/retry [post]
func (h *CertificationHandler) RetryStep(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.response.Unauthorized(c, "用户未认证")
return
}
certificationID := c.Param("id")
if certificationID == "" {
h.response.BadRequest(c, "认证申请ID不能为空")
return
}
step := c.Query("step")
if step == "" {
h.response.BadRequest(c, "重试步骤不能为空")
return
}
// TODO: 实现重试逻辑
// 这里需要根据不同的步骤调用状态机进行状态重置
h.logger.Info("重试认证步骤",
zap.String("certification_id", certificationID),
zap.String("user_id", userID),
zap.String("step", step),
)
h.response.Success(c, gin.H{
"certification_id": certificationID,
"step": step,
"message": "重试操作已提交",
}, "重试操作成功")
}
// GetProgressStats 获取进度统计
// @Summary 获取进度统计
// @Description 获取用户认证申请的进度统计信息
// @Tags 认证
// @Accept json
// @Produce json
// @Success 200 {object} map[string]interface{}
// @Failure 400 {object} interfaces.APIResponse
// @Failure 500 {object} interfaces.APIResponse
// @Router /api/v1/certification/progress [get]
func (h *CertificationHandler) GetProgressStats(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.response.Unauthorized(c, "用户未认证")
return
}
// 获取认证状态
status, err := h.certificationService.GetCertificationStatus(c.Request.Context(), userID)
if err != nil {
if strings.Contains(err.Error(), "不存在") {
h.response.Success(c, gin.H{
"has_certification": false,
"progress": 0,
"status": "",
"next_steps": []string{"开始企业认证"},
}, "查询成功")
return
}
h.response.InternalError(c, "查询失败")
return
}
// 构建进度统计
nextSteps := []string{}
if status.IsUserActionRequired {
switch status.Status {
case "pending":
nextSteps = append(nextSteps, "上传营业执照")
case "info_submitted":
nextSteps = append(nextSteps, "进行人脸识别")
case "face_verified":
nextSteps = append(nextSteps, "申请电子合同")
case "contract_approved":
nextSteps = append(nextSteps, "签署电子合同")
case "face_failed":
nextSteps = append(nextSteps, "重新进行人脸识别")
case "sign_failed":
nextSteps = append(nextSteps, "重新签署合同")
}
} else if status.IsAdminActionRequired {
nextSteps = append(nextSteps, "等待管理员审核")
} else {
nextSteps = append(nextSteps, "认证流程已完成")
}
result := gin.H{
"has_certification": true,
"certification_id": status.ID,
"progress": status.Progress,
"status": status.Status,
"status_name": status.StatusName,
"is_user_action_required": status.IsUserActionRequired,
"is_admin_action_required": status.IsAdminActionRequired,
"next_steps": nextSteps,
"created_at": status.CreatedAt,
"updated_at": status.UpdatedAt,
}
h.response.Success(c, result, "查询成功")
}
// parsePageParams 解析分页参数
func (h *CertificationHandler) parsePageParams(c *gin.Context) (int, int) {
page := 1
pageSize := 20
if pageStr := c.Query("page"); pageStr != "" {
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
page = p
}
}
if sizeStr := c.Query("page_size"); sizeStr != "" {
if s, err := strconv.Atoi(sizeStr); err == nil && s > 0 && s <= 100 {
pageSize = s
}
}
return page, pageSize
}

View File

@@ -0,0 +1,98 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/repositories/queries"
"tyapi-server/internal/shared/interfaces"
)
// CertificationStats 认证统计信息
type CertificationStats struct {
TotalCertifications int64
PendingCertifications int64
CompletedCertifications int64
RejectedCertifications int64
TodaySubmissions int64
}
// CertificationRepository 认证申请仓储接口
type CertificationRepository interface {
interfaces.Repository[entities.Certification]
// 基础查询 - 直接使用实体
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)
// 复杂查询 - 使用查询参数
ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) ([]*entities.Certification, int64, error)
// 业务操作
UpdateStatus(ctx context.Context, certificationID string, status string, adminID *string, notes 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]
// 基础查询 - 直接使用实体
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.FaceVerifyRecord, error)
GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.FaceVerifyRecord, 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)
// 业务操作
UpdateContractStatus(ctx context.Context, recordID string, status string, adminID *string, notes string) error
}
// LicenseUploadRecordRepository 营业执照上传记录仓储接口
type LicenseUploadRecordRepository interface {
interfaces.Repository[entities.LicenseUploadRecord]
// 基础查询 - 直接使用实体
GetByCertificationID(ctx context.Context, certificationID string) (*entities.LicenseUploadRecord, error)
// 复杂查询 - 使用查询参数
ListRecords(ctx context.Context, query *queries.ListLicenseUploadRecordsQuery) ([]*entities.LicenseUploadRecord, int64, error)
// 业务操作
UpdateOCRResult(ctx context.Context, recordID string, ocrResult string, confidence float64) error
}
// NotificationRecordRepository 通知记录仓储接口
type NotificationRecordRepository interface {
interfaces.Repository[entities.NotificationRecord]
// 基础查询 - 直接使用实体
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.NotificationRecord, error)
GetUnreadByUserID(ctx context.Context, userID string) ([]*entities.NotificationRecord, error)
// 复杂查询 - 使用查询参数
ListRecords(ctx context.Context, query *queries.ListNotificationRecordsQuery) ([]*entities.NotificationRecord, int64, error)
// 批量操作
BatchCreate(ctx context.Context, records []entities.NotificationRecord) error
MarkAsRead(ctx context.Context, recordIDs []string) error
MarkAllAsReadByUser(ctx context.Context, userID string) error
}

View File

@@ -1,223 +0,0 @@
package repositories
import (
"context"
"fmt"
"go.uber.org/zap"
"gorm.io/gorm"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
)
// GormCertificationRepository GORM认证仓储实现
type GormCertificationRepository struct {
db *gorm.DB
logger *zap.Logger
}
// NewGormCertificationRepository 创建GORM认证仓储
func NewGormCertificationRepository(db *gorm.DB, logger *zap.Logger) CertificationRepository {
return &GormCertificationRepository{
db: db,
logger: logger,
}
}
// Create 创建认证记录
func (r *GormCertificationRepository) Create(ctx context.Context, cert *entities.Certification) error {
if err := r.db.WithContext(ctx).Create(cert).Error; err != nil {
r.logger.Error("创建认证记录失败",
zap.String("user_id", cert.UserID),
zap.Error(err),
)
return fmt.Errorf("创建认证记录失败: %w", err)
}
r.logger.Info("认证记录创建成功",
zap.String("id", cert.ID),
zap.String("user_id", cert.UserID),
zap.String("status", string(cert.Status)),
)
return nil
}
// GetByID 根据ID获取认证记录
func (r *GormCertificationRepository) GetByID(ctx context.Context, id string) (*entities.Certification, error) {
var cert entities.Certification
if err := r.db.WithContext(ctx).First(&cert, "id = ?", id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("认证记录不存在")
}
r.logger.Error("获取认证记录失败",
zap.String("id", id),
zap.Error(err),
)
return nil, fmt.Errorf("获取认证记录失败: %w", err)
}
return &cert, nil
}
// GetByUserID 根据用户ID获取认证记录
func (r *GormCertificationRepository) GetByUserID(ctx context.Context, userID string) (*entities.Certification, error) {
var cert entities.Certification
if err := r.db.WithContext(ctx).First(&cert, "user_id = ?", userID).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("用户认证记录不存在")
}
r.logger.Error("获取用户认证记录失败",
zap.String("user_id", userID),
zap.Error(err),
)
return nil, fmt.Errorf("获取用户认证记录失败: %w", err)
}
return &cert, nil
}
// Update 更新认证记录
func (r *GormCertificationRepository) Update(ctx context.Context, cert *entities.Certification) error {
if err := r.db.WithContext(ctx).Save(cert).Error; err != nil {
r.logger.Error("更新认证记录失败",
zap.String("id", cert.ID),
zap.Error(err),
)
return fmt.Errorf("更新认证记录失败: %w", err)
}
r.logger.Info("认证记录更新成功",
zap.String("id", cert.ID),
zap.String("status", string(cert.Status)),
)
return nil
}
// Delete 删除认证记录(软删除)
func (r *GormCertificationRepository) Delete(ctx context.Context, id string) error {
if err := r.db.WithContext(ctx).Delete(&entities.Certification{}, "id = ?", id).Error; err != nil {
r.logger.Error("删除认证记录失败",
zap.String("id", id),
zap.Error(err),
)
return fmt.Errorf("删除认证记录失败: %w", err)
}
r.logger.Info("认证记录删除成功", zap.String("id", id))
return nil
}
// List 获取认证记录列表
func (r *GormCertificationRepository) List(ctx context.Context, page, pageSize int, status enums.CertificationStatus) ([]*entities.Certification, int, error) {
var certs []*entities.Certification
var total int64
query := r.db.WithContext(ctx).Model(&entities.Certification{})
// 如果指定了状态,添加状态过滤
if status != "" {
query = query.Where("status = ?", status)
}
// 获取总数
if err := query.Count(&total).Error; err != nil {
r.logger.Error("获取认证记录总数失败", zap.Error(err))
return nil, 0, fmt.Errorf("获取认证记录总数失败: %w", err)
}
// 分页查询
offset := (page - 1) * pageSize
if err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&certs).Error; err != nil {
r.logger.Error("获取认证记录列表失败", zap.Error(err))
return nil, 0, fmt.Errorf("获取认证记录列表失败: %w", err)
}
return certs, int(total), nil
}
// GetByStatus 根据状态获取认证记录
func (r *GormCertificationRepository) GetByStatus(ctx context.Context, status enums.CertificationStatus, page, pageSize int) ([]*entities.Certification, int, error) {
return r.List(ctx, page, pageSize, status)
}
// GetPendingApprovals 获取待审核的认证申请
func (r *GormCertificationRepository) GetPendingApprovals(ctx context.Context, page, pageSize int) ([]*entities.Certification, int, error) {
return r.GetByStatus(ctx, enums.StatusContractPending, page, pageSize)
}
// GetWithEnterprise 获取包含企业信息的认证记录
func (r *GormCertificationRepository) GetWithEnterprise(ctx context.Context, id string) (*entities.Certification, error) {
var cert entities.Certification
if err := r.db.WithContext(ctx).Preload("Enterprise").First(&cert, "id = ?", id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("认证记录不存在")
}
r.logger.Error("获取认证记录(含企业信息)失败",
zap.String("id", id),
zap.Error(err),
)
return nil, fmt.Errorf("获取认证记录失败: %w", err)
}
return &cert, nil
}
// GetWithAllRelations 获取包含所有关联关系的认证记录
func (r *GormCertificationRepository) GetWithAllRelations(ctx context.Context, id string) (*entities.Certification, error) {
var cert entities.Certification
if err := r.db.WithContext(ctx).
Preload("Enterprise").
Preload("LicenseUploadRecord").
Preload("FaceVerifyRecords").
Preload("ContractRecords").
Preload("NotificationRecords").
First(&cert, "id = ?", id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("认证记录不存在")
}
r.logger.Error("获取认证记录(含所有关联)失败",
zap.String("id", id),
zap.Error(err),
)
return nil, fmt.Errorf("获取认证记录失败: %w", err)
}
return &cert, nil
}
// CountByStatus 根据状态统计认证记录数量
func (r *GormCertificationRepository) CountByStatus(ctx context.Context, status enums.CertificationStatus) (int64, error) {
var count int64
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("status = ?", status).Count(&count).Error; err != nil {
r.logger.Error("统计认证记录数量失败",
zap.String("status", string(status)),
zap.Error(err),
)
return 0, fmt.Errorf("统计认证记录数量失败: %w", err)
}
return count, nil
}
// CountByUserID 根据用户ID统计认证记录数量
func (r *GormCertificationRepository) CountByUserID(ctx context.Context, userID string) (int64, error) {
var count int64
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("user_id = ?", userID).Count(&count).Error; err != nil {
r.logger.Error("统计用户认证记录数量失败",
zap.String("user_id", userID),
zap.Error(err),
)
return 0, fmt.Errorf("统计用户认证记录数量失败: %w", err)
}
return count, nil
}

View File

@@ -1,175 +0,0 @@
package repositories
import (
"context"
"fmt"
"go.uber.org/zap"
"gorm.io/gorm"
"tyapi-server/internal/domains/certification/entities"
)
// GormContractRecordRepository GORM合同记录仓储实现
type GormContractRecordRepository struct {
db *gorm.DB
logger *zap.Logger
}
// NewGormContractRecordRepository 创建GORM合同记录仓储
func NewGormContractRecordRepository(db *gorm.DB, logger *zap.Logger) ContractRecordRepository {
return &GormContractRecordRepository{
db: db,
logger: logger,
}
}
// Create 创建合同记录
func (r *GormContractRecordRepository) Create(ctx context.Context, record *entities.ContractRecord) error {
if err := r.db.WithContext(ctx).Create(record).Error; err != nil {
r.logger.Error("创建合同记录失败",
zap.String("certification_id", record.CertificationID),
zap.String("contract_type", record.ContractType),
zap.Error(err),
)
return fmt.Errorf("创建合同记录失败: %w", err)
}
r.logger.Info("合同记录创建成功",
zap.String("id", record.ID),
zap.String("contract_type", record.ContractType),
)
return nil
}
// GetByID 根据ID获取合同记录
func (r *GormContractRecordRepository) GetByID(ctx context.Context, id string) (*entities.ContractRecord, error) {
var record entities.ContractRecord
if err := r.db.WithContext(ctx).First(&record, "id = ?", id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("合同记录不存在")
}
r.logger.Error("获取合同记录失败",
zap.String("id", id),
zap.Error(err),
)
return nil, fmt.Errorf("获取合同记录失败: %w", err)
}
return &record, nil
}
// GetByCertificationID 根据认证申请ID获取合同记录列表
func (r *GormContractRecordRepository) GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.ContractRecord, error) {
var records []*entities.ContractRecord
if err := r.db.WithContext(ctx).Where("certification_id = ?", certificationID).Order("created_at DESC").Find(&records).Error; err != nil {
r.logger.Error("根据认证申请ID获取合同记录失败",
zap.String("certification_id", certificationID),
zap.Error(err),
)
return nil, fmt.Errorf("获取合同记录失败: %w", err)
}
return records, nil
}
// Update 更新合同记录
func (r *GormContractRecordRepository) Update(ctx context.Context, record *entities.ContractRecord) error {
if err := r.db.WithContext(ctx).Save(record).Error; err != nil {
r.logger.Error("更新合同记录失败",
zap.String("id", record.ID),
zap.Error(err),
)
return fmt.Errorf("更新合同记录失败: %w", err)
}
return nil
}
// Delete 删除合同记录
func (r *GormContractRecordRepository) Delete(ctx context.Context, id string) error {
if err := r.db.WithContext(ctx).Delete(&entities.ContractRecord{}, "id = ?", id).Error; err != nil {
r.logger.Error("删除合同记录失败",
zap.String("id", id),
zap.Error(err),
)
return fmt.Errorf("删除合同记录失败: %w", err)
}
return nil
}
// GetByUserID 根据用户ID获取合同记录列表
func (r *GormContractRecordRepository) GetByUserID(ctx context.Context, userID string, page, pageSize int) ([]*entities.ContractRecord, int, error) {
var records []*entities.ContractRecord
var total int64
query := r.db.WithContext(ctx).Model(&entities.ContractRecord{}).Where("user_id = ?", userID)
// 获取总数
if err := query.Count(&total).Error; err != nil {
r.logger.Error("获取用户合同记录总数失败", zap.Error(err))
return nil, 0, fmt.Errorf("获取合同记录总数失败: %w", err)
}
// 分页查询
offset := (page - 1) * pageSize
if err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&records).Error; err != nil {
r.logger.Error("获取用户合同记录列表失败", zap.Error(err))
return nil, 0, fmt.Errorf("获取合同记录列表失败: %w", err)
}
return records, int(total), nil
}
// GetByStatus 根据状态获取合同记录列表
func (r *GormContractRecordRepository) GetByStatus(ctx context.Context, status string, page, pageSize int) ([]*entities.ContractRecord, int, error) {
var records []*entities.ContractRecord
var total int64
query := r.db.WithContext(ctx).Model(&entities.ContractRecord{}).Where("status = ?", status)
// 获取总数
if err := query.Count(&total).Error; err != nil {
r.logger.Error("根据状态获取合同记录总数失败", zap.Error(err))
return nil, 0, fmt.Errorf("获取合同记录总数失败: %w", err)
}
// 分页查询
offset := (page - 1) * pageSize
if err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&records).Error; err != nil {
r.logger.Error("根据状态获取合同记录列表失败", zap.Error(err))
return nil, 0, fmt.Errorf("获取合同记录列表失败: %w", err)
}
return records, int(total), nil
}
// GetPendingContracts 获取待审核的合同记录
func (r *GormContractRecordRepository) GetPendingContracts(ctx context.Context, page, pageSize int) ([]*entities.ContractRecord, int, error) {
return r.GetByStatus(ctx, "PENDING", page, pageSize)
}
// GetExpiredSigningContracts 获取签署链接已过期的合同记录
func (r *GormContractRecordRepository) GetExpiredSigningContracts(ctx context.Context, limit int) ([]*entities.ContractRecord, error) {
var records []*entities.ContractRecord
if err := r.db.WithContext(ctx).
Where("expires_at < NOW() AND status = ?", "APPROVED").
Limit(limit).
Order("expires_at ASC").
Find(&records).Error; err != nil {
r.logger.Error("获取过期签署合同记录失败", zap.Error(err))
return nil, fmt.Errorf("获取过期签署合同记录失败: %w", err)
}
return records, nil
}
// GetExpiredContracts 获取已过期的合同记录(通用方法)
func (r *GormContractRecordRepository) GetExpiredContracts(ctx context.Context, limit int) ([]*entities.ContractRecord, error) {
return r.GetExpiredSigningContracts(ctx, limit)
}

View File

@@ -1,148 +0,0 @@
package repositories
import (
"context"
"fmt"
"go.uber.org/zap"
"gorm.io/gorm"
"tyapi-server/internal/domains/certification/entities"
)
// GormEnterpriseRepository GORM企业信息仓储实现
type GormEnterpriseRepository struct {
db *gorm.DB
logger *zap.Logger
}
// NewGormEnterpriseRepository 创建GORM企业信息仓储
func NewGormEnterpriseRepository(db *gorm.DB, logger *zap.Logger) EnterpriseRepository {
return &GormEnterpriseRepository{
db: db,
logger: logger,
}
}
// Create 创建企业信息
func (r *GormEnterpriseRepository) Create(ctx context.Context, enterprise *entities.Enterprise) error {
if err := r.db.WithContext(ctx).Create(enterprise).Error; err != nil {
r.logger.Error("创建企业信息失败",
zap.String("certification_id", enterprise.CertificationID),
zap.String("company_name", enterprise.CompanyName),
zap.Error(err),
)
return fmt.Errorf("创建企业信息失败: %w", err)
}
r.logger.Info("企业信息创建成功",
zap.String("id", enterprise.ID),
zap.String("company_name", enterprise.CompanyName),
zap.String("unified_social_code", enterprise.UnifiedSocialCode),
)
return nil
}
// GetByID 根据ID获取企业信息
func (r *GormEnterpriseRepository) GetByID(ctx context.Context, id string) (*entities.Enterprise, error) {
var enterprise entities.Enterprise
if err := r.db.WithContext(ctx).First(&enterprise, "id = ?", id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("企业信息不存在")
}
r.logger.Error("获取企业信息失败",
zap.String("id", id),
zap.Error(err),
)
return nil, fmt.Errorf("获取企业信息失败: %w", err)
}
return &enterprise, nil
}
// GetByCertificationID 根据认证ID获取企业信息
func (r *GormEnterpriseRepository) GetByCertificationID(ctx context.Context, certificationID string) (*entities.Enterprise, error) {
var enterprise entities.Enterprise
if err := r.db.WithContext(ctx).First(&enterprise, "certification_id = ?", certificationID).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("企业信息不存在")
}
r.logger.Error("根据认证ID获取企业信息失败",
zap.String("certification_id", certificationID),
zap.Error(err),
)
return nil, fmt.Errorf("获取企业信息失败: %w", err)
}
return &enterprise, nil
}
// Update 更新企业信息
func (r *GormEnterpriseRepository) Update(ctx context.Context, enterprise *entities.Enterprise) error {
if err := r.db.WithContext(ctx).Save(enterprise).Error; err != nil {
r.logger.Error("更新企业信息失败",
zap.String("id", enterprise.ID),
zap.String("company_name", enterprise.CompanyName),
zap.Error(err),
)
return fmt.Errorf("更新企业信息失败: %w", err)
}
r.logger.Info("企业信息更新成功",
zap.String("id", enterprise.ID),
zap.String("company_name", enterprise.CompanyName),
)
return nil
}
// Delete 删除企业信息(软删除)
func (r *GormEnterpriseRepository) Delete(ctx context.Context, id string) error {
if err := r.db.WithContext(ctx).Delete(&entities.Enterprise{}, "id = ?", id).Error; err != nil {
r.logger.Error("删除企业信息失败",
zap.String("id", id),
zap.Error(err),
)
return fmt.Errorf("删除企业信息失败: %w", err)
}
r.logger.Info("企业信息删除成功", zap.String("id", id))
return nil
}
// GetByUnifiedSocialCode 根据统一社会信用代码获取企业信息
func (r *GormEnterpriseRepository) GetByUnifiedSocialCode(ctx context.Context, code string) (*entities.Enterprise, error) {
var enterprise entities.Enterprise
if err := r.db.WithContext(ctx).First(&enterprise, "unified_social_code = ?", code).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("企业信息不存在")
}
r.logger.Error("根据统一社会信用代码获取企业信息失败",
zap.String("unified_social_code", code),
zap.Error(err),
)
return nil, fmt.Errorf("获取企业信息失败: %w", err)
}
return &enterprise, nil
}
// ExistsByUnifiedSocialCode 检查统一社会信用代码是否已存在
func (r *GormEnterpriseRepository) ExistsByUnifiedSocialCode(ctx context.Context, code string) (bool, error) {
var count int64
if err := r.db.WithContext(ctx).Model(&entities.Enterprise{}).
Where("unified_social_code = ?", code).Count(&count).Error; err != nil {
r.logger.Error("检查统一社会信用代码是否存在失败",
zap.String("unified_social_code", code),
zap.Error(err),
)
return false, fmt.Errorf("检查统一社会信用代码失败: %w", err)
}
return count > 0, nil
}

View File

@@ -1,160 +0,0 @@
package repositories
import (
"context"
"fmt"
"go.uber.org/zap"
"gorm.io/gorm"
"tyapi-server/internal/domains/certification/entities"
)
// GormFaceVerifyRecordRepository GORM人脸识别记录仓储实现
type GormFaceVerifyRecordRepository struct {
db *gorm.DB
logger *zap.Logger
}
// NewGormFaceVerifyRecordRepository 创建GORM人脸识别记录仓储
func NewGormFaceVerifyRecordRepository(db *gorm.DB, logger *zap.Logger) FaceVerifyRecordRepository {
return &GormFaceVerifyRecordRepository{
db: db,
logger: logger,
}
}
// Create 创建人脸识别记录
func (r *GormFaceVerifyRecordRepository) Create(ctx context.Context, record *entities.FaceVerifyRecord) error {
if err := r.db.WithContext(ctx).Create(record).Error; err != nil {
r.logger.Error("创建人脸识别记录失败",
zap.String("certification_id", record.CertificationID),
zap.String("certify_id", record.CertifyID),
zap.Error(err),
)
return fmt.Errorf("创建人脸识别记录失败: %w", err)
}
r.logger.Info("人脸识别记录创建成功",
zap.String("id", record.ID),
zap.String("certify_id", record.CertifyID),
)
return nil
}
// GetByID 根据ID获取人脸识别记录
func (r *GormFaceVerifyRecordRepository) GetByID(ctx context.Context, id string) (*entities.FaceVerifyRecord, error) {
var record entities.FaceVerifyRecord
if err := r.db.WithContext(ctx).First(&record, "id = ?", id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("人脸识别记录不存在")
}
r.logger.Error("获取人脸识别记录失败",
zap.String("id", id),
zap.Error(err),
)
return nil, fmt.Errorf("获取人脸识别记录失败: %w", err)
}
return &record, nil
}
// GetByCertifyID 根据认证ID获取人脸识别记录
func (r *GormFaceVerifyRecordRepository) GetByCertifyID(ctx context.Context, certifyID string) (*entities.FaceVerifyRecord, error) {
var record entities.FaceVerifyRecord
if err := r.db.WithContext(ctx).First(&record, "certify_id = ?", certifyID).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("人脸识别记录不存在")
}
r.logger.Error("根据认证ID获取人脸识别记录失败",
zap.String("certify_id", certifyID),
zap.Error(err),
)
return nil, fmt.Errorf("获取人脸识别记录失败: %w", err)
}
return &record, nil
}
// GetByCertificationID 根据认证申请ID获取人脸识别记录列表
func (r *GormFaceVerifyRecordRepository) GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.FaceVerifyRecord, error) {
var records []*entities.FaceVerifyRecord
if err := r.db.WithContext(ctx).Where("certification_id = ?", certificationID).Order("created_at DESC").Find(&records).Error; err != nil {
r.logger.Error("根据认证申请ID获取人脸识别记录失败",
zap.String("certification_id", certificationID),
zap.Error(err),
)
return nil, fmt.Errorf("获取人脸识别记录失败: %w", err)
}
return records, nil
}
// Update 更新人脸识别记录
func (r *GormFaceVerifyRecordRepository) Update(ctx context.Context, record *entities.FaceVerifyRecord) error {
if err := r.db.WithContext(ctx).Save(record).Error; err != nil {
r.logger.Error("更新人脸识别记录失败",
zap.String("id", record.ID),
zap.Error(err),
)
return fmt.Errorf("更新人脸识别记录失败: %w", err)
}
return nil
}
// Delete 删除人脸识别记录
func (r *GormFaceVerifyRecordRepository) Delete(ctx context.Context, id string) error {
if err := r.db.WithContext(ctx).Delete(&entities.FaceVerifyRecord{}, "id = ?", id).Error; err != nil {
r.logger.Error("删除人脸识别记录失败",
zap.String("id", id),
zap.Error(err),
)
return fmt.Errorf("删除人脸识别记录失败: %w", err)
}
return nil
}
// GetByUserID 根据用户ID获取人脸识别记录列表
func (r *GormFaceVerifyRecordRepository) GetByUserID(ctx context.Context, userID string, page, pageSize int) ([]*entities.FaceVerifyRecord, int, error) {
var records []*entities.FaceVerifyRecord
var total int64
query := r.db.WithContext(ctx).Model(&entities.FaceVerifyRecord{}).Where("user_id = ?", userID)
// 获取总数
if err := query.Count(&total).Error; err != nil {
r.logger.Error("获取用户人脸识别记录总数失败", zap.Error(err))
return nil, 0, fmt.Errorf("获取人脸识别记录总数失败: %w", err)
}
// 分页查询
offset := (page - 1) * pageSize
if err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&records).Error; err != nil {
r.logger.Error("获取用户人脸识别记录列表失败", zap.Error(err))
return nil, 0, fmt.Errorf("获取人脸识别记录列表失败: %w", err)
}
return records, int(total), nil
}
// GetExpiredRecords 获取已过期的人脸识别记录
func (r *GormFaceVerifyRecordRepository) GetExpiredRecords(ctx context.Context, limit int) ([]*entities.FaceVerifyRecord, error) {
var records []*entities.FaceVerifyRecord
if err := r.db.WithContext(ctx).
Where("expires_at < NOW() AND status = ?", "PROCESSING").
Limit(limit).
Order("expires_at ASC").
Find(&records).Error; err != nil {
r.logger.Error("获取过期人脸识别记录失败", zap.Error(err))
return nil, fmt.Errorf("获取过期人脸识别记录失败: %w", err)
}
return records, nil
}

View File

@@ -1,163 +0,0 @@
package repositories
import (
"context"
"fmt"
"go.uber.org/zap"
"gorm.io/gorm"
"tyapi-server/internal/domains/certification/entities"
)
// GormLicenseUploadRecordRepository GORM营业执照上传记录仓储实现
type GormLicenseUploadRecordRepository struct {
db *gorm.DB
logger *zap.Logger
}
// NewGormLicenseUploadRecordRepository 创建GORM营业执照上传记录仓储
func NewGormLicenseUploadRecordRepository(db *gorm.DB, logger *zap.Logger) LicenseUploadRecordRepository {
return &GormLicenseUploadRecordRepository{
db: db,
logger: logger,
}
}
// Create 创建上传记录
func (r *GormLicenseUploadRecordRepository) Create(ctx context.Context, record *entities.LicenseUploadRecord) error {
if err := r.db.WithContext(ctx).Create(record).Error; err != nil {
r.logger.Error("创建上传记录失败",
zap.String("user_id", record.UserID),
zap.String("file_name", record.OriginalFileName),
zap.Error(err),
)
return fmt.Errorf("创建上传记录失败: %w", err)
}
r.logger.Info("上传记录创建成功",
zap.String("id", record.ID),
zap.String("file_name", record.OriginalFileName),
)
return nil
}
// GetByID 根据ID获取上传记录
func (r *GormLicenseUploadRecordRepository) GetByID(ctx context.Context, id string) (*entities.LicenseUploadRecord, error) {
var record entities.LicenseUploadRecord
if err := r.db.WithContext(ctx).First(&record, "id = ?", id).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("上传记录不存在")
}
r.logger.Error("获取上传记录失败",
zap.String("id", id),
zap.Error(err),
)
return nil, fmt.Errorf("获取上传记录失败: %w", err)
}
return &record, nil
}
// GetByUserID 根据用户ID获取上传记录列表
func (r *GormLicenseUploadRecordRepository) GetByUserID(ctx context.Context, userID string, page, pageSize int) ([]*entities.LicenseUploadRecord, int, error) {
var records []*entities.LicenseUploadRecord
var total int64
query := r.db.WithContext(ctx).Model(&entities.LicenseUploadRecord{}).Where("user_id = ?", userID)
// 获取总数
if err := query.Count(&total).Error; err != nil {
r.logger.Error("获取用户上传记录总数失败", zap.Error(err))
return nil, 0, fmt.Errorf("获取上传记录总数失败: %w", err)
}
// 分页查询
offset := (page - 1) * pageSize
if err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&records).Error; err != nil {
r.logger.Error("获取用户上传记录列表失败", zap.Error(err))
return nil, 0, fmt.Errorf("获取上传记录列表失败: %w", err)
}
return records, int(total), nil
}
// GetByCertificationID 根据认证ID获取上传记录
func (r *GormLicenseUploadRecordRepository) GetByCertificationID(ctx context.Context, certificationID string) (*entities.LicenseUploadRecord, error) {
var record entities.LicenseUploadRecord
if err := r.db.WithContext(ctx).First(&record, "certification_id = ?", certificationID).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("上传记录不存在")
}
r.logger.Error("根据认证ID获取上传记录失败",
zap.String("certification_id", certificationID),
zap.Error(err),
)
return nil, fmt.Errorf("获取上传记录失败: %w", err)
}
return &record, nil
}
// Update 更新上传记录
func (r *GormLicenseUploadRecordRepository) Update(ctx context.Context, record *entities.LicenseUploadRecord) error {
if err := r.db.WithContext(ctx).Save(record).Error; err != nil {
r.logger.Error("更新上传记录失败",
zap.String("id", record.ID),
zap.Error(err),
)
return fmt.Errorf("更新上传记录失败: %w", err)
}
return nil
}
// Delete 删除上传记录
func (r *GormLicenseUploadRecordRepository) Delete(ctx context.Context, id string) error {
if err := r.db.WithContext(ctx).Delete(&entities.LicenseUploadRecord{}, "id = ?", id).Error; err != nil {
r.logger.Error("删除上传记录失败",
zap.String("id", id),
zap.Error(err),
)
return fmt.Errorf("删除上传记录失败: %w", err)
}
return nil
}
// GetByQiNiuKey 根据七牛云Key获取上传记录
func (r *GormLicenseUploadRecordRepository) GetByQiNiuKey(ctx context.Context, key string) (*entities.LicenseUploadRecord, error) {
var record entities.LicenseUploadRecord
if err := r.db.WithContext(ctx).First(&record, "qiniu_key = ?", key).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("上传记录不存在")
}
r.logger.Error("根据七牛云Key获取上传记录失败",
zap.String("qiniu_key", key),
zap.Error(err),
)
return nil, fmt.Errorf("获取上传记录失败: %w", err)
}
return &record, nil
}
// GetPendingOCR 获取待OCR处理的上传记录
func (r *GormLicenseUploadRecordRepository) GetPendingOCR(ctx context.Context, limit int) ([]*entities.LicenseUploadRecord, error) {
var records []*entities.LicenseUploadRecord
if err := r.db.WithContext(ctx).
Where("ocr_processed = ? OR (ocr_processed = ? AND ocr_success = ?)", false, true, false).
Limit(limit).
Order("created_at ASC").
Find(&records).Error; err != nil {
r.logger.Error("获取待OCR处理记录失败", zap.Error(err))
return nil, fmt.Errorf("获取待OCR处理记录失败: %w", err)
}
return records, nil
}

View File

@@ -1,105 +0,0 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
)
// CertificationRepository 认证仓储接口
type CertificationRepository interface {
// 基础CRUD操作
Create(ctx context.Context, cert *entities.Certification) error
GetByID(ctx context.Context, id string) (*entities.Certification, error)
GetByUserID(ctx context.Context, userID string) (*entities.Certification, error)
Update(ctx context.Context, cert *entities.Certification) error
Delete(ctx context.Context, id string) error
// 查询操作
List(ctx context.Context, page, pageSize int, status enums.CertificationStatus) ([]*entities.Certification, int, error)
GetByStatus(ctx context.Context, status enums.CertificationStatus, page, pageSize int) ([]*entities.Certification, int, error)
GetPendingApprovals(ctx context.Context, page, pageSize int) ([]*entities.Certification, int, error)
// 关联查询
GetWithEnterprise(ctx context.Context, id string) (*entities.Certification, error)
GetWithAllRelations(ctx context.Context, id string) (*entities.Certification, error)
// 统计操作
CountByStatus(ctx context.Context, status enums.CertificationStatus) (int64, error)
CountByUserID(ctx context.Context, userID string) (int64, error)
}
// EnterpriseRepository 企业信息仓储接口
type EnterpriseRepository interface {
// 基础CRUD操作
Create(ctx context.Context, enterprise *entities.Enterprise) error
GetByID(ctx context.Context, id string) (*entities.Enterprise, error)
GetByCertificationID(ctx context.Context, certificationID string) (*entities.Enterprise, error)
Update(ctx context.Context, enterprise *entities.Enterprise) error
Delete(ctx context.Context, id string) error
// 查询操作
GetByUnifiedSocialCode(ctx context.Context, code string) (*entities.Enterprise, error)
ExistsByUnifiedSocialCode(ctx context.Context, code string) (bool, error)
}
// LicenseUploadRecordRepository 营业执照上传记录仓储接口
type LicenseUploadRecordRepository interface {
// 基础CRUD操作
Create(ctx context.Context, record *entities.LicenseUploadRecord) error
GetByID(ctx context.Context, id string) (*entities.LicenseUploadRecord, error)
GetByUserID(ctx context.Context, userID string, page, pageSize int) ([]*entities.LicenseUploadRecord, int, error)
GetByCertificationID(ctx context.Context, certificationID string) (*entities.LicenseUploadRecord, error)
Update(ctx context.Context, record *entities.LicenseUploadRecord) error
Delete(ctx context.Context, id string) error
// 查询操作
GetByQiNiuKey(ctx context.Context, key string) (*entities.LicenseUploadRecord, error)
GetPendingOCR(ctx context.Context, limit int) ([]*entities.LicenseUploadRecord, error)
}
// FaceVerifyRecordRepository 人脸识别记录仓储接口
type FaceVerifyRecordRepository interface {
// 基础CRUD操作
Create(ctx context.Context, record *entities.FaceVerifyRecord) error
GetByID(ctx context.Context, id string) (*entities.FaceVerifyRecord, error)
GetByCertifyID(ctx context.Context, certifyID string) (*entities.FaceVerifyRecord, error)
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.FaceVerifyRecord, error)
Update(ctx context.Context, record *entities.FaceVerifyRecord) error
Delete(ctx context.Context, id string) error
// 查询操作
GetByUserID(ctx context.Context, userID string, page, pageSize int) ([]*entities.FaceVerifyRecord, int, error)
GetExpiredRecords(ctx context.Context, limit int) ([]*entities.FaceVerifyRecord, error)
}
// ContractRecordRepository 合同记录仓储接口
type ContractRecordRepository interface {
// 基础CRUD操作
Create(ctx context.Context, record *entities.ContractRecord) error
GetByID(ctx context.Context, id string) (*entities.ContractRecord, error)
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.ContractRecord, error)
Update(ctx context.Context, record *entities.ContractRecord) error
Delete(ctx context.Context, id string) error
// 查询操作
GetByUserID(ctx context.Context, userID string, page, pageSize int) ([]*entities.ContractRecord, int, error)
GetByStatus(ctx context.Context, status string, page, pageSize int) ([]*entities.ContractRecord, int, error)
GetExpiredContracts(ctx context.Context, limit int) ([]*entities.ContractRecord, error)
}
// NotificationRecordRepository 通知记录仓储接口
type NotificationRecordRepository interface {
// 基础CRUD操作
Create(ctx context.Context, record *entities.NotificationRecord) error
GetByID(ctx context.Context, id string) (*entities.NotificationRecord, error)
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.NotificationRecord, error)
Update(ctx context.Context, record *entities.NotificationRecord) error
Delete(ctx context.Context, id string) error
// 查询操作
GetByUserID(ctx context.Context, userID string, page, pageSize int) ([]*entities.NotificationRecord, int, error)
GetPendingNotifications(ctx context.Context, limit int) ([]*entities.NotificationRecord, error)
GetFailedNotifications(ctx context.Context, limit int) ([]*entities.NotificationRecord, error)
}

View File

@@ -0,0 +1,72 @@
package queries
import "tyapi-server/internal/domains/certification/enums"
// ListCertificationsQuery 认证申请列表查询参数
type ListCertificationsQuery struct {
Page int `json:"page"`
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"`
}
// ListEnterprisesQuery 企业信息列表查询参数
type ListEnterprisesQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
UserID string `json:"user_id"`
EnterpriseName string `json:"enterprise_name"`
LicenseNumber string `json:"license_number"`
LegalPersonName string `json:"legal_person_name"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
}
// ListFaceVerifyRecordsQuery 人脸识别记录列表查询参数
type ListFaceVerifyRecordsQuery 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"`
}
// ListContractRecordsQuery 合同记录列表查询参数
type ListContractRecordsQuery 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"`
}
// ListLicenseUploadRecordsQuery 营业执照上传记录列表查询参数
type ListLicenseUploadRecordsQuery 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"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
}

View File

@@ -1,62 +0,0 @@
package routes
import (
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"tyapi-server/internal/domains/certification/handlers"
"tyapi-server/internal/shared/middleware"
)
// CertificationRoutes 认证路由组
type CertificationRoutes struct {
certificationHandler *handlers.CertificationHandler
authMiddleware *middleware.JWTAuthMiddleware
logger *zap.Logger
}
// NewCertificationRoutes 创建认证路由
func NewCertificationRoutes(
certificationHandler *handlers.CertificationHandler,
authMiddleware *middleware.JWTAuthMiddleware,
logger *zap.Logger,
) *CertificationRoutes {
return &CertificationRoutes{
certificationHandler: certificationHandler,
authMiddleware: authMiddleware,
logger: logger,
}
}
// RegisterRoutes 注册认证相关路由
func (r *CertificationRoutes) RegisterRoutes(router *gin.Engine) {
// 认证相关路由组,需要用户认证
certificationGroup := router.Group("/api/v1/certification")
certificationGroup.Use(r.authMiddleware.Handle())
{
// 创建认证申请
certificationGroup.POST("/create", r.certificationHandler.CreateCertification)
// 上传营业执照
certificationGroup.POST("/upload-license", r.certificationHandler.UploadLicense)
// 获取认证状态
certificationGroup.GET("/status", r.certificationHandler.GetCertificationStatus)
// 获取进度统计
certificationGroup.GET("/progress", r.certificationHandler.GetProgressStats)
// 提交企业信息
certificationGroup.PUT("/:id/submit-info", r.certificationHandler.SubmitEnterpriseInfo)
// 发起人脸识别验证
certificationGroup.POST("/:id/face-verify", r.certificationHandler.InitiateFaceVerify)
// 申请合同签署
certificationGroup.POST("/:id/apply-contract", r.certificationHandler.ApplyContract)
// 获取认证详情
certificationGroup.GET("/:id", r.certificationHandler.GetCertificationDetails)
// 重试认证步骤
certificationGroup.POST("/:id/retry", r.certificationHandler.RetryStep)
}
r.logger.Info("认证路由注册完成")
}

View File

@@ -124,17 +124,17 @@ func (sm *CertificationStateMachine) TransitionTo(
}
// 执行状态转换前的验证
if err := sm.validateTransition(ctx, cert, targetStatus, metadata); err != nil {
if err := sm.validateTransition(ctx, &cert, targetStatus, metadata); err != nil {
return fmt.Errorf("状态转换验证失败: %w", err)
}
// 更新状态和时间戳
oldStatus := cert.Status
cert.Status = targetStatus
sm.updateTimestamp(cert, targetStatus)
sm.updateTimestamp(&cert, targetStatus)
// 更新其他字段
sm.updateCertificationFields(cert, targetStatus, metadata)
sm.updateCertificationFields(&cert, targetStatus, metadata)
// 保存到数据库
if err := sm.certRepo.Update(ctx, cert); err != nil {
@@ -215,9 +215,9 @@ func (sm *CertificationStateMachine) validateTransition(
switch targetStatus {
case enums.StatusInfoSubmitted:
// 验证企业信息是否完整
if cert.EnterpriseID == nil {
return fmt.Errorf("企业信息未提交")
}
// 这里应该检查用户是否有企业信息,通过用户域的企业服务验证
// 暂时跳过验证,由应用服务层协调
break
case enums.StatusFaceVerified:
// 验证人脸识别是否成功
@@ -285,3 +285,164 @@ func (sm *CertificationStateMachine) GetTransitionAction(
return ""
}
// GetTransitionHistory 获取状态转换历史
func (sm *CertificationStateMachine) GetTransitionHistory(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)
}
history := []map[string]interface{}{}
// 添加创建时间
history = append(history, map[string]interface{}{
"status": "CREATED",
"timestamp": cert.CreatedAt,
"action": "create",
"performer": "system",
"metadata": map[string]interface{}{},
})
// 添加各个时间节点的状态转换
if cert.InfoSubmittedAt != nil {
history = append(history, map[string]interface{}{
"status": string(enums.StatusInfoSubmitted),
"timestamp": *cert.InfoSubmittedAt,
"action": "submit_info",
"performer": "user",
"metadata": map[string]interface{}{},
})
}
if cert.FaceVerifiedAt != nil {
history = append(history, map[string]interface{}{
"status": string(enums.StatusFaceVerified),
"timestamp": *cert.FaceVerifiedAt,
"action": "face_verify",
"performer": "system",
"metadata": map[string]interface{}{},
})
}
if cert.ContractAppliedAt != nil {
history = append(history, map[string]interface{}{
"status": string(enums.StatusContractApplied),
"timestamp": *cert.ContractAppliedAt,
"action": "apply_contract",
"performer": "user",
"metadata": map[string]interface{}{},
})
}
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 != "" {
metadata["contract_url"] = cert.ContractURL
}
history = append(history, map[string]interface{}{
"status": string(enums.StatusContractSigned),
"timestamp": *cert.ContractSignedAt,
"action": "user_sign",
"performer": "user",
"metadata": metadata,
})
}
if cert.CompletedAt != nil {
history = append(history, map[string]interface{}{
"status": string(enums.StatusCompleted),
"timestamp": *cert.CompletedAt,
"action": "system_complete",
"performer": "system",
"metadata": map[string]interface{}{},
})
}
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
}