Files
tyapi-server/docs/企业认证系统实施计划.md
2025-07-11 21:05:58 +08:00

49 KiB
Raw Blame History

企业认证系统实施计划

📋 需求概述

业务流程

用户注册后需要进行企业认证以使用平台核心功能,认证流程如下:

  1. 提交企业信息:用户上传营业执照,系统通过百度 OCR 自动识别企业四要素
  2. 人脸识别:法人进行人脸识别验证
  3. 申请电子合同:系统生成申请记录
  4. 管理员审核:管理员收到企业微信通知,审核后手工上传签署链接
  5. 签署合同:用户收到短信通知,点击链接完成签署
  6. 认证完成:系统为用户生成钱包和 Access Key

企业四要素

  • 企业名称
  • 统一信用代码
  • 企业法人姓名
  • 法人身份证号

📊 业务流程图

整个企业认证流程包含 10 个主要状态节点,涉及用户、系统、管理员和第三方服务的协同工作:

流程说明

  1. 用户操作阶段(蓝色节点)

    • 用户上传营业执照图片(自动上传至七牛云并 OCR 识别)
    • 确认或修改 OCR 识别的企业四要素信息
    • 进行阿里云人脸识别验证
    • 申请电子合同
    • 查看合同链接并签署电子合同
  2. 系统自动处理(紫色节点)

    • 创建认证申请记录
    • 七牛云文件存储和百度 OCR 识别
    • 将合同申请转为待审核状态
    • 确认合同签署状态
    • 生成钱包和 Access Key
  3. 管理员审核(橙色节点)

    • 审核企业认证申请
    • 手工上传电子合同签署链接
    • 处理审核结果(通过/拒绝)
  4. 等待状态(紫色节点)

    • 合同待审核状态(等待管理员处理)
    • 合同已审核状态(等待用户签署)
  5. 第三方服务(绿色节点)

    • 七牛云文件存储
    • 百度 OCR 识别营业执照
    • 阿里云 InitFaceVerify 人脸识别
  6. 决策节点(红色节点)

    • 企业信息确认OCR 成功与否都允许用户确认)
    • 人脸识别结果验证
    • 管理员审核结果(通过/拒绝)
    • 合同签署状态

关键特性

  • 一体化处理营业执照上传、存储、OCR 识别一次完成
  • 容错性强OCR 识别失败不影响流程,用户可手动填写信息
  • 异步审核:用户申请合同后,管理员异步审核并上传链接,用户无需实时等待
  • 手工上传:管理员手工上传电子合同签署链接,确保合同的准确性和合规性
  • 状态分离:合同申请、待审核、已审核(有链接)、已签署状态清晰分离
  • 重试机制:人脸识别、合同签署都支持失败重试
  • 多渠道通知:企业微信通知管理员新申请,短信通知用户合同就绪和认证完成
  • 状态追踪:每个环节都有明确的状态标识和时间戳记录
  • 安全存储:营业执照文件安全存储在七牛云,支持 CDN 加速访问

🏗️ 架构设计

新增域结构

internal/domains/
├── certification/              # 企业认证域
│   ├── entities/              # 实体层
│   │   ├── certification.go      # 认证申请实体
│   │   ├── enterprise.go         # 企业信息实体
│   │   └── contract.go           # 电子合同实体
│   ├── dto/                   # 数据传输对象
│   │   ├── certification_dto.go
│   │   ├── enterprise_dto.go
│   │   └── ocr_dto.go
│   ├── repositories/          # 仓储层
│   │   ├── certification_repository.go
│   │   ├── enterprise_repository.go
│   │   └── contract_repository.go
│   ├── services/              # 服务层
│   │   ├── certification_service.go
│   │   ├── enterprise_service.go
│   │   ├── ocr_service.go
│   │   └── face_verification_service.go
│   ├── handlers/              # 处理器层
│   │   ├── certification_handler.go
│   │   └── admin_certification_handler.go
│   ├── routes/                # 路由层
│   │   ├── certification_routes.go
│   │   └── admin_certification_routes.go
│   ├── events/                # 事件层
│   │   └── certification_events.go
│   ├── enums/                 # 枚举定义
│   │   └── certification_status.go
│   └── migrations/            # 数据库迁移
├── finance/                   # 财务域(独立域)
│   ├── entities/
│   │   └── wallet.go
│   ├── dto/
│   │   └── wallet_dto.go
│   ├── repositories/
│   │   └── wallet_repository.go
│   ├── services/
│   │   └── wallet_service.go
│   ├── handlers/
│   │   └── wallet_handler.go
│   ├── routes/
│   │   └── wallet_routes.go
│   └── migrations/
├── admin/                     # 管理员域
│   ├── entities/
│   │   └── admin.go
│   ├── repositories/
│   │   └── admin_repository.go
│   ├── services/
│   │   └── admin_service.go
│   ├── handlers/
│   │   └── admin_handler.go
│   └── routes/
│       └── admin_routes.go
└── notification/              # 通知域(扩展)
    ├── services/
    │   ├── notification_service.go
    │   ├── wechat_work_service.go
    │   └── enhanced_sms_service.go
    └── providers/
        ├── wechat_work_provider.go
        └── sms_template_provider.go

共享服务重构

internal/shared/
├── sms/                       # 短信服务(现有,需扩展)
│   ├── sms_service.go            # 基础短信服务
│   ├── template_service.go       # 短信模板服务(新增)
│   └── notification_sms.go       # 通知类短信服务(新增)
├── ocr/                       # OCR服务新增
│   ├── baidu_ocr_service.go      # 百度OCR实现
│   └── ocr_interface.go          # OCR接口定义
├── storage/                   # 文件存储服务(新增)
│   ├── qiniu_storage_service.go   # 七牛云存储实现
│   └── storage_interface.go       # 存储接口定义
└── third_party/               # 第三方服务(新增)
    ├── aliyun/
    │   ├── face_verify_service.go    # 阿里云人脸识别
    │   └── sms_service.go           # 阿里云短信服务
    ├── qiniu/
    │   └── qiniu_client.go          # 七牛云客户端
    └── wechat_work/
        └── wechat_work_api.go

