1607 lines
49 KiB
Markdown
1607 lines
49 KiB
Markdown
# 企业认证系统实施计划
|
||
|
||
## 📋 需求概述
|
||
|
||
### 业务流程
|
||
|
||
用户注册后需要进行企业认证以使用平台核心功能,认证流程如下:
|
||
|
||
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 天**
|