49 KiB
49 KiB
企业认证系统实施计划
📋 需求概述
业务流程
用户注册后需要进行企业认证以使用平台核心功能,认证流程如下:
- 提交企业信息:用户上传营业执照,系统通过百度 OCR 自动识别企业四要素
- 人脸识别:法人进行人脸识别验证
- 申请电子合同:系统生成申请记录
- 管理员审核:管理员收到企业微信通知,审核后手工上传签署链接
- 签署合同:用户收到短信通知,点击链接完成签署
- 认证完成:系统为用户生成钱包和 Access Key
企业四要素
- 企业名称
- 统一信用代码
- 企业法人姓名
- 法人身份证号
📊 业务流程图
整个企业认证流程包含 10 个主要状态节点,涉及用户、系统、管理员和第三方服务的协同工作:
流程说明
-
用户操作阶段(蓝色节点)
- 用户上传营业执照图片(自动上传至七牛云并 OCR 识别)
- 确认或修改 OCR 识别的企业四要素信息
- 进行阿里云人脸识别验证
- 申请电子合同
- 查看合同链接并签署电子合同
-
系统自动处理(紫色节点)
- 创建认证申请记录
- 七牛云文件存储和百度 OCR 识别
- 将合同申请转为待审核状态
- 确认合同签署状态
- 生成钱包和 Access Key
-
管理员审核(橙色节点)
- 审核企业认证申请
- 手工上传电子合同签署链接
- 处理审核结果(通过/拒绝)
-
等待状态(紫色节点)
- 合同待审核状态(等待管理员处理)
- 合同已审核状态(等待用户签署)
-
第三方服务(绿色节点)
- 七牛云文件存储
- 百度 OCR 识别营业执照
- 阿里云 InitFaceVerify 人脸识别
-
决策节点(红色节点)
- 企业信息确认(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 识别结果缓存
- 状态查询接口优化
- 批量通知处理
- 数据库查询优化
📝 注意事项
- 状态一致性:所有状态变更都需要通过事务保证一致性
- 幂等性:关键操作需要支持幂等,避免重复处理
- 错误处理:第三方服务调用需要完善的错误处理和重试机制
- 数据安全:敏感信息需要加密存储
- 监控告警:关键业务节点需要监控和告警
- 备份恢复:重要数据需要定期备份
🎯 成功标准
- 用户可以顺利完成企业认证全流程
- 管理员可以高效处理认证审核
- 系统可以自动识别营业执照信息
- 通知机制工作正常
- 财务系统功能完整
- API 接口稳定可靠
- 系统性能满足要求
- 代码质量符合规范
预计总开发时间:20-26 天 核心开发人员:1-2 人 测试时间:3-5 天