📊 核心实体设计

认证状态枚举

type CertificationStatus string

const (
    StatusPending              CertificationStatus = "pending"               // 待开始
    StatusEnterpriseSubmitted                     = "enterprise_submitted"  // 企业信息已提交
    StatusOCRProcessed                            = "ocr_processed"         // OCR识别完成
    StatusFaceVerified                            = "face_verified"         // 人脸识别完成
    StatusContractApplied                         = "contract_applied"      // 已申请合同
    StatusContractPending                         = "contract_pending"      // 合同待审核
    StatusContractApproved                        = "contract_approved"     // 合同已审核(有链接)
    StatusContractSigned                          = "contract_signed"       // 合同已签署
    StatusCompleted                               = "completed"             // 认证完成
    StatusRejected                                = "rejected"              // 已拒绝
)

认证申请实体

type Certification struct {
    ID           string              `gorm:"primaryKey;type:varchar(36)"`
    UserID       string              `gorm:"type:varchar(36);not null;index"`
    EnterpriseID string              `gorm:"type:varchar(36);index"`
    Status       CertificationStatus `gorm:"type:varchar(50);not null;index"`

    // 流程节点时间戳
    InfoSubmittedAt       *time.Time `json:"info_submitted_at"`
    FaceVerifiedAt        *time.Time `json:"face_verified_at"`
    ContractAppliedAt     *time.Time `json:"contract_applied_at"`
    ContractApprovedAt    *time.Time `json:"contract_approved_at"`
    ContractSignedAt      *time.Time `json:"contract_signed_at"`
    CompletedAt           *time.Time `json:"completed_at"`

    // 审核信息
    AdminID       *string `gorm:"type:varchar(36)"`
    ApprovalNotes string  `gorm:"type:text"`
    RejectReason  string  `gorm:"type:text"`

    // 合同信息
    ContractURL   string `gorm:"type:varchar(500)"`
    SigningURL    string `gorm:"type:varchar(500)"`
    SignedAt      *time.Time

    // OCR识别信息
    OCRRequestID  string `gorm:"type:varchar(100)"`
    OCRConfidence float64 `gorm:"type:decimal(5,2)"`

    CreatedAt time.Time      `gorm:"autoCreateTime"`
    UpdatedAt time.Time      `gorm:"autoUpdateTime"`
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

企业信息实体

type Enterprise struct {
    ID                string `gorm:"primaryKey;type:varchar(36)"`
    CertificationID   string `gorm:"type:varchar(36);not null"`

    // 企业四要素
    CompanyName       string `gorm:"type:varchar(255);not null"`        // 企业名称
    UnifiedSocialCode string `gorm:"type:varchar(50);not null"`         // 统一社会信用代码
    LegalPersonName   string `gorm:"type:varchar(100);not null"`        // 法定代表人姓名
    LegalPersonID     string `gorm:"type:varchar(50);not null"`         // 法定代表人身份证号

    // OCR识别结果
    BusinessLicenseURL string `gorm:"type:varchar(500);not null"`       // 营业执照图片URL
    OCRRawData        string `gorm:"type:text"`                         // OCR原始返回数据
    OCRConfidence     float64 `gorm:"type:decimal(5,2)"`                // 识别置信度

    // 验证状态
    IsOCRVerified     bool   `gorm:"default:false"`
    IsFaceVerified    bool   `gorm:"default:false"`
    VerificationData  string `gorm:"type:text"`

    CreatedAt time.Time      `gorm:"autoCreateTime"`
    UpdatedAt time.Time      `gorm:"autoUpdateTime"`
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

钱包实体(财务域)

type Wallet struct {
    ID        string `gorm:"primaryKey;type:varchar(36)"`
    UserID    string `gorm:"type:varchar(36);not null;uniqueIndex"`

    // 钱包状态
    IsActive  bool    `gorm:"default:true"`
    Balance   decimal.Decimal `gorm:"type:decimal(20,8);default:0"`

    CreatedAt time.Time      `gorm:"autoCreateTime"`
    UpdatedAt time.Time      `gorm:"autoUpdateTime"`
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

用户密钥实体(财务域)

type UserSecrets struct {
    ID        string `gorm:"primaryKey;type:varchar(36)"`
    UserID    string `gorm:"type:varchar(36);not null;uniqueIndex"`
    AccessID  string `gorm:"type:varchar(100);not null;uniqueIndex"`
    AccessKey string `gorm:"type:varchar(255);not null"`

    // 密钥状态
    IsActive    bool      `gorm:"default:true"`
    LastUsedAt  *time.Time
    ExpiresAt   *time.Time

    CreatedAt time.Time      `gorm:"autoCreateTime"`
    UpdatedAt time.Time      `gorm:"autoUpdateTime"`
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

营业执照上传记录实体

type LicenseUploadRecord struct {
    ID              string `gorm:"primaryKey;type:varchar(36)"`
    CertificationID string `gorm:"type:varchar(36);not null;index"`
    UserID          string `gorm:"type:varchar(36);not null;index"`

    // 文件信息
    OriginalFileName string `gorm:"type:varchar(255);not null"`
    FileSize         int64  `gorm:"not null"`
    FileType         string `gorm:"type:varchar(50);not null"`
    FileURL          string `gorm:"type:varchar(500);not null"`
    QiNiuKey         string `gorm:"type:varchar(255);not null"`

    // OCR处理结果
    OCRProcessed     bool    `gorm:"default:false"`
    OCRSuccess       bool    `gorm:"default:false"`
    OCRConfidence    float64 `gorm:"type:decimal(5,2)"`
    OCRRawData       string  `gorm:"type:text"`
    OCRErrorMessage  string  `gorm:"type:varchar(500)"`

    CreatedAt time.Time      `gorm:"autoCreateTime"`
    UpdatedAt time.Time      `gorm:"autoUpdateTime"`
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

人脸识别记录实体

type FaceVerifyRecord struct {
    ID              string `gorm:"primaryKey;type:varchar(36)"`
    CertificationID string `gorm:"type:varchar(36);not null;index"`
    UserID          string `gorm:"type:varchar(36);not null;index"`

    // 阿里云人脸识别信息
    CertifyID       string `gorm:"type:varchar(100);not null"`
    VerifyURL       string `gorm:"type:varchar(500)"`
    ReturnURL       string `gorm:"type:varchar(500)"`

    // 身份信息
    RealName        string `gorm:"type:varchar(100);not null"`
    IDCardNumber    string `gorm:"type:varchar(50);not null"`

    // 验证结果
    Status          string `gorm:"type:varchar(50);not null"` // PROCESSING, SUCCESS, FAIL
    ResultCode      string `gorm:"type:varchar(50)"`
    ResultMessage   string `gorm:"type:varchar(500)"`
    VerifyScore     float64 `gorm:"type:decimal(5,2)"`

    // 时间信息
    InitiatedAt     time.Time  `gorm:"autoCreateTime"`
    CompletedAt     *time.Time
    ExpiresAt       time.Time  `gorm:"not null"`

    CreatedAt time.Time      `gorm:"autoCreateTime"`
    UpdatedAt time.Time      `gorm:"autoUpdateTime"`
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

通知记录实体

type NotificationRecord struct {
    ID              string `gorm:"primaryKey;type:varchar(36)"`
    CertificationID string `gorm:"type:varchar(36);index"`
    UserID          string `gorm:"type:varchar(36);index"`

    // 通知类型和渠道
    NotificationType string `gorm:"type:varchar(50);not null"` // SMS, WECHAT_WORK, EMAIL
    NotificationScene string `gorm:"type:varchar(50);not null"` // ADMIN_NEW_APPLICATION, USER_CONTRACT_READY, etc.

    // 接收方信息
    Recipient       string `gorm:"type:varchar(255);not null"`

    // 消息内容
    Title           string `gorm:"type:varchar(255)"`
    Content         string `gorm:"type:text;not null"`
    TemplateID      string `gorm:"type:varchar(100)"`
    TemplateParams  string `gorm:"type:text"` // JSON格式

    // 发送状态
    Status          string `gorm:"type:varchar(50);not null"` // PENDING, SENT, FAILED
    ErrorMessage    string `gorm:"type:varchar(500)"`
    SentAt          *time.Time
    RetryCount      int    `gorm:"default:0"`
    MaxRetryCount   int    `gorm:"default:3"`

    CreatedAt time.Time      `gorm:"autoCreateTime"`
    UpdatedAt time.Time      `gorm:"autoUpdateTime"`
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

合同记录实体

type ContractRecord struct {
    ID              string `gorm:"primaryKey;type:varchar(36)"`
    CertificationID string `gorm:"type:varchar(36);not null;index"`
    UserID          string `gorm:"type:varchar(36);not null;index"`
    AdminID         string `gorm:"type:varchar(36);index"`

    // 合同信息
    ContractType    string `gorm:"type:varchar(50);not null"` // ENTERPRISE_CERTIFICATION
    ContractURL     string `gorm:"type:varchar(500)"`
    SigningURL      string `gorm:"type:varchar(500)"`

    // 签署信息
    SignatureData   string `gorm:"type:text"`
    SignedAt        *time.Time
    ClientIP        string `gorm:"type:varchar(50)"`
    UserAgent       string `gorm:"type:varchar(500)"`

    // 状态信息
    Status          string `gorm:"type:varchar(50);not null"` // PENDING, APPROVED, SIGNED, EXPIRED
    ApprovalNotes   string `gorm:"type:text"`
    RejectReason    string `gorm:"type:text"`
    ExpiresAt       *time.Time

    CreatedAt time.Time      `gorm:"autoCreateTime"`
    UpdatedAt time.Time      `gorm:"autoUpdateTime"`
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

🔄 状态管理设计

认证状态枚举扩展

type CertificationStatus string

const (
    // 主流程状态
    StatusPending              CertificationStatus = "pending"               // 待开始
    StatusInfoSubmitted                           = "info_submitted"        // 企业信息已提交
    StatusFaceVerified                            = "face_verified"         // 人脸识别完成
    StatusContractApplied                         = "contract_applied"      // 已申请合同
    StatusContractPending                         = "contract_pending"      // 合同待审核
    StatusContractApproved                        = "contract_approved"     // 合同已审核(有链接)
    StatusContractSigned                          = "contract_signed"       // 合同已签署
    StatusCompleted                               = "completed"             // 认证完成

    // 失败和重试状态
    StatusFaceFailed                             = "face_failed"           // 人脸识别失败
    StatusSignFailed                             = "sign_failed"           // 签署失败
    StatusRejected                               = "rejected"              // 已拒绝
)

状态转换规则

type StateTransition struct {
    From      CertificationStatus
    To        CertificationStatus
    Action    string
    AllowUser bool  // 是否允许用户操作
    AllowAdmin bool // 是否允许管理员操作
}

var stateTransitions = []StateTransition{
    // 正常流程转换
    {StatusPending, StatusInfoSubmitted, "submit_info", true, false},
    {StatusInfoSubmitted, StatusFaceVerified, "face_verify", true, false},
    {StatusFaceVerified, StatusContractApplied, "apply_contract", true, false},
    {StatusContractApplied, StatusContractPending, "system_process", false, false}, // 系统自动
    {StatusContractPending, StatusContractApproved, "admin_approve", false, true},
    {StatusContractApproved, StatusContractSigned, "user_sign", true, false},
    {StatusContractSigned, StatusCompleted, "system_complete", false, false}, // 系统自动

    // 失败和重试转换
    {StatusInfoSubmitted, StatusFaceFailed, "face_fail", false, false},
    {StatusFaceFailed, StatusInfoSubmitted, "retry_face", true, false},
    {StatusContractPending, StatusRejected, "admin_reject", false, true},
    {StatusRejected, StatusInfoSubmitted, "restart_process", true, false},
    {StatusContractApproved, StatusSignFailed, "sign_fail", false, false},
    {StatusSignFailed, StatusContractApproved, "retry_sign", true, false},
}

状态管理服务

type CertificationStateMachine struct {
    transitions map[CertificationStatus][]CertificationStatus
    repo        *CertificationRepository
    logger      *zap.Logger
}

func NewCertificationStateMachine(repo *CertificationRepository, logger *zap.Logger) *CertificationStateMachine {
    sm := &CertificationStateMachine{
        transitions: make(map[CertificationStatus][]CertificationStatus),
        repo:        repo,
        logger:      logger,
    }

    // 构建状态转换映射
    for _, transition := range stateTransitions {
        sm.transitions[transition.From] = append(sm.transitions[transition.From], transition.To)
    }

    return sm
}

func (sm *CertificationStateMachine) CanTransition(from, to CertificationStatus) bool {
    allowedStates, exists := sm.transitions[from]
    if !exists {
        return false
    }

    for _, allowedState := range allowedStates {
        if allowedState == to {
            return true
        }
    }
    return false
}

func (sm *CertificationStateMachine) TransitionTo(ctx context.Context, certificationID string, newStatus CertificationStatus, operatorID string, notes string) error {
    // 获取当前认证记录
    certification, err := sm.repo.GetByID(ctx, certificationID)
    if err != nil {
        return fmt.Errorf("获取认证记录失败: %w", err)
    }

    // 检查状态转换是否合法
    if !sm.CanTransition(certification.Status, newStatus) {
        return fmt.Errorf("不允许从状态 %s 转换到 %s", certification.Status, newStatus)
    }

    // 更新状态和时间戳
    oldStatus := certification.Status
    certification.Status = newStatus
    sm.updateTimestamp(certification, newStatus)

    // 记录操作信息
    if operatorID != "" {
        certification.AdminID = &operatorID
    }
    if notes != "" {
        certification.ApprovalNotes = notes
    }

    // 保存更新
    if err := sm.repo.Update(ctx, certification); err != nil {
        return fmt.Errorf("更新认证状态失败: %w", err)
    }

    sm.logger.Info("认证状态已更新",
        zap.String("certification_id", certificationID),
        zap.String("from_status", string(oldStatus)),
        zap.String("to_status", string(newStatus)),
        zap.String("operator_id", operatorID))

    return nil
}

func (sm *CertificationStateMachine) updateTimestamp(cert *entities.Certification, status CertificationStatus) {
    now := time.Now()
    switch status {
    case StatusInfoSubmitted:
        cert.InfoSubmittedAt = &now
    case StatusFaceVerified:
        cert.FaceVerifiedAt = &now
    case StatusContractApplied:
        cert.ContractAppliedAt = &now
    case StatusContractApproved:
        cert.ContractApprovedAt = &now
    case StatusContractSigned:
        cert.ContractSignedAt = &now
    case StatusCompleted:
        cert.CompletedAt = &now
    }
}

func (sm *CertificationStateMachine) GetNextAllowedStates(currentStatus CertificationStatus) []CertificationStatus {
    return sm.transitions[currentStatus]
}

func (sm *CertificationStateMachine) GetRetryAction(status CertificationStatus) *string {
    retryMap := map[CertificationStatus]string{
        StatusFaceFailed:    "重新进行人脸识别",
        StatusSignFailed:    "重新签署合同",
        StatusRejected:      "重新提交申请",
    }

    if action, exists := retryMap[status]; exists {
        return &action
    }
    return nil
}

📱 分阶段接口设计

阶段 1: 上传营业执照(前端交互)

POST /api/v1/certification/upload-license
Content-Type: multipart/form-data

{
    "business_license": "file", // 营业执照图片文件
    "user_id": "string"        // 用户ID
}

响应:

{
    "code": 200,
    "message": "营业执照上传成功",
    "data": {
        "upload_record_id": "upload_123456",
        "license_url": "https://qiniu.example.com/licenses/cert_123456.jpg",
        "ocr_result": {
            "success": true,
            "confidence": 95.5,
            "enterprise_info": {
                "company_name": "示例科技有限公司",
                "unified_social_code": "91110000123456789X",
                "legal_person_name": "张三",
                "legal_person_id": "110101199001011234"
            }
        }
    }
}

OCR 失败响应:

{
    "code": 200,
    "message": "营业执照上传成功OCR识别失败",
    "data": {
        "upload_record_id": "upload_123456",
        "license_url": "https://qiniu.example.com/licenses/cert_123456.jpg",
        "ocr_result": {
            "success": false,
            "error": "图片模糊,无法识别企业信息",
            "enterprise_info": {
                "company_name": "",
                "unified_social_code": "",
                "legal_person_name": "",
                "legal_person_id": ""
            }
        }
    }
}

阶段 2: 提交企业信息 (pending → info_submitted)

POST /api/v1/certification/submit-enterprise-info
Content-Type: application/json

{
    "upload_record_id": "upload_123456",
    "company_name": "示例科技有限公司",
    "unified_social_code": "91110000123456789X",
    "legal_person_name": "张三",
    "legal_person_id": "110101199001011234"
}

信息提交成功:

{
    "code": 200,
    "message": "企业信息提交成功",
    "data": {
        "certification_id": "cert_123456",
        "status": "info_submitted",
        "next_step": "进行人脸识别",
        "next_action": "face_verify"
    }
}

阶段 3: 人脸识别 (info_submitted → face_verified/face_failed)

POST /api/v1/certification/{certification_id}/face-verify
Content-Type: application/json

{
    "legal_person_name": "张三",
    "legal_person_id": "110101199001011234",
    "return_url": "https://yourdomain.com/certification/face-result"
}

人脸识别初始化成功:

{
    "code": 200,
    "message": "人脸识别初始化成功",
    "data": {
        "certification_id": "cert_123456",
        "certify_id": "face_certify_789",
        "verify_url": "https://cloudauth.aliyun.com/web/verify?token=xxx",
        "status": "face_processing",
        "next_step": "请在30分钟内完成人脸识别",
        "expire_time": "2024-01-15T10:30:00Z"
    }
}

人脸识别结果查询:

GET /api/v1/certification/{certification_id}/face-result

人脸识别成功:

{
    "code": 200,
    "message": "人脸识别成功",
    "data": {
        "certification_id": "cert_123456",
        "status": "face_verified",
        "verify_result": "通过",
        "next_step": "申请电子合同",
        "next_action": "apply_contract"
    }
}

阶段 4: 申请电子合同 (face_verified → contract_applied → contract_pending)

POST /api/v1/certification/{certification_id}/apply-contract
Content-Type: application/json

{
    "contact_phone": "13800138000",
    "contact_email": "example@company.com"
}

申请成功:

{
    "code": 200,
    "message": "电子合同申请已提交",
    "data": {
        "certification_id": "cert_123456",
        "status": "contract_applied",
        "next_step": "系统正在处理申请",
        "estimated_time": "1-3个工作日"
    }
}

系统自动转换状态查询:

GET /api/v1/certification/{certification_id}/status

转换为待审核状态:

{
    "code": 200,
    "message": "合同申请已进入审核队列",
    "data": {
        "certification_id": "cert_123456",
        "status": "contract_pending",
        "next_step": "等待管理员审核并上传合同链接",
        "estimated_time": "1-3个工作日",
        "notification": "已通过企业微信通知管理员,审核结果将通过短信通知您"
    }
}

阶段 5: 管理员审核和合同链接上传 (contract_pending → contract_approved/rejected)

管理员获取待审核列表:

GET /api/v1/admin/certifications?status=contract_pending

管理员审核通过并上传合同链接:

POST /api/v1/admin/certifications/{certification_id}/approve
Content-Type: application/json

{
    "admin_id": "admin_123",
    "contract_signing_url": "https://contract-platform.com/sign/contract_456",
    "approval_notes": "企业信息核实无误,准予签署合同",
    "expire_hours": 72
}

审核通过响应:

{
    "code": 200,
    "message": "审核通过,合同链接已上传",
    "data": {
        "certification_id": "cert_123456",
        "status": "contract_approved",
        "signing_url": "https://contract-platform.com/sign/contract_456",
        "expire_time": "2024-01-18T15:00:00Z",
        "next_step": "已通过短信通知用户签署合同"
    }
}

管理员拒绝申请:

POST /api/v1/admin/certifications/{certification_id}/reject
Content-Type: application/json

{
    "admin_id": "admin_123",
    "reject_reason": "企业信息与工商数据不符,请重新提交",
    "allow_retry": true
}

拒绝响应:

{
    "code": 200,
    "message": "申请已拒绝",
    "data": {
        "certification_id": "cert_123456",
        "status": "rejected",
        "reject_reason": "企业信息与工商数据不符,请重新提交",
        "next_step": "已通过短信通知用户重新申请"
    }
}

阶段 6: 用户查看合同链接并签署 (contract_approved → contract_signed/sign_failed)

用户查询状态(收到短信通知后):

GET /api/v1/certification/{certification_id}/status

用户看到合同链接:

{
    "code": 200,
    "message": "合同已准备就绪",
    "data": {
        "certification_id": "cert_123456",
        "status": "contract_approved",
        "contract_info": {
            "signing_url": "https://contract-platform.com/sign/contract_456",
            "expire_time": "2024-01-18T15:00:00Z",
            "remaining_hours": 48
        },
        "next_step": "请点击链接签署电子合同",
        "next_action": "sign_contract"
    }
}

阶段 7: 用户签署合同 (contract_approved → contract_signed/sign_failed)

POST /api/v1/certification/{certification_id}/sign-contract
Content-Type: application/json

{
    "signature_data": "base64_signature_string",
    "sign_timestamp": "2024-01-16T14:30:00Z",
    "client_ip": "192.168.1.100"
}

签署成功:

{
    "code": 200,
    "message": "合同签署成功",
    "data": {
        "certification_id": "cert_123456",
        "status": "contract_signed",
        "signed_at": "2024-01-16T14:30:00Z",
        "next_step": "正在生成钱包账户",
        "estimated_completion": "2-5分钟"
    }
}

阶段 8: 认证完成 (contract_signed → completed)

系统自动完成,用户查询最终状态:

GET /api/v1/certification/{certification_id}/final-result

认证完成响应:

{
    "code": 200,
    "message": "企业认证已完成",
    "data": {
        "certification_id": "cert_123456",
        "status": "completed",
        "completed_at": "2024-01-16T14:35:00Z",
        "wallet_info": {
            "wallet_id": "wallet_789",
            "balance": "0.00",
            "is_active": true
        },
        "user_secrets": {
            "access_id": "AK_123456789",
            "access_key": "SK_abcdef123456", // 加密显示
            "is_active": true
        },
        "certificate_url": "https://cdn.example.com/certificates/cert_123456.pdf"
    }
}

🔧 状态查询统一接口

GET /api/v1/certification/status?user_id={user_id}

通用状态响应格式:

{
    "code": 200,
    "message": "状态查询成功",
    "data": {
        "certification_id": "cert_123456",
        "user_id": "user_123",
        "current_status": "face_verified",
        "current_status_name": "人脸识别完成",
        "progress": {
            "total_steps": 8,
            "completed_steps": 4,
            "percentage": 50
        },
        "timeline": [
            {
                "status": "pending",
                "status_name": "待开始",
                "completed_at": "2024-01-15T09:00:00Z",
                "is_completed": true
            },
            {
                "status": "info_submitted",
                "status_name": "企业信息已提交",
                "completed_at": "2024-01-15T09:05:00Z",
                "is_completed": true
            },
            {
                "status": "face_verified",
                "status_name": "人脸识别完成",
                "completed_at": "2024-01-15T09:15:00Z",
                "is_completed": true
            },
            {
                "status": "contract_applied",
                "status_name": "已申请合同",
                "completed_at": "2024-01-15T09:20:00Z",
                "is_completed": true
            },
            {
                "status": "contract_pending",
                "status_name": "合同待审核",
                "completed_at": null,
                "is_completed": false,
                "is_current": true
            }
        ],
        "next_action": {
            "action": "apply_contract",
            "action_name": "申请电子合同",
            "description": "请点击申请电子合同按钮继续",
            "can_retry": false
        },
        "retry_info": null,
        "estimated_completion": "2024-01-18T17:00:00Z"
    }
}

🔄 重试机制接口

通用重试接口:

POST /api/v1/certification/{certification_id}/retry
Content-Type: application/json

{
            "retry_type": "face_failed|sign_failed|rejected",
    "additional_data": {}  // 根据重试类型提供额外数据
}

重试响应:

{
    "code": 200,
    "message": "重试请求已处理",
    "data": {
        "certification_id": "cert_123456",
        "old_status": "face_failed",
        "new_status": "info_submitted",
        "retry_count": 2,
        "max_retry_count": 3,
        "next_step": "请重新进行人脸识别"
    }
}

🔧 核心服务设计

企业认证服务

type CertificationService struct {
    // 依赖注入
    repo                  *CertificationRepository
    enterpriseRepo        *EnterpriseRepository
    ocrService           *OCRService
    faceVerificationSvc   *AliYunFaceVerifyService
    notificationService   *NotificationService
    walletService        *WalletService
    eventBus             interfaces.EventBus
    logger               *zap.Logger
}

// 核心业务方法
func (s *CertificationService) SubmitBusinessLicense(ctx context.Context, userID string, licenseImageURL string) (*dto.CertificationResponse, error)
func (s *CertificationService) ConfirmEnterpriseInfo(ctx context.Context, certificationID string, req *dto.ConfirmEnterpriseInfoRequest) error
func (s *CertificationService) PerformFaceVerification(ctx context.Context, certificationID string, req *dto.FaceVerificationRequest) error
func (s *CertificationService) ApplyForContract(ctx context.Context, certificationID string) error
func (s *CertificationService) SignContract(ctx context.Context, certificationID string, signatureData string) error
func (s *CertificationService) GetCertificationStatus(ctx context.Context, userID string) (*dto.CertificationStatusResponse, error)

百度 OCR 服务

type BaiduOCRService struct {
    client    *ocr.Client
    appID     string
    apiKey    string
    secretKey string
    logger    *zap.Logger
}

func (s *BaiduOCRService) RecognizeBusinessLicense(ctx context.Context, imageURL string) (*dto.BusinessLicenseOCRResult, error)
func (s *BaiduOCRService) ValidateOCRResult(result *dto.BusinessLicenseOCRResult) error

阿里云人脸识别服务

type AliYunFaceVerifyService struct {
    client        *cloudauth.Client
    accessKeyId   string
    accessSecret  string
    regionId      string
    logger        *zap.Logger
}

func (s *AliYunFaceVerifyService) InitFaceVerify(ctx context.Context, req *dto.FaceVerifyInitRequest) (*dto.FaceVerifyInitResponse, error)
func (s *AliYunFaceVerifyService) DescribeFaceVerify(ctx context.Context, certifyId string) (*dto.FaceVerifyResultResponse, error)
func (s *AliYunFaceVerifyService) ValidateFaceVerifyResult(ctx context.Context, certifyId string) error

通知服务扩展

type NotificationService struct {
    smsService         *sms.Service
    wechatWorkService  *WechatWorkService
    logger            *zap.Logger
}

func (s *NotificationService) NotifyAdminNewApplication(ctx context.Context, certification *entities.Certification) error
func (s *NotificationService) NotifyUserContractReady(ctx context.Context, userPhone, contractURL string) error
func (s *NotificationService) NotifyUserCertificationCompleted(ctx context.Context, userPhone string, accessID string) error

七牛云存储服务

type QiNiuStorageService struct {
    accessKey  string
    secretKey  string
    bucket     string
    domain     string
    region     string
    logger     *zap.Logger
}

func (s *QiNiuStorageService) UploadFile(ctx context.Context, fileBytes []byte, fileName string) (*dto.UploadResult, error)
func (s *QiNiuStorageService) GenerateUploadToken(ctx context.Context, key string) (string, error)
func (s *QiNiuStorageService) GetFileURL(ctx context.Context, key string) string
func (s *QiNiuStorageService) DeleteFile(ctx context.Context, key string) error

营业执照处理服务

type LicenseProcessService struct {
    ocrService     *BaiduOCRService
    storageService *QiNiuStorageService
    logger         *zap.Logger
}

func (s *LicenseProcessService) ProcessLicense(ctx context.Context, fileBytes []byte, userID string) (*dto.LicenseProcessResult, error) {
    // 1. 上传文件到七牛云
    uploadResult, err := s.storageService.UploadFile(ctx, fileBytes, generateFileName(userID))
    if err != nil {
        return nil, fmt.Errorf("文件上传失败: %w", err)
    }

    // 2. OCR识别营业执照
    ocrResult, err := s.ocrService.RecognizeBusinessLicense(ctx, uploadResult.URL)
    if err != nil {
        s.logger.Warn("OCR识别失败", zap.Error(err))
        // OCR失败不影响整体流程返回空的企业信息供用户手动填写
    }

    return &dto.LicenseProcessResult{
        LicenseURL:     uploadResult.URL,
        EnterpriseInfo: ocrResult,
        OCRSuccess:     err == nil,
        OCRError:       getErrorMessage(err),
    }, nil
}

🌐 API 设计

用户端 API

POST /api/v1/certification/submit-license         # 提交营业执照
PUT  /api/v1/certification/{id}/confirm-info      # 确认企业信息
POST /api/v1/certification/{id}/face-verify       # 人脸识别
POST /api/v1/certification/{id}/apply-contract    # 申请电子合同
POST /api/v1/certification/{id}/sign-contract     # 签署合同
GET  /api/v1/certification/status                 # 查询认证状态
GET  /api/v1/certification/{id}                   # 获取认证详情

管理员端 API

GET  /api/v1/admin/certifications                 # 获取待审核申请列表
GET  /api/v1/admin/certifications/{id}            # 获取认证详情
POST /api/v1/admin/certifications/{id}/approve    # 审核通过并上传合同链接
POST /api/v1/admin/certifications/{id}/reject     # 审核拒绝
PUT  /api/v1/admin/certifications/{id}/contract   # 上传合同签署链接

财务 API

GET  /api/v1/finance/wallet                       # 获取钱包信息
GET  /api/v1/finance/wallet/balance               # 获取余额
GET  /api/v1/finance/wallet/transactions          # 获取交易记录
GET  /api/v1/finance/secrets                      # 获取用户密钥信息
POST /api/v1/finance/secrets/regenerate           # 重新生成Access Key

🗄️ 数据库设计

新增数据表

-- 认证申请表
CREATE TABLE certifications (
    id VARCHAR(36) PRIMARY KEY,
    user_id VARCHAR(36) NOT NULL,
    enterprise_id VARCHAR(36),
    status VARCHAR(50) NOT NULL,
    info_submitted_at DATETIME,
    face_verified_at DATETIME,
    contract_applied_at DATETIME,
    contract_approved_at DATETIME,
    contract_signed_at DATETIME,
    completed_at DATETIME,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    deleted_at DATETIME,

    INDEX idx_user_id (user_id),
    INDEX idx_status (status),
    INDEX idx_created_at (created_at)
);

-- 企业信息表
CREATE TABLE enterprises (
    id VARCHAR(36) PRIMARY KEY,
    certification_id VARCHAR(36) NOT NULL,
    company_name VARCHAR(255) NOT NULL,
    unified_social_code VARCHAR(50) NOT NULL,
    legal_person_name VARCHAR(100) NOT NULL,
    legal_person_id VARCHAR(50) NOT NULL,
    license_upload_record_id VARCHAR(36) NOT NULL,  -- 关联营业执照上传记录
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    deleted_at DATETIME,

    INDEX idx_certification_id (certification_id),
    INDEX idx_unified_social_code (unified_social_code),
    INDEX idx_license_upload_record_id (license_upload_record_id)
);

-- 钱包表
CREATE TABLE wallets (
    id VARCHAR(36) PRIMARY KEY,
    user_id VARCHAR(36) NOT NULL UNIQUE,
    is_active BOOLEAN DEFAULT TRUE,
    balance DECIMAL(20,8) DEFAULT 0,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    deleted_at DATETIME,

    INDEX idx_user_id (user_id)
);

-- 用户密钥表
CREATE TABLE user_secrets (
    id VARCHAR(36) PRIMARY KEY,
    user_id VARCHAR(36) NOT NULL UNIQUE,
    access_id VARCHAR(100) NOT NULL UNIQUE,
    access_key VARCHAR(255) NOT NULL,
    is_active BOOLEAN DEFAULT TRUE,
    last_used_at DATETIME,
    expires_at DATETIME,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    deleted_at DATETIME,

    INDEX idx_user_id (user_id),
    INDEX idx_access_id (access_id)
);

-- 营业执照上传记录表
CREATE TABLE license_upload_records (
    id VARCHAR(36) PRIMARY KEY,
    certification_id VARCHAR(36),
    user_id VARCHAR(36) NOT NULL,
    original_file_name VARCHAR(255) NOT NULL,
    file_size BIGINT NOT NULL,
    file_type VARCHAR(50) NOT NULL,
    file_url VARCHAR(500) NOT NULL,
    qiniu_key VARCHAR(255) NOT NULL,
    ocr_processed BOOLEAN DEFAULT FALSE,
    ocr_success BOOLEAN DEFAULT FALSE,
    ocr_confidence DECIMAL(5,2),
    ocr_raw_data TEXT,
    ocr_error_message VARCHAR(500),
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    deleted_at DATETIME,

    INDEX idx_certification_id (certification_id),
    INDEX idx_user_id (user_id),
    INDEX idx_qiniu_key (qiniu_key)
);

-- 人脸识别记录表
CREATE TABLE face_verify_records (
    id VARCHAR(36) PRIMARY KEY,
    certification_id VARCHAR(36) NOT NULL,
    user_id VARCHAR(36) NOT NULL,
    certify_id VARCHAR(100) NOT NULL,
    verify_url VARCHAR(500),
    return_url VARCHAR(500),
    real_name VARCHAR(100) NOT NULL,
    id_card_number VARCHAR(50) NOT NULL,
    status VARCHAR(50) NOT NULL,
    result_code VARCHAR(50),
    result_message VARCHAR(500),
    verify_score DECIMAL(5,2),
    initiated_at DATETIME NOT NULL,
    completed_at DATETIME,
    expires_at DATETIME NOT NULL,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    deleted_at DATETIME,

    INDEX idx_certification_id (certification_id),
    INDEX idx_user_id (user_id),
    INDEX idx_certify_id (certify_id),
    INDEX idx_status (status)
);

-- 通知记录表
CREATE TABLE notification_records (
    id VARCHAR(36) PRIMARY KEY,
    certification_id VARCHAR(36),
    user_id VARCHAR(36),
    notification_type VARCHAR(50) NOT NULL,
    notification_scene VARCHAR(50) NOT NULL,
    recipient VARCHAR(255) NOT NULL,
    title VARCHAR(255),
    content TEXT NOT NULL,
    template_id VARCHAR(100),
    template_params TEXT,
    status VARCHAR(50) NOT NULL,
    error_message VARCHAR(500),
    sent_at DATETIME,
    retry_count INT DEFAULT 0,
    max_retry_count INT DEFAULT 3,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    deleted_at DATETIME,

    INDEX idx_certification_id (certification_id),
    INDEX idx_user_id (user_id),
    INDEX idx_status (status),
    INDEX idx_notification_type (notification_type)
);

-- 合同记录表
CREATE TABLE contract_records (
    id VARCHAR(36) PRIMARY KEY,
    certification_id VARCHAR(36) NOT NULL,
    user_id VARCHAR(36) NOT NULL,
    admin_id VARCHAR(36),
    contract_type VARCHAR(50) NOT NULL,
    contract_url VARCHAR(500),
    signing_url VARCHAR(500),
    signature_data TEXT,
    signed_at DATETIME,
    client_ip VARCHAR(50),
    user_agent VARCHAR(500),
    status VARCHAR(50) NOT NULL,
    approval_notes TEXT,
    reject_reason TEXT,
    expires_at DATETIME,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    deleted_at DATETIME,

    INDEX idx_certification_id (certification_id),
    INDEX idx_user_id (user_id),
    INDEX idx_admin_id (admin_id),
    INDEX idx_status (status)
);

-- 管理员表
CREATE TABLE admins (
    id VARCHAR(36) PRIMARY KEY,
    username VARCHAR(100) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255),
    phone VARCHAR(20),
    is_active BOOLEAN DEFAULT TRUE,
    last_login_at DATETIME,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    deleted_at DATETIME,

    INDEX idx_username (username)
);

🚀 分阶段实施计划

阶段一基础架构搭建3-4 天)- 已完成

1.1 创建域结构

  • 创建 certification 域的完整目录结构
  • 创建 finance 域的完整目录结构
  • 创建 admin 域的基础结构
  • 定义核心实体和枚举类型

1.2 数据库层

  • 使用 GORM AutoMigrate 进行数据库迁移(已更新)
  • 实现基础的 Repository 接口和实现
  • 添加必要的数据库索引和约束

1.3 共享服务扩展

  • 扩展短信服务支持模板化消息
  • 创建百度 OCR 服务基础架构
  • 实现七牛云存储服务
  • 实现企业微信通知服务基础结构

阶段二核心业务逻辑5-6 天)- 已完成

2.1 认证服务核心功能

  • 实现营业执照上传、存储和 OCR 识别一体化服务
  • 实现企业信息确认功能
  • 实现人脸识别接口
  • 实现申请电子合同功能
  • 实现状态查询功能

2.2 状态管理

  • 实现认证状态机(第一阶段已完成)
  • 添加状态转换验证
  • 实现状态变更事件发布

2.3 基础 API 接口

  • 实现用户端认证相关 API
  • 添加参数验证和错误处理
  • 完善 API 响应格式

阶段三第三方集成4-5 天)- 已完成

3.1 百度 OCR 和七牛云存储集成

  • 实现七牛云文件上传服务
  • 实现百度 OCR API 调用
  • 处理 OCR 识别结果解析
  • 添加 OCR 识别置信度验证
  • 实现营业执照处理一体化服务

3.2 阿里云人脸识别集成

  • 集成阿里云 InitFaceVerify API
  • 实现人脸识别初始化流程
  • 实现人脸识别结果查询
  • 添加人脸识别状态验证

3.3 通知服务完善

  • 实现企业微信机器人通知
  • 扩展短信模板支持认证流程
  • 添加通知发送失败重试机制

阶段四管理员功能3-4 天)

4.1 管理员系统

  • 实现管理员登录认证
  • 创建管理员权限管理
  • 实现管理员操作日志

4.2 审核功能

  • 实现待审核申请列表
  • 实现认证详情查看
  • 实现审核通过/拒绝功能
  • 实现合同链接上传功能

4.3 管理后台 API

  • 完善管理员端 API 接口
  • 添加分页和筛选功能
  • 实现审核操作记录

阶段五财务系统3-4 天)- 已完成

5.1 财务服务

  • 实现钱包生成功能
  • 生成 Access ID 和 Access Key
  • 实现钱包状态管理

5.2 财务 API

  • 实现钱包信息查询
  • 实现 Access Key 重新生成
  • 添加钱包安全验证

5.3 认证完成流程

  • 完善认证完成后的钱包创建
  • 实现认证完成通知
  • 添加钱包激活功能

🔄 阶段六事件驱动和完善2-3 天)

6.1 事件系统

  • 实现完整的认证事件定义
  • 添加事件处理器
  • 完善事件驱动的通知机制

6.2 系统完善

  • 添加全面的日志记录
  • 实现性能监控和指标
  • 完善错误处理和回滚机制

6.3 测试和文档

  • 编写单元测试
  • 完善 API 文档
  • 编写操作手册

🔧 技术要点

第三方服务配置

百度 OCR 配置

baidu_ocr:
    app_id: "your_app_id"
    api_key: "your_api_key"
    secret_key: "your_secret_key"
    endpoint: "https://aip.baidubce.com"
    timeout: 30s

七牛云存储配置

qiniu_storage:
    access_key: "your_access_key"
    secret_key: "your_secret_key"
    bucket: "enterprise-certification"
    domain: "https://qiniu.example.com"
    region: "z0" # 华东区域
    timeout: 30s

阿里云人脸识别配置

aliyun_face_verify:
    access_key_id: "your_access_key_id"
    access_key_secret: "your_access_key_secret"
    region_id: "cn-hangzhou"
    endpoint: "https://cloudauth.cn-hangzhou.aliyuncs.com"
    timeout: 30s

企业微信配置

wechat_work:
    webhook_url: "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx"
    timeout: 10s

安全考虑

  • Access Key 采用加密存储
  • 营业执照图片需要安全存储
  • 人脸识别数据不保存原始图片
  • 管理员操作需要审计日志

性能优化

  • OCR 识别结果缓存
  • 状态查询接口优化
  • 批量通知处理
  • 数据库查询优化

📝 注意事项

  1. 状态一致性:所有状态变更都需要通过事务保证一致性
  2. 幂等性:关键操作需要支持幂等,避免重复处理
  3. 错误处理:第三方服务调用需要完善的错误处理和重试机制
  4. 数据安全:敏感信息需要加密存储
  5. 监控告警:关键业务节点需要监控和告警
  6. 备份恢复:重要数据需要定期备份

🎯 成功标准

  • 用户可以顺利完成企业认证全流程
  • 管理员可以高效处理认证审核
  • 系统可以自动识别营业执照信息
  • 通知机制工作正常
  • 财务系统功能完整
  • API 接口稳定可靠
  • 系统性能满足要求
  • 代码质量符合规范

预计总开发时间20-26 天 核心开发人员1-2 人 测试时间3-5 天