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

1607 lines
49 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 企业认证系统实施计划
## 📋 需求概述
### 业务流程
用户注册后需要进行企业认证以使用平台核心功能,认证流程如下:
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
```
## 📊 核心实体设计
### 认证状态枚举
```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" // 已拒绝
)
```
### 认证申请实体
```go
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"`
}
```
### 企业信息实体
```go
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"`
}
```
### 钱包实体(财务域)
```go
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"`
}
```
### 用户密钥实体(财务域)
```go
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"`
}
```
### 营业执照上传记录实体
```go
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"`
}
```
### 人脸识别记录实体
```go
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"`
}
```
### 通知记录实体
```go
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"`
}
```
### 合同记录实体
```go
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"`
}
```
## 🔄 状态管理设计
### 认证状态枚举扩展
```go
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" // 已拒绝
)
```
### 状态转换规则
```go
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},
}
```
### 状态管理服务
```go
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: 上传营业执照(前端交互)
```http
POST /api/v1/certification/upload-license
Content-Type: multipart/form-data
{
"business_license": "file", // 营业执照图片文件
"user_id": "string" // 用户ID
}
```
**响应:**
```json
{
"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 失败响应:**
```json
{
"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)
```http
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"
}
```
**信息提交成功:**
```json
{
"code": 200,
"message": "企业信息提交成功",
"data": {
"certification_id": "cert_123456",
"status": "info_submitted",
"next_step": "进行人脸识别",
"next_action": "face_verify"
}
}
```
### 阶段 3: 人脸识别 (info_submitted → face_verified/face_failed)
```http
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"
}
```
**人脸识别初始化成功:**
```json
{
"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"
}
}
```
**人脸识别结果查询:**
```http
GET /api/v1/certification/{certification_id}/face-result
```
**人脸识别成功:**
```json
{
"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)
```http
POST /api/v1/certification/{certification_id}/apply-contract
Content-Type: application/json
{
"contact_phone": "13800138000",
"contact_email": "example@company.com"
}
```
**申请成功:**
```json
{
"code": 200,
"message": "电子合同申请已提交",
"data": {
"certification_id": "cert_123456",
"status": "contract_applied",
"next_step": "系统正在处理申请",
"estimated_time": "1-3个工作日"
}
}
```
**系统自动转换状态查询:**
```http
GET /api/v1/certification/{certification_id}/status
```
**转换为待审核状态:**
```json
{
"code": 200,
"message": "合同申请已进入审核队列",
"data": {
"certification_id": "cert_123456",
"status": "contract_pending",
"next_step": "等待管理员审核并上传合同链接",
"estimated_time": "1-3个工作日",
"notification": "已通过企业微信通知管理员,审核结果将通过短信通知您"
}
}
```
### 阶段 5: 管理员审核和合同链接上传 (contract_pending → contract_approved/rejected)
**管理员获取待审核列表:**
```http
GET /api/v1/admin/certifications?status=contract_pending
```
**管理员审核通过并上传合同链接:**
```http
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
}
```
**审核通过响应:**
```json
{
"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": "已通过短信通知用户签署合同"
}
}
```
**管理员拒绝申请:**
```http
POST /api/v1/admin/certifications/{certification_id}/reject
Content-Type: application/json
{
"admin_id": "admin_123",
"reject_reason": "企业信息与工商数据不符,请重新提交",
"allow_retry": true
}
```
**拒绝响应:**
```json
{
"code": 200,
"message": "申请已拒绝",
"data": {
"certification_id": "cert_123456",
"status": "rejected",
"reject_reason": "企业信息与工商数据不符,请重新提交",
"next_step": "已通过短信通知用户重新申请"
}
}
```
### 阶段 6: 用户查看合同链接并签署 (contract_approved → contract_signed/sign_failed)
**用户查询状态(收到短信通知后):**
```http
GET /api/v1/certification/{certification_id}/status
```
**用户看到合同链接:**
```json
{
"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)
```http
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"
}
```
**签署成功:**
```json
{
"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)
**系统自动完成,用户查询最终状态:**
```http
GET /api/v1/certification/{certification_id}/final-result
```
**认证完成响应:**
```json
{
"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"
}
}
```
## 🔧 状态查询统一接口
```http
GET /api/v1/certification/status?user_id={user_id}
```
**通用状态响应格式:**
```json
{
"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"
}
}
```
## 🔄 重试机制接口
**通用重试接口:**
```http
POST /api/v1/certification/{certification_id}/retry
Content-Type: application/json
{
"retry_type": "face_failed|sign_failed|rejected",
"additional_data": {} // 根据重试类型提供额外数据
}
```
**重试响应:**
```json
{
"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": "请重新进行人脸识别"
}
}
```
## 🔧 核心服务设计
### 企业认证服务
```go
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 服务
```go
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
```
### 阿里云人脸识别服务
```go
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
```
### 通知服务扩展
```go
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
```
### 七牛云存储服务
```go
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
```
### 营业执照处理服务
```go
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
```
## 🗄️ 数据库设计
### 新增数据表
```sql
-- 认证申请表
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 创建域结构
- [x] 创建 `certification` 域的完整目录结构
- [x] 创建 `finance` 域的完整目录结构
- [x] 创建 `admin` 域的基础结构
- [x] 定义核心实体和枚举类型
#### 1.2 数据库层
- [x] 使用 GORM AutoMigrate 进行数据库迁移(已更新)
- [x] 实现基础的 Repository 接口和实现
- [x] 添加必要的数据库索引和约束
#### 1.3 共享服务扩展
- [x] 扩展短信服务支持模板化消息
- [x] 创建百度 OCR 服务基础架构
- [x] 实现七牛云存储服务
- [x] 实现企业微信通知服务基础结构
### ✅ 阶段二核心业务逻辑5-6 天)- 已完成
#### 2.1 认证服务核心功能
- [x] 实现营业执照上传、存储和 OCR 识别一体化服务
- [x] 实现企业信息确认功能
- [x] 实现人脸识别接口
- [x] 实现申请电子合同功能
- [x] 实现状态查询功能
#### 2.2 状态管理
- [x] 实现认证状态机(第一阶段已完成)
- [x] 添加状态转换验证
- [x] 实现状态变更事件发布
#### 2.3 基础 API 接口
- [x] 实现用户端认证相关 API
- [x] 添加参数验证和错误处理
- [x] 完善 API 响应格式
### ✅ 阶段三第三方集成4-5 天)- 已完成
#### 3.1 百度 OCR 和七牛云存储集成
- [x] 实现七牛云文件上传服务
- [x] 实现百度 OCR API 调用
- [x] 处理 OCR 识别结果解析
- [x] 添加 OCR 识别置信度验证
- [x] 实现营业执照处理一体化服务
#### 3.2 阿里云人脸识别集成
- [ ] 集成阿里云 InitFaceVerify API
- [ ] 实现人脸识别初始化流程
- [ ] 实现人脸识别结果查询
- [ ] 添加人脸识别状态验证
#### 3.3 通知服务完善
- [x] 实现企业微信机器人通知
- [ ] 扩展短信模板支持认证流程
- [ ] 添加通知发送失败重试机制
### 阶段四管理员功能3-4 天)
#### 4.1 管理员系统
- [ ] 实现管理员登录认证
- [ ] 创建管理员权限管理
- [ ] 实现管理员操作日志
#### 4.2 审核功能
- [ ] 实现待审核申请列表
- [ ] 实现认证详情查看
- [ ] 实现审核通过/拒绝功能
- [ ] 实现合同链接上传功能
#### 4.3 管理后台 API
- [ ] 完善管理员端 API 接口
- [ ] 添加分页和筛选功能
- [ ] 实现审核操作记录
### ✅ 阶段五财务系统3-4 天)- 已完成
#### 5.1 财务服务
- [x] 实现钱包生成功能
- [x] 生成 Access ID 和 Access Key
- [x] 实现钱包状态管理
#### 5.2 财务 API
- [x] 实现钱包信息查询
- [x] 实现 Access Key 重新生成
- [x] 添加钱包安全验证
#### 5.3 认证完成流程
- [x] 完善认证完成后的钱包创建
- [x] 实现认证完成通知
- [x] 添加钱包激活功能
### 🔄 阶段六事件驱动和完善2-3 天)
#### 6.1 事件系统
- [ ] 实现完整的认证事件定义
- [ ] 添加事件处理器
- [ ] 完善事件驱动的通知机制
#### 6.2 系统完善
- [ ] 添加全面的日志记录
- [ ] 实现性能监控和指标
- [ ] 完善错误处理和回滚机制
#### 6.3 测试和文档
- [ ] 编写单元测试
- [ ] 完善 API 文档
- [ ] 编写操作手册
## 🔧 技术要点
### 第三方服务配置
#### 百度 OCR 配置
```yaml
baidu_ocr:
app_id: "your_app_id"
api_key: "your_api_key"
secret_key: "your_secret_key"
endpoint: "https://aip.baidubce.com"
timeout: 30s
```
#### 七牛云存储配置
```yaml
qiniu_storage:
access_key: "your_access_key"
secret_key: "your_secret_key"
bucket: "enterprise-certification"
domain: "https://qiniu.example.com"
region: "z0" # 华东区域
timeout: 30s
```
#### 阿里云人脸识别配置
```yaml
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
```
#### 企业微信配置
```yaml
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 天**