This commit is contained in:
2025-07-21 15:13:26 +08:00
parent 8ad1d7288e
commit b03129667a
41 changed files with 8497 additions and 3968 deletions

View File

@@ -0,0 +1,348 @@
# Certification域DDD重构规范文档
## 📋 概述
本文档规范了certification域企业认证域的DDD架构重构方案目标是实现清晰的职责划分、完善的状态管理、以及高度可维护的代码结构。
## 🎯 重构目标
### 业务目标
- 企业认证流程通过e签宝API实现
- 异步回调驱动的状态转换机制
- 简化状态设计,专注核心业务节点
- 已签署合同即代表认证完成
### 技术目标
- 符合DDD架构原则的分层设计
- 清晰的职责边界和依赖关系
- 基于CachedBaseRepositoryImpl的仓储实现
- 完善的事件驱动架构
- 强类型的状态机设计
## 🏗️ 架构设计
### 分层架构图
```
┌─────────────────────────────────────────────────┐
│ 表现层 (API) │
│ HTTP Controllers + Handlers │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ 应用服务层 │
│ 用例协调 + DTO转换 + 事务管理 │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ 领域服务层 │
│ 工作流编排 + e签宝集成 + 回调处理 + 状态机 │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ 领域模型层 │
│ 聚合根 + 实体 + 值对象 + 领域规则 │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ 基础设施层 │
│ 仓储实现 + 外部服务 + 缓存 + 事件发布 │
└─────────────────────────────────────────────────┘
```
## 📊 状态设计
### 状态枚举定义
```go
const (
// 主流程状态
StatusPending = "pending" // 待认证
StatusInfoSubmitted = "info_submitted" // 已提交企业信息
StatusEnterpriseVerified = "enterprise_verified" // 已企业认证
StatusContractApplied = "contract_applied" // 已申请签署合同
StatusContractSigned = "contract_signed" // 已签署合同(认证完成)
// 失败状态
StatusInfoRejected = "info_rejected" // 企业信息被拒绝
StatusContractRejected = "contract_rejected" // 合同被拒签
StatusContractExpired = "contract_expired" // 合同签署超时
)
```
### 状态转换规则
| 当前状态 | 目标状态 | 触发条件 | 操作者 |
|---------|---------|----------|-------|
| Pending | InfoSubmitted | 用户提交企业信息 | USER |
| InfoSubmitted | EnterpriseVerified | e签宝认证成功回调 | SYSTEM |
| InfoSubmitted | InfoRejected | e签宝认证失败回调 | SYSTEM |
| EnterpriseVerified | ContractApplied | 用户申请合同 | USER |
| ContractApplied | ContractSigned | e签宝签署成功回调 | SYSTEM |
| ContractApplied | ContractRejected | e签宝拒签回调 | SYSTEM |
| ContractApplied | ContractExpired | 签署超时 | SYSTEM |
| InfoRejected | InfoSubmitted | 用户重新提交 | USER |
| ContractRejected | EnterpriseVerified | 重置状态 | SYSTEM |
| ContractExpired | EnterpriseVerified | 重置状态 | SYSTEM |
## 📁 文件结构规范
### 目录结构
```
internal/domains/certification/
├── entities/ # 实体层
│ ├── certification.go # 认证聚合根
│ ├── enterprise_info_submit_record.go # 企业信息提交记录
│ ├── esign_contract_generate_record.go # 合同生成记录
│ ├── esign_contract_sign_record.go # 合同签署记录
│ └── value_objects/ # 值对象
│ ├── enterprise_info.go # 企业信息
│ └── contract_info.go # 合同信息
├── enums/ # 枚举定义
│ ├── certification_status.go # 认证状态
│ ├── failure_reason.go # 失败原因
│ └── actor_type.go # 操作者类型
├── specifications/ # 规格模式
│ └── certification_specifications.go # 认证规格
├── services/ # 领域服务
│ ├── certification_aggregate_service.go # 聚合服务
│ ├── enterprise_verification_service.go # 企业验证服务
│ ├── contract_management_service.go # 合同管理服务
│ ├── certification_workflow_orchestrator.go # 工作流编排器
│ ├── state_machine/ # 状态机
│ │ ├── certification_state_machine.go # 状态机核心
│ │ ├── state_config.go # 状态配置
│ │ └── esign_callback_handler.go # 回调处理器
│ └── factories/ # 工厂模式
│ └── certification_factory.go # 认证工厂
├── repositories/ # 仓储接口
│ ├── certification_command_repository.go # 命令仓储
│ ├── certification_query_repository.go # 查询仓储
│ ├── enterprise_info_repository.go # 企业信息仓储
│ ├── contract_record_repository.go # 合同记录仓储
│ └── queries/ # 查询对象
│ ├── certification_queries.go # 认证查询
│ └── list_certifications_query.go # 列表查询
└── events/ # 领域事件
├── certification_events.go # 事件定义
└── event_handlers/ # 事件处理器
├── certification_event_handler.go # 认证事件处理
└── notification_event_handler.go # 通知处理
```
## 🔧 各层接口规范
### 1. 聚合根接口 (certification.go)
```go
type Certification struct {
// 聚合根必须实现的核心方法
// 状态转换
CanTransitionTo(targetStatus CertificationStatus, actor ActorType) bool
TransitionTo(targetStatus CertificationStatus, actor ActorType, reason string) error
// 业务操作
SubmitEnterpriseInfo(info EnterpriseInfo) error
ApplyContract() error
HandleEsignCallback(callbackType string, success bool, data map[string]interface{}) error
// 业务规则验证
ValidateBusinessRules() error
ValidateInvariance() error
// 查询方法
GetProgress() int
IsUserActionRequired() bool
GetAvailableActions() []string
}
```
### 2. 领域服务接口规范
#### 聚合服务
```go
type CertificationAggregateService interface {
// 聚合管理
CreateCertification(ctx context.Context, userID string) (*entities.Certification, error)
LoadCertification(ctx context.Context, id string) (*entities.Certification, error)
SaveCertification(ctx context.Context, cert *entities.Certification) error
// 状态管理
TransitionState(ctx context.Context, cert *entities.Certification, targetStatus enums.CertificationStatus, actor enums.ActorType, metadata map[string]interface{}) error
}
```
#### 工作流编排器
```go
type CertificationWorkflowOrchestrator interface {
// 用户操作用例
SubmitEnterpriseInfo(ctx context.Context, cmd *SubmitEnterpriseInfoCommand) (*WorkflowResult, error)
ApplyContract(ctx context.Context, cmd *ApplyContractCommand) (*WorkflowResult, error)
// e签宝回调处理
HandleEnterpriseVerificationCallback(ctx context.Context, cmd *EsignCallbackCommand) error
HandleContractSignCallback(ctx context.Context, cmd *EsignCallbackCommand) error
// 异常处理
HandleFailure(ctx context.Context, certificationID string, failureType string, reason string) error
RetryOperation(ctx context.Context, certificationID string, operation string) error
}
```
### 3. 仓储接口规范
#### 命令仓储
```go
type CertificationCommandRepository interface {
Create(ctx context.Context, cert entities.Certification) error
Update(ctx context.Context, cert entities.Certification) error
UpdateStatus(ctx context.Context, id string, status enums.CertificationStatus) error
Delete(ctx context.Context, id string) error
// 事务支持
WithTx(tx interfaces.Transaction) CertificationCommandRepository
}
```
#### 查询仓储
```go
type CertificationQueryRepository interface {
GetByID(ctx context.Context, id string) (*entities.Certification, error)
GetByUserID(ctx context.Context, userID string) (*entities.Certification, error)
List(ctx context.Context, query *queries.ListCertificationsQuery) ([]*entities.Certification, int64, error)
// 业务查询
GetPendingCertifications(ctx context.Context) ([]*entities.Certification, error)
GetExpiredContracts(ctx context.Context) ([]*entities.Certification, error)
GetStatistics(ctx context.Context, period TimePeriod) (*CertificationStats, error)
}
```
### 4. 应用服务接口规范
```go
type CertificationApplicationService interface {
// 用户操作用例
CreateCertification(ctx context.Context, cmd *commands.CreateCertificationCommand) (*responses.CertificationResponse, error)
SubmitEnterpriseInfo(ctx context.Context, cmd *commands.SubmitEnterpriseInfoCommand) (*responses.CertificationResponse, error)
ApplyContract(ctx context.Context, cmd *commands.ApplyContractCommand) (*responses.ContractSignUrlResponse, error)
// 查询用例
GetCertification(ctx context.Context, query *queries.GetCertificationQuery) (*responses.CertificationResponse, error)
ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) (*responses.CertificationListResponse, error)
// e签宝回调处理
HandleEsignCallback(ctx context.Context, cmd *commands.EsignCallbackCommand) error
}
```
## 🔄 业务流程规范
### 主要业务流程
#### 流程1用户提交企业信息
```
HTTP Request → Application Service → Workflow Orchestrator → Aggregate Service → Domain Entity → State Machine → Repository → Event Publishing
```
#### 流程2e签宝企业认证回调
```
HTTP Callback → Application Service → Workflow Orchestrator → Esign Callback Handler → Aggregate Service → State Machine → Repository → Event Publishing
```
#### 流程3用户申请合同签署
```
HTTP Request → Application Service → Workflow Orchestrator → Contract Management Service → Aggregate Service → State Machine → Repository → Return Sign URL
```
#### 流程4e签宝合同签署回调
```
HTTP Callback → Application Service → Workflow Orchestrator → Esign Callback Handler → Aggregate Service → State Machine → Repository → Event Publishing
```
## 📝 编码规范
### 命名规范
- **接口**: 以`Interface`或不带后缀结尾,如`CertificationRepository`
- **实现类**: 以`Impl`结尾,如`CertificationRepositoryImpl`
- **领域服务**: 以`Service`结尾,如`CertificationAggregateService`
- **DTO**: 以`Command`、`Query`、`Response`结尾
- **事件**: 以`Event`结尾,如`CertificationCreatedEvent`
### 错误处理规范
- 使用领域特定的错误类型
- 错误消息必须使用中文
- 包含足够的上下文信息
- 支持错误链追踪
### 日志规范
- 关键业务操作必须记录日志
- 日志消息使用中文
- 包含关键业务标识符用户ID、认证ID等
- 使用结构化日志格式
### 缓存规范
- 查询仓储使用CachedBaseRepositoryImpl
- 根据查询频率选择合适的缓存策略
- 写操作自动失效相关缓存
- 支持手动缓存刷新
## 🧪 测试规范
### 单元测试
- 每个领域服务必须有单元测试
- 状态机转换逻辑必须全覆盖测试
- 业务规则验证必须有测试
- Mock外部依赖
### 集成测试
- e签宝回调处理的集成测试
- 完整业务流程的端到端测试
- 数据库操作的集成测试
## 📊 监控规范
### 业务指标
- 认证申请创建数量
- 企业认证成功/失败率
- 合同签署成功/失败率
- 各状态的停留时间
### 技术指标
- 缓存命中率
- API响应时间
- 数据库查询性能
- 错误率和类型分布
## 🚀 部署规范
### 配置管理
- 环境相关配置外部化
- e签宝API配置独立管理
- 缓存配置可调整
- 日志级别可配置
### 数据迁移
- 状态枚举的兼容性迁移
- 历史数据的清理策略
- 缓存预热脚本
## 📋 验收标准
### 功能验收
- [ ] 完整的企业认证流程正常运行
- [ ] e签宝回调正确处理
- [ ] 状态转换严格按照规则执行
- [ ] 异常情况正确处理和恢复
### 技术验收
- [ ] 各层职责清晰,无逻辑泄漏
- [ ] 代码符合DDD架构原则
- [ ] 缓存策略有效,性能提升明显
- [ ] 事件驱动架构正常工作
### 质量验收
- [ ] 单元测试覆盖率 > 80%
- [ ] 集成测试通过
- [ ] 代码质量检查通过
- [ ] 性能指标达标
---
本规范文档将指导整个certification域的重构实施确保重构后的代码具有良好的可维护性、可扩展性和健壮性。

View File

@@ -9,25 +9,49 @@ import (
)
// CertificationApplicationService 认证应用服务接口
// 负责用例协调,提供精简的应用层接口
type CertificationApplicationService interface {
// 认证状态查询
GetCertificationStatus(ctx context.Context, query *queries.GetCertificationStatusQuery) (*responses.CertificationResponse, error)
GetCertificationDetails(ctx context.Context, query *queries.GetCertificationDetailsQuery) (*responses.CertificationResponse, error)
GetCertificationProgress(ctx context.Context, userID string) (map[string]interface{}, error)
// ================ 用户操作用例 ================
// 企业信息管理
// 创建认证申请
CreateCertification(ctx context.Context, cmd *commands.CreateCertificationCommand) (*responses.CertificationResponse, error)
// 提交企业信息
SubmitEnterpriseInfo(ctx context.Context, cmd *commands.SubmitEnterpriseInfoCommand) (*responses.CertificationResponse, error)
// 企业认证
GetEnterpriseAuthURL(ctx context.Context, userID string) (*responses.EnterpriseAuthURLResponse, error)
// 申请合同签署
ApplyContract(ctx context.Context, cmd *commands.ApplyContractCommand) (*responses.ContractSignUrlResponse, error)
// 合同管理
ApplyContract(ctx context.Context, userID string) (*responses.CertificationResponse, error)
GetContractSignURL(ctx context.Context, cmd *commands.GetContractSignURLCommand) (*responses.ContractSignURLResponse, error)
}
// 重试失败操作
RetryOperation(ctx context.Context, cmd *commands.RetryOperationCommand) (*responses.CertificationResponse, error)
// ================ 查询用例 ================
// 获取认证详情
GetCertification(ctx context.Context, query *queries.GetCertificationQuery) (*responses.CertificationResponse, error)
// 获取用户认证列表
GetUserCertifications(ctx context.Context, query *queries.GetUserCertificationsQuery) (*responses.CertificationListResponse, error)
// 获取认证列表(管理员)
ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) (*responses.CertificationListResponse, error)
// 搜索认证
SearchCertifications(ctx context.Context, query *queries.SearchCertificationsQuery) (*responses.CertificationListResponse, error)
// 获取认证统计
GetCertificationStatistics(ctx context.Context, query *queries.GetCertificationStatisticsQuery) (*responses.CertificationStatisticsResponse, error)
// ================ e签宝回调处理 ================
// EsignCallbackApplicationService e签宝回调应用服务接口
type EsignCallbackApplicationService interface {
// 处理e签宝回调
HandleCallback(ctx context.Context, callbackData map[string]interface{}, headers map[string]string, queryParams map[string]string) error
HandleEsignCallback(ctx context.Context, cmd *commands.EsignCallbackCommand) (*responses.CallbackResponse, error)
// ================ 管理员操作 ================
// 手动状态转换(管理员)
ForceTransitionStatus(ctx context.Context, cmd *commands.ForceTransitionStatusCommand) (*responses.CertificationResponse, error)
// 获取系统监控数据
GetSystemMonitoring(ctx context.Context, query *queries.GetSystemMonitoringQuery) (*responses.SystemMonitoringResponse, error)
}

View File

@@ -1,21 +1,50 @@
package commands
// SubmitEnterpriseInfoCommand 提交企业信息命令
// 用于用户提交企业四要素信息,完成企业信息验证
// 如果用户没有认证申请,系统会自动创建
type SubmitEnterpriseInfoCommand struct {
UserID string `json:"-" comment:"用户唯一标识从JWT token获取不在JSON中暴露"`
CompanyName string `json:"company_name" binding:"required,min=2,max=100" comment:"企业名称,如:北京科技有限公司"`
UnifiedSocialCode string `json:"unified_social_code" binding:"required,social_credit_code" comment:"统一社会信用代码18位企业唯一标识91110000123456789X"`
LegalPersonName string `json:"legal_person_name" binding:"required,min=2,max=20" comment:"法定代表人姓名,如:张三"`
LegalPersonID string `json:"legal_person_id" binding:"required,id_card" comment:"法定代表人身份证号码18位110101199001011234"`
LegalPersonPhone string `json:"legal_person_phone" binding:"required,phone" comment:"法定代表人手机号11位13800138000"`
VerificationCode string `json:"verification_code" binding:"required,len=6" comment:"验证码"`
import (
"tyapi-server/internal/domains/certification/entities/value_objects"
"tyapi-server/internal/domains/certification/enums"
)
// CreateCertificationCommand 创建认证申请命令
type CreateCertificationCommand struct {
UserID string `json:"-"`
}
// CompleteContractSignCommand 完成合同签署命令
// 用于用户完成合同签署提交合同URL
type CompleteContractSignCommand struct {
UserID string `json:"-" comment:"用户唯一标识从JWT token获取不在JSON中暴露"`
ContractURL string `json:"contract_url" binding:"required,url,min=10,max=500" comment:"合同签署后的URL地址"`
// ApplyContractCommand 申请合同命令
type ApplyContractCommand struct {
CertificationID string `json:"certification_id" validate:"required"`
UserID string `json:"user_id" validate:"required"`
}
// RetryOperationCommand 重试操作命令
type RetryOperationCommand struct {
CertificationID string `json:"certification_id" validate:"required"`
UserID string `json:"user_id" validate:"required"`
Operation string `json:"operation" validate:"required,oneof=enterprise_verification contract_application"`
Reason string `json:"reason,omitempty"`
}
// EsignCallbackCommand e签宝回调命令
type EsignCallbackCommand struct {
CertificationID string `json:"certification_id" validate:"required"`
CallbackType string `json:"callback_type" validate:"required,oneof=auth_result sign_result flow_status"`
RawData string `json:"raw_data" validate:"required"`
Headers map[string]string `json:"headers,omitempty"`
QueryParams map[string]string `json:"query_params,omitempty"`
}
// ForceTransitionStatusCommand 强制状态转换命令(管理员)
type ForceTransitionStatusCommand struct {
CertificationID string `json:"certification_id" validate:"required"`
AdminID string `json:"admin_id" validate:"required"`
TargetStatus enums.CertificationStatus `json:"target_status" validate:"required"`
Reason string `json:"reason" validate:"required"`
Force bool `json:"force,omitempty"` // 是否强制执行,跳过业务规则验证
}
// SubmitEnterpriseInfoCommand 提交企业信息命令
type SubmitEnterpriseInfoCommand struct {
CertificationID string `json:"certification_id" validate:"required"`
UserID string `json:"-" validate:"required"`
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info" validate:"required"`
}

View File

@@ -1,13 +1,185 @@
package queries
// GetCertificationStatusQuery 获取认证状态查询
// 用于查询用户当前认证申请的进度状态
type GetCertificationStatusQuery struct {
UserID string `json:"user_id" binding:"required,uuid" comment:"用户唯一标识,用于查询该用户的认证申请状态"`
import (
"time"
"tyapi-server/internal/domains/certification/enums"
domainQueries "tyapi-server/internal/domains/certification/repositories/queries"
)
// GetCertificationQuery 获取认证详情查询
type GetCertificationQuery struct {
CertificationID string `json:"certification_id" validate:"required"`
UserID string `json:"user_id,omitempty"` // 用于权限验证
}
// GetCertificationDetailsQuery 获取认证详情查询
// 用于查询用户认证申请的详细信息,包括所有相关记录
type GetCertificationDetailsQuery struct {
UserID string `json:"user_id" binding:"required,uuid" comment:"用户唯一标识,用于查询该用户的认证申请详细信息"`
// GetUserCertificationsQuery 获取用户认证列表查询
type GetUserCertificationsQuery struct {
UserID string `json:"user_id" validate:"required"`
Status enums.CertificationStatus `json:"status,omitempty"`
IncludeCompleted bool `json:"include_completed,omitempty"`
IncludeFailed bool `json:"include_failed,omitempty"`
Page int `json:"page"`
PageSize int `json:"page_size"`
}
// ToDomainQuery 转换为领域查询对象
func (q *GetUserCertificationsQuery) ToDomainQuery() *domainQueries.UserCertificationsQuery {
domainQuery := &domainQueries.UserCertificationsQuery{
UserID: q.UserID,
Status: q.Status,
IncludeCompleted: q.IncludeCompleted,
IncludeFailed: q.IncludeFailed,
Page: q.Page,
PageSize: q.PageSize,
}
domainQuery.DefaultValues()
return domainQuery
}
// ListCertificationsQuery 认证列表查询(管理员)
type ListCertificationsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
SortBy string `json:"sort_by"`
SortOrder string `json:"sort_order"`
UserID string `json:"user_id,omitempty"`
Status enums.CertificationStatus `json:"status,omitempty"`
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
FailureReason enums.FailureReason `json:"failure_reason,omitempty"`
CreatedAfter *time.Time `json:"created_after,omitempty"`
CreatedBefore *time.Time `json:"created_before,omitempty"`
CompanyName string `json:"company_name,omitempty"`
LegalPersonName string `json:"legal_person_name,omitempty"`
SearchKeyword string `json:"search_keyword,omitempty"`
}
// ToDomainQuery 转换为领域查询对象
func (q *ListCertificationsQuery) ToDomainQuery() *domainQueries.ListCertificationsQuery {
domainQuery := &domainQueries.ListCertificationsQuery{
Page: q.Page,
PageSize: q.PageSize,
SortBy: q.SortBy,
SortOrder: q.SortOrder,
UserID: q.UserID,
Status: q.Status,
Statuses: q.Statuses,
FailureReason: q.FailureReason,
CreatedAfter: q.CreatedAfter,
CreatedBefore: q.CreatedBefore,
CompanyName: q.CompanyName,
LegalPersonName: q.LegalPersonName,
SearchKeyword: q.SearchKeyword,
}
domainQuery.DefaultValues()
return domainQuery
}
// SearchCertificationsQuery 搜索认证查询
type SearchCertificationsQuery struct {
Keyword string `json:"keyword" validate:"required,min=2"`
SearchFields []string `json:"search_fields,omitempty"`
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
UserID string `json:"user_id,omitempty"`
Page int `json:"page"`
PageSize int `json:"page_size"`
SortBy string `json:"sort_by"`
SortOrder string `json:"sort_order"`
ExactMatch bool `json:"exact_match,omitempty"`
}
// ToDomainQuery 转换为领域查询对象
func (q *SearchCertificationsQuery) ToDomainQuery() *domainQueries.SearchCertificationsQuery {
domainQuery := &domainQueries.SearchCertificationsQuery{
Keyword: q.Keyword,
SearchFields: q.SearchFields,
Statuses: q.Statuses,
UserID: q.UserID,
Page: q.Page,
PageSize: q.PageSize,
SortBy: q.SortBy,
SortOrder: q.SortOrder,
ExactMatch: q.ExactMatch,
}
domainQuery.DefaultValues()
return domainQuery
}
// GetCertificationStatisticsQuery 认证统计查询
type GetCertificationStatisticsQuery struct {
StartDate time.Time `json:"start_date" validate:"required"`
EndDate time.Time `json:"end_date" validate:"required"`
Period string `json:"period" validate:"oneof=daily weekly monthly yearly"`
GroupBy []string `json:"group_by,omitempty"`
UserIDs []string `json:"user_ids,omitempty"`
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
IncludeProgressStats bool `json:"include_progress_stats,omitempty"`
IncludeRetryStats bool `json:"include_retry_stats,omitempty"`
IncludeTimeStats bool `json:"include_time_stats,omitempty"`
}
// ToDomainQuery 转换为领域查询对象
func (q *GetCertificationStatisticsQuery) ToDomainQuery() *domainQueries.CertificationStatisticsQuery {
return &domainQueries.CertificationStatisticsQuery{
StartDate: q.StartDate,
EndDate: q.EndDate,
Period: q.Period,
GroupBy: q.GroupBy,
UserIDs: q.UserIDs,
Statuses: q.Statuses,
IncludeProgressStats: q.IncludeProgressStats,
IncludeRetryStats: q.IncludeRetryStats,
IncludeTimeStats: q.IncludeTimeStats,
}
}
// GetSystemMonitoringQuery 系统监控查询
type GetSystemMonitoringQuery struct {
TimeRange string `json:"time_range" validate:"oneof=1h 6h 24h 7d 30d"`
Metrics []string `json:"metrics,omitempty"` // 指定要获取的指标类型
}
// GetAvailableMetrics 获取可用的监控指标
func (q *GetSystemMonitoringQuery) GetAvailableMetrics() []string {
return []string{
"certification_count",
"success_rate",
"failure_rate",
"avg_processing_time",
"status_distribution",
"retry_count",
"esign_callback_success_rate",
}
}
// GetTimeRangeDuration 获取时间范围对应的持续时间
func (q *GetSystemMonitoringQuery) GetTimeRangeDuration() time.Duration {
switch q.TimeRange {
case "1h":
return time.Hour
case "6h":
return 6 * time.Hour
case "24h":
return 24 * time.Hour
case "7d":
return 7 * 24 * time.Hour
case "30d":
return 30 * 24 * time.Hour
default:
return 24 * time.Hour // 默认24小时
}
}
// ShouldIncludeMetric 检查是否应该包含指定指标
func (q *GetSystemMonitoringQuery) ShouldIncludeMetric(metric string) bool {
if len(q.Metrics) == 0 {
return true // 如果没有指定,包含所有指标
}
for _, m := range q.Metrics {
if m == metric {
return true
}
}
return false
}

View File

@@ -1,45 +1,235 @@
package responses
import (
"fmt"
"time"
"tyapi-server/internal/domains/certification/entities/value_objects"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
"tyapi-server/internal/domains/certification/services/state_machine"
)
// CertificationResponse 认证响应
type CertificationResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
Status enums.CertificationStatus `json:"status"`
StatusName string `json:"status_name"`
Progress int `json:"progress"`
IsUserActionRequired bool `json:"is_user_action_required"`
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty"`
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty"`
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty"`
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
Enterprise *EnterpriseInfoResponse `json:"enterprise,omitempty"`
ContractURL string `json:"contract_url,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
ID string `json:"id"`
UserID string `json:"user_id"`
Status enums.CertificationStatus `json:"status"`
StatusName string `json:"status_name"`
Progress int `json:"progress"`
// 企业信息
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info,omitempty"`
// 合同信息
ContractInfo *value_objects.ContractInfo `json:"contract_info,omitempty"`
// 时间戳
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty"`
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty"`
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty"`
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty"`
// 业务状态
IsCompleted bool `json:"is_completed"`
IsFailed bool `json:"is_failed"`
IsUserActionRequired bool `json:"is_user_action_required"`
// 失败信息
FailureReason enums.FailureReason `json:"failure_reason,omitempty"`
FailureReasonName string `json:"failure_reason_name,omitempty"`
FailureMessage string `json:"failure_message,omitempty"`
CanRetry bool `json:"can_retry,omitempty"`
RetryCount int `json:"retry_count,omitempty"`
// 用户操作提示
NextAction string `json:"next_action,omitempty"`
AvailableActions []string `json:"available_actions,omitempty"`
// 元数据
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
// EnterpriseInfoResponse 企业信息响应
type EnterpriseInfoResponse struct {
ID string `json:"id"`
CompanyName string `json:"company_name"`
UnifiedSocialCode string `json:"unified_social_code"`
LegalPersonName string `json:"legal_person_name"`
LegalPersonID string `json:"legal_person_id"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
// CertificationListResponse 认证列表响应
type CertificationListResponse struct {
Items []*CertificationResponse `json:"items"`
Total int64 `json:"total"`
Page int `json:"page"`
PageSize int `json:"page_size"`
TotalPages int `json:"total_pages"`
}
// EnterpriseAuthURLResponse 企业认证链接响应
type EnterpriseAuthURLResponse struct {
EsignFlowID string `json:"esign_flow_id"` // e签宝认证流程ID
AuthURL string `json:"auth_url"` // 认证链接
ShortURL string `json:"short_url"` // 短链接
ExpireAt string `json:"expire_at"` // 过期时间
// ContractSignUrlResponse 合同签署URL响应
type ContractSignUrlResponse struct {
CertificationID string `json:"certification_id"`
ContractSignURL string `json:"contract_sign_url"`
ContractURL string `json:"contract_url,omitempty"`
ExpireAt *time.Time `json:"expire_at,omitempty"`
NextAction string `json:"next_action"`
Message string `json:"message"`
}
// CallbackResponse 回调响应
type CallbackResponse struct {
Success bool `json:"success"`
CertificationID string `json:"certification_id"`
CallbackType string `json:"callback_type"`
ProcessedAt time.Time `json:"processed_at"`
OldStatus enums.CertificationStatus `json:"old_status,omitempty"`
NewStatus enums.CertificationStatus `json:"new_status,omitempty"`
Message string `json:"message"`
StateTransition *state_machine.StateTransitionResult `json:"state_transition,omitempty"`
}
// CertificationStatisticsResponse 认证统计响应
type CertificationStatisticsResponse struct {
Period string `json:"period"`
TimeRange string `json:"time_range"`
Statistics *repositories.CertificationStatistics `json:"statistics"`
ProgressStats *repositories.CertificationProgressStats `json:"progress_stats,omitempty"`
Charts map[string]interface{} `json:"charts,omitempty"`
GeneratedAt time.Time `json:"generated_at"`
}
// SystemMonitoringResponse 系统监控响应
type SystemMonitoringResponse struct {
TimeRange string `json:"time_range"`
Metrics map[string]interface{} `json:"metrics"`
Alerts []SystemAlert `json:"alerts,omitempty"`
SystemHealth SystemHealthStatus `json:"system_health"`
LastUpdatedAt time.Time `json:"last_updated_at"`
}
// SystemAlert 系统警告
type SystemAlert struct {
Level string `json:"level"` // info, warning, error, critical
Type string `json:"type"` // 警告类型
Message string `json:"message"` // 警告消息
Metric string `json:"metric"` // 相关指标
Value interface{} `json:"value"` // 当前值
Threshold interface{} `json:"threshold"` // 阈值
CreatedAt time.Time `json:"created_at"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
// SystemHealthStatus 系统健康状态
type SystemHealthStatus struct {
Overall string `json:"overall"` // healthy, warning, critical
Components map[string]string `json:"components"` // 各组件状态
LastCheck time.Time `json:"last_check"`
Details map[string]interface{} `json:"details,omitempty"`
}
// ================ 响应构建辅助方法 ================
// NewCertificationListResponse 创建认证列表响应
func NewCertificationListResponse(items []*CertificationResponse, total int64, page, pageSize int) *CertificationListResponse {
totalPages := int((total + int64(pageSize) - 1) / int64(pageSize))
if totalPages == 0 {
totalPages = 1
}
return &CertificationListResponse{
Items: items,
Total: total,
Page: page,
PageSize: pageSize,
TotalPages: totalPages,
}
}
// NewContractSignUrlResponse 创建合同签署URL响应
func NewContractSignUrlResponse(certificationID, signURL, contractURL, nextAction, message string) *ContractSignUrlResponse {
response := &ContractSignUrlResponse{
CertificationID: certificationID,
ContractSignURL: signURL,
ContractURL: contractURL,
NextAction: nextAction,
Message: message,
}
// 设置过期时间默认24小时
expireAt := time.Now().Add(24 * time.Hour)
response.ExpireAt = &expireAt
return response
}
// NewCallbackResponse 创建回调响应
func NewCallbackResponse(success bool, certificationID, callbackType, message string) *CallbackResponse {
return &CallbackResponse{
Success: success,
CertificationID: certificationID,
CallbackType: callbackType,
ProcessedAt: time.Now(),
Message: message,
}
}
// NewSystemAlert 创建系统警告
func NewSystemAlert(level, alertType, message, metric string, value, threshold interface{}) *SystemAlert {
return &SystemAlert{
Level: level,
Type: alertType,
Message: message,
Metric: metric,
Value: value,
Threshold: threshold,
CreatedAt: time.Now(),
Metadata: make(map[string]interface{}),
}
}
// IsSuccess 检查响应是否成功
func (r *CallbackResponse) IsSuccess() bool {
return r.Success
}
// HasStateTransition 检查是否有状态转换
func (r *CallbackResponse) HasStateTransition() bool {
return r.StateTransition != nil
}
// GetStatusChange 获取状态变更描述
func (r *CallbackResponse) GetStatusChange() string {
if !r.HasStateTransition() {
return ""
}
if r.OldStatus == r.NewStatus {
return "状态无变化"
}
return fmt.Sprintf("从 %s 转换为 %s",
enums.GetStatusName(r.OldStatus),
enums.GetStatusName(r.NewStatus))
}
// IsHealthy 检查系统是否健康
func (r *SystemMonitoringResponse) IsHealthy() bool {
return r.SystemHealth.Overall == "healthy"
}
// GetCriticalAlerts 获取严重警告
func (r *SystemMonitoringResponse) GetCriticalAlerts() []*SystemAlert {
var criticalAlerts []*SystemAlert
for i := range r.Alerts {
if r.Alerts[i].Level == "critical" {
criticalAlerts = append(criticalAlerts, &r.Alerts[i])
}
}
return criticalAlerts
}
// HasAlerts 检查是否有警告
func (r *SystemMonitoringResponse) HasAlerts() bool {
return len(r.Alerts) > 0
}
// GetMetricValue 获取指标值
func (r *SystemMonitoringResponse) GetMetricValue(metric string) (interface{}, bool) {
value, exists := r.Metrics[metric]
return value, exists
}

View File

@@ -1,385 +0,0 @@
package certification
import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"sort"
"strings"
"go.uber.org/zap"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/services"
user_services "tyapi-server/internal/domains/user/services"
"tyapi-server/internal/shared/database"
esign_service "tyapi-server/internal/shared/esign"
)
// EsignCallbackData e签宝回调数据结构
type EsignCallbackData struct {
Action string `json:"action"`
Timestamp int64 `json:"timestamp"`
AuthFlowId string `json:"authFlowId,omitempty"`
SignFlowId string `json:"signFlowId,omitempty"`
CustomBizNum string `json:"customBizNum,omitempty"`
SignOrder int `json:"signOrder,omitempty"`
OperateTime int64 `json:"operateTime,omitempty"`
SignResult int `json:"signResult,omitempty"`
ResultDescription string `json:"resultDescription,omitempty"`
AuthType string `json:"authType,omitempty"`
SignFlowStatus string `json:"signFlowStatus,omitempty"`
Operator *EsignOperator `json:"operator,omitempty"`
PsnInfo *EsignPsnInfo `json:"psnInfo,omitempty"`
Organization *EsignOrganization `json:"organization,omitempty"`
}
// EsignOperator 签署人信息
type EsignOperator struct {
PsnId string `json:"psnId"`
PsnAccount *EsignPsnAccount `json:"psnAccount"`
}
// EsignPsnInfo 个人认证信息
type EsignPsnInfo struct {
PsnId string `json:"psnId"`
PsnAccount *EsignPsnAccount `json:"psnAccount"`
}
// EsignPsnAccount 个人账户信息
type EsignPsnAccount struct {
AccountMobile string `json:"accountMobile"`
AccountEmail string `json:"accountEmail"`
}
// EsignOrganization 企业信息
type EsignOrganization struct {
OrgName string `json:"orgName"`
// 可以根据需要添加更多企业信息字段
}
// EsignCallbackApplicationServiceImpl e签宝回调应用服务实现
type EsignCallbackApplicationServiceImpl struct {
certManagementService *services.CertificationManagementService
certWorkflowService *services.CertificationWorkflowService
certificationEsignService *services.CertificationEsignService
enterpriseService *user_services.EnterpriseService
esignService *esign_service.Client
enterpriseRecordService *services.EnterpriseInfoSubmitRecordService
txManager *database.TransactionManager
logger *zap.Logger
}
// NewEsignCallbackApplicationService 创建e签宝回调应用服务
func NewEsignCallbackApplicationService(
certManagementService *services.CertificationManagementService,
certWorkflowService *services.CertificationWorkflowService,
certificationEsignService *services.CertificationEsignService,
enterpriseService *user_services.EnterpriseService,
esignService *esign_service.Client,
enterpriseRecordService *services.EnterpriseInfoSubmitRecordService,
txManager *database.TransactionManager,
logger *zap.Logger,
) EsignCallbackApplicationService {
return &EsignCallbackApplicationServiceImpl{
certManagementService: certManagementService,
certWorkflowService: certWorkflowService,
certificationEsignService: certificationEsignService,
enterpriseService: enterpriseService,
esignService: esignService,
enterpriseRecordService: enterpriseRecordService,
txManager: txManager,
logger: logger,
}
}
// HandleCallback 处理e签宝回调
func (s *EsignCallbackApplicationServiceImpl) HandleCallback(ctx context.Context, callbackData map[string]interface{}, headers map[string]string, queryParams map[string]string) error {
s.logger.Info("开始处理e签宝回调", zap.Any("callback_data", callbackData))
// 1. 验签
if err := s.verifySignature(callbackData, headers, queryParams); err != nil {
s.logger.Error("e签宝回调验签失败", zap.Error(err))
return fmt.Errorf("验签失败: %w", err)
}
// 2. 解析回调数据为结构体
var callback EsignCallbackData
jsonBytes, err := json.Marshal(callbackData)
if err != nil {
return fmt.Errorf("序列化回调数据失败: %w", err)
}
if err := json.Unmarshal(jsonBytes, &callback); err != nil {
return fmt.Errorf("解析回调数据失败: %w", err)
}
// 3. 记录回调信息
s.logger.Info("e签宝回调信息解析",
zap.String("action", callback.Action),
zap.String("auth_flow_id", callback.AuthFlowId),
zap.String("sign_flow_id", callback.SignFlowId),
zap.String("auth_type", callback.AuthType),
zap.String("sign_flow_status", callback.SignFlowStatus),
zap.Int64("timestamp", callback.Timestamp),
)
// 4. 根据回调类型处理业务逻辑
switch callback.Action {
case "AUTH_PASS":
// 只处理企业认证通过
if callback.AuthType == "ORG" {
return s.handleEnterpriseAuthPass(ctx, &callback)
}
s.logger.Info("忽略非企业认证通过回调", zap.String("auth_type", callback.AuthType))
return nil
case "AUTH_FAIL":
// 只处理企业认证失败
if callback.AuthType == "ORG" {
return s.handleEnterpriseAuthFail(ctx, &callback)
}
s.logger.Info("忽略非企业认证失败回调", zap.String("auth_type", callback.AuthType))
return nil
case "SIGN_FLOW_COMPLETE":
// 合同签署流程完成
return s.handleContractSignFlowComplete(ctx, &callback)
default:
s.logger.Info("忽略未知的回调动作", zap.String("action", callback.Action))
return nil
}
}
// verifySignature 验证e签宝回调签名
func (s *EsignCallbackApplicationServiceImpl) verifySignature(callbackData map[string]interface{}, headers map[string]string, queryParams map[string]string) error {
// 1. 获取签名相关参数
signature, ok := headers["X-Tsign-Open-Signature"]
if !ok {
return fmt.Errorf("缺少签名头: X-Tsign-Open-Signature")
}
timestamp, ok := headers["X-Tsign-Open-Timestamp"]
if !ok {
return fmt.Errorf("缺少时间戳头: X-Tsign-Open-Timestamp")
}
// 2. 构建查询参数字符串
var queryKeys []string
for key := range queryParams {
queryKeys = append(queryKeys, key)
}
sort.Strings(queryKeys) // 按ASCII码升序排序
var queryValues []string
for _, key := range queryKeys {
queryValues = append(queryValues, queryParams[key])
}
queryString := strings.Join(queryValues, "")
// 3. 获取请求体数据
bodyData, err := s.getRequestBodyString(callbackData)
if err != nil {
return fmt.Errorf("获取请求体数据失败: %w", err)
}
// 4. 构建验签数据
data := timestamp + queryString + bodyData
// 5. 计算签名
expectedSignature := s.calculateSignature(data, s.esignService.GetConfig().AppSecret)
// 6. 比较签名
if strings.ToLower(expectedSignature) != strings.ToLower(signature) {
s.logger.Error("签名验证失败",
zap.String("expected", strings.ToLower(expectedSignature)),
zap.String("received", strings.ToLower(signature)),
zap.String("data", data),
)
return fmt.Errorf("签名验证失败")
}
s.logger.Info("e签宝回调验签成功")
return nil
}
// calculateSignature 计算HMAC-SHA256签名
func (s *EsignCallbackApplicationServiceImpl) calculateSignature(data, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(data))
return strings.ToUpper(hex.EncodeToString(h.Sum(nil)))
}
// getRequestBodyString 获取请求体字符串
func (s *EsignCallbackApplicationServiceImpl) getRequestBodyString(callbackData map[string]interface{}) (string, error) {
// 将map转换为JSON字符串
jsonBytes, err := json.Marshal(callbackData)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %w", err)
}
return string(jsonBytes), nil
}
// handleEnterpriseAuthPass 处理企业认证通过回调
func (s *EsignCallbackApplicationServiceImpl) handleEnterpriseAuthPass(ctx context.Context, callback *EsignCallbackData) error {
s.logger.Info("处理企业认证通过回调")
if callback.Organization == nil {
return fmt.Errorf("回调数据中缺少organization字段")
}
if callback.AuthFlowId == "" {
return fmt.Errorf("回调数据中缺少authFlowId字段")
}
// 查找对应的认证申请
certification, err := s.certManagementService.GetCertificationByAuthFlowID(ctx, callback.AuthFlowId)
if err != nil {
return fmt.Errorf("查找认证申请失败: %w", err)
}
if certification.Status != enums.StatusInfoSubmitted {
s.logger.Warn("当前状态不允许完成企业认证", zap.String("status", string(certification.Status)))
return nil
}
if err := s.completeEnterpriseAuth(ctx, certification); err != nil {
return fmt.Errorf("完成企业认证失败: %w", err)
}
s.logger.Info("企业认证通过处理完成",
zap.String("user_id", certification.UserID),
zap.String("certification_id", certification.ID),
zap.String("org_name", callback.Organization.OrgName),
)
return nil
}
// handleEnterpriseAuthFail 处理企业认证失败回调
func (s *EsignCallbackApplicationServiceImpl) handleEnterpriseAuthFail(ctx context.Context, callback *EsignCallbackData) error {
s.logger.Info("处理企业认证失败回调")
if callback.Organization == nil {
return fmt.Errorf("回调数据中缺少organization字段")
}
// 暂时忽略
return nil
}
// handleContractSignFlowComplete 处理合同签署流程完成回调
func (s *EsignCallbackApplicationServiceImpl) handleContractSignFlowComplete(ctx context.Context, callback *EsignCallbackData) error {
s.logger.Info("处理合同签署流程完成回调")
if callback.SignFlowId == "" {
return fmt.Errorf("回调数据中缺少signFlowId字段")
}
if callback.SignFlowStatus == "" {
return fmt.Errorf("回调数据中缺少signFlowStatus字段")
}
// 查找对应的认证申请
certification, err := s.certManagementService.GetCertificationByEsignFlowID(ctx, callback.SignFlowId)
if err != nil {
return fmt.Errorf("查找认证申请失败: %w", err)
}
// 根据签署流程状态处理
switch callback.SignFlowStatus {
case "2": // 已完成(所有签署方完成签署)
s.logger.Info("合同签署流程已完成,所有签署方完成签署")
// 完成合同签署
if err := s.certWorkflowService.CompleteContractSign(ctx, certification.ID, "所有签署方完成签署"); err != nil {
return fmt.Errorf("完成合同签署失败: %w", err)
}
// 自动完成认证
if err := s.certWorkflowService.CompleteCertification(ctx, certification.ID); err != nil {
return fmt.Errorf("完成认证失败: %w", err)
}
s.logger.Info("合同签署流程完成处理成功",
zap.String("user_id", certification.UserID),
zap.String("certification_id", certification.ID),
zap.String("sign_flow_id", callback.SignFlowId),
zap.String("sign_flow_status", callback.SignFlowStatus),
)
case "3": // 已撤销(发起方撤销签署任务)
s.logger.Info("合同签署流程已撤销")
// 可以在这里添加撤销处理逻辑
s.logger.Info("合同签署流程撤销处理完成",
zap.String("user_id", certification.UserID),
zap.String("certification_id", certification.ID),
zap.String("sign_flow_id", callback.SignFlowId),
zap.String("sign_flow_status", callback.SignFlowStatus),
)
// 暂无撤销业务逻辑
case "5": // 已过期(签署截止日到期后触发)
s.logger.Info("合同签署流程已过期")
// 可以在这里添加过期处理逻辑
s.logger.Info("合同签署流程过期处理完成",
zap.String("user_id", certification.UserID),
zap.String("certification_id", certification.ID),
zap.String("sign_flow_id", callback.SignFlowId),
zap.String("sign_flow_status", callback.SignFlowStatus),
)
// 暂无过期业务逻辑
case "7": // 已拒签(签署方拒绝签署)
s.logger.Info("合同签署流程已拒签")
// 可以在这里添加拒签处理逻辑
s.logger.Info("合同签署流程拒签处理完成",
zap.String("user_id", certification.UserID),
zap.String("certification_id", certification.ID),
zap.String("sign_flow_id", callback.SignFlowId),
zap.String("sign_flow_status", callback.SignFlowStatus),
)
default:
s.logger.Warn("未知的签署流程状态",
zap.String("sign_flow_status", callback.SignFlowStatus),
zap.String("sign_flow_id", callback.SignFlowId),
)
// 暂无拒签业务逻辑
}
return nil
}
// 企业认证成功后操作
func (s *EsignCallbackApplicationServiceImpl) completeEnterpriseAuth(ctx context.Context, certification *entities.Certification) error {
err := s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
// 1. 获取企业信息提交记录
enterpriseRecord, err := s.enterpriseRecordService.GetLatestByUserID(txCtx, certification.UserID)
if err != nil {
return fmt.Errorf("获取企业信息失败: %w", err)
}
// 2. 转换状态
if err := s.certWorkflowService.CompleteEnterpriseVerification(txCtx, certification.ID); err != nil {
return err
}
// 3. 创建企业信息
_, err = s.enterpriseService.CreateEnterpriseInfo(txCtx, certification.UserID, enterpriseRecord.CompanyName, enterpriseRecord.UnifiedSocialCode, enterpriseRecord.LegalPersonName, enterpriseRecord.LegalPersonID)
if err != nil {
s.logger.Warn("创建用户企业信息失败", zap.Error(err))
return err
}
return nil
})
if err != nil {
return fmt.Errorf("完成企业认证失败: %w", err)
}
return nil
}

View File

@@ -15,7 +15,6 @@ import (
"tyapi-server/internal/application/user"
"tyapi-server/internal/config"
domain_certification_repo "tyapi-server/internal/domains/certification/repositories"
certification_service "tyapi-server/internal/domains/certification/services"
domain_finance_repo "tyapi-server/internal/domains/finance/repositories"
finance_service "tyapi-server/internal/domains/finance/services"
domain_product_repo "tyapi-server/internal/domains/product/repositories"
@@ -251,25 +250,15 @@ func NewContainer() *Container {
// 仓储层 - 认证域
fx.Provide(
// 认证申请仓储
// 认证命令仓储
fx.Annotate(
certification_repo.NewGormCertificationRepository,
fx.As(new(domain_certification_repo.CertificationRepository)),
certification_repo.NewGormCertificationCommandRepository,
fx.As(new(domain_certification_repo.CertificationCommandRepository)),
),
// 企业信息提交记录仓储
// 认证查询仓储
fx.Annotate(
certification_repo.NewGormEnterpriseInfoSubmitRecordRepository,
fx.As(new(domain_certification_repo.EnterpriseInfoSubmitRecordRepository)),
),
// e签宝生成合同记录仓储
fx.Annotate(
certification_repo.NewGormEsignContractGenerateRecordRepository,
fx.As(new(domain_certification_repo.EsignContractGenerateRecordRepository)),
),
// e签宝签署合同记录仓储
fx.Annotate(
certification_repo.NewGormEsignContractSignRecordRepository,
fx.As(new(domain_certification_repo.EsignContractSignRecordRepository)),
certification_repo.NewGormCertificationQueryRepository,
fx.As(new(domain_certification_repo.CertificationQueryRepository)),
),
),
@@ -314,11 +303,7 @@ func NewContainer() *Container {
user_service.NewEnterpriseService,
product_service.NewProductManagementService,
product_service.NewProductSubscriptionService,
certification_service.NewCertificationManagementService,
certification_service.NewCertificationWorkflowService,
certification_service.NewCertificationStateMachine,
certification_service.NewEnterpriseInfoSubmitRecordService,
certification_service.NewCertificationEsignService,
// 认证域的领域服务已经整合到应用服务中
finance_service.NewFinanceService,
),
@@ -334,11 +319,6 @@ func NewContainer() *Container {
certification.NewCertificationApplicationService,
fx.As(new(certification.CertificationApplicationService)),
),
// e签宝回调应用服务 - 绑定到接口
fx.Annotate(
certification.NewEsignCallbackApplicationService,
fx.As(new(certification.EsignCallbackApplicationService)),
),
// 财务应用服务 - 绑定到接口
fx.Annotate(
finance.NewFinanceApplicationService,

View File

@@ -1,43 +1,55 @@
package entities
import (
"errors"
"fmt"
"time"
"tyapi-server/internal/domains/certification/entities/value_objects"
"tyapi-server/internal/domains/certification/enums"
"github.com/google/uuid"
"gorm.io/gorm"
)
// Certification 认证申请实体
// 这是企业认证流程的核心实体,负责管理整个认证申请的生命周期
// Certification 认证聚合根
// 这是企业认证流程的核心聚合根,封装了完整的认证业务逻辑和状态管理
type Certification struct {
// 基础信息
// === 基础信息 ===
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"认证申请唯一标识"`
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"申请用户ID"`
Status enums.CertificationStatus `gorm:"type:varchar(50);not null;index" json:"status" comment:"当前认证状态"`
// 流程节点时间戳 - 记录每个关键步骤的完成时间
// === 流程时间戳 - 记录每个关键步骤的完成时间 ===
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty" comment:"企业认证完成时间"`
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty" comment:"合同申请时间"`
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty" comment:"合同签署完成时间"`
CompletedAt *time.Time `json:"completed_at,omitempty" comment:"认证完成时间"`
// 合同信息 - 电子合同相关链接
// === e签宝相关信息 ===
AuthFlowID string `gorm:"type:varchar(500)" json:"auth_flow_id,omitempty" comment:"企业认证流程ID"`
ContractFileID string `gorm:"type:varchar(500)" json:"contract_file_id,omitempty" comment:"合同文件ID"`
EsignFlowID string `gorm:"type:varchar(500)" json:"esign_flow_id,omitempty" comment:"签署流程ID"`
ContractURL string `gorm:"type:varchar(500)" json:"contract_url,omitempty" comment:"合同文件访问链接"`
ContractSignURL string `gorm:"type:varchar(500)" json:"contract_sign_url,omitempty" comment:"合同签署链接"`
// 认证信息
AuthFlowID string `gorm:"type:varchar(500)" json:"auth_flow_id,omitempty" comment:"认证流程ID"`
// === 失败信息 ===
FailureReason enums.FailureReason `gorm:"type:varchar(100)" json:"failure_reason,omitempty" comment:"失败原因"`
FailureMessage string `gorm:"type:text" json:"failure_message,omitempty" comment:"失败详细信息"`
RetryCount int `gorm:"default:0" json:"retry_count" comment:"重试次数"`
// 时间戳字段
// === 审计信息 ===
LastTransitionAt *time.Time `json:"last_transition_at,omitempty" comment:"最后状态转换时间"`
LastTransitionBy enums.ActorType `gorm:"type:varchar(20)" json:"last_transition_by,omitempty" comment:"最后操作者类型"`
LastTransitionActor string `gorm:"type:varchar(100)" json:"last_transition_actor,omitempty" comment:"最后操作者ID"`
// === 系统字段 ===
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
// === 领域事件 (不持久化) ===
domainEvents []interface{} `gorm:"-" json:"-"`
}
// TableName 指定数据库表名
@@ -50,98 +62,622 @@ func (c *Certification) BeforeCreate(tx *gorm.DB) error {
if c.ID == "" {
c.ID = uuid.New().String()
}
// 设置初始状态
if c.Status == "" {
c.Status = enums.StatusPending
}
return nil
}
// IsStatusChangeable 检查状态是否可以变更
func (c *Certification) IsStatusChangeable() bool {
return !enums.IsFinalStatus(c.Status)
// ================ 工厂方法 ================
// NewCertification 创建新的认证申请
func NewCertification(userID string) (*Certification, error) {
if userID == "" {
return nil, errors.New("用户ID不能为空")
}
certification := &Certification{
ID: uuid.New().String(),
UserID: userID,
Status: enums.StatusPending,
RetryCount: 0,
domainEvents: make([]interface{}, 0),
}
// 添加领域事件
certification.addDomainEvent(&CertificationCreatedEvent{
CertificationID: certification.ID,
UserID: userID,
CreatedAt: time.Now(),
})
return certification, nil
}
// GetStatusName 获取状态名称
func (c *Certification) GetStatusName() string {
// ================ 状态转换方法 ================
// CanTransitionTo 检查是否可以转换到目标状态
func (c *Certification) CanTransitionTo(targetStatus enums.CertificationStatus, actor enums.ActorType) (bool, string) {
// 检查状态转换规则
if !enums.CanTransitionTo(c.Status, targetStatus) {
return false, fmt.Sprintf("不允许从 %s 转换到 %s", enums.GetStatusName(c.Status), enums.GetStatusName(targetStatus))
}
// 检查操作者权限
if !c.validateActorPermission(targetStatus, actor) {
return false, fmt.Sprintf("%s 无权执行此状态转换", enums.GetActorTypeName(actor))
}
// 检查业务规则
if err := c.validateBusinessRules(targetStatus, actor); err != nil {
return false, err.Error()
}
return true, ""
}
// TransitionTo 执行状态转换
func (c *Certification) TransitionTo(targetStatus enums.CertificationStatus, actor enums.ActorType, actorID string, reason string) error {
// 验证转换合法性
canTransition, message := c.CanTransitionTo(targetStatus, actor)
if !canTransition {
return fmt.Errorf("状态转换失败: %s", message)
}
oldStatus := c.Status
// 执行状态转换
c.Status = targetStatus
c.updateTimestampByStatus(targetStatus)
c.updateTransitionAudit(actor, actorID)
// 清除失败信息(如果转换到成功状态)
if !enums.IsFailureStatus(targetStatus) {
c.clearFailureInfo()
}
// 添加状态转换事件
c.addDomainEvent(&CertificationStatusChangedEvent{
CertificationID: c.ID,
UserID: c.UserID,
FromStatus: oldStatus,
ToStatus: targetStatus,
Actor: actor,
ActorID: actorID,
Reason: reason,
TransitionedAt: time.Now(),
})
return nil
}
// ================ 业务操作方法 ================
// SubmitEnterpriseInfo 提交企业信息
func (c *Certification) SubmitEnterpriseInfo(enterpriseInfo *value_objects.EnterpriseInfo) error {
// 验证当前状态
if c.Status != enums.StatusPending && c.Status != enums.StatusInfoRejected {
return fmt.Errorf("当前状态 %s 不允许提交企业信息", enums.GetStatusName(c.Status))
}
// 验证企业信息
if err := enterpriseInfo.Validate(); err != nil {
return fmt.Errorf("企业信息验证失败: %w", err)
}
// 状态转换
if err := c.TransitionTo(enums.StatusInfoSubmitted, enums.ActorTypeUser, c.UserID, "用户提交企业信息"); err != nil {
return err
}
// 添加业务事件
c.addDomainEvent(&EnterpriseInfoSubmittedEvent{
CertificationID: c.ID,
UserID: c.UserID,
EnterpriseInfo: enterpriseInfo,
SubmittedAt: time.Now(),
})
return nil
}
// HandleEnterpriseVerificationCallback 处理企业认证回调
func (c *Certification) HandleEnterpriseVerificationCallback(success bool, authFlowID string, failureReason enums.FailureReason, message string) error {
// 验证当前状态
if c.Status != enums.StatusInfoSubmitted {
return fmt.Errorf("当前状态 %s 不允许处理企业认证回调", enums.GetStatusName(c.Status))
}
c.AuthFlowID = authFlowID
if success {
// 认证成功
if err := c.TransitionTo(enums.StatusEnterpriseVerified, enums.ActorTypeEsign, "esign_system", "企业认证成功"); err != nil {
return err
}
c.addDomainEvent(&EnterpriseVerificationSuccessEvent{
CertificationID: c.ID,
UserID: c.UserID,
AuthFlowID: authFlowID,
VerifiedAt: time.Now(),
})
} else {
// 认证失败
c.setFailureInfo(failureReason, message)
if err := c.TransitionTo(enums.StatusInfoRejected, enums.ActorTypeEsign, "esign_system", "企业认证失败"); err != nil {
return err
}
c.addDomainEvent(&EnterpriseVerificationFailedEvent{
CertificationID: c.ID,
UserID: c.UserID,
AuthFlowID: authFlowID,
FailureReason: failureReason,
FailureMessage: message,
FailedAt: time.Now(),
})
}
return nil
}
// ApplyContract 申请合同签署
func (c *Certification) ApplyContract() error {
// 验证当前状态
if c.Status != enums.StatusEnterpriseVerified {
return fmt.Errorf("当前状态 %s 不允许申请合同", enums.GetStatusName(c.Status))
}
// 状态转换
if err := c.TransitionTo(enums.StatusContractApplied, enums.ActorTypeUser, c.UserID, "用户申请合同签署"); err != nil {
return err
}
// 添加业务事件
c.addDomainEvent(&ContractAppliedEvent{
CertificationID: c.ID,
UserID: c.UserID,
AppliedAt: time.Now(),
})
return nil
}
// UpdateContractInfo 更新合同信息
func (c *Certification) UpdateContractInfo(contractInfo *value_objects.ContractInfo) error {
// 验证合同信息
if err := contractInfo.Validate(); err != nil {
return fmt.Errorf("合同信息验证失败: %w", err)
}
// 更新合同相关字段
c.ContractFileID = contractInfo.ContractFileID
c.EsignFlowID = contractInfo.EsignFlowID
c.ContractURL = contractInfo.ContractURL
c.ContractSignURL = contractInfo.ContractSignURL
return nil
}
// HandleContractSignCallback 处理合同签署回调
func (c *Certification) HandleContractSignCallback(success bool, contractURL string, failureReason enums.FailureReason, message string) error {
// 验证当前状态
if c.Status != enums.StatusContractApplied {
return fmt.Errorf("当前状态 %s 不允许处理合同签署回调", enums.GetStatusName(c.Status))
}
if success {
// 签署成功 - 认证完成
c.ContractURL = contractURL
if err := c.TransitionTo(enums.StatusContractSigned, enums.ActorTypeEsign, "esign_system", "合同签署成功,认证完成"); err != nil {
return err
}
c.addDomainEvent(&ContractSignedEvent{
CertificationID: c.ID,
UserID: c.UserID,
ContractURL: contractURL,
SignedAt: time.Now(),
})
c.addDomainEvent(&CertificationCompletedEvent{
CertificationID: c.ID,
UserID: c.UserID,
CompletedAt: time.Now(),
})
} else {
// 签署失败
c.setFailureInfo(failureReason, message)
var targetStatus enums.CertificationStatus
if failureReason == enums.FailureReasonContractExpired {
targetStatus = enums.StatusContractExpired
} else {
targetStatus = enums.StatusContractRejected
}
if err := c.TransitionTo(targetStatus, enums.ActorTypeEsign, "esign_system", "合同签署失败"); err != nil {
return err
}
c.addDomainEvent(&ContractSignFailedEvent{
CertificationID: c.ID,
UserID: c.UserID,
FailureReason: failureReason,
FailureMessage: message,
FailedAt: time.Now(),
})
}
return nil
}
// RetryFromFailure 从失败状态重试
func (c *Certification) RetryFromFailure(actor enums.ActorType, actorID string) error {
if !enums.IsFailureStatus(c.Status) {
return errors.New("当前状态不是失败状态,无需重试")
}
// 检查重试次数限制
if c.RetryCount >= 3 {
return errors.New("已达到最大重试次数限制")
}
// 检查失败原因是否可重试
if !enums.IsRetryable(c.FailureReason) {
return fmt.Errorf("失败原因 %s 不支持重试", enums.GetFailureReasonName(c.FailureReason))
}
var targetStatus enums.CertificationStatus
var reason string
switch c.Status {
case enums.StatusInfoRejected:
targetStatus = enums.StatusInfoSubmitted
reason = "重新提交企业信息"
case enums.StatusContractRejected, enums.StatusContractExpired:
targetStatus = enums.StatusEnterpriseVerified
reason = "重置状态,准备重新申请合同"
default:
return fmt.Errorf("不支持从状态 %s 重试", enums.GetStatusName(c.Status))
}
// 增加重试次数
c.RetryCount++
// 状态转换
if err := c.TransitionTo(targetStatus, actor, actorID, reason); err != nil {
return err
}
// 添加重试事件
c.addDomainEvent(&CertificationRetryEvent{
CertificationID: c.ID,
UserID: c.UserID,
FromStatus: c.Status,
ToStatus: targetStatus,
RetryCount: c.RetryCount,
RetriedAt: time.Now(),
})
return nil
}
// ================ 查询方法 ================
// GetProgress 获取认证进度百分比
func (c *Certification) GetProgress() int {
return enums.GetProgressPercentage(c.Status)
}
// IsUserActionRequired 是否需要用户操作
func (c *Certification) IsUserActionRequired() bool {
return enums.IsUserActionRequired(c.Status)
}
// GetCurrentStatusName 获取当前状态名称
func (c *Certification) GetCurrentStatusName() string {
return enums.GetStatusName(c.Status)
}
// IsFinalStatus 判断是否为最终状态
// GetUserActionHint 获取用户操作提示
func (c *Certification) GetUserActionHint() string {
return enums.GetUserActionHint(c.Status)
}
// GetAvailableActions 获取当前可执行的操作
func (c *Certification) GetAvailableActions() []string {
actions := make([]string, 0)
switch c.Status {
case enums.StatusPending:
actions = append(actions, "submit_enterprise_info")
case enums.StatusEnterpriseVerified:
actions = append(actions, "apply_contract")
case enums.StatusInfoRejected, enums.StatusContractRejected, enums.StatusContractExpired:
if enums.IsRetryable(c.FailureReason) && c.RetryCount < 3 {
actions = append(actions, "retry")
}
}
return actions
}
// IsFinalStatus 是否为最终状态
func (c *Certification) IsFinalStatus() bool {
return enums.IsFinalStatus(c.Status)
}
// GetStatusCategory 获取状态分类
func (c *Certification) GetStatusCategory() string {
return enums.GetStatusCategory(c.Status)
}
// GetStatusPriority 获取状态优先级
func (c *Certification) GetStatusPriority() int {
return enums.GetStatusPriority(c.Status)
}
// GetProgressPercentage 获取进度百分比
func (c *Certification) GetProgressPercentage() int {
progressMap := map[enums.CertificationStatus]int{
enums.StatusPending: 0,
enums.StatusInfoSubmitted: 20,
enums.StatusEnterpriseVerified: 40,
enums.StatusContractApplied: 60,
enums.StatusContractSigned: 80,
enums.StatusCompleted: 100,
}
if progress, exists := progressMap[c.Status]; exists {
return progress
}
return 0
}
// IsUserActionRequired 检查是否需要用户操作
func (c *Certification) IsUserActionRequired() bool {
userActionRequired := map[enums.CertificationStatus]bool{
enums.StatusPending: true,
enums.StatusInfoSubmitted: true,
enums.StatusEnterpriseVerified: true,
enums.StatusContractApplied: true,
enums.StatusContractSigned: false,
enums.StatusCompleted: false,
}
if required, exists := userActionRequired[c.Status]; exists {
return required
}
return false
// IsCompleted 是否已完成认证
func (c *Certification) IsCompleted() bool {
return c.Status == enums.StatusContractSigned
}
// GetNextValidStatuses 获取下一个有效状态
func (c *Certification) GetNextValidStatuses() []enums.CertificationStatus {
nextStatusMap := map[enums.CertificationStatus][]enums.CertificationStatus{
enums.StatusPending: {enums.StatusInfoSubmitted},
enums.StatusInfoSubmitted: {enums.StatusEnterpriseVerified, enums.StatusInfoSubmitted}, // 可以重新提交
enums.StatusEnterpriseVerified: {enums.StatusContractApplied},
enums.StatusContractApplied: {enums.StatusContractSigned},
enums.StatusContractSigned: {enums.StatusCompleted},
enums.StatusCompleted: {},
}
if nextStatuses, exists := nextStatusMap[c.Status]; exists {
return nextStatuses
}
return []enums.CertificationStatus{}
return enums.GetNextValidStatuses(c.Status)
}
// CanTransitionTo 检查是否可以转换到指定状态
func (c *Certification) CanTransitionTo(targetStatus enums.CertificationStatus, isUser bool) (bool, string) {
nextStatuses := c.GetNextValidStatuses()
// GetFailureInfo 获取失败信息
func (c *Certification) GetFailureInfo() (enums.FailureReason, string) {
return c.FailureReason, c.FailureMessage
}
for _, nextStatus := range nextStatuses {
if nextStatus == targetStatus {
// 检查权限
if isUser && !c.IsUserActionRequired() {
return false, "当前状态不需要用户操作"
}
return true, ""
// ================ 业务规则验证 ================
// ValidateBusinessRules 验证业务规则
func (c *Certification) ValidateBusinessRules() error {
// 基础验证
if c.UserID == "" {
return errors.New("用户ID不能为空")
}
if !enums.IsValidStatus(c.Status) {
return fmt.Errorf("无效的认证状态: %s", c.Status)
}
// 状态相关验证
switch c.Status {
case enums.StatusEnterpriseVerified:
if c.AuthFlowID == "" {
return errors.New("企业认证状态下必须有认证流程ID")
}
case enums.StatusContractApplied:
if c.AuthFlowID == "" {
return errors.New("合同申请状态下必须有企业认证流程ID")
}
case enums.StatusContractSigned:
if c.ContractFileID == "" || c.EsignFlowID == "" {
return errors.New("合同签署状态下必须有完整的合同信息")
}
}
return false, "不支持的状态转换"
// 失败状态验证
if enums.IsFailureStatus(c.Status) {
if c.FailureReason == "" {
return errors.New("失败状态下必须有失败原因")
}
if !enums.IsValidFailureReason(c.FailureReason) {
return fmt.Errorf("无效的失败原因: %s", c.FailureReason)
}
}
return nil
}
// validateActorPermission 验证操作者权限
func (c *Certification) validateActorPermission(targetStatus enums.CertificationStatus, actor enums.ActorType) bool {
// 定义状态转换的权限规则
permissions := map[enums.CertificationStatus][]enums.ActorType{
enums.StatusInfoSubmitted: {enums.ActorTypeUser},
enums.StatusEnterpriseVerified: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusInfoRejected: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusContractApplied: {enums.ActorTypeUser},
enums.StatusContractSigned: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusContractRejected: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
enums.StatusContractExpired: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
}
allowedActors, exists := permissions[targetStatus]
if !exists {
return false
}
for _, allowedActor := range allowedActors {
if actor == allowedActor {
return true
}
}
return false
}
// validateBusinessRules 验证业务规则
func (c *Certification) validateBusinessRules(targetStatus enums.CertificationStatus, actor enums.ActorType) error {
// 用户操作验证
if actor == enums.ActorTypeUser {
switch targetStatus {
case enums.StatusInfoSubmitted:
// 用户提交企业信息时的验证
if c.Status != enums.StatusPending && c.Status != enums.StatusInfoRejected {
return fmt.Errorf("当前状态 %s 不允许提交企业信息", enums.GetStatusName(c.Status))
}
case enums.StatusContractApplied:
// 用户申请合同时的验证
if c.Status != enums.StatusEnterpriseVerified {
return fmt.Errorf("必须先完成企业认证才能申请合同")
}
if c.AuthFlowID == "" {
return errors.New("缺少企业认证流程ID")
}
}
}
// e签宝回调验证
if actor == enums.ActorTypeEsign {
switch targetStatus {
case enums.StatusEnterpriseVerified, enums.StatusInfoRejected:
if c.Status != enums.StatusInfoSubmitted {
return fmt.Errorf("当前状态 %s 不允许处理企业认证回调", enums.GetStatusName(c.Status))
}
case enums.StatusContractSigned, enums.StatusContractRejected, enums.StatusContractExpired:
if c.Status != enums.StatusContractApplied {
return fmt.Errorf("当前状态 %s 不允许处理合同签署回调", enums.GetStatusName(c.Status))
}
}
}
return nil
}
// ================ 辅助方法 ================
// updateTimestampByStatus 根据状态更新对应的时间戳
func (c *Certification) updateTimestampByStatus(status enums.CertificationStatus) {
now := time.Now()
switch status {
case enums.StatusInfoSubmitted:
c.InfoSubmittedAt = &now
case enums.StatusEnterpriseVerified:
c.EnterpriseVerifiedAt = &now
case enums.StatusContractApplied:
c.ContractAppliedAt = &now
case enums.StatusContractSigned:
c.ContractSignedAt = &now
}
}
// updateTransitionAudit 更新状态转换审计信息
func (c *Certification) updateTransitionAudit(actor enums.ActorType, actorID string) {
now := time.Now()
c.LastTransitionAt = &now
c.LastTransitionBy = actor
c.LastTransitionActor = actorID
}
// setFailureInfo 设置失败信息
func (c *Certification) setFailureInfo(reason enums.FailureReason, message string) {
c.FailureReason = reason
c.FailureMessage = message
}
// clearFailureInfo 清除失败信息
func (c *Certification) clearFailureInfo() {
c.FailureReason = ""
c.FailureMessage = ""
}
// ================ 领域事件管理 ================
// addDomainEvent 添加领域事件
func (c *Certification) addDomainEvent(event interface{}) {
if c.domainEvents == nil {
c.domainEvents = make([]interface{}, 0)
}
c.domainEvents = append(c.domainEvents, event)
}
// GetDomainEvents 获取领域事件
func (c *Certification) GetDomainEvents() []interface{} {
return c.domainEvents
}
// ClearDomainEvents 清除领域事件
func (c *Certification) ClearDomainEvents() {
c.domainEvents = make([]interface{}, 0)
}
// ================ 领域事件定义 ================
// CertificationCreatedEvent 认证创建事件
type CertificationCreatedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
CreatedAt time.Time `json:"created_at"`
}
// CertificationStatusChangedEvent 认证状态变更事件
type CertificationStatusChangedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
FromStatus enums.CertificationStatus `json:"from_status"`
ToStatus enums.CertificationStatus `json:"to_status"`
Actor enums.ActorType `json:"actor"`
ActorID string `json:"actor_id"`
Reason string `json:"reason"`
TransitionedAt time.Time `json:"transitioned_at"`
}
// EnterpriseInfoSubmittedEvent 企业信息提交事件
type EnterpriseInfoSubmittedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info"`
SubmittedAt time.Time `json:"submitted_at"`
}
// EnterpriseVerificationSuccessEvent 企业认证成功事件
type EnterpriseVerificationSuccessEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
AuthFlowID string `json:"auth_flow_id"`
VerifiedAt time.Time `json:"verified_at"`
}
// EnterpriseVerificationFailedEvent 企业认证失败事件
type EnterpriseVerificationFailedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
AuthFlowID string `json:"auth_flow_id"`
FailureReason enums.FailureReason `json:"failure_reason"`
FailureMessage string `json:"failure_message"`
FailedAt time.Time `json:"failed_at"`
}
// ContractAppliedEvent 合同申请事件
type ContractAppliedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
AppliedAt time.Time `json:"applied_at"`
}
// ContractSignedEvent 合同签署成功事件
type ContractSignedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
ContractURL string `json:"contract_url"`
SignedAt time.Time `json:"signed_at"`
}
// ContractSignFailedEvent 合同签署失败事件
type ContractSignFailedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
FailureReason enums.FailureReason `json:"failure_reason"`
FailureMessage string `json:"failure_message"`
FailedAt time.Time `json:"failed_at"`
}
// CertificationCompletedEvent 认证完成事件
type CertificationCompletedEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
CompletedAt time.Time `json:"completed_at"`
}
// CertificationRetryEvent 认证重试事件
type CertificationRetryEvent struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
FromStatus enums.CertificationStatus `json:"from_status"`
ToStatus enums.CertificationStatus `json:"to_status"`
RetryCount int `json:"retry_count"`
RetriedAt time.Time `json:"retried_at"`
}

View File

@@ -9,8 +9,8 @@ import (
// EnterpriseInfoSubmitRecord 企业信息提交记录
type EnterpriseInfoSubmitRecord struct {
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
// 企业信息
CompanyName string `json:"company_name" gorm:"type:varchar(200);not null"`
@@ -19,11 +19,11 @@ type EnterpriseInfoSubmitRecord struct {
LegalPersonID string `json:"legal_person_id" gorm:"type:varchar(50);not null"`
LegalPersonPhone string `json:"legal_person_phone" gorm:"type:varchar(50);not null"`
// 提交状态
Status string `json:"status" gorm:"type:varchar(20);not null;default:'submitted'"` // submitted, verified, failed
SubmitAt time.Time `json:"submit_at" gorm:"not null"`
VerifiedAt *time.Time `json:"verified_at"`
FailedAt *time.Time `json:"failed_at"`
FailureReason string `json:"failure_reason" gorm:"type:text"`
Status string `json:"status" gorm:"type:varchar(20);not null;default:'submitted'"` // submitted, verified, failed
SubmitAt time.Time `json:"submit_at" gorm:"not null"`
VerifiedAt *time.Time `json:"verified_at"`
FailedAt *time.Time `json:"failed_at"`
FailureReason string `json:"failure_reason" gorm:"type:text"`
// 系统字段
CreatedAt time.Time `json:"created_at" gorm:"not null"`

View File

@@ -0,0 +1,516 @@
package value_objects
import (
"errors"
"fmt"
"regexp"
"strings"
"time"
)
// ContractInfo 合同信息值对象
// 封装电子合同相关的核心信息,包含合同状态和签署流程管理
type ContractInfo struct {
// 合同基本信息
ContractFileID string `json:"contract_file_id"` // 合同文件ID
EsignFlowID string `json:"esign_flow_id"` // e签宝签署流程ID
ContractURL string `json:"contract_url"` // 合同文件访问链接
ContractSignURL string `json:"contract_sign_url"` // 合同签署链接
// 合同元数据
ContractTitle string `json:"contract_title"` // 合同标题
ContractVersion string `json:"contract_version"` // 合同版本
TemplateID string `json:"template_id"` // 模板ID
// 签署相关信息
SignerAccount string `json:"signer_account"` // 签署人账号
SignerName string `json:"signer_name"` // 签署人姓名
TransactorPhone string `json:"transactor_phone"` // 经办人手机号
TransactorName string `json:"transactor_name"` // 经办人姓名
TransactorIDCardNum string `json:"transactor_id_card_num"` // 经办人身份证号
// 时间信息
GeneratedAt *time.Time `json:"generated_at,omitempty"` // 合同生成时间
SignFlowCreatedAt *time.Time `json:"sign_flow_created_at,omitempty"` // 签署流程创建时间
SignedAt *time.Time `json:"signed_at,omitempty"` // 签署完成时间
ExpiresAt *time.Time `json:"expires_at,omitempty"` // 签署链接过期时间
// 状态信息
Status string `json:"status"` // 合同状态
SignProgress int `json:"sign_progress"` // 签署进度
// 附加信息
Metadata map[string]interface{} `json:"metadata,omitempty"` // 元数据
}
// ContractStatus 合同状态常量
const (
ContractStatusDraft = "draft" // 草稿
ContractStatusGenerated = "generated" // 已生成
ContractStatusSigning = "signing" // 签署中
ContractStatusSigned = "signed" // 已签署
ContractStatusExpired = "expired" // 已过期
ContractStatusRejected = "rejected" // 被拒绝
ContractStatusCancelled = "cancelled" // 已取消
)
// NewContractInfo 创建合同信息值对象
func NewContractInfo(contractFileID, esignFlowID, contractURL, contractSignURL string) (*ContractInfo, error) {
info := &ContractInfo{
ContractFileID: strings.TrimSpace(contractFileID),
EsignFlowID: strings.TrimSpace(esignFlowID),
ContractURL: strings.TrimSpace(contractURL),
ContractSignURL: strings.TrimSpace(contractSignURL),
Status: ContractStatusGenerated,
SignProgress: 0,
Metadata: make(map[string]interface{}),
}
if err := info.Validate(); err != nil {
return nil, fmt.Errorf("合同信息验证失败: %w", err)
}
return info, nil
}
// Validate 验证合同信息的完整性和格式
func (c *ContractInfo) Validate() error {
if err := c.validateContractFileID(); err != nil {
return err
}
if err := c.validateEsignFlowID(); err != nil {
return err
}
if err := c.validateContractURL(); err != nil {
return err
}
if err := c.validateContractSignURL(); err != nil {
return err
}
if err := c.validateSignerInfo(); err != nil {
return err
}
if err := c.validateStatus(); err != nil {
return err
}
return nil
}
// validateContractFileID 验证合同文件ID
func (c *ContractInfo) validateContractFileID() error {
if c.ContractFileID == "" {
return errors.New("合同文件ID不能为空")
}
// 简单的格式验证
if len(c.ContractFileID) < 10 {
return errors.New("合同文件ID格式不正确")
}
return nil
}
// validateEsignFlowID 验证e签宝流程ID
func (c *ContractInfo) validateEsignFlowID() error {
if c.EsignFlowID == "" {
return errors.New("e签宝流程ID不能为空")
}
// 简单的格式验证
if len(c.EsignFlowID) < 10 {
return errors.New("e签宝流程ID格式不正确")
}
return nil
}
// validateContractURL 验证合同访问链接
func (c *ContractInfo) validateContractURL() error {
if c.ContractURL == "" {
return errors.New("合同访问链接不能为空")
}
// URL格式验证
urlPattern := `^https?://.*`
matched, err := regexp.MatchString(urlPattern, c.ContractURL)
if err != nil {
return fmt.Errorf("合同访问链接格式验证错误: %w", err)
}
if !matched {
return errors.New("合同访问链接格式不正确必须以http://或https://开头")
}
return nil
}
// validateContractSignURL 验证合同签署链接
func (c *ContractInfo) validateContractSignURL() error {
if c.ContractSignURL == "" {
return errors.New("合同签署链接不能为空")
}
// URL格式验证
urlPattern := `^https?://.*`
matched, err := regexp.MatchString(urlPattern, c.ContractSignURL)
if err != nil {
return fmt.Errorf("合同签署链接格式验证错误: %w", err)
}
if !matched {
return errors.New("合同签署链接格式不正确必须以http://或https://开头")
}
return nil
}
// validateSignerInfo 验证签署人信息
func (c *ContractInfo) validateSignerInfo() error {
// 如果有签署人信息,进行验证
if c.SignerAccount != "" || c.SignerName != "" || c.TransactorPhone != "" {
if c.SignerAccount == "" {
return errors.New("签署人账号不能为空")
}
if c.SignerName == "" {
return errors.New("签署人姓名不能为空")
}
if c.TransactorPhone != "" {
// 手机号格式验证
phonePattern := `^1[3-9]\d{9}$`
matched, err := regexp.MatchString(phonePattern, c.TransactorPhone)
if err != nil {
return fmt.Errorf("经办人手机号格式验证错误: %w", err)
}
if !matched {
return errors.New("经办人手机号格式不正确")
}
}
if c.TransactorIDCardNum != "" {
// 身份证号格式验证
idPattern := `^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$`
matched, err := regexp.MatchString(idPattern, c.TransactorIDCardNum)
if err != nil {
return fmt.Errorf("经办人身份证号格式验证错误: %w", err)
}
if !matched {
return errors.New("经办人身份证号格式不正确")
}
}
}
return nil
}
// validateStatus 验证合同状态
func (c *ContractInfo) validateStatus() error {
validStatuses := []string{
ContractStatusDraft,
ContractStatusGenerated,
ContractStatusSigning,
ContractStatusSigned,
ContractStatusExpired,
ContractStatusRejected,
ContractStatusCancelled,
}
for _, status := range validStatuses {
if c.Status == status {
return nil
}
}
return fmt.Errorf("无效的合同状态: %s", c.Status)
}
// SetSignerInfo 设置签署人信息
func (c *ContractInfo) SetSignerInfo(signerAccount, signerName, transactorPhone, transactorName, transactorIDCardNum string) error {
c.SignerAccount = strings.TrimSpace(signerAccount)
c.SignerName = strings.TrimSpace(signerName)
c.TransactorPhone = strings.TrimSpace(transactorPhone)
c.TransactorName = strings.TrimSpace(transactorName)
c.TransactorIDCardNum = strings.TrimSpace(transactorIDCardNum)
return c.validateSignerInfo()
}
// UpdateStatus 更新合同状态
func (c *ContractInfo) UpdateStatus(status string) error {
oldStatus := c.Status
c.Status = status
if err := c.validateStatus(); err != nil {
c.Status = oldStatus // 回滚
return err
}
// 根据状态更新进度
c.updateProgressByStatus()
return nil
}
// updateProgressByStatus 根据状态更新进度
func (c *ContractInfo) updateProgressByStatus() {
progressMap := map[string]int{
ContractStatusDraft: 0,
ContractStatusGenerated: 25,
ContractStatusSigning: 50,
ContractStatusSigned: 100,
ContractStatusExpired: 50,
ContractStatusRejected: 50,
ContractStatusCancelled: 0,
}
if progress, exists := progressMap[c.Status]; exists {
c.SignProgress = progress
}
}
// MarkAsSigning 标记为签署中
func (c *ContractInfo) MarkAsSigning() error {
c.Status = ContractStatusSigning
c.SignProgress = 50
now := time.Now()
c.SignFlowCreatedAt = &now
return nil
}
// MarkAsSigned 标记为已签署
func (c *ContractInfo) MarkAsSigned() error {
c.Status = ContractStatusSigned
c.SignProgress = 100
now := time.Now()
c.SignedAt = &now
return nil
}
// MarkAsExpired 标记为已过期
func (c *ContractInfo) MarkAsExpired() error {
c.Status = ContractStatusExpired
now := time.Now()
c.ExpiresAt = &now
return nil
}
// MarkAsRejected 标记为被拒绝
func (c *ContractInfo) MarkAsRejected() error {
c.Status = ContractStatusRejected
c.SignProgress = 50
return nil
}
// IsExpired 检查合同是否已过期
func (c *ContractInfo) IsExpired() bool {
if c.ExpiresAt == nil {
return false
}
return time.Now().After(*c.ExpiresAt)
}
// IsSigned 检查合同是否已签署
func (c *ContractInfo) IsSigned() bool {
return c.Status == ContractStatusSigned
}
// CanSign 检查是否可以签署
func (c *ContractInfo) CanSign() bool {
return c.Status == ContractStatusGenerated || c.Status == ContractStatusSigning
}
// GetStatusName 获取状态的中文名称
func (c *ContractInfo) GetStatusName() string {
statusNames := map[string]string{
ContractStatusDraft: "草稿",
ContractStatusGenerated: "已生成",
ContractStatusSigning: "签署中",
ContractStatusSigned: "已签署",
ContractStatusExpired: "已过期",
ContractStatusRejected: "被拒绝",
ContractStatusCancelled: "已取消",
}
if name, exists := statusNames[c.Status]; exists {
return name
}
return c.Status
}
// GetDisplayTitle 获取显示用的合同标题
func (c *ContractInfo) GetDisplayTitle() string {
if c.ContractTitle != "" {
return c.ContractTitle
}
return "企业认证服务合同"
}
// GetMaskedSignerAccount 获取脱敏的签署人账号
func (c *ContractInfo) GetMaskedSignerAccount() string {
if len(c.SignerAccount) <= 6 {
return c.SignerAccount
}
// 保留前3位和后3位中间用*替代
return c.SignerAccount[:3] + "***" + c.SignerAccount[len(c.SignerAccount)-3:]
}
// GetMaskedTransactorPhone 获取脱敏的经办人手机号
func (c *ContractInfo) GetMaskedTransactorPhone() string {
if len(c.TransactorPhone) != 11 {
return c.TransactorPhone
}
// 保留前3位和后4位中间用*替代
return c.TransactorPhone[:3] + "****" + c.TransactorPhone[7:]
}
// GetMaskedTransactorIDCardNum 获取脱敏的经办人身份证号
func (c *ContractInfo) GetMaskedTransactorIDCardNum() string {
if len(c.TransactorIDCardNum) != 18 {
return c.TransactorIDCardNum
}
// 保留前6位和后4位中间用*替代
return c.TransactorIDCardNum[:6] + "********" + c.TransactorIDCardNum[14:]
}
// AddMetadata 添加元数据
func (c *ContractInfo) AddMetadata(key string, value interface{}) {
if c.Metadata == nil {
c.Metadata = make(map[string]interface{})
}
c.Metadata[key] = value
}
// GetMetadata 获取元数据
func (c *ContractInfo) GetMetadata(key string) (interface{}, bool) {
if c.Metadata == nil {
return nil, false
}
value, exists := c.Metadata[key]
return value, exists
}
// Equals 比较两个合同信息是否相等
func (c *ContractInfo) Equals(other *ContractInfo) bool {
if other == nil {
return false
}
return c.ContractFileID == other.ContractFileID &&
c.EsignFlowID == other.EsignFlowID &&
c.Status == other.Status
}
// Clone 创建合同信息的副本
func (c *ContractInfo) Clone() *ContractInfo {
cloned := &ContractInfo{
ContractFileID: c.ContractFileID,
EsignFlowID: c.EsignFlowID,
ContractURL: c.ContractURL,
ContractSignURL: c.ContractSignURL,
ContractTitle: c.ContractTitle,
ContractVersion: c.ContractVersion,
TemplateID: c.TemplateID,
SignerAccount: c.SignerAccount,
SignerName: c.SignerName,
TransactorPhone: c.TransactorPhone,
TransactorName: c.TransactorName,
TransactorIDCardNum: c.TransactorIDCardNum,
Status: c.Status,
SignProgress: c.SignProgress,
}
// 复制时间字段
if c.GeneratedAt != nil {
generatedAt := *c.GeneratedAt
cloned.GeneratedAt = &generatedAt
}
if c.SignFlowCreatedAt != nil {
signFlowCreatedAt := *c.SignFlowCreatedAt
cloned.SignFlowCreatedAt = &signFlowCreatedAt
}
if c.SignedAt != nil {
signedAt := *c.SignedAt
cloned.SignedAt = &signedAt
}
if c.ExpiresAt != nil {
expiresAt := *c.ExpiresAt
cloned.ExpiresAt = &expiresAt
}
// 复制元数据
if c.Metadata != nil {
cloned.Metadata = make(map[string]interface{})
for k, v := range c.Metadata {
cloned.Metadata[k] = v
}
}
return cloned
}
// String 返回合同信息的字符串表示
func (c *ContractInfo) String() string {
return fmt.Sprintf("合同信息[文件ID:%s, 流程ID:%s, 状态:%s, 进度:%d%%]",
c.ContractFileID,
c.EsignFlowID,
c.GetStatusName(),
c.SignProgress)
}
// ToMap 转换为map格式用于序列化
func (c *ContractInfo) ToMap() map[string]interface{} {
result := map[string]interface{}{
"contract_file_id": c.ContractFileID,
"esign_flow_id": c.EsignFlowID,
"contract_url": c.ContractURL,
"contract_sign_url": c.ContractSignURL,
"contract_title": c.ContractTitle,
"contract_version": c.ContractVersion,
"template_id": c.TemplateID,
"signer_account": c.SignerAccount,
"signer_name": c.SignerName,
"transactor_phone": c.TransactorPhone,
"transactor_name": c.TransactorName,
"transactor_id_card_num": c.TransactorIDCardNum,
"status": c.Status,
"sign_progress": c.SignProgress,
}
// 添加时间字段
if c.GeneratedAt != nil {
result["generated_at"] = c.GeneratedAt
}
if c.SignFlowCreatedAt != nil {
result["sign_flow_created_at"] = c.SignFlowCreatedAt
}
if c.SignedAt != nil {
result["signed_at"] = c.SignedAt
}
if c.ExpiresAt != nil {
result["expires_at"] = c.ExpiresAt
}
// 添加元数据
if c.Metadata != nil {
result["metadata"] = c.Metadata
}
return result
}

View File

@@ -0,0 +1,352 @@
package value_objects
import (
"errors"
"fmt"
"regexp"
"strings"
)
// EnterpriseInfo 企业信息值对象
// 封装企业认证所需的核心信息,包含完整的业务规则验证
type EnterpriseInfo struct {
// 企业基本信息
CompanyName string `json:"company_name"` // 企业名称
UnifiedSocialCode string `json:"unified_social_code"` // 统一社会信用代码
// 法定代表人信息
LegalPersonName string `json:"legal_person_name"` // 法定代表人姓名
LegalPersonID string `json:"legal_person_id"` // 法定代表人身份证号
LegalPersonPhone string `json:"legal_person_phone"` // 法定代表人手机号
// 企业详细信息
RegisteredAddress string `json:"registered_address"` // 注册地址
BusinessScope string `json:"business_scope"` // 经营范围
RegisteredCapital string `json:"registered_capital"` // 注册资本
EstablishmentDate string `json:"establishment_date"` // 成立日期
}
// NewEnterpriseInfo 创建企业信息值对象
func NewEnterpriseInfo(companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone string) (*EnterpriseInfo, error) {
info := &EnterpriseInfo{
CompanyName: strings.TrimSpace(companyName),
UnifiedSocialCode: strings.TrimSpace(unifiedSocialCode),
LegalPersonName: strings.TrimSpace(legalPersonName),
LegalPersonID: strings.TrimSpace(legalPersonID),
LegalPersonPhone: strings.TrimSpace(legalPersonPhone),
}
if err := info.Validate(); err != nil {
return nil, fmt.Errorf("企业信息验证失败: %w", err)
}
return info, nil
}
// Validate 验证企业信息的完整性和格式
func (e *EnterpriseInfo) Validate() error {
if err := e.validateCompanyName(); err != nil {
return err
}
if err := e.validateUnifiedSocialCode(); err != nil {
return err
}
if err := e.validateLegalPersonName(); err != nil {
return err
}
if err := e.validateLegalPersonID(); err != nil {
return err
}
if err := e.validateLegalPersonPhone(); err != nil {
return err
}
return nil
}
// validateCompanyName 验证企业名称
func (e *EnterpriseInfo) validateCompanyName() error {
if e.CompanyName == "" {
return errors.New("企业名称不能为空")
}
if len(e.CompanyName) < 2 {
return errors.New("企业名称长度不能少于2个字符")
}
if len(e.CompanyName) > 100 {
return errors.New("企业名称长度不能超过100个字符")
}
// 检查是否包含非法字符
invalidChars := []string{"`", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "+", "=", "{", "}", "[", "]", "\\", "|", ";", ":", "'", "\"", "<", ">", ",", ".", "?", "/"}
for _, char := range invalidChars {
if strings.Contains(e.CompanyName, char) {
return fmt.Errorf("企业名称不能包含特殊字符: %s", char)
}
}
return nil
}
// validateUnifiedSocialCode 验证统一社会信用代码
func (e *EnterpriseInfo) validateUnifiedSocialCode() error {
if e.UnifiedSocialCode == "" {
return errors.New("统一社会信用代码不能为空")
}
// 统一社会信用代码格式验证18位数字和字母
pattern := `^[0-9A-HJ-NPQRTUWXY]{2}[0-9]{6}[0-9A-HJ-NPQRTUWXY]{10}$`
matched, err := regexp.MatchString(pattern, e.UnifiedSocialCode)
if err != nil {
return fmt.Errorf("统一社会信用代码格式验证错误: %w", err)
}
if !matched {
return errors.New("统一社会信用代码格式不正确应为18位数字和字母组合")
}
return nil
}
// validateLegalPersonName 验证法定代表人姓名
func (e *EnterpriseInfo) validateLegalPersonName() error {
if e.LegalPersonName == "" {
return errors.New("法定代表人姓名不能为空")
}
if len(e.LegalPersonName) < 2 {
return errors.New("法定代表人姓名长度不能少于2个字符")
}
if len(e.LegalPersonName) > 50 {
return errors.New("法定代表人姓名长度不能超过50个字符")
}
// 中文姓名格式验证
pattern := `^[\u4e00-\u9fa5·]+$`
matched, err := regexp.MatchString(pattern, e.LegalPersonName)
if err != nil {
return fmt.Errorf("法定代表人姓名格式验证错误: %w", err)
}
if !matched {
return errors.New("法定代表人姓名只能包含中文字符和间隔号")
}
return nil
}
// validateLegalPersonID 验证法定代表人身份证号
func (e *EnterpriseInfo) validateLegalPersonID() error {
if e.LegalPersonID == "" {
return errors.New("法定代表人身份证号不能为空")
}
// 身份证号格式验证18位
if len(e.LegalPersonID) != 18 {
return errors.New("身份证号必须为18位")
}
pattern := `^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$`
matched, err := regexp.MatchString(pattern, e.LegalPersonID)
if err != nil {
return fmt.Errorf("身份证号格式验证错误: %w", err)
}
if !matched {
return errors.New("身份证号格式不正确")
}
// 身份证号校验码验证
if !e.validateIDChecksum() {
return errors.New("身份证号校验码错误")
}
return nil
}
// validateIDChecksum 验证身份证号校验码
func (e *EnterpriseInfo) validateIDChecksum() bool {
if len(e.LegalPersonID) != 18 {
return false
}
// 加权因子
weights := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
// 校验码对应表
checkCodes := []string{"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"}
sum := 0
for i := 0; i < 17; i++ {
digit := int(e.LegalPersonID[i] - '0')
sum += digit * weights[i]
}
checkCodeIndex := sum % 11
expectedCheckCode := checkCodes[checkCodeIndex]
actualCheckCode := strings.ToUpper(string(e.LegalPersonID[17]))
return expectedCheckCode == actualCheckCode
}
// validateLegalPersonPhone 验证法定代表人手机号
func (e *EnterpriseInfo) validateLegalPersonPhone() error {
if e.LegalPersonPhone == "" {
return errors.New("法定代表人手机号不能为空")
}
// 手机号格式验证11位数字1开头
pattern := `^1[3-9]\d{9}$`
matched, err := regexp.MatchString(pattern, e.LegalPersonPhone)
if err != nil {
return fmt.Errorf("手机号格式验证错误: %w", err)
}
if !matched {
return errors.New("手机号格式不正确应为11位数字且以1开头")
}
return nil
}
// IsComplete 检查企业信息是否完整
func (e *EnterpriseInfo) IsComplete() bool {
return e.CompanyName != "" &&
e.UnifiedSocialCode != "" &&
e.LegalPersonName != "" &&
e.LegalPersonID != "" &&
e.LegalPersonPhone != ""
}
// IsDetailComplete 检查企业详细信息是否完整
func (e *EnterpriseInfo) IsDetailComplete() bool {
return e.IsComplete() &&
e.RegisteredAddress != "" &&
e.BusinessScope != "" &&
e.RegisteredCapital != "" &&
e.EstablishmentDate != ""
}
// GetDisplayName 获取显示用的企业名称
func (e *EnterpriseInfo) GetDisplayName() string {
if e.CompanyName == "" {
return "未知企业"
}
return e.CompanyName
}
// GetMaskedUnifiedSocialCode 获取脱敏的统一社会信用代码
func (e *EnterpriseInfo) GetMaskedUnifiedSocialCode() string {
if len(e.UnifiedSocialCode) != 18 {
return e.UnifiedSocialCode
}
// 保留前6位和后4位中间用*替代
return e.UnifiedSocialCode[:6] + "********" + e.UnifiedSocialCode[14:]
}
// GetMaskedLegalPersonID 获取脱敏的法定代表人身份证号
func (e *EnterpriseInfo) GetMaskedLegalPersonID() string {
if len(e.LegalPersonID) != 18 {
return e.LegalPersonID
}
// 保留前6位和后4位中间用*替代
return e.LegalPersonID[:6] + "********" + e.LegalPersonID[14:]
}
// GetMaskedLegalPersonPhone 获取脱敏的法定代表人手机号
func (e *EnterpriseInfo) GetMaskedLegalPersonPhone() string {
if len(e.LegalPersonPhone) != 11 {
return e.LegalPersonPhone
}
// 保留前3位和后4位中间用*替代
return e.LegalPersonPhone[:3] + "****" + e.LegalPersonPhone[7:]
}
// Equals 比较两个企业信息是否相等
func (e *EnterpriseInfo) Equals(other *EnterpriseInfo) bool {
if other == nil {
return false
}
return e.CompanyName == other.CompanyName &&
e.UnifiedSocialCode == other.UnifiedSocialCode &&
e.LegalPersonName == other.LegalPersonName &&
e.LegalPersonID == other.LegalPersonID &&
e.LegalPersonPhone == other.LegalPersonPhone
}
// Clone 创建企业信息的副本
func (e *EnterpriseInfo) Clone() *EnterpriseInfo {
return &EnterpriseInfo{
CompanyName: e.CompanyName,
UnifiedSocialCode: e.UnifiedSocialCode,
LegalPersonName: e.LegalPersonName,
LegalPersonID: e.LegalPersonID,
LegalPersonPhone: e.LegalPersonPhone,
RegisteredAddress: e.RegisteredAddress,
BusinessScope: e.BusinessScope,
RegisteredCapital: e.RegisteredCapital,
EstablishmentDate: e.EstablishmentDate,
}
}
// String 返回企业信息的字符串表示
func (e *EnterpriseInfo) String() string {
return fmt.Sprintf("企业信息[名称:%s, 信用代码:%s, 法人:%s]",
e.CompanyName,
e.GetMaskedUnifiedSocialCode(),
e.LegalPersonName)
}
// ToMap 转换为map格式用于序列化
func (e *EnterpriseInfo) ToMap() map[string]interface{} {
return map[string]interface{}{
"company_name": e.CompanyName,
"unified_social_code": e.UnifiedSocialCode,
"legal_person_name": e.LegalPersonName,
"legal_person_id": e.LegalPersonID,
"legal_person_phone": e.LegalPersonPhone,
"registered_address": e.RegisteredAddress,
"business_scope": e.BusinessScope,
"registered_capital": e.RegisteredCapital,
"establishment_date": e.EstablishmentDate,
}
}
// FromMap 从map格式创建企业信息用于反序列化
func FromMap(data map[string]interface{}) (*EnterpriseInfo, error) {
getString := func(key string) string {
if val, exists := data[key]; exists {
if str, ok := val.(string); ok {
return strings.TrimSpace(str)
}
}
return ""
}
info := &EnterpriseInfo{
CompanyName: getString("company_name"),
UnifiedSocialCode: getString("unified_social_code"),
LegalPersonName: getString("legal_person_name"),
LegalPersonID: getString("legal_person_id"),
LegalPersonPhone: getString("legal_person_phone"),
RegisteredAddress: getString("registered_address"),
BusinessScope: getString("business_scope"),
RegisteredCapital: getString("registered_capital"),
EstablishmentDate: getString("establishment_date"),
}
if err := info.Validate(); err != nil {
return nil, fmt.Errorf("从Map创建企业信息失败: %w", err)
}
return info, nil
}

View File

@@ -0,0 +1,215 @@
package enums
// ActorType 操作者类型枚举
type ActorType string
const (
// === 操作者类型 ===
ActorTypeUser ActorType = "user" // 用户操作
ActorTypeSystem ActorType = "system" // 系统操作
ActorTypeAdmin ActorType = "admin" // 管理员操作
ActorTypeEsign ActorType = "esign" // e签宝回调操作
)
// AllActorTypes 所有操作者类型列表
var AllActorTypes = []ActorType{
ActorTypeUser,
ActorTypeSystem,
ActorTypeAdmin,
ActorTypeEsign,
}
// IsValidActorType 检查操作者类型是否有效
func IsValidActorType(actorType ActorType) bool {
for _, validType := range AllActorTypes {
if actorType == validType {
return true
}
}
return false
}
// GetActorTypeName 获取操作者类型的中文名称
func GetActorTypeName(actorType ActorType) string {
typeNames := map[ActorType]string{
ActorTypeUser: "用户",
ActorTypeSystem: "系统",
ActorTypeAdmin: "管理员",
ActorTypeEsign: "e签宝",
}
if name, exists := typeNames[actorType]; exists {
return name
}
return string(actorType)
}
// GetActorTypeDescription 获取操作者类型描述
func GetActorTypeDescription(actorType ActorType) string {
descriptions := map[ActorType]string{
ActorTypeUser: "用户主动操作",
ActorTypeSystem: "系统自动操作",
ActorTypeAdmin: "管理员操作",
ActorTypeEsign: "e签宝回调操作",
}
if desc, exists := descriptions[actorType]; exists {
return desc
}
return string(actorType)
}
// IsAutomatedActor 判断是否为自动化操作者
func IsAutomatedActor(actorType ActorType) bool {
automatedActors := map[ActorType]bool{
ActorTypeUser: false, // 用户手动操作
ActorTypeSystem: true, // 系统自动操作
ActorTypeAdmin: false, // 管理员手动操作
ActorTypeEsign: true, // e签宝自动回调
}
if automated, exists := automatedActors[actorType]; exists {
return automated
}
return false
}
// IsHumanActor 判断是否为人工操作者
func IsHumanActor(actorType ActorType) bool {
return !IsAutomatedActor(actorType)
}
// GetActorTypePriority 获取操作者类型优先级(用于日志排序等)
func GetActorTypePriority(actorType ActorType) int {
priorities := map[ActorType]int{
ActorTypeUser: 1, // 用户操作最重要
ActorTypeAdmin: 2, // 管理员操作次之
ActorTypeEsign: 3, // e签宝回调
ActorTypeSystem: 4, // 系统操作最后
}
if priority, exists := priorities[actorType]; exists {
return priority
}
return 999
}
// GetPermissionLevel 获取权限级别
func GetPermissionLevel(actorType ActorType) int {
levels := map[ActorType]int{
ActorTypeUser: 1, // 普通用户权限
ActorTypeSystem: 2, // 系统权限
ActorTypeEsign: 2, // e签宝权限与系统同级
ActorTypeAdmin: 3, // 管理员最高权限
}
if level, exists := levels[actorType]; exists {
return level
}
return 0
}
// CanPerformAction 检查操作者是否可以执行指定操作
func CanPerformAction(actorType ActorType, action string) bool {
permissions := map[ActorType][]string{
ActorTypeUser: {
"submit_enterprise_info", // 提交企业信息
"apply_contract", // 申请合同
"view_certification", // 查看认证信息
},
ActorTypeSystem: {
"auto_transition", // 自动状态转换
"system_maintenance", // 系统维护
"data_cleanup", // 数据清理
},
ActorTypeAdmin: {
"manual_transition", // 手动状态转换
"view_all_certifications", // 查看所有认证
"system_configuration", // 系统配置
"user_management", // 用户管理
},
ActorTypeEsign: {
"callback_notification", // 回调通知
"status_update", // 状态更新
"verification_result", // 验证结果
},
}
if actions, exists := permissions[actorType]; exists {
for _, permittedAction := range actions {
if permittedAction == action {
return true
}
}
}
return false
}
// GetAllowedActions 获取操作者允许执行的所有操作
func GetAllowedActions(actorType ActorType) []string {
permissions := map[ActorType][]string{
ActorTypeUser: {
"submit_enterprise_info",
"apply_contract",
"view_certification",
},
ActorTypeSystem: {
"auto_transition",
"system_maintenance",
"data_cleanup",
},
ActorTypeAdmin: {
"manual_transition",
"view_all_certifications",
"system_configuration",
"user_management",
},
ActorTypeEsign: {
"callback_notification",
"status_update",
"verification_result",
},
}
if actions, exists := permissions[actorType]; exists {
return actions
}
return []string{}
}
// GetActorTypeFromContext 从上下文推断操作者类型
func GetActorTypeFromContext(context map[string]interface{}) ActorType {
// 检查是否为e签宝回调
if _, exists := context["esign_callback"]; exists {
return ActorTypeEsign
}
// 检查是否为管理员操作
if isAdmin, exists := context["is_admin"]; exists && isAdmin.(bool) {
return ActorTypeAdmin
}
// 检查是否为用户操作
if userID, exists := context["user_id"]; exists && userID != nil {
return ActorTypeUser
}
// 默认为系统操作
return ActorTypeSystem
}
// FormatActorInfo 格式化操作者信息
func FormatActorInfo(actorType ActorType, actorID string) string {
switch actorType {
case ActorTypeUser:
return "用户(" + actorID + ")"
case ActorTypeAdmin:
return "管理员(" + actorID + ")"
case ActorTypeSystem:
return "系统"
case ActorTypeEsign:
return "e签宝回调"
default:
return string(actorType) + "(" + actorID + ")"
}
}

View File

@@ -4,23 +4,50 @@ package enums
type CertificationStatus string
const (
// 主流程状态
StatusPending CertificationStatus = "pending" // 待认证
StatusInfoSubmitted CertificationStatus = "info_submitted" // 已提交企业信息
StatusEnterpriseVerified CertificationStatus = "enterprise_verified" // 已企业认证
StatusContractApplied CertificationStatus = "contract_applied" // 已申请签署合同
StatusContractSigned CertificationStatus = "contract_signed" // 已签署合同
StatusCompleted CertificationStatus = "completed" // 认证完成
// === 主流程状态 ===
StatusPending CertificationStatus = "pending" // 待认证
StatusInfoSubmitted CertificationStatus = "info_submitted" // 已提交企业信息
StatusEnterpriseVerified CertificationStatus = "enterprise_verified" // 已企业认证
StatusContractApplied CertificationStatus = "contract_applied" // 已申请签署合同
StatusContractSigned CertificationStatus = "contract_signed" // 已签署合同(认证完成)
// === 失败状态 ===
StatusInfoRejected CertificationStatus = "info_rejected" // 企业信息被拒绝
StatusContractRejected CertificationStatus = "contract_rejected" // 合同被拒签
StatusContractExpired CertificationStatus = "contract_expired" // 合同签署超时
)
// AllStatuses 所有有效状态列表
var AllStatuses = []CertificationStatus{
StatusPending,
StatusInfoSubmitted,
StatusEnterpriseVerified,
StatusContractApplied,
StatusContractSigned,
StatusInfoRejected,
StatusContractRejected,
StatusContractExpired,
}
// MainFlowStatuses 主流程状态列表
var MainFlowStatuses = []CertificationStatus{
StatusPending,
StatusInfoSubmitted,
StatusEnterpriseVerified,
StatusContractApplied,
StatusContractSigned,
}
// FailureStatuses 失败状态列表
var FailureStatuses = []CertificationStatus{
StatusInfoRejected,
StatusContractRejected,
StatusContractExpired,
}
// IsValidStatus 检查状态是否有效
func IsValidStatus(status CertificationStatus) bool {
validStatuses := []CertificationStatus{
StatusPending, StatusInfoSubmitted, StatusEnterpriseVerified,
StatusContractApplied, StatusContractSigned, StatusCompleted,
}
for _, validStatus := range validStatuses {
for _, validStatus := range AllStatuses {
if status == validStatus {
return true
}
@@ -31,12 +58,14 @@ func IsValidStatus(status CertificationStatus) bool {
// GetStatusName 获取状态的中文名称
func GetStatusName(status CertificationStatus) string {
statusNames := map[CertificationStatus]string{
StatusPending: "待认证",
StatusInfoSubmitted: "已提交企业信息",
StatusPending: "待认证",
StatusInfoSubmitted: "已提交企业信息",
StatusEnterpriseVerified: "已企业认证",
StatusContractApplied: "已申请合同",
StatusContractSigned: "已签署合同",
StatusCompleted: "认证完成",
StatusContractApplied: "已申请签署合同",
StatusContractSigned: "认证完成",
StatusInfoRejected: "企业信息被拒绝",
StatusContractRejected: "合同被拒签",
StatusContractExpired: "合同签署超时",
}
if name, exists := statusNames[status]; exists {
@@ -47,32 +76,51 @@ func GetStatusName(status CertificationStatus) string {
// IsFinalStatus 判断是否为最终状态
func IsFinalStatus(status CertificationStatus) bool {
return status == StatusCompleted
return status == StatusContractSigned
}
// IsFailureStatus 判断是否为失败状态
func IsFailureStatus(status CertificationStatus) bool {
for _, failureStatus := range FailureStatuses {
if status == failureStatus {
return true
}
}
return false
}
// IsMainFlowStatus 判断是否为主流程状态
func IsMainFlowStatus(status CertificationStatus) bool {
for _, mainStatus := range MainFlowStatuses {
if status == mainStatus {
return true
}
}
return false
}
// GetStatusCategory 获取状态分类
func GetStatusCategory(status CertificationStatus) string {
switch status {
case StatusPending:
return "initial"
case StatusInfoSubmitted, StatusEnterpriseVerified, StatusContractApplied, StatusContractSigned:
return "processing"
case StatusCompleted:
return "completed"
default:
return "unknown"
if IsMainFlowStatus(status) {
return "主流程"
}
if IsFailureStatus(status) {
return "失败状态"
}
return "未知"
}
// GetStatusPriority 获取状态优先级(用于排序)
func GetStatusPriority(status CertificationStatus) int {
priorities := map[CertificationStatus]int{
StatusPending: 0,
StatusInfoSubmitted: 1,
StatusEnterpriseVerified: 2,
StatusContractApplied: 3,
StatusContractSigned: 4,
StatusCompleted: 5,
StatusPending: 1,
StatusInfoSubmitted: 2,
StatusEnterpriseVerified: 3,
StatusContractApplied: 4,
StatusContractSigned: 5,
StatusInfoRejected: 6,
StatusContractRejected: 7,
StatusContractExpired: 8,
}
if priority, exists := priorities[status]; exists {
@@ -80,3 +128,131 @@ func GetStatusPriority(status CertificationStatus) int {
}
return 999
}
// GetProgressPercentage 获取进度百分比
func GetProgressPercentage(status CertificationStatus) int {
progressMap := map[CertificationStatus]int{
StatusPending: 0,
StatusInfoSubmitted: 25,
StatusEnterpriseVerified: 50,
StatusContractApplied: 75,
StatusContractSigned: 100,
StatusInfoRejected: 25,
StatusContractRejected: 75,
StatusContractExpired: 75,
}
if progress, exists := progressMap[status]; exists {
return progress
}
return 0
}
// IsUserActionRequired 检查是否需要用户操作
func IsUserActionRequired(status CertificationStatus) bool {
userActionRequired := map[CertificationStatus]bool{
StatusPending: true, // 需要提交企业信息
StatusInfoSubmitted: false, // 等待系统验证
StatusEnterpriseVerified: true, // 需要申请合同
StatusContractApplied: true, // 需要签署合同
StatusContractSigned: false, // 已完成
StatusInfoRejected: true, // 需要重新提交
StatusContractRejected: true, // 需要重新申请
StatusContractExpired: true, // 需要重新申请
}
if required, exists := userActionRequired[status]; exists {
return required
}
return false
}
// GetUserActionHint 获取用户操作提示
func GetUserActionHint(status CertificationStatus) string {
hints := map[CertificationStatus]string{
StatusPending: "请提交企业信息",
StatusInfoSubmitted: "系统正在验证企业信息,请稍候",
StatusEnterpriseVerified: "企业认证完成,请申请签署合同",
StatusContractApplied: "请在规定时间内完成合同签署",
StatusContractSigned: "认证已完成",
StatusInfoRejected: "企业信息验证失败,请修正后重新提交",
StatusContractRejected: "合同签署被拒绝,可重新申请",
StatusContractExpired: "合同签署已超时,请重新申请",
}
if hint, exists := hints[status]; exists {
return hint
}
return ""
}
// GetNextValidStatuses 获取当前状态的下一个有效状态列表
func GetNextValidStatuses(currentStatus CertificationStatus) []CertificationStatus {
nextStatusMap := map[CertificationStatus][]CertificationStatus{
StatusPending: {
StatusInfoSubmitted,
},
StatusInfoSubmitted: {
StatusEnterpriseVerified,
StatusInfoRejected,
},
StatusEnterpriseVerified: {
StatusContractApplied,
},
StatusContractApplied: {
StatusContractSigned,
StatusContractRejected,
StatusContractExpired,
},
StatusContractSigned: {
// 最终状态,无后续状态
},
StatusInfoRejected: {
StatusInfoSubmitted, // 可以重新提交
},
StatusContractRejected: {
StatusEnterpriseVerified, // 重置到企业认证状态
},
StatusContractExpired: {
StatusEnterpriseVerified, // 重置到企业认证状态
},
}
if nextStatuses, exists := nextStatusMap[currentStatus]; exists {
return nextStatuses
}
return []CertificationStatus{}
}
// CanTransitionTo 检查是否可以从当前状态转换到目标状态
func CanTransitionTo(currentStatus, targetStatus CertificationStatus) bool {
validNextStatuses := GetNextValidStatuses(currentStatus)
for _, validStatus := range validNextStatuses {
if validStatus == targetStatus {
return true
}
}
return false
}
// GetTransitionReason 获取状态转换的原因描述
func GetTransitionReason(from, to CertificationStatus) string {
transitionReasons := map[string]string{
string(StatusPending) + "->" + string(StatusInfoSubmitted): "用户提交企业信息",
string(StatusInfoSubmitted) + "->" + string(StatusEnterpriseVerified): "e签宝企业认证成功",
string(StatusInfoSubmitted) + "->" + string(StatusInfoRejected): "e签宝企业认证失败",
string(StatusEnterpriseVerified) + "->" + string(StatusContractApplied): "用户申请签署合同",
string(StatusContractApplied) + "->" + string(StatusContractSigned): "e签宝合同签署成功",
string(StatusContractApplied) + "->" + string(StatusContractRejected): "用户拒绝签署合同",
string(StatusContractApplied) + "->" + string(StatusContractExpired): "合同签署超时",
string(StatusInfoRejected) + "->" + string(StatusInfoSubmitted): "用户重新提交企业信息",
string(StatusContractRejected) + "->" + string(StatusEnterpriseVerified): "重置状态,准备重新申请",
string(StatusContractExpired) + "->" + string(StatusEnterpriseVerified): "重置状态,准备重新申请",
}
key := string(from) + "->" + string(to)
if reason, exists := transitionReasons[key]; exists {
return reason
}
return "未知转换"
}

View File

@@ -0,0 +1,270 @@
package enums
// FailureReason 失败原因枚举
type FailureReason string
const (
// === 企业信息验证失败原因 ===
FailureReasonEnterpriseNotExists FailureReason = "enterprise_not_exists" // 企业不存在
FailureReasonEnterpriseInfoMismatch FailureReason = "enterprise_info_mismatch" // 企业信息不匹配
FailureReasonEnterpriseStatusAbnormal FailureReason = "enterprise_status_abnormal" // 企业状态异常
FailureReasonLegalPersonMismatch FailureReason = "legal_person_mismatch" // 法定代表人信息不匹配
FailureReasonEsignVerificationFailed FailureReason = "esign_verification_failed" // e签宝验证失败
FailureReasonInvalidDocument FailureReason = "invalid_document" // 证件信息无效
// === 合同签署失败原因 ===
FailureReasonContractRejectedByUser FailureReason = "contract_rejected_by_user" // 用户拒绝签署
FailureReasonContractExpired FailureReason = "contract_expired" // 合同签署超时
FailureReasonSignProcessFailed FailureReason = "sign_process_failed" // 签署流程失败
FailureReasonContractGenFailed FailureReason = "contract_gen_failed" // 合同生成失败
FailureReasonEsignFlowError FailureReason = "esign_flow_error" // e签宝流程错误
// === 系统错误原因 ===
FailureReasonSystemError FailureReason = "system_error" // 系统错误
FailureReasonNetworkError FailureReason = "network_error" // 网络错误
FailureReasonTimeout FailureReason = "timeout" // 操作超时
FailureReasonUnknownError FailureReason = "unknown_error" // 未知错误
)
// AllFailureReasons 所有失败原因列表
var AllFailureReasons = []FailureReason{
// 企业信息验证失败
FailureReasonEnterpriseNotExists,
FailureReasonEnterpriseInfoMismatch,
FailureReasonEnterpriseStatusAbnormal,
FailureReasonLegalPersonMismatch,
FailureReasonEsignVerificationFailed,
FailureReasonInvalidDocument,
// 合同签署失败
FailureReasonContractRejectedByUser,
FailureReasonContractExpired,
FailureReasonSignProcessFailed,
FailureReasonContractGenFailed,
FailureReasonEsignFlowError,
// 系统错误
FailureReasonSystemError,
FailureReasonNetworkError,
FailureReasonTimeout,
FailureReasonUnknownError,
}
// EnterpriseVerificationFailureReasons 企业验证失败原因列表
var EnterpriseVerificationFailureReasons = []FailureReason{
FailureReasonEnterpriseNotExists,
FailureReasonEnterpriseInfoMismatch,
FailureReasonEnterpriseStatusAbnormal,
FailureReasonLegalPersonMismatch,
FailureReasonEsignVerificationFailed,
FailureReasonInvalidDocument,
}
// ContractSignFailureReasons 合同签署失败原因列表
var ContractSignFailureReasons = []FailureReason{
FailureReasonContractRejectedByUser,
FailureReasonContractExpired,
FailureReasonSignProcessFailed,
FailureReasonContractGenFailed,
FailureReasonEsignFlowError,
}
// SystemErrorReasons 系统错误原因列表
var SystemErrorReasons = []FailureReason{
FailureReasonSystemError,
FailureReasonNetworkError,
FailureReasonTimeout,
FailureReasonUnknownError,
}
// IsValidFailureReason 检查失败原因是否有效
func IsValidFailureReason(reason FailureReason) bool {
for _, validReason := range AllFailureReasons {
if reason == validReason {
return true
}
}
return false
}
// GetFailureReasonName 获取失败原因的中文名称
func GetFailureReasonName(reason FailureReason) string {
reasonNames := map[FailureReason]string{
// 企业信息验证失败
FailureReasonEnterpriseNotExists: "企业不存在",
FailureReasonEnterpriseInfoMismatch: "企业信息不匹配",
FailureReasonEnterpriseStatusAbnormal: "企业状态异常",
FailureReasonLegalPersonMismatch: "法定代表人信息不匹配",
FailureReasonEsignVerificationFailed: "e签宝验证失败",
FailureReasonInvalidDocument: "证件信息无效",
// 合同签署失败
FailureReasonContractRejectedByUser: "用户拒绝签署",
FailureReasonContractExpired: "合同签署超时",
FailureReasonSignProcessFailed: "签署流程失败",
FailureReasonContractGenFailed: "合同生成失败",
FailureReasonEsignFlowError: "e签宝流程错误",
// 系统错误
FailureReasonSystemError: "系统错误",
FailureReasonNetworkError: "网络错误",
FailureReasonTimeout: "操作超时",
FailureReasonUnknownError: "未知错误",
}
if name, exists := reasonNames[reason]; exists {
return name
}
return string(reason)
}
// GetFailureReasonCategory 获取失败原因分类
func GetFailureReasonCategory(reason FailureReason) string {
categories := map[FailureReason]string{
// 企业信息验证失败
FailureReasonEnterpriseNotExists: "企业验证",
FailureReasonEnterpriseInfoMismatch: "企业验证",
FailureReasonEnterpriseStatusAbnormal: "企业验证",
FailureReasonLegalPersonMismatch: "企业验证",
FailureReasonEsignVerificationFailed: "企业验证",
FailureReasonInvalidDocument: "企业验证",
// 合同签署失败
FailureReasonContractRejectedByUser: "合同签署",
FailureReasonContractExpired: "合同签署",
FailureReasonSignProcessFailed: "合同签署",
FailureReasonContractGenFailed: "合同签署",
FailureReasonEsignFlowError: "合同签署",
// 系统错误
FailureReasonSystemError: "系统错误",
FailureReasonNetworkError: "系统错误",
FailureReasonTimeout: "系统错误",
FailureReasonUnknownError: "系统错误",
}
if category, exists := categories[reason]; exists {
return category
}
return "未知"
}
// IsEnterpriseVerificationFailure 判断是否为企业验证失败
func IsEnterpriseVerificationFailure(reason FailureReason) bool {
for _, verifyReason := range EnterpriseVerificationFailureReasons {
if reason == verifyReason {
return true
}
}
return false
}
// IsContractSignFailure 判断是否为合同签署失败
func IsContractSignFailure(reason FailureReason) bool {
for _, signReason := range ContractSignFailureReasons {
if reason == signReason {
return true
}
}
return false
}
// IsSystemError 判断是否为系统错误
func IsSystemError(reason FailureReason) bool {
for _, systemReason := range SystemErrorReasons {
if reason == systemReason {
return true
}
}
return false
}
// GetSuggestedAction 获取建议的后续操作
func GetSuggestedAction(reason FailureReason) string {
actions := map[FailureReason]string{
// 企业信息验证失败
FailureReasonEnterpriseNotExists: "请检查企业名称和统一社会信用代码是否正确",
FailureReasonEnterpriseInfoMismatch: "请核对企业信息是否与工商登记信息一致",
FailureReasonEnterpriseStatusAbnormal: "请确认企业状态正常,如有疑问请联系客服",
FailureReasonLegalPersonMismatch: "请核对法定代表人信息是否正确",
FailureReasonEsignVerificationFailed: "请稍后重试,如持续失败请联系客服",
FailureReasonInvalidDocument: "请检查证件信息是否有效",
// 合同签署失败
FailureReasonContractRejectedByUser: "您可以重新申请签署合同",
FailureReasonContractExpired: "请重新申请签署合同",
FailureReasonSignProcessFailed: "请重新尝试签署,如持续失败请联系客服",
FailureReasonContractGenFailed: "系统正在处理,请稍后重试",
FailureReasonEsignFlowError: "请稍后重试,如持续失败请联系客服",
// 系统错误
FailureReasonSystemError: "系统暂时不可用,请稍后重试",
FailureReasonNetworkError: "网络连接异常,请检查网络后重试",
FailureReasonTimeout: "操作超时,请重新尝试",
FailureReasonUnknownError: "发生未知错误,请联系客服",
}
if action, exists := actions[reason]; exists {
return action
}
return "请联系客服处理"
}
// IsRetryable 判断是否可以重试
func IsRetryable(reason FailureReason) bool {
retryableReasons := map[FailureReason]bool{
// 企业信息验证失败 - 用户数据问题,可重试
FailureReasonEnterpriseNotExists: true,
FailureReasonEnterpriseInfoMismatch: true,
FailureReasonEnterpriseStatusAbnormal: false, // 企业状态问题,需要外部解决
FailureReasonLegalPersonMismatch: true,
FailureReasonEsignVerificationFailed: true, // 可能是临时问题
FailureReasonInvalidDocument: true,
// 合同签署失败
FailureReasonContractRejectedByUser: true, // 用户可以改变主意
FailureReasonContractExpired: true, // 可以重新申请
FailureReasonSignProcessFailed: true, // 可能是临时问题
FailureReasonContractGenFailed: true, // 可能是临时问题
FailureReasonEsignFlowError: true, // 可能是临时问题
// 系统错误 - 大部分可重试
FailureReasonSystemError: true,
FailureReasonNetworkError: true,
FailureReasonTimeout: true,
FailureReasonUnknownError: false, // 未知错误,不建议自动重试
}
if retryable, exists := retryableReasons[reason]; exists {
return retryable
}
return false
}
// GetRetrySuggestion 获取重试建议
func GetRetrySuggestion(reason FailureReason) string {
if !IsRetryable(reason) {
return "此问题不建议重试,请联系客服处理"
}
suggestions := map[FailureReason]string{
FailureReasonEnterpriseNotExists: "请修正企业信息后重新提交",
FailureReasonEnterpriseInfoMismatch: "请核对企业信息后重新提交",
FailureReasonLegalPersonMismatch: "请确认法定代表人信息后重新提交",
FailureReasonEsignVerificationFailed: "请稍后重新尝试",
FailureReasonInvalidDocument: "请检查证件信息后重新提交",
FailureReasonContractRejectedByUser: "如需要可重新申请合同",
FailureReasonContractExpired: "请重新申请合同签署",
FailureReasonSignProcessFailed: "请重新尝试签署",
FailureReasonContractGenFailed: "请稍后重新申请",
FailureReasonEsignFlowError: "请稍后重新尝试",
FailureReasonSystemError: "请稍后重试",
FailureReasonNetworkError: "请检查网络连接后重试",
FailureReasonTimeout: "请重新尝试操作",
}
if suggestion, exists := suggestions[reason]; exists {
return suggestion
}
return "请重新尝试操作"
}

View File

@@ -0,0 +1,29 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/shared/interfaces"
)
// CertificationCommandRepository 认证命令仓储接口
// 专门处理认证数据的变更操作符合CQRS模式
type CertificationCommandRepository interface {
// 基础CRUD操作
Create(ctx context.Context, cert entities.Certification) error
Update(ctx context.Context, cert entities.Certification) error
Delete(ctx context.Context, id string) error
// 业务特定的更新操作
UpdateStatus(ctx context.Context, id string, status enums.CertificationStatus) error
UpdateAuthFlowID(ctx context.Context, id string, authFlowID string) error
UpdateContractInfo(ctx context.Context, id string, contractFileID, esignFlowID, contractURL, contractSignURL string) error
UpdateFailureInfo(ctx context.Context, id string, reason enums.FailureReason, message string) error
// 批量操作
BatchUpdateStatus(ctx context.Context, ids []string, status enums.CertificationStatus) error
// 事务支持
WithTx(tx interfaces.Transaction) CertificationCommandRepository
}

View File

@@ -0,0 +1,122 @@
package repositories
import (
"context"
"time"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories/queries"
)
// CertificationQueryRepository 认证查询仓储接口
// 专门处理认证数据的查询操作符合CQRS模式
type CertificationQueryRepository interface {
// 基础查询操作
GetByID(ctx context.Context, id string) (*entities.Certification, error)
GetByUserID(ctx context.Context, userID string) (*entities.Certification, error)
Exists(ctx context.Context, id string) (bool, error)
// 列表查询
List(ctx context.Context, query *queries.ListCertificationsQuery) ([]*entities.Certification, int64, error)
ListByUserIDs(ctx context.Context, userIDs []string) ([]*entities.Certification, error)
ListByStatus(ctx context.Context, status enums.CertificationStatus, limit int) ([]*entities.Certification, error)
// 业务查询
FindByAuthFlowID(ctx context.Context, authFlowID string) (*entities.Certification, error)
FindByEsignFlowID(ctx context.Context, esignFlowID string) (*entities.Certification, error)
ListPendingRetry(ctx context.Context, maxRetryCount int) ([]*entities.Certification, error)
GetPendingCertifications(ctx context.Context) ([]*entities.Certification, error)
GetExpiredContracts(ctx context.Context) ([]*entities.Certification, error)
GetCertificationsByDateRange(ctx context.Context, startDate, endDate time.Time) ([]*entities.Certification, error)
GetUserActiveCertification(ctx context.Context, userID string) (*entities.Certification, error)
// 统计查询
GetStatistics(ctx context.Context, period CertificationTimePeriod) (*CertificationStatistics, error)
CountByStatus(ctx context.Context, status enums.CertificationStatus) (int64, error)
CountByFailureReason(ctx context.Context, reason enums.FailureReason) (int64, error)
GetProgressStatistics(ctx context.Context) (*CertificationProgressStats, error)
// 搜索查询
SearchByCompanyName(ctx context.Context, companyName string, limit int) ([]*entities.Certification, error)
SearchByLegalPerson(ctx context.Context, legalPersonName string, limit int) ([]*entities.Certification, error)
// 缓存相关
InvalidateCache(ctx context.Context, keys ...string) error
RefreshCache(ctx context.Context, certificationID string) error
}
// CertificationTimePeriod 时间周期枚举
type CertificationTimePeriod string
const (
PeriodDaily CertificationTimePeriod = "daily"
PeriodWeekly CertificationTimePeriod = "weekly"
PeriodMonthly CertificationTimePeriod = "monthly"
PeriodYearly CertificationTimePeriod = "yearly"
)
// CertificationStatistics 认证统计信息
type CertificationStatistics struct {
Period CertificationTimePeriod `json:"period"`
StartDate time.Time `json:"start_date"`
EndDate time.Time `json:"end_date"`
// 总体统计
TotalCertifications int64 `json:"total_certifications"`
CompletedCount int64 `json:"completed_count"`
FailedCount int64 `json:"failed_count"`
InProgressCount int64 `json:"in_progress_count"`
// 状态分布
StatusDistribution map[enums.CertificationStatus]int64 `json:"status_distribution"`
// 失败原因分布
FailureDistribution map[enums.FailureReason]int64 `json:"failure_distribution"`
// 成功率统计
SuccessRate float64 `json:"success_rate"`
EnterpriseVerifyRate float64 `json:"enterprise_verify_rate"`
ContractSignRate float64 `json:"contract_sign_rate"`
// 时间统计
AvgProcessingTime time.Duration `json:"avg_processing_time"`
AvgVerificationTime time.Duration `json:"avg_verification_time"`
AvgSigningTime time.Duration `json:"avg_signing_time"`
// 重试统计
RetryStats *CertificationRetryStats `json:"retry_stats"`
}
// CertificationProgressStats 进度统计信息
type CertificationProgressStats struct {
StatusProgress map[enums.CertificationStatus]int64 `json:"status_progress"`
ProgressDistribution map[int]int64 `json:"progress_distribution"` // key: progress percentage
// 各阶段耗时统计
StageTimeStats map[string]*CertificationStageTimeInfo `json:"stage_time_stats"`
}
// CertificationStageTimeInfo 阶段耗时信息
type CertificationStageTimeInfo struct {
StageName string `json:"stage_name"`
AverageTime time.Duration `json:"average_time"`
MinTime time.Duration `json:"min_time"`
MaxTime time.Duration `json:"max_time"`
SampleCount int64 `json:"sample_count"`
}
// CertificationRetryStats 重试统计信息
type CertificationRetryStats struct {
TotalRetries int64 `json:"total_retries"`
SuccessfulRetries int64 `json:"successful_retries"`
FailedRetries int64 `json:"failed_retries"`
RetrySuccessRate float64 `json:"retry_success_rate"`
// 各阶段重试统计
EnterpriseRetries int64 `json:"enterprise_retries"`
ContractRetries int64 `json:"contract_retries"`
// 重试原因分布
RetryReasonStats map[enums.FailureReason]int64 `json:"retry_reason_stats"`
}

View File

@@ -1,91 +0,0 @@
package repositories
import (
"context"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/repositories/queries"
"tyapi-server/internal/shared/interfaces"
)
// CertificationStats 认证统计信息
type CertificationStats struct {
TotalCertifications int64
PendingCertifications int64
CompletedCertifications int64
TodaySubmissions int64
}
// CertificationRepository 认证申请仓储接口
type CertificationRepository interface {
interfaces.Repository[entities.Certification]
// 基础查询 - 直接使用实体
GetByUserID(ctx context.Context, userID string) (*entities.Certification, error)
GetByStatus(ctx context.Context, status string) ([]*entities.Certification, error)
GetPendingCertifications(ctx context.Context) ([]*entities.Certification, error)
GetByAuthFlowID(ctx context.Context, authFlowID string) (entities.Certification, error)
GetByEsignFlowID(ctx context.Context, esignFlowID string) (entities.Certification, error)
// 复杂查询 - 使用查询参数
ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) ([]*entities.Certification, int64, error)
// 业务操作
UpdateStatus(ctx context.Context, certificationID string, status string) error
// 统计信息
GetStats(ctx context.Context) (*CertificationStats, error)
GetStatsByDateRange(ctx context.Context, startDate, endDate string) (*CertificationStats, error)
}
// EnterpriseInfoSubmitRecordRepository 企业信息提交记录仓储接口
type EnterpriseInfoSubmitRecordRepository interface {
interfaces.Repository[entities.EnterpriseInfoSubmitRecord]
// 基础查询
GetByUserID(ctx context.Context, userID string) ([]*entities.EnterpriseInfoSubmitRecord, error)
GetLatestByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error)
// 复杂查询
ListRecords(ctx context.Context, query *queries.ListEnterpriseInfoSubmitRecordsQuery) ([]*entities.EnterpriseInfoSubmitRecord, int64, error)
// 业务操作
UpdateStatus(ctx context.Context, recordID string, status string, reason string) error
}
// EsignContractGenerateRecordRepository e签宝生成合同记录仓储接口
type EsignContractGenerateRecordRepository interface {
interfaces.Repository[entities.EsignContractGenerateRecord]
// 基础查询
GetByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractGenerateRecord, error)
GetByUserID(ctx context.Context, userID string) ([]*entities.EsignContractGenerateRecord, error)
GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractGenerateRecord, error)
// 复杂查询
ListRecords(ctx context.Context, query *queries.ListEsignContractGenerateRecordsQuery) ([]*entities.EsignContractGenerateRecord, int64, error)
// 业务操作
UpdateStatus(ctx context.Context, recordID string, status string, reason string) error
UpdateSuccessInfo(ctx context.Context, recordID, esignFlowID, contractFileID, contractURL string) error
IncrementRetry(ctx context.Context, recordID string) error
}
// EsignContractSignRecordRepository e签宝签署合同记录仓储接口
type EsignContractSignRecordRepository interface {
interfaces.Repository[entities.EsignContractSignRecord]
// 基础查询
GetByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractSignRecord, error)
GetByUserID(ctx context.Context, userID string) ([]*entities.EsignContractSignRecord, error)
GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractSignRecord, error)
GetByGenerateRecordID(ctx context.Context, generateRecordID string) (*entities.EsignContractSignRecord, error)
// 复杂查询
ListRecords(ctx context.Context, query *queries.ListEsignContractSignRecordsQuery) ([]*entities.EsignContractSignRecord, int64, error)
// 业务操作
UpdateStatus(ctx context.Context, recordID string, status string, reason string) error
UpdateSuccessInfo(ctx context.Context, recordID, signedFileURL string) error
SetSignURL(ctx context.Context, recordID, signURL string) error
IncrementRetry(ctx context.Context, recordID string) error
MarkExpiredRecords(ctx context.Context) error
}

View File

@@ -1,62 +1,279 @@
package queries
import "tyapi-server/internal/domains/certification/enums"
import (
"fmt"
"time"
// ListCertificationsQuery 认证申请列表查询参数
"tyapi-server/internal/domains/certification/enums"
)
// GetCertificationQuery 获取单个认证查询
type GetCertificationQuery struct {
ID string `json:"id" validate:"required"`
UserID string `json:"user_id,omitempty"` // 可选的用户ID用于权限验证
}
// ListCertificationsQuery 认证列表查询
type ListCertificationsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
UserID string `json:"user_id"`
Status enums.CertificationStatus `json:"status"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
EnterpriseName string `json:"enterprise_name"`
// 分页参数
Page int `json:"page" validate:"min=1"`
PageSize int `json:"page_size" validate:"min=1,max=100"`
// 排序参数
SortBy string `json:"sort_by"` // 排序字段: created_at, updated_at, status, progress
SortOrder string `json:"sort_order"` // 排序方向: asc, desc
// 过滤条件
UserID string `json:"user_id,omitempty"`
Status enums.CertificationStatus `json:"status,omitempty"`
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
FailureReason enums.FailureReason `json:"failure_reason,omitempty"`
// 时间范围过滤
CreatedAfter *time.Time `json:"created_after,omitempty"`
CreatedBefore *time.Time `json:"created_before,omitempty"`
UpdatedAfter *time.Time `json:"updated_after,omitempty"`
UpdatedBefore *time.Time `json:"updated_before,omitempty"`
// 企业信息过滤
CompanyName string `json:"company_name,omitempty"`
LegalPersonName string `json:"legal_person_name,omitempty"`
// 业务状态过滤
IsCompleted *bool `json:"is_completed,omitempty"`
IsFailed *bool `json:"is_failed,omitempty"`
IsUserActionRequired *bool `json:"is_user_action_required,omitempty"`
// 高级过滤
MinRetryCount *int `json:"min_retry_count,omitempty"`
MaxRetryCount *int `json:"max_retry_count,omitempty"`
MinProgress *int `json:"min_progress,omitempty"`
MaxProgress *int `json:"max_progress,omitempty"`
// 搜索参数
SearchKeyword string `json:"search_keyword,omitempty"` // 通用搜索关键词
// 包含关联数据
IncludeMetadata bool `json:"include_metadata,omitempty"`
}
// ListEnterprisesQuery 企业信息列表查询参数
type ListEnterprisesQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
UserID string `json:"user_id"`
EnterpriseName string `json:"enterprise_name"`
LicenseNumber string `json:"license_number"`
LegalPersonName string `json:"legal_person_name"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
// DefaultValues 设置默认值
func (q *ListCertificationsQuery) DefaultValues() {
if q.Page <= 0 {
q.Page = 1
}
if q.PageSize <= 0 {
q.PageSize = 20
}
if q.SortBy == "" {
q.SortBy = "created_at"
}
if q.SortOrder == "" {
q.SortOrder = "desc"
}
}
// ListEnterpriseInfoSubmitRecordsQuery 企业信息提交记录列表查询参数
type ListEnterpriseInfoSubmitRecordsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
Status string `json:"status"`
CompanyName string `json:"company_name"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
// GetOffset 计算分页偏移量
func (q *ListCertificationsQuery) GetOffset() int {
return (q.Page - 1) * q.PageSize
}
// ListEsignContractGenerateRecordsQuery e签宝生成合同记录列表查询参数
type ListEsignContractGenerateRecordsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
Status string `json:"status"`
ContractType string `json:"contract_type"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
// GetLimit 获取查询限制数量
func (q *ListCertificationsQuery) GetLimit() int {
return q.PageSize
}
// ListEsignContractSignRecordsQuery e签宝签署合同记录列表查询参数
type ListEsignContractSignRecordsQuery struct {
Page int `json:"page"`
PageSize int `json:"page_size"`
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
Status string `json:"status"`
SignerName string `json:"signer_name"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
// HasTimeFilter 检查是否有时间过滤条件
func (q *ListCertificationsQuery) HasTimeFilter() bool {
return q.CreatedAfter != nil || q.CreatedBefore != nil ||
q.UpdatedAfter != nil || q.UpdatedBefore != nil
}
// HasStatusFilter 检查是否有状态过滤条件
func (q *ListCertificationsQuery) HasStatusFilter() bool {
return q.Status != "" || len(q.Statuses) > 0
}
// HasSearchFilter 检查是否有搜索过滤条件
func (q *ListCertificationsQuery) HasSearchFilter() bool {
return q.CompanyName != "" || q.LegalPersonName != "" || q.SearchKeyword != ""
}
// GetSearchFields 获取搜索字段映射
func (q *ListCertificationsQuery) GetSearchFields() map[string]string {
fields := make(map[string]string)
if q.CompanyName != "" {
fields["company_name"] = q.CompanyName
}
if q.LegalPersonName != "" {
fields["legal_person_name"] = q.LegalPersonName
}
if q.SearchKeyword != "" {
fields["keyword"] = q.SearchKeyword
}
return fields
}
// CertificationStatisticsQuery 认证统计查询
type CertificationStatisticsQuery struct {
// 时间范围
StartDate time.Time `json:"start_date" validate:"required"`
EndDate time.Time `json:"end_date" validate:"required"`
// 统计周期
Period string `json:"period" validate:"oneof=daily weekly monthly yearly"`
// 分组维度
GroupBy []string `json:"group_by,omitempty"` // status, failure_reason, user_type, date
// 过滤条件
UserIDs []string `json:"user_ids,omitempty"`
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
// 统计类型
IncludeProgressStats bool `json:"include_progress_stats,omitempty"`
IncludeRetryStats bool `json:"include_retry_stats,omitempty"`
IncludeTimeStats bool `json:"include_time_stats,omitempty"`
}
// Validate 验证统计查询参数
func (q *CertificationStatisticsQuery) Validate() error {
if q.EndDate.Before(q.StartDate) {
return fmt.Errorf("结束时间不能早于开始时间")
}
// 检查时间范围是否合理不超过1年
if q.EndDate.Sub(q.StartDate) > 365*24*time.Hour {
return fmt.Errorf("查询时间范围不能超过1年")
}
return nil
}
// GetTimeRange 获取时间范围描述
func (q *CertificationStatisticsQuery) GetTimeRange() string {
return fmt.Sprintf("%s 到 %s",
q.StartDate.Format("2006-01-02"),
q.EndDate.Format("2006-01-02"))
}
// SearchCertificationsQuery 搜索认证查询
type SearchCertificationsQuery struct {
// 搜索关键词
Keyword string `json:"keyword" validate:"required,min=2"`
// 搜索字段
SearchFields []string `json:"search_fields,omitempty"` // company_name, legal_person_name, unified_social_code
// 过滤条件
Statuses []enums.CertificationStatus `json:"statuses,omitempty"`
UserID string `json:"user_id,omitempty"`
// 分页参数
Page int `json:"page" validate:"min=1"`
PageSize int `json:"page_size" validate:"min=1,max=50"`
// 排序参数
SortBy string `json:"sort_by"`
SortOrder string `json:"sort_order"`
// 搜索选项
ExactMatch bool `json:"exact_match,omitempty"` // 是否精确匹配
IgnoreCase bool `json:"ignore_case,omitempty"` // 是否忽略大小写
}
// DefaultValues 设置搜索查询默认值
func (q *SearchCertificationsQuery) DefaultValues() {
if q.Page <= 0 {
q.Page = 1
}
if q.PageSize <= 0 {
q.PageSize = 10
}
if q.SortBy == "" {
q.SortBy = "created_at"
}
if q.SortOrder == "" {
q.SortOrder = "desc"
}
if len(q.SearchFields) == 0 {
q.SearchFields = []string{"company_name", "legal_person_name"}
}
// 默认忽略大小写
q.IgnoreCase = true
}
// GetLimit 获取查询限制数量
func (q *SearchCertificationsQuery) GetLimit() int {
return q.PageSize
}
// GetSearchPattern 获取搜索模式
func (q *SearchCertificationsQuery) GetSearchPattern() string {
if q.ExactMatch {
return q.Keyword
}
// 模糊搜索,添加通配符
return "%" + q.Keyword + "%"
}
// UserCertificationsQuery 用户认证查询
type UserCertificationsQuery struct {
UserID string `json:"user_id" validate:"required"`
// 状态过滤
Status enums.CertificationStatus `json:"status,omitempty"`
IncludeCompleted bool `json:"include_completed,omitempty"`
IncludeFailed bool `json:"include_failed,omitempty"`
// 时间过滤
After *time.Time `json:"after,omitempty"`
Before *time.Time `json:"before,omitempty"`
// 分页
Page int `json:"page"`
PageSize int `json:"page_size"`
// 排序
SortBy string `json:"sort_by"`
SortOrder string `json:"sort_order"`
}
// DefaultValues 设置用户认证查询默认值
func (q *UserCertificationsQuery) DefaultValues() {
if q.Page <= 0 {
q.Page = 1
}
if q.PageSize <= 0 {
q.PageSize = 10
}
if q.SortBy == "" {
q.SortBy = "created_at"
}
if q.SortOrder == "" {
q.SortOrder = "desc"
}
}
// ShouldIncludeStatus 检查是否应该包含指定状态
func (q *UserCertificationsQuery) ShouldIncludeStatus(status enums.CertificationStatus) bool {
// 如果指定了特定状态,只返回该状态
if q.Status != "" {
return status == q.Status
}
// 根据包含选项决定
if enums.IsFinalStatus(status) && !q.IncludeCompleted {
return false
}
if enums.IsFailureStatus(status) && !q.IncludeFailed {
return false
}
return true
}

View File

@@ -0,0 +1,384 @@
package services
import (
"context"
"fmt"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
"tyapi-server/internal/domains/certification/services/state_machine"
"go.uber.org/zap"
)
// CertificationAggregateService 认证聚合服务接口
// 负责认证聚合根的生命周期管理和状态转换协调
type CertificationAggregateService interface {
// 聚合根管理
CreateCertification(ctx context.Context, userID string) (*entities.Certification, error)
LoadCertification(ctx context.Context, certificationID string) (*entities.Certification, error)
SaveCertification(ctx context.Context, cert *entities.Certification) error
// 状态转换管理
TransitionState(ctx context.Context, certificationID string, targetStatus enums.CertificationStatus, actor enums.ActorType, actorID string, reason string, metadata map[string]interface{}) (*state_machine.StateTransitionResult, error)
ValidateStateTransition(ctx context.Context, certificationID string, targetStatus enums.CertificationStatus, actor enums.ActorType) error
// 业务规则验证
ValidateBusinessRules(ctx context.Context, cert *entities.Certification) error
CheckInvariance(ctx context.Context, cert *entities.Certification) error
// 查询方法
GetStateInfo(status enums.CertificationStatus) *state_machine.StateConfig
GetValidTransitions(ctx context.Context, certificationID string, actor enums.ActorType) ([]*state_machine.StateTransitionRule, error)
}
// CertificationAggregateServiceImpl 认证聚合服务实现
type CertificationAggregateServiceImpl struct {
commandRepo repositories.CertificationCommandRepository
queryRepo repositories.CertificationQueryRepository
stateMachine *state_machine.CertificationStateMachine
logger *zap.Logger
}
// NewCertificationAggregateService 创建认证聚合服务
func NewCertificationAggregateService(
commandRepo repositories.CertificationCommandRepository,
queryRepo repositories.CertificationQueryRepository,
stateMachine *state_machine.CertificationStateMachine,
logger *zap.Logger,
) CertificationAggregateService {
return &CertificationAggregateServiceImpl{
commandRepo: commandRepo,
queryRepo: queryRepo,
stateMachine: stateMachine,
logger: logger,
}
}
// ================ 聚合根管理 ================
// CreateCertification 创建认证申请
func (s *CertificationAggregateServiceImpl) CreateCertification(ctx context.Context, userID string) (*entities.Certification, error) {
s.logger.Info("创建认证申请", zap.String("user_id", userID))
// 1. 检查用户是否已有认证申请
existingCert, err := s.queryRepo.GetByUserID(ctx, userID)
if err == nil && existingCert != nil {
// 检查现有认证的状态
if !existingCert.IsFinalStatus() {
return nil, fmt.Errorf("用户已有进行中的认证申请,请先完成或取消现有申请")
}
s.logger.Info("用户已有完成的认证申请,允许创建新申请",
zap.String("user_id", userID),
zap.String("existing_cert_id", existingCert.ID),
zap.String("existing_status", string(existingCert.Status)))
}
// 2. 创建新的认证聚合根
cert, err := entities.NewCertification(userID)
if err != nil {
s.logger.Error("创建认证实体失败", zap.Error(err), zap.String("user_id", userID))
return nil, fmt.Errorf("创建认证实体失败: %w", err)
}
// 3. 验证业务规则
if err := s.ValidateBusinessRules(ctx, cert); err != nil {
s.logger.Error("认证业务规则验证失败", zap.Error(err))
return nil, fmt.Errorf("业务规则验证失败: %w", err)
}
// 4. 保存聚合根
if err := s.SaveCertification(ctx, cert); err != nil {
s.logger.Error("保存认证申请失败", zap.Error(err))
return nil, fmt.Errorf("保存认证申请失败: %w", err)
}
s.logger.Info("认证申请创建成功",
zap.String("user_id", userID),
zap.String("certification_id", cert.ID))
return cert, nil
}
// LoadCertification 加载认证聚合根
func (s *CertificationAggregateServiceImpl) LoadCertification(ctx context.Context, certificationID string) (*entities.Certification, error) {
s.logger.Debug("加载认证聚合根", zap.String("certification_id", certificationID))
// 从查询仓储加载
cert, err := s.queryRepo.GetByID(ctx, certificationID)
if err != nil {
s.logger.Error("加载认证聚合根失败", zap.Error(err), zap.String("certification_id", certificationID))
return nil, fmt.Errorf("认证申请不存在: %w", err)
}
// 验证聚合根完整性
if err := s.CheckInvariance(ctx, cert); err != nil {
s.logger.Error("认证聚合根完整性验证失败", zap.Error(err))
return nil, fmt.Errorf("认证数据完整性验证失败: %w", err)
}
return cert, nil
}
// SaveCertification 保存认证聚合根
func (s *CertificationAggregateServiceImpl) SaveCertification(ctx context.Context, cert *entities.Certification) error {
s.logger.Debug("保存认证聚合根", zap.String("certification_id", cert.ID))
// 1. 验证业务规则
if err := s.ValidateBusinessRules(ctx, cert); err != nil {
return fmt.Errorf("业务规则验证失败: %w", err)
}
// 2. 检查聚合根是否存在
exists, err := s.queryRepo.Exists(ctx, cert.ID)
if err != nil {
return fmt.Errorf("检查认证存在性失败: %w", err)
}
// 3. 保存到命令仓储
if exists {
err = s.commandRepo.Update(ctx, *cert)
if err != nil {
s.logger.Error("更新认证聚合根失败", zap.Error(err))
return fmt.Errorf("更新认证失败: %w", err)
}
} else {
err = s.commandRepo.Create(ctx, *cert)
if err != nil {
s.logger.Error("创建认证聚合根失败", zap.Error(err))
return fmt.Errorf("创建认证失败: %w", err)
}
}
s.logger.Debug("认证聚合根保存成功", zap.String("certification_id", cert.ID))
return nil
}
// ================ 状态转换管理 ================
// TransitionState 执行状态转换
func (s *CertificationAggregateServiceImpl) TransitionState(
ctx context.Context,
certificationID string,
targetStatus enums.CertificationStatus,
actor enums.ActorType,
actorID string,
reason string,
metadata map[string]interface{},
) (*state_machine.StateTransitionResult, error) {
s.logger.Info("执行状态转换",
zap.String("certification_id", certificationID),
zap.String("target_status", string(targetStatus)),
zap.String("actor", string(actor)),
zap.String("actor_id", actorID))
// 构建状态转换请求
req := &state_machine.StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: targetStatus,
Actor: actor,
ActorID: actorID,
Reason: reason,
Context: metadata,
AllowRollback: true,
}
// 执行状态转换
result, err := s.stateMachine.ExecuteTransition(ctx, req)
if err != nil {
s.logger.Error("状态转换执行失败",
zap.String("certification_id", certificationID),
zap.Error(err))
return result, err
}
s.logger.Info("状态转换执行成功",
zap.String("certification_id", certificationID),
zap.String("from_status", string(result.OldStatus)),
zap.String("to_status", string(result.NewStatus)))
return result, nil
}
// ValidateStateTransition 验证状态转换
func (s *CertificationAggregateServiceImpl) ValidateStateTransition(
ctx context.Context,
certificationID string,
targetStatus enums.CertificationStatus,
actor enums.ActorType,
) error {
// 加载认证聚合根
cert, err := s.LoadCertification(ctx, certificationID)
if err != nil {
return err
}
// 检查是否可以转换
canTransition, message := s.stateMachine.CanTransition(cert, targetStatus, actor)
if !canTransition {
return fmt.Errorf("状态转换验证失败: %s", message)
}
return nil
}
// ================ 业务规则验证 ================
// ValidateBusinessRules 验证业务规则
func (s *CertificationAggregateServiceImpl) ValidateBusinessRules(ctx context.Context, cert *entities.Certification) error {
s.logger.Debug("验证认证业务规则", zap.String("certification_id", cert.ID))
// 1. 实体内部业务规则验证
if err := cert.ValidateBusinessRules(); err != nil {
return fmt.Errorf("实体业务规则验证失败: %w", err)
}
// 2. 跨聚合根业务规则验证
if err := s.validateCrossAggregateRules(ctx, cert); err != nil {
return fmt.Errorf("跨聚合根业务规则验证失败: %w", err)
}
// 3. 领域级业务规则验证
if err := s.validateDomainRules(ctx, cert); err != nil {
return fmt.Errorf("领域业务规则验证失败: %w", err)
}
return nil
}
// CheckInvariance 检查聚合根不变量
func (s *CertificationAggregateServiceImpl) CheckInvariance(ctx context.Context, cert *entities.Certification) error {
s.logger.Debug("检查认证聚合根不变量", zap.String("certification_id", cert.ID))
// 1. 基础不变量检查
if cert.ID == "" {
return fmt.Errorf("认证ID不能为空")
}
if cert.UserID == "" {
return fmt.Errorf("用户ID不能为空")
}
if !enums.IsValidStatus(cert.Status) {
return fmt.Errorf("无效的认证状态: %s", cert.Status)
}
// 2. 状态相关不变量检查
if err := s.validateStatusInvariance(cert); err != nil {
return err
}
// 3. 时间戳不变量检查
if err := s.validateTimestampInvariance(cert); err != nil {
return err
}
return nil
}
// ================ 查询方法 ================
// GetStateInfo 获取状态信息
func (s *CertificationAggregateServiceImpl) GetStateInfo(status enums.CertificationStatus) *state_machine.StateConfig {
return s.stateMachine.GetStateInfo(status)
}
// GetValidTransitions 获取有效的状态转换
func (s *CertificationAggregateServiceImpl) GetValidTransitions(
ctx context.Context,
certificationID string,
actor enums.ActorType,
) ([]*state_machine.StateTransitionRule, error) {
// 加载认证聚合根
cert, err := s.LoadCertification(ctx, certificationID)
if err != nil {
return nil, err
}
// 获取有效转换
transitions := s.stateMachine.GetValidTransitions(cert, actor)
return transitions, nil
}
// ================ 私有方法 ================
// validateCrossAggregateRules 验证跨聚合根业务规则
func (s *CertificationAggregateServiceImpl) validateCrossAggregateRules(ctx context.Context, cert *entities.Certification) error {
// TODO: 实现跨聚合根业务规则验证
// 例如:检查用户是否有权限申请认证、检查企业信息是否已被其他用户使用等
return nil
}
// validateDomainRules 验证领域级业务规则
func (s *CertificationAggregateServiceImpl) validateDomainRules(ctx context.Context, cert *entities.Certification) error {
// TODO: 实现领域级业务规则验证
// 例如:检查认证流程是否符合法规要求、检查时间窗口限制等
return nil
}
// validateStatusInvariance 验证状态相关不变量
func (s *CertificationAggregateServiceImpl) validateStatusInvariance(cert *entities.Certification) error {
switch cert.Status {
case enums.StatusEnterpriseVerified:
if cert.AuthFlowID == "" {
return fmt.Errorf("企业认证状态下必须有认证流程ID")
}
if cert.EnterpriseVerifiedAt == nil {
return fmt.Errorf("企业认证状态下必须有认证完成时间")
}
case enums.StatusContractApplied:
if cert.AuthFlowID == "" {
return fmt.Errorf("合同申请状态下必须有企业认证流程ID")
}
if cert.ContractAppliedAt == nil {
return fmt.Errorf("合同申请状态下必须有合同申请时间")
}
case enums.StatusContractSigned:
if cert.ContractFileID == "" || cert.EsignFlowID == "" {
return fmt.Errorf("合同签署状态下必须有完整的合同信息")
}
if cert.ContractSignedAt == nil {
return fmt.Errorf("合同签署状态下必须有签署完成时间")
}
}
// 失败状态检查
if enums.IsFailureStatus(cert.Status) {
if cert.FailureReason == "" {
return fmt.Errorf("失败状态下必须有失败原因")
}
if !enums.IsValidFailureReason(cert.FailureReason) {
return fmt.Errorf("无效的失败原因: %s", cert.FailureReason)
}
}
return nil
}
// validateTimestampInvariance 验证时间戳不变量
func (s *CertificationAggregateServiceImpl) validateTimestampInvariance(cert *entities.Certification) error {
// 检查时间戳的逻辑顺序
if cert.InfoSubmittedAt != nil && cert.EnterpriseVerifiedAt != nil {
if cert.InfoSubmittedAt.After(*cert.EnterpriseVerifiedAt) {
return fmt.Errorf("企业信息提交时间不能晚于企业认证时间")
}
}
if cert.EnterpriseVerifiedAt != nil && cert.ContractAppliedAt != nil {
if cert.EnterpriseVerifiedAt.After(*cert.ContractAppliedAt) {
return fmt.Errorf("企业认证时间不能晚于合同申请时间")
}
}
if cert.ContractAppliedAt != nil && cert.ContractSignedAt != nil {
if cert.ContractAppliedAt.After(*cert.ContractSignedAt) {
return fmt.Errorf("合同申请时间不能晚于合同签署时间")
}
}
return nil
}

View File

@@ -1,126 +0,0 @@
package services
import (
"context"
"fmt"
"go.uber.org/zap"
cert_entities "tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/repositories"
user_entities "tyapi-server/internal/domains/user/entities"
"tyapi-server/internal/shared/esign"
)
// CertificationEsignService 负责与e签宝相关的认证业务逻辑
type CertificationEsignService struct {
certRepo repositories.CertificationRepository
esignClient *esign.Client
esignContractGenerateRecordRepo repositories.EsignContractGenerateRecordRepository
esignContractSignRecordRepo repositories.EsignContractSignRecordRepository
logger *zap.Logger
}
// NewCertificationEsignService 创建CertificationEsignService实例
func NewCertificationEsignService(
certRepo repositories.CertificationRepository,
esignClient *esign.Client,
esignContractGenerateRecordRepo repositories.EsignContractGenerateRecordRepository,
logger *zap.Logger,
) *CertificationEsignService {
return &CertificationEsignService{
certRepo: certRepo,
esignClient: esignClient,
esignContractGenerateRecordRepo: esignContractGenerateRecordRepo,
logger: logger,
}
}
// FillTemplate 生成合同文件e签宝模板填充
func (s *CertificationEsignService) FillTemplate(ctx context.Context, certification *cert_entities.Certification, components map[string]string) (*esign.FillTemplate, error) {
resp, err := s.esignClient.FillTemplate(components)
record := &cert_entities.EsignContractGenerateRecord{
CertificationID: certification.ID,
UserID: certification.UserID,
}
if err != nil {
s.logger.Error("生成合同文件失败", zap.Any("components", components), zap.Error(err))
record.Status = "failed"
} else {
record.TemplateID = resp.TemplateID
record.ContractName = resp.FileName
record.ContractFileID = resp.FileID
record.ContractURL = resp.FileDownloadUrl
record.Status = "success"
record.FillTime = &resp.FillTime
}
if _, createErr := s.esignContractGenerateRecordRepo.Create(ctx, *record); createErr != nil {
s.logger.Error("创建合同生成记录失败", zap.Error(createErr))
if err == nil {
return nil, fmt.Errorf("创建合同生成记录失败: %w", createErr)
}
}
if err != nil {
return nil, fmt.Errorf("生成合同文件失败: %w", err)
}
certification.ContractURL = resp.FileDownloadUrl
certification.ContractFileID = resp.FileID
err = s.certRepo.Update(ctx, *certification)
if err != nil {
s.logger.Error("更新认证申请失败", zap.Error(err))
return nil, fmt.Errorf("更新认证申请失败: %w", err)
}
s.logger.Info("生成合同文件成功", zap.String("template_id", resp.TemplateID), zap.String("file_id", resp.FileID))
return resp, nil
}
// 发起签署
func (s *CertificationEsignService) InitiateSign(ctx context.Context, certification *cert_entities.Certification, enterpriseInfo *user_entities.EnterpriseInfo) (*cert_entities.EsignContractSignRecord, error) {
// 发起签署流程
flowID, err := s.esignClient.CreateSignFlow(&esign.CreateSignFlowRequest{
FileID: certification.ContractFileID,
SignerAccount: enterpriseInfo.UnifiedSocialCode,
SignerName: enterpriseInfo.CompanyName,
TransactorPhone: enterpriseInfo.LegalPersonPhone,
TransactorName: enterpriseInfo.LegalPersonName,
TransactorIDCardNum: enterpriseInfo.LegalPersonID,
})
if err != nil {
s.logger.Error("获取签署链接失败",
zap.String("user_id", enterpriseInfo.UserID),
zap.Error(err),
)
return nil, fmt.Errorf("获取签署链接失败: %w", err)
}
signURL, shortURL, err := s.esignClient.GetSignURL(flowID, enterpriseInfo.UnifiedSocialCode, enterpriseInfo.CompanyName)
if err != nil {
s.logger.Error("获取签署链接失败", zap.Error(err))
return nil, fmt.Errorf("获取签署链接失败: %w", err)
}
esignContractSignRecord := cert_entities.NewEsignContractSignRecord(
certification.ID,
enterpriseInfo.UserID,
flowID,
certification.ContractFileID,
enterpriseInfo.UnifiedSocialCode,
enterpriseInfo.LegalPersonPhone,
enterpriseInfo.LegalPersonID,
signURL,
shortURL,
)
signContractSignRecord, err := s.esignContractSignRecordRepo.Create(ctx, *esignContractSignRecord)
if err != nil {
s.logger.Error("创建签署记录失败", zap.Error(err))
return nil, fmt.Errorf("创建签署记录失败: %w", err)
}
certification.EsignFlowID = signContractSignRecord.EsignFlowID
certification.ContractSignURL = signContractSignRecord.SignShortURL // 记录的是短链接
err = s.certRepo.Update(ctx, *certification)
if err != nil {
s.logger.Error("更新认证申请失败", zap.Error(err))
return nil, fmt.Errorf("更新认证申请失败: %w", err)
}
return &signContractSignRecord, nil
}

View File

@@ -1,153 +0,0 @@
package services
import (
"context"
"fmt"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
esign_service "tyapi-server/internal/shared/esign"
"go.uber.org/zap"
)
// CertificationManagementService 认证管理领域服务
// 负责认证申请的生命周期管理,包括创建、状态转换、进度查询等
type CertificationManagementService struct {
certRepo repositories.CertificationRepository
esignService *esign_service.Client
stateMachine *CertificationStateMachine
logger *zap.Logger
}
// NewCertificationManagementService 创建认证管理领域服务
func NewCertificationManagementService(
certRepo repositories.CertificationRepository,
esignService *esign_service.Client,
stateMachine *CertificationStateMachine,
logger *zap.Logger,
) *CertificationManagementService {
return &CertificationManagementService{
certRepo: certRepo,
esignService: esignService,
stateMachine: stateMachine,
logger: logger,
}
}
// CreateCertification 创建认证申请
func (s *CertificationManagementService) CreateCertification(ctx context.Context, userID string) (*entities.Certification, error) {
// 检查用户是否已有认证申请
existingCert, err := s.certRepo.GetByUserID(ctx, userID)
if err == nil && existingCert != nil {
return nil, fmt.Errorf("用户已有认证申请")
}
certification := &entities.Certification{
UserID: userID,
Status: enums.StatusPending,
}
createdCert, err := s.certRepo.Create(ctx, *certification)
if err != nil {
s.logger.Error("创建认证申请失败", zap.Error(err))
return nil, fmt.Errorf("创建认证申请失败: %w", err)
}
certification = &createdCert
s.logger.Info("认证申请创建成功",
zap.String("certification_id", certification.ID),
zap.String("user_id", userID),
)
return certification, nil
}
// GetCertificationByUserID 根据用户ID获取认证申请
func (s *CertificationManagementService) GetCertificationByUserID(ctx context.Context, userID string) (*entities.Certification, error) {
return s.certRepo.GetByUserID(ctx, userID)
}
// GetCertificationByID 根据ID获取认证申请
func (s *CertificationManagementService) GetCertificationByID(ctx context.Context, certificationID string) (*entities.Certification, error) {
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
return nil, err
}
return &cert, nil
}
// GetCertificationByAuthFlowID 根据认证流程ID获取认证申请
func (s *CertificationManagementService) GetCertificationByAuthFlowID(ctx context.Context, authFlowID string) (*entities.Certification, error) {
cert, err := s.certRepo.GetByAuthFlowID(ctx, authFlowID)
if err != nil {
return nil, err
}
return &cert, nil
}
// 根据签署流程ID获取认证申请
func (s *CertificationManagementService) GetCertificationByEsignFlowID(ctx context.Context, esignFlowID string) (*entities.Certification, error) {
cert, err := s.certRepo.GetByEsignFlowID(ctx, esignFlowID)
if err != nil {
return nil, err
}
return &cert, nil
}
// GetCertificationProgress 获取认证进度信息
func (s *CertificationManagementService) GetCertificationProgress(ctx context.Context, certificationID string) (map[string]interface{}, error) {
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
return nil, fmt.Errorf("认证申请不存在: %w", err)
}
progress := map[string]interface{}{
"certification_id": cert.ID,
"user_id": cert.UserID,
"current_status": cert.Status,
"progress_percentage": cert.GetProgressPercentage(),
"is_user_action_required": cert.IsUserActionRequired(),
"next_valid_statuses": cert.GetNextValidStatuses(),
"created_at": cert.CreatedAt,
"updated_at": cert.UpdatedAt,
}
// 添加时间节点信息
if cert.InfoSubmittedAt != nil {
progress["info_submitted_at"] = cert.InfoSubmittedAt
}
if cert.EnterpriseVerifiedAt != nil {
progress["enterprise_verified_at"] = cert.EnterpriseVerifiedAt
}
if cert.ContractAppliedAt != nil {
progress["contract_applied_at"] = cert.ContractAppliedAt
}
if cert.ContractSignedAt != nil {
progress["contract_signed_at"] = cert.ContractSignedAt
}
if cert.CompletedAt != nil {
progress["completed_at"] = cert.CompletedAt
}
return progress, nil
}
// 通过e签宝检查是否有过认证
func (s *CertificationManagementService) CheckCertification(ctx context.Context, companyName string, unifiedSocialCode string) (bool, error) {
// 查询企业是否已经过认证
queryOrgIdentityInfo := &esign_service.QueryOrgIdentityRequest{
OrgName: companyName,
OrgIDCardNum: unifiedSocialCode,
OrgIDCardType: esign_service.OrgIDCardTypeUSCC,
}
queryOrgIdentityResponse, err := s.esignService.QueryOrgIdentityInfo(queryOrgIdentityInfo)
if err != nil {
return false, fmt.Errorf("查询机构认证信息失败: %w", err)
}
if queryOrgIdentityResponse.Data.RealnameStatus == 1 {
s.logger.Info("该企业已进行过认证", zap.String("company_name", companyName), zap.String("unified_social_code", unifiedSocialCode))
return true, nil
}
return false, nil
}

View File

@@ -0,0 +1,636 @@
package services
import (
"context"
"fmt"
"time"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/entities/value_objects"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/services/state_machine"
"go.uber.org/zap"
)
// WorkflowResult 工作流执行结果
type WorkflowResult struct {
Success bool `json:"success"`
CertificationID string `json:"certification_id"`
CurrentStatus enums.CertificationStatus `json:"current_status"`
Message string `json:"message"`
Data map[string]interface{} `json:"data,omitempty"`
StateTransition *state_machine.StateTransitionResult `json:"state_transition,omitempty"`
ExecutedAt time.Time `json:"executed_at"`
}
// SubmitEnterpriseInfoCommand 提交企业信息命令
type SubmitEnterpriseInfoCommand struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info"`
}
// ApplyContractCommand 申请合同命令
type ApplyContractCommand struct {
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
}
// EsignCallbackCommand e签宝回调命令
type EsignCallbackCommand struct {
CertificationID string `json:"certification_id"`
CallbackType string `json:"callback_type"` // "auth_result" | "sign_result" | "flow_status"
CallbackData *state_machine.EsignCallbackData `json:"callback_data"`
}
// CertificationWorkflowOrchestrator 认证工作流编排器接口
// 负责编排认证业务流程,协调各个领域服务的协作
type CertificationWorkflowOrchestrator interface {
// 用户操作用例
SubmitEnterpriseInfo(ctx context.Context, cmd *SubmitEnterpriseInfoCommand) (*WorkflowResult, error)
ApplyContract(ctx context.Context, cmd *ApplyContractCommand) (*WorkflowResult, error)
// e签宝回调处理
HandleEnterpriseVerificationCallback(ctx context.Context, cmd *EsignCallbackCommand) (*WorkflowResult, error)
HandleContractSignCallback(ctx context.Context, cmd *EsignCallbackCommand) (*WorkflowResult, error)
// 异常处理
HandleFailure(ctx context.Context, certificationID string, failureType string, reason string) (*WorkflowResult, error)
RetryOperation(ctx context.Context, certificationID string, operation string) (*WorkflowResult, error)
// 查询操作
GetWorkflowStatus(ctx context.Context, certificationID string) (*WorkflowResult, error)
}
// CertificationWorkflowOrchestratorImpl 认证工作流编排器实现
type CertificationWorkflowOrchestratorImpl struct {
aggregateService CertificationAggregateService
callbackHandler *state_machine.EsignCallbackHandler
logger *zap.Logger
}
// NewCertificationWorkflowOrchestrator 创建认证工作流编排器
func NewCertificationWorkflowOrchestrator(
aggregateService CertificationAggregateService,
callbackHandler *state_machine.EsignCallbackHandler,
logger *zap.Logger,
) CertificationWorkflowOrchestrator {
return &CertificationWorkflowOrchestratorImpl{
aggregateService: aggregateService,
callbackHandler: callbackHandler,
logger: logger,
}
}
// ================ 用户操作用例 ================
// SubmitEnterpriseInfo 用户提交企业信息
func (o *CertificationWorkflowOrchestratorImpl) SubmitEnterpriseInfo(
ctx context.Context,
cmd *SubmitEnterpriseInfoCommand,
) (*WorkflowResult, error) {
o.logger.Info("开始处理企业信息提交",
zap.String("certification_id", cmd.CertificationID),
zap.String("user_id", cmd.UserID))
// 1. 验证命令完整性
if err := o.validateSubmitEnterpriseInfoCommand(cmd); err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("命令验证失败: %s", err.Error())), err
}
// 2. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 3. 验证业务前置条件
if err := o.validateEnterpriseInfoSubmissionPreconditions(cert, cmd.UserID); err != nil {
return o.createFailureResult(cmd.CertificationID, cert.Status, err.Error()), err
}
// 4. 执行状态转换
metadata := map[string]interface{}{
"enterprise_info": cmd.EnterpriseInfo,
"user_id": cmd.UserID,
}
result, err := o.aggregateService.TransitionState(
ctx,
cmd.CertificationID,
enums.StatusInfoSubmitted,
enums.ActorTypeUser,
cmd.UserID,
"用户提交企业信息",
metadata,
)
if err != nil {
o.logger.Error("企业信息提交状态转换失败", zap.Error(err))
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("状态转换失败: %s", err.Error())), err
}
// 5. 执行后续处理如调用e签宝API
if err := o.triggerEnterpriseVerification(ctx, cmd.CertificationID, cmd.EnterpriseInfo); err != nil {
o.logger.Warn("触发企业认证失败", zap.Error(err))
// 这里不返回错误因为状态已经成功转换e签宝调用失败可以通过重试机制处理
}
// 6. 构建成功结果
return o.createSuccessResult(cmd.CertificationID, enums.StatusInfoSubmitted, "企业信息提交成功", map[string]interface{}{
"enterprise_info": cmd.EnterpriseInfo,
"next_action": "等待企业认证结果",
}, result), nil
}
// ApplyContract 用户申请合同签署
func (o *CertificationWorkflowOrchestratorImpl) ApplyContract(
ctx context.Context,
cmd *ApplyContractCommand,
) (*WorkflowResult, error) {
o.logger.Info("开始处理合同申请",
zap.String("certification_id", cmd.CertificationID),
zap.String("user_id", cmd.UserID))
// 1. 验证命令完整性
if err := o.validateApplyContractCommand(cmd); err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("命令验证失败: %s", err.Error())), err
}
// 2. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 3. 验证业务前置条件
if err := o.validateContractApplicationPreconditions(cert, cmd.UserID); err != nil {
return o.createFailureResult(cmd.CertificationID, cert.Status, err.Error()), err
}
// 4. 执行状态转换
result, err := o.aggregateService.TransitionState(
ctx,
cmd.CertificationID,
enums.StatusContractApplied,
enums.ActorTypeUser,
cmd.UserID,
"用户申请合同签署",
map[string]interface{}{},
)
if err != nil {
o.logger.Error("合同申请状态转换失败", zap.Error(err))
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("状态转换失败: %s", err.Error())), err
}
// 5. 生成合同和签署链接
contractInfo, err := o.generateContractAndSignURL(ctx, cmd.CertificationID, cert)
if err != nil {
o.logger.Error("生成合同失败", zap.Error(err))
// 需要回滚状态
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("生成合同失败: %s", err.Error())), err
}
// 6. 构建成功结果
return o.createSuccessResult(cmd.CertificationID, enums.StatusContractApplied, "合同申请成功", map[string]interface{}{
"contract_sign_url": contractInfo.ContractSignURL,
"contract_url": contractInfo.ContractURL,
"next_action": "请在规定时间内完成合同签署",
}, result), nil
}
// ================ e签宝回调处理 ================
// HandleEnterpriseVerificationCallback 处理企业认证回调
func (o *CertificationWorkflowOrchestratorImpl) HandleEnterpriseVerificationCallback(
ctx context.Context,
cmd *EsignCallbackCommand,
) (*WorkflowResult, error) {
o.logger.Info("开始处理企业认证回调",
zap.String("certification_id", cmd.CertificationID),
zap.String("callback_type", cmd.CallbackType))
// 1. 验证回调数据
if err := o.callbackHandler.ValidateCallbackData(cmd.CallbackData); err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("回调数据验证失败: %s", err.Error())), err
}
// 2. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 3. 验证回调处理前置条件
if cert.Status != enums.StatusInfoSubmitted {
return o.createFailureResult(cmd.CertificationID, cert.Status,
fmt.Sprintf("当前状态 %s 不允许处理企业认证回调", enums.GetStatusName(cert.Status))),
fmt.Errorf("无效的状态转换")
}
// 4. 处理回调
err = o.callbackHandler.HandleCallback(ctx, cmd.CertificationID, cmd.CallbackData)
if err != nil {
o.logger.Error("处理企业认证回调失败", zap.Error(err))
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("回调处理失败: %s", err.Error())), err
}
// 5. 重新加载认证信息获取最新状态
updatedCert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, cert.Status, "加载更新后的认证信息失败"), err
}
// 6. 构建结果
message := "企业认证回调处理成功"
data := map[string]interface{}{
"auth_flow_id": cmd.CallbackData.FlowID,
"status": cmd.CallbackData.Status,
}
if updatedCert.Status == enums.StatusEnterpriseVerified {
message = "企业认证成功"
data["next_action"] = "可以申请合同签署"
} else if updatedCert.Status == enums.StatusInfoRejected {
message = "企业认证失败"
data["next_action"] = "请修正企业信息后重新提交"
data["failure_reason"] = enums.GetFailureReasonName(updatedCert.FailureReason)
data["failure_message"] = updatedCert.FailureMessage
}
return o.createSuccessResult(cmd.CertificationID, updatedCert.Status, message, data, nil), nil
}
// HandleContractSignCallback 处理合同签署回调
func (o *CertificationWorkflowOrchestratorImpl) HandleContractSignCallback(
ctx context.Context,
cmd *EsignCallbackCommand,
) (*WorkflowResult, error) {
o.logger.Info("开始处理合同签署回调",
zap.String("certification_id", cmd.CertificationID),
zap.String("callback_type", cmd.CallbackType))
// 1. 验证回调数据
if err := o.callbackHandler.ValidateCallbackData(cmd.CallbackData); err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("回调数据验证失败: %s", err.Error())), err
}
// 2. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 3. 验证回调处理前置条件
if cert.Status != enums.StatusContractApplied {
return o.createFailureResult(cmd.CertificationID, cert.Status,
fmt.Sprintf("当前状态 %s 不允许处理合同签署回调", enums.GetStatusName(cert.Status))),
fmt.Errorf("无效的状态转换")
}
// 4. 处理回调
err = o.callbackHandler.HandleCallback(ctx, cmd.CertificationID, cmd.CallbackData)
if err != nil {
o.logger.Error("处理合同签署回调失败", zap.Error(err))
return o.createFailureResult(cmd.CertificationID, cert.Status, fmt.Sprintf("回调处理失败: %s", err.Error())), err
}
// 5. 重新加载认证信息获取最新状态
updatedCert, err := o.aggregateService.LoadCertification(ctx, cmd.CertificationID)
if err != nil {
return o.createFailureResult(cmd.CertificationID, cert.Status, "加载更新后的认证信息失败"), err
}
// 6. 构建结果
message := "合同签署回调处理成功"
data := map[string]interface{}{
"esign_flow_id": cmd.CallbackData.FlowID,
"status": cmd.CallbackData.Status,
}
if updatedCert.Status == enums.StatusContractSigned {
message = "认证完成"
data["next_action"] = "认证流程已完成"
data["contract_url"] = updatedCert.ContractURL
} else if enums.IsFailureStatus(updatedCert.Status) {
message = "合同签署失败"
data["next_action"] = "可以重新申请合同签署"
data["failure_reason"] = enums.GetFailureReasonName(updatedCert.FailureReason)
data["failure_message"] = updatedCert.FailureMessage
}
return o.createSuccessResult(cmd.CertificationID, updatedCert.Status, message, data, nil), nil
}
// ================ 异常处理 ================
// HandleFailure 处理业务失败
func (o *CertificationWorkflowOrchestratorImpl) HandleFailure(
ctx context.Context,
certificationID string,
failureType string,
reason string,
) (*WorkflowResult, error) {
o.logger.Info("开始处理业务失败",
zap.String("certification_id", certificationID),
zap.String("failure_type", failureType),
zap.String("reason", reason))
// 1. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, certificationID)
if err != nil {
return o.createFailureResult(certificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 2. 根据失败类型执行相应处理
var targetStatus enums.CertificationStatus
var failureReason enums.FailureReason
switch failureType {
case "enterprise_verification_failed":
targetStatus = enums.StatusInfoRejected
failureReason = enums.FailureReasonEsignVerificationFailed
case "contract_sign_failed":
targetStatus = enums.StatusContractRejected
failureReason = enums.FailureReasonSignProcessFailed
case "contract_expired":
targetStatus = enums.StatusContractExpired
failureReason = enums.FailureReasonContractExpired
default:
return o.createFailureResult(certificationID, cert.Status, fmt.Sprintf("未知的失败类型: %s", failureType)),
fmt.Errorf("未知的失败类型")
}
// 3. 执行状态转换
metadata := map[string]interface{}{
"failure_reason": failureReason,
"failure_message": reason,
}
result, err := o.aggregateService.TransitionState(
ctx,
certificationID,
targetStatus,
enums.ActorTypeSystem,
"failure_handler",
fmt.Sprintf("系统处理失败: %s", reason),
metadata,
)
if err != nil {
return o.createFailureResult(certificationID, cert.Status, fmt.Sprintf("失败处理状态转换失败: %s", err.Error())), err
}
return o.createSuccessResult(certificationID, targetStatus, "失败处理完成", map[string]interface{}{
"failure_type": failureType,
"failure_reason": enums.GetFailureReasonName(failureReason),
"can_retry": enums.IsRetryable(failureReason),
}, result), nil
}
// RetryOperation 重试操作
func (o *CertificationWorkflowOrchestratorImpl) RetryOperation(
ctx context.Context,
certificationID string,
operation string,
) (*WorkflowResult, error) {
o.logger.Info("开始重试操作",
zap.String("certification_id", certificationID),
zap.String("operation", operation))
// 1. 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, certificationID)
if err != nil {
return o.createFailureResult(certificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 2. 检查是否可以重试
if !enums.IsFailureStatus(cert.Status) {
return o.createFailureResult(certificationID, cert.Status, "当前状态不是失败状态,无需重试"),
fmt.Errorf("不需要重试")
}
if !enums.IsRetryable(cert.FailureReason) {
return o.createFailureResult(certificationID, cert.Status,
fmt.Sprintf("失败原因 %s 不支持重试", enums.GetFailureReasonName(cert.FailureReason))),
fmt.Errorf("不支持重试")
}
if cert.RetryCount >= 3 {
return o.createFailureResult(certificationID, cert.Status, "已达到最大重试次数限制"),
fmt.Errorf("超过重试限制")
}
// 3. 根据操作类型执行重试
var targetStatus enums.CertificationStatus
var reason string
switch operation {
case "enterprise_verification":
if cert.Status != enums.StatusInfoRejected {
return o.createFailureResult(certificationID, cert.Status, "当前状态不支持企业认证重试"),
fmt.Errorf("无效的重试操作")
}
targetStatus = enums.StatusInfoSubmitted
reason = "重新提交企业信息"
case "contract_application":
if cert.Status != enums.StatusContractRejected && cert.Status != enums.StatusContractExpired {
return o.createFailureResult(certificationID, cert.Status, "当前状态不支持合同申请重试"),
fmt.Errorf("无效的重试操作")
}
targetStatus = enums.StatusEnterpriseVerified
reason = "重置状态,准备重新申请合同"
default:
return o.createFailureResult(certificationID, cert.Status, fmt.Sprintf("不支持的重试操作: %s", operation)),
fmt.Errorf("不支持的重试操作")
}
// 4. 执行状态转换
result, err := o.aggregateService.TransitionState(
ctx,
certificationID,
targetStatus,
enums.ActorTypeSystem,
"retry_handler",
reason,
map[string]interface{}{},
)
if err != nil {
return o.createFailureResult(certificationID, cert.Status, fmt.Sprintf("重试状态转换失败: %s", err.Error())), err
}
return o.createSuccessResult(certificationID, targetStatus, "重试操作成功", map[string]interface{}{
"retry_operation": operation,
"retry_count": cert.RetryCount + 1,
"next_action": o.getNextActionForStatus(targetStatus),
}, result), nil
}
// ================ 查询操作 ================
// GetWorkflowStatus 获取工作流状态
func (o *CertificationWorkflowOrchestratorImpl) GetWorkflowStatus(
ctx context.Context,
certificationID string,
) (*WorkflowResult, error) {
// 加载认证聚合根
cert, err := o.aggregateService.LoadCertification(ctx, certificationID)
if err != nil {
return o.createFailureResult(certificationID, "", fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
// 构建状态信息
data := map[string]interface{}{
"status": cert.Status,
"status_name": enums.GetStatusName(cert.Status),
"progress": cert.GetProgress(),
"is_final": cert.IsFinalStatus(),
"is_completed": cert.IsCompleted(),
"user_action_required": cert.IsUserActionRequired(),
"next_action": o.getNextActionForStatus(cert.Status),
"available_actions": cert.GetAvailableActions(),
}
// 添加失败信息(如果存在)
if enums.IsFailureStatus(cert.Status) {
data["failure_reason"] = enums.GetFailureReasonName(cert.FailureReason)
data["failure_message"] = cert.FailureMessage
data["can_retry"] = enums.IsRetryable(cert.FailureReason)
data["retry_count"] = cert.RetryCount
}
// 添加时间戳信息
if cert.InfoSubmittedAt != nil {
data["info_submitted_at"] = cert.InfoSubmittedAt
}
if cert.EnterpriseVerifiedAt != nil {
data["enterprise_verified_at"] = cert.EnterpriseVerifiedAt
}
if cert.ContractAppliedAt != nil {
data["contract_applied_at"] = cert.ContractAppliedAt
}
if cert.ContractSignedAt != nil {
data["contract_signed_at"] = cert.ContractSignedAt
}
return o.createSuccessResult(certificationID, cert.Status, "工作流状态查询成功", data, nil), nil
}
// ================ 辅助方法 ================
// validateSubmitEnterpriseInfoCommand 验证提交企业信息命令
func (o *CertificationWorkflowOrchestratorImpl) validateSubmitEnterpriseInfoCommand(cmd *SubmitEnterpriseInfoCommand) error {
if cmd.CertificationID == "" {
return fmt.Errorf("认证ID不能为空")
}
if cmd.UserID == "" {
return fmt.Errorf("用户ID不能为空")
}
if cmd.EnterpriseInfo == nil {
return fmt.Errorf("企业信息不能为空")
}
return cmd.EnterpriseInfo.Validate()
}
// validateApplyContractCommand 验证申请合同命令
func (o *CertificationWorkflowOrchestratorImpl) validateApplyContractCommand(cmd *ApplyContractCommand) error {
if cmd.CertificationID == "" {
return fmt.Errorf("认证ID不能为空")
}
if cmd.UserID == "" {
return fmt.Errorf("用户ID不能为空")
}
return nil
}
// validateEnterpriseInfoSubmissionPreconditions 验证企业信息提交前置条件
func (o *CertificationWorkflowOrchestratorImpl) validateEnterpriseInfoSubmissionPreconditions(cert *entities.Certification, userID string) error {
if cert.UserID != userID {
return fmt.Errorf("用户无权限操作此认证申请")
}
if cert.Status != enums.StatusPending && cert.Status != enums.StatusInfoRejected {
return fmt.Errorf("当前状态 %s 不允许提交企业信息", enums.GetStatusName(cert.Status))
}
return nil
}
// validateContractApplicationPreconditions 验证合同申请前置条件
func (o *CertificationWorkflowOrchestratorImpl) validateContractApplicationPreconditions(cert *entities.Certification, userID string) error {
if cert.UserID != userID {
return fmt.Errorf("用户无权限操作此认证申请")
}
if cert.Status != enums.StatusEnterpriseVerified {
return fmt.Errorf("必须先完成企业认证才能申请合同")
}
if cert.AuthFlowID == "" {
return fmt.Errorf("缺少企业认证流程ID")
}
return nil
}
// triggerEnterpriseVerification 触发企业认证
func (o *CertificationWorkflowOrchestratorImpl) triggerEnterpriseVerification(ctx context.Context, certificationID string, enterpriseInfo *value_objects.EnterpriseInfo) error {
// TODO: 调用e签宝API进行企业认证
o.logger.Info("触发企业认证",
zap.String("certification_id", certificationID),
zap.String("company_name", enterpriseInfo.CompanyName))
return nil
}
// generateContractAndSignURL 生成合同和签署链接
func (o *CertificationWorkflowOrchestratorImpl) generateContractAndSignURL(ctx context.Context, certificationID string, cert *entities.Certification) (*value_objects.ContractInfo, error) {
// TODO: 调用e签宝API生成合同和签署链接
o.logger.Info("生成合同和签署链接", zap.String("certification_id", certificationID))
// 临时返回模拟数据
contractInfo, err := value_objects.NewContractInfo(
"contract_file_"+certificationID,
"esign_flow_"+certificationID,
"https://example.com/contract/"+certificationID,
"https://example.com/sign/"+certificationID,
)
if err != nil {
return nil, err
}
return contractInfo, nil
}
// getNextActionForStatus 获取状态对应的下一步操作提示
func (o *CertificationWorkflowOrchestratorImpl) getNextActionForStatus(status enums.CertificationStatus) string {
return enums.GetUserActionHint(status)
}
// createSuccessResult 创建成功结果
func (o *CertificationWorkflowOrchestratorImpl) createSuccessResult(
certificationID string,
status enums.CertificationStatus,
message string,
data map[string]interface{},
stateTransition *state_machine.StateTransitionResult,
) *WorkflowResult {
return &WorkflowResult{
Success: true,
CertificationID: certificationID,
CurrentStatus: status,
Message: message,
Data: data,
StateTransition: stateTransition,
ExecutedAt: time.Now(),
}
}
// createFailureResult 创建失败结果
func (o *CertificationWorkflowOrchestratorImpl) createFailureResult(
certificationID string,
status enums.CertificationStatus,
message string,
) *WorkflowResult {
return &WorkflowResult{
Success: false,
CertificationID: certificationID,
CurrentStatus: status,
Message: message,
Data: map[string]interface{}{},
ExecutedAt: time.Now(),
}
}

View File

@@ -1,162 +0,0 @@
package services
import (
"context"
"fmt"
"go.uber.org/zap"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
)
// CertificationWorkflowService 认证工作流领域服务
// 负责认证流程的状态转换和业务逻辑处理
type CertificationWorkflowService struct {
certRepo repositories.CertificationRepository
stateMachine *CertificationStateMachine
logger *zap.Logger
}
// NewCertificationWorkflowService 创建认证工作流领域服务
func NewCertificationWorkflowService(
certRepo repositories.CertificationRepository,
stateMachine *CertificationStateMachine,
logger *zap.Logger,
) *CertificationWorkflowService {
return &CertificationWorkflowService{
certRepo: certRepo,
stateMachine: stateMachine,
logger: logger,
}
}
// SubmitEnterpriseInfo 提交企业信息
func (s *CertificationWorkflowService) SubmitEnterpriseInfo(ctx context.Context, certificationID string) error {
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
return fmt.Errorf("认证申请不存在: %w", err)
}
// 检查当前状态是否可以提交企业信息
if cert.Status != enums.StatusPending && cert.Status != enums.StatusInfoSubmitted {
return fmt.Errorf("当前状态不允许提交企业信息")
}
// 使用状态机转换状态
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusInfoSubmitted, true, false, nil); err != nil {
return fmt.Errorf("状态转换失败: %w", err)
}
s.logger.Info("企业信息提交成功",
zap.String("certification_id", certificationID),
zap.String("user_id", cert.UserID),
)
return nil
}
// CompleteEnterpriseVerification 完成企业认证
func (s *CertificationWorkflowService) CompleteEnterpriseVerification(ctx context.Context, certificationID string) error {
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
return fmt.Errorf("认证申请不存在: %w", err)
}
// 检查当前状态是否可以完成企业认证
if cert.Status != enums.StatusInfoSubmitted {
return fmt.Errorf("当前状态不允许完成企业认证")
}
// 使用状态机转换状态
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusEnterpriseVerified, true, false, nil); err != nil {
return fmt.Errorf("状态转换失败: %w", err)
}
s.logger.Info("企业认证完成",
zap.String("certification_id", certificationID),
zap.String("user_id", cert.UserID),
)
return nil
}
// ApplyContract 申请签署合同
func (s *CertificationWorkflowService) ApplyContract(ctx context.Context, certificationID string) error {
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
return fmt.Errorf("认证申请不存在: %w", err)
}
// 检查当前状态是否可以申请签署合同
if cert.Status != enums.StatusEnterpriseVerified {
return fmt.Errorf("当前状态不允许申请签署合同")
}
// 使用状态机转换状态
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractApplied, true, false, nil); err != nil {
return fmt.Errorf("状态转换失败: %w", err)
}
s.logger.Info("签署合同申请成功",
zap.String("certification_id", certificationID),
zap.String("user_id", cert.UserID),
)
return nil
}
// CompleteContractSign 完成合同签署
func (s *CertificationWorkflowService) CompleteContractSign(ctx context.Context, certificationID, contractURL string) error {
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
return fmt.Errorf("认证申请不存在: %w", err)
}
// 检查当前状态是否可以签署
if cert.Status != enums.StatusContractApplied {
return fmt.Errorf("当前状态不允许签署")
}
// 准备签署元数据
metadata := map[string]interface{}{
"contract_url": contractURL,
}
// 使用状态机转换状态
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractSigned, true, false, metadata); err != nil {
return fmt.Errorf("状态转换失败: %w", err)
}
s.logger.Info("合同签署完成",
zap.String("certification_id", certificationID),
zap.String("user_id", cert.UserID),
)
return nil
}
// CompleteCertification 完成认证
func (s *CertificationWorkflowService) CompleteCertification(ctx context.Context, certificationID string) error {
cert, err := s.certRepo.GetByID(ctx, certificationID)
if err != nil {
return fmt.Errorf("认证申请不存在: %w", err)
}
// 检查当前状态是否可以完成
if cert.Status != enums.StatusContractSigned {
return fmt.Errorf("当前状态不允许完成认证")
}
// 使用状态机转换状态
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusCompleted, false, false, nil); err != nil {
return fmt.Errorf("状态转换失败: %w", err)
}
s.logger.Info("认证完成",
zap.String("certification_id", certificationID),
zap.String("user_id", cert.UserID),
)
return nil
}

View File

@@ -1,180 +0,0 @@
package services
import (
"context"
"fmt"
"go.uber.org/zap"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/repositories"
)
// EnterpriseInfoSubmitRecordService 企业信息提交记录领域服务
// 负责企业信息提交记录的业务逻辑处理
type EnterpriseInfoSubmitRecordService struct {
enterpriseRecordRepo repositories.EnterpriseInfoSubmitRecordRepository
logger *zap.Logger
}
// NewEnterpriseInfoSubmitRecordService 创建企业信息提交记录领域服务
func NewEnterpriseInfoSubmitRecordService(
enterpriseRecordRepo repositories.EnterpriseInfoSubmitRecordRepository,
logger *zap.Logger,
) *EnterpriseInfoSubmitRecordService {
return &EnterpriseInfoSubmitRecordService{
enterpriseRecordRepo: enterpriseRecordRepo,
logger: logger,
}
}
// CreateEnterpriseInfoSubmitRecord 创建企业信息提交记录
func (s *EnterpriseInfoSubmitRecordService) CreateEnterpriseInfoSubmitRecord(
ctx context.Context,
userID string,
companyName string,
unifiedSocialCode string,
legalPersonName string,
legalPersonID string,
legalPersonPhone string,
) (*entities.EnterpriseInfoSubmitRecord, error) {
// 创建企业信息提交记录实体
record := entities.NewEnterpriseInfoSubmitRecord(
userID,
companyName,
unifiedSocialCode,
legalPersonName,
legalPersonID,
legalPersonPhone,
)
// 保存到仓储
createdRecord, err := s.enterpriseRecordRepo.Create(ctx, *record)
if err != nil {
s.logger.Error("创建企业信息提交记录失败",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("创建企业信息提交记录失败: %w", err)
}
s.logger.Info("企业信息提交记录创建成功",
zap.String("record_id", createdRecord.ID),
zap.String("user_id", userID),
zap.String("company_name", companyName))
return &createdRecord, nil
}
// GetLatestByUserID 根据用户ID获取最新的企业信息提交记录
func (s *EnterpriseInfoSubmitRecordService) GetLatestByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error) {
record, err := s.enterpriseRecordRepo.GetLatestByUserID(ctx, userID)
if err != nil {
s.logger.Error("获取企业信息提交记录失败",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("获取企业信息提交记录失败: %w", err)
}
return record, nil
}
// UpdateEnterpriseInfoSubmitRecord 更新企业信息提交记录
func (s *EnterpriseInfoSubmitRecordService) UpdateEnterpriseInfoSubmitRecord(ctx context.Context, record *entities.EnterpriseInfoSubmitRecord) error {
err := s.enterpriseRecordRepo.Update(ctx, *record)
if err != nil {
s.logger.Error("更新企业信息提交记录失败",
zap.String("record_id", record.ID),
zap.Error(err))
return fmt.Errorf("更新企业信息提交记录失败: %w", err)
}
s.logger.Info("企业信息提交记录更新成功",
zap.String("record_id", record.ID),
zap.String("status", record.Status))
return nil
}
// MarkAsVerified 标记企业信息为已验证
func (s *EnterpriseInfoSubmitRecordService) MarkAsVerified(ctx context.Context, recordID string) error {
record, err := s.enterpriseRecordRepo.GetByID(ctx, recordID)
if err != nil {
return fmt.Errorf("获取企业信息提交记录失败: %w", err)
}
record.MarkAsVerified()
err = s.enterpriseRecordRepo.Update(ctx, record)
if err != nil {
s.logger.Error("标记企业信息为已验证失败",
zap.String("record_id", recordID),
zap.Error(err))
return fmt.Errorf("标记企业信息为已验证失败: %w", err)
}
s.logger.Info("企业信息标记为已验证成功",
zap.String("record_id", recordID))
return nil
}
// UpdateVerificationStatus 更新企业信息验证状态
func (s *EnterpriseInfoSubmitRecordService) UpdateVerificationStatus(ctx context.Context, userID string, isVerified bool, reason string) error {
// 获取用户最新的企业信息提交记录
record, err := s.enterpriseRecordRepo.GetLatestByUserID(ctx, userID)
if err != nil {
return fmt.Errorf("获取企业信息提交记录失败: %w", err)
}
// 更新验证状态
if isVerified {
record.MarkAsVerified()
} else {
record.MarkAsFailed(reason)
}
// 保存更新
err = s.enterpriseRecordRepo.Update(ctx, *record)
if err != nil {
s.logger.Error("更新企业信息验证状态失败",
zap.String("user_id", userID),
zap.Bool("is_verified", isVerified),
zap.Error(err))
return fmt.Errorf("更新企业信息验证状态失败: %w", err)
}
s.logger.Info("企业信息验证状态更新成功",
zap.String("user_id", userID),
zap.Bool("is_verified", isVerified),
zap.String("reason", reason))
return nil
}
// DeleteEnterpriseInfoSubmitRecord 删除企业信息提交记录
func (s *EnterpriseInfoSubmitRecordService) DeleteEnterpriseInfoSubmitRecord(ctx context.Context, recordID string) error {
err := s.enterpriseRecordRepo.Delete(ctx, recordID)
if err != nil {
s.logger.Error("删除企业信息提交记录失败",
zap.String("record_id", recordID),
zap.Error(err))
return fmt.Errorf("删除企业信息提交记录失败: %w", err)
}
s.logger.Info("企业信息提交记录删除成功",
zap.String("record_id", recordID))
return nil
}
// GetByUserID 根据用户ID获取企业信息提交记录列表
func (s *EnterpriseInfoSubmitRecordService) GetByUserID(ctx context.Context, userID string) ([]*entities.EnterpriseInfoSubmitRecord, error) {
records, err := s.enterpriseRecordRepo.GetByUserID(ctx, userID)
if err != nil {
s.logger.Error("获取用户企业信息提交记录失败",
zap.String("user_id", userID),
zap.Error(err))
return nil, fmt.Errorf("获取用户企业信息提交记录失败: %w", err)
}
return records, nil
}

View File

@@ -97,18 +97,9 @@ func (manager *CertificationStateManager) initStateConfigs() {
IsAdminActionRequired: false,
TimestampField: "ContractSignedAt",
Description: "合同已签署",
NextValidStatuses: []enums.CertificationStatus{enums.StatusCompleted},
},
{
Status: enums.StatusCompleted,
Name: "认证完成",
ProgressPercentage: 100,
IsUserActionRequired: false,
IsAdminActionRequired: false,
TimestampField: "CompletedAt",
Description: "认证流程已完成",
NextValidStatuses: []enums.CertificationStatus{},
},
// 已完成状态已合并到StatusContractSigned中
}
// 转换配置
@@ -168,17 +159,7 @@ func (manager *CertificationStateManager) initStateConfigs() {
RequiresValidation: true,
Description: "用户签署合同",
},
// 完成认证
{
From: enums.StatusContractSigned,
To: enums.StatusCompleted,
Action: "complete",
ActionName: "完成认证",
AllowUser: false,
AllowAdmin: false,
RequiresValidation: false,
Description: "系统自动完成认证",
},
// 合同签署即为认证完成,无需额外状态转换
}
// 构建映射

View File

@@ -1,258 +0,0 @@
package services
import (
"context"
"fmt"
"time"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
"go.uber.org/zap"
)
// CertificationStateMachine 认证状态机
type CertificationStateMachine struct {
stateManager *CertificationStateManager
certRepo repositories.CertificationRepository
logger *zap.Logger
}
// NewCertificationStateMachine 创建认证状态机
func NewCertificationStateMachine(
certRepo repositories.CertificationRepository,
logger *zap.Logger,
) *CertificationStateMachine {
return &CertificationStateMachine{
stateManager: NewCertificationStateManager(),
certRepo: certRepo,
logger: logger,
}
}
// CanTransition 检查是否可以转换到指定状态
func (sm *CertificationStateMachine) CanTransition(
from enums.CertificationStatus,
to enums.CertificationStatus,
isUser bool,
isAdmin bool,
) (bool, string) {
return sm.stateManager.CanTransition(from, to, isUser, isAdmin)
}
// TransitionTo 执行状态转换
func (sm *CertificationStateMachine) TransitionTo(
ctx context.Context,
certificationID string,
targetStatus enums.CertificationStatus,
isUser bool,
isAdmin bool,
metadata map[string]interface{},
) error {
// 获取当前认证记录
cert, err := sm.certRepo.GetByID(ctx, certificationID)
if err != nil {
return fmt.Errorf("获取认证记录失败: %w", err)
}
// 检查是否可以转换
canTransition, reason := sm.CanTransition(cert.Status, targetStatus, isUser, isAdmin)
if !canTransition {
return fmt.Errorf("状态转换失败: %s", reason)
}
// 更新状态和时间戳
oldStatus := cert.Status
cert.Status = targetStatus
sm.updateTimestamp(&cert, targetStatus)
// 更新其他字段
sm.updateCertificationFields(&cert, targetStatus, metadata)
// 保存到数据库
if err := sm.certRepo.Update(ctx, cert); 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(targetStatus)),
zap.Bool("is_user", isUser),
zap.Bool("is_admin", isAdmin),
)
return nil
}
// updateTimestamp 更新对应的时间戳字段
func (sm *CertificationStateMachine) updateTimestamp(cert *entities.Certification, status enums.CertificationStatus) {
stateConfig := sm.stateManager.GetStateConfig(status)
if stateConfig == nil || stateConfig.TimestampField == "" {
return
}
now := time.Now()
switch stateConfig.TimestampField {
case "InfoSubmittedAt":
cert.InfoSubmittedAt = &now
case "EnterpriseVerifiedAt":
cert.EnterpriseVerifiedAt = &now
case "ContractAppliedAt":
cert.ContractAppliedAt = &now
case "ContractSignedAt":
cert.ContractSignedAt = &now
case "CompletedAt":
cert.CompletedAt = &now
}
}
// updateCertificationFields 根据状态更新认证记录的其他字段
func (sm *CertificationStateMachine) updateCertificationFields(
cert *entities.Certification,
status enums.CertificationStatus,
metadata map[string]interface{},
) {
switch status {
case enums.StatusContractSigned:
if contractURL, ok := metadata["contract_url"].(string); ok {
cert.ContractURL = contractURL
}
}
}
// GetValidNextStatuses 获取当前状态可以转换到的下一个状态列表
func (sm *CertificationStateMachine) GetValidNextStatuses(
currentStatus enums.CertificationStatus,
isUser bool,
isAdmin bool,
) []enums.CertificationStatus {
return sm.stateManager.GetNextValidStatuses(currentStatus)
}
// GetTransitionAction 获取状态转换对应的操作名称
func (sm *CertificationStateMachine) GetTransitionAction(
from enums.CertificationStatus,
to enums.CertificationStatus,
) string {
transitions := sm.stateManager.GetTransitionConfigs(from)
for _, transition := range transitions {
if transition.To == to {
return transition.Action
}
}
return ""
}
// GetTransitionActionName 获取状态转换对应的操作中文名称
func (sm *CertificationStateMachine) GetTransitionActionName(
from enums.CertificationStatus,
to enums.CertificationStatus,
) string {
transitions := sm.stateManager.GetTransitionConfigs(from)
for _, transition := range transitions {
if transition.To == to {
return transition.ActionName
}
}
return ""
}
// GetStateConfig 获取状态配置
func (sm *CertificationStateMachine) GetStateConfig(status enums.CertificationStatus) *StateConfig {
return sm.stateManager.GetStateConfig(status)
}
// GetProgressPercentage 获取进度百分比
func (sm *CertificationStateMachine) GetProgressPercentage(status enums.CertificationStatus) int {
return sm.stateManager.GetProgressPercentage(status)
}
// IsUserActionRequired 检查是否需要用户操作
func (sm *CertificationStateMachine) IsUserActionRequired(status enums.CertificationStatus) bool {
return sm.stateManager.IsUserActionRequired(status)
}
// IsAdminActionRequired 检查是否需要管理员操作
func (sm *CertificationStateMachine) IsAdminActionRequired(status enums.CertificationStatus) bool {
return sm.stateManager.IsAdminActionRequired(status)
}
// GetTransitionHistory 获取状态转换历史
func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, certificationID string) ([]map[string]interface{}, error) {
cert, err := sm.certRepo.GetByID(ctx, certificationID)
if err != nil {
return nil, fmt.Errorf("获取认证记录失败: %w", err)
}
history := []map[string]interface{}{}
// 添加创建时间
history = append(history, map[string]interface{}{
"status": "CREATED",
"timestamp": cert.CreatedAt,
"action": "create",
"performer": "system",
"metadata": map[string]interface{}{},
})
// 添加各个时间节点的状态转换
if cert.InfoSubmittedAt != nil {
history = append(history, map[string]interface{}{
"status": string(enums.StatusInfoSubmitted),
"timestamp": *cert.InfoSubmittedAt,
"action": "submit_info",
"performer": "user",
"metadata": map[string]interface{}{},
})
}
if cert.EnterpriseVerifiedAt != nil {
history = append(history, map[string]interface{}{
"status": string(enums.StatusEnterpriseVerified),
"timestamp": *cert.EnterpriseVerifiedAt,
"action": "enterprise_verify",
"performer": "user",
"metadata": map[string]interface{}{},
})
}
if cert.ContractAppliedAt != nil {
history = append(history, map[string]interface{}{
"status": string(enums.StatusContractApplied),
"timestamp": *cert.ContractAppliedAt,
"action": "apply_contract",
"performer": "user",
"metadata": map[string]interface{}{},
})
}
if cert.ContractSignedAt != nil {
metadata := map[string]interface{}{}
if cert.ContractURL != "" {
metadata["contract_url"] = cert.ContractURL
}
history = append(history, map[string]interface{}{
"status": string(enums.StatusContractSigned),
"timestamp": *cert.ContractSignedAt,
"action": "sign_contract",
"performer": "user",
"metadata": metadata,
})
}
if cert.CompletedAt != nil {
history = append(history, map[string]interface{}{
"status": string(enums.StatusCompleted),
"timestamp": *cert.CompletedAt,
"action": "complete",
"performer": "system",
"metadata": map[string]interface{}{},
})
}
return history, nil
}

View File

@@ -0,0 +1,455 @@
package state_machine
import (
"context"
"fmt"
"time"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
"go.uber.org/zap"
)
// CertificationStateMachine 认证状态机
// 负责管理认证流程的状态转换、业务规则验证和事件发布
type CertificationStateMachine struct {
configManager *StateConfigManager
repository repositories.CertificationCommandRepository
eventPublisher interface{} // TODO: 使用 interfaces.EventPublisher
logger *zap.Logger
}
// NewCertificationStateMachine 创建认证状态机
func NewCertificationStateMachine(
repository repositories.CertificationCommandRepository,
eventPublisher interface{}, // TODO: 使用 interfaces.EventPublisher
logger *zap.Logger,
) *CertificationStateMachine {
return &CertificationStateMachine{
configManager: NewStateConfigManager(),
repository: repository,
eventPublisher: eventPublisher,
logger: logger,
}
}
// StateTransitionRequest 状态转换请求
type StateTransitionRequest struct {
CertificationID string `json:"certification_id"`
TargetStatus enums.CertificationStatus `json:"target_status"`
Actor enums.ActorType `json:"actor"`
ActorID string `json:"actor_id"`
Reason string `json:"reason"`
Context map[string]interface{} `json:"context"`
AllowRollback bool `json:"allow_rollback"`
}
// StateTransitionResult 状态转换结果
type StateTransitionResult struct {
Success bool `json:"success"`
OldStatus enums.CertificationStatus `json:"old_status"`
NewStatus enums.CertificationStatus `json:"new_status"`
Message string `json:"message"`
TransitionedAt time.Time `json:"transitioned_at"`
Events []interface{} `json:"events,omitempty"`
}
// CanTransition 检查是否可以执行状态转换
func (sm *CertificationStateMachine) CanTransition(
cert *entities.Certification,
targetStatus enums.CertificationStatus,
actor enums.ActorType,
) (bool, string) {
// 1. 检查基本状态转换规则
canTransition, message := sm.configManager.CanTransition(cert.Status, targetStatus, actor)
if !canTransition {
return false, message
}
// 2. 检查认证实体的业务规则
if canTransition, message := cert.CanTransitionTo(targetStatus, actor); !canTransition {
return false, message
}
// 3. 检查是否为最终状态
if cert.IsFinalStatus() {
return false, "认证已完成,无法进行状态转换"
}
return true, ""
}
// ExecuteTransition 执行状态转换
func (sm *CertificationStateMachine) ExecuteTransition(
ctx context.Context,
req *StateTransitionRequest,
) (*StateTransitionResult, error) {
sm.logger.Info("开始执行状态转换",
zap.String("certification_id", req.CertificationID),
zap.String("target_status", string(req.TargetStatus)),
zap.String("actor", string(req.Actor)),
zap.String("actor_id", req.ActorID))
// 1. 加载认证聚合根
cert, err := sm.loadCertification(ctx, req.CertificationID)
if err != nil {
return sm.createFailureResult(cert.Status, req.TargetStatus, fmt.Sprintf("加载认证信息失败: %s", err.Error())), err
}
oldStatus := cert.Status
// 2. 验证转换合法性
if canTransition, message := sm.CanTransition(cert, req.TargetStatus, req.Actor); !canTransition {
return sm.createFailureResult(oldStatus, req.TargetStatus, message), fmt.Errorf("状态转换验证失败: %s", message)
}
// 3. 验证业务规则
if err := sm.validateBusinessRules(cert, req); err != nil {
return sm.createFailureResult(oldStatus, req.TargetStatus, fmt.Sprintf("业务规则验证失败: %s", err.Error())), err
}
// 4. 执行状态转换
if err := cert.TransitionTo(req.TargetStatus, req.Actor, req.ActorID, req.Reason); err != nil {
return sm.createFailureResult(oldStatus, req.TargetStatus, fmt.Sprintf("状态转换执行失败: %s", err.Error())), err
}
// 5. 保存到数据库
if err := sm.repository.Update(ctx, *cert); err != nil {
// 如果保存失败,需要回滚状态
sm.logger.Error("状态转换保存失败,尝试回滚",
zap.String("certification_id", req.CertificationID),
zap.Error(err))
if req.AllowRollback {
if rollbackErr := sm.rollbackStateTransition(ctx, cert, oldStatus, req.Actor, req.ActorID); rollbackErr != nil {
sm.logger.Error("状态回滚失败", zap.Error(rollbackErr))
}
}
return sm.createFailureResult(oldStatus, req.TargetStatus, fmt.Sprintf("保存状态转换失败: %s", err.Error())), err
}
// 6. 发布领域事件
events := cert.GetDomainEvents()
for _, event := range events {
// TODO: 实现事件发布
// if err := sm.eventPublisher.PublishEvent(ctx, event); err != nil {
// sm.logger.Error("发布领域事件失败",
// zap.String("certification_id", req.CertificationID),
// zap.Error(err))
// }
sm.logger.Info("领域事件待发布",
zap.String("certification_id", req.CertificationID),
zap.Any("event", event))
}
// 7. 清理领域事件
cert.ClearDomainEvents()
// 8. 记录成功日志
sm.logger.Info("状态转换执行成功",
zap.String("certification_id", req.CertificationID),
zap.String("from_status", string(oldStatus)),
zap.String("to_status", string(req.TargetStatus)),
zap.String("actor", string(req.Actor)))
// 9. 返回成功结果
return &StateTransitionResult{
Success: true,
OldStatus: oldStatus,
NewStatus: req.TargetStatus,
Message: "状态转换成功",
TransitionedAt: time.Now(),
Events: events,
}, nil
}
// GetValidTransitions 获取有效的状态转换
func (sm *CertificationStateMachine) GetValidTransitions(
cert *entities.Certification,
actor enums.ActorType,
) []*StateTransitionRule {
return sm.configManager.GetAllowedTransitions(cert.Status, actor)
}
// GetStateInfo 获取状态信息
func (sm *CertificationStateMachine) GetStateInfo(status enums.CertificationStatus) *StateConfig {
return sm.configManager.GetStateConfig(status)
}
// ValidateBusinessRules 验证业务规则
func (sm *CertificationStateMachine) ValidateBusinessRules(
cert *entities.Certification,
req *StateTransitionRequest,
) error {
return sm.validateBusinessRules(cert, req)
}
// IsUserActionRequired 检查是否需要用户操作
func (sm *CertificationStateMachine) IsUserActionRequired(status enums.CertificationStatus) bool {
return sm.configManager.IsUserActionRequired(status)
}
// GetProgressPercentage 获取进度百分比
func (sm *CertificationStateMachine) GetProgressPercentage(status enums.CertificationStatus) int {
return sm.configManager.GetStateProgress(status)
}
// ================ 私有方法 ================
// loadCertification 加载认证聚合根
func (sm *CertificationStateMachine) loadCertification(ctx context.Context, certificationID string) (*entities.Certification, error) {
// 这里需要通过查询仓储获取认证信息
// 由于当前只有命令仓储,这里使用简单的方法
// 在实际实现中,应该使用查询仓储
cert := &entities.Certification{ID: certificationID}
// TODO: 实现从查询仓储加载认证信息
// cert, err := sm.queryRepository.GetByID(ctx, certificationID)
// if err != nil {
// return nil, fmt.Errorf("认证信息不存在: %w", err)
// }
return cert, nil
}
// validateBusinessRules 验证业务规则
func (sm *CertificationStateMachine) validateBusinessRules(
cert *entities.Certification,
req *StateTransitionRequest,
) error {
// 获取转换规则
rule := sm.configManager.GetTransitionRule(cert.Status, req.TargetStatus)
if rule == nil {
return fmt.Errorf("找不到状态转换规则")
}
// 如果不需要验证,直接返回
if !rule.RequiresValidation {
return nil
}
// 构建验证上下文
context := make(map[string]interface{})
// 添加认证基本信息
context["certification_id"] = cert.ID
context["user_id"] = cert.UserID
context["current_status"] = string(cert.Status)
context["retry_count"] = cert.RetryCount
context["auth_flow_id"] = cert.AuthFlowID
// 添加请求中的上下文信息
for key, value := range req.Context {
context[key] = value
}
// 执行业务规则验证
return sm.configManager.ValidateBusinessRules(rule, context)
}
// rollbackStateTransition 回滚状态转换
func (sm *CertificationStateMachine) rollbackStateTransition(
ctx context.Context,
cert *entities.Certification,
originalStatus enums.CertificationStatus,
actor enums.ActorType,
actorID string,
) error {
sm.logger.Info("开始回滚状态转换",
zap.String("certification_id", cert.ID),
zap.String("original_status", string(originalStatus)),
zap.String("current_status", string(cert.Status)))
// 直接设置回原状态(跳过业务规则验证)
cert.Status = originalStatus
// 更新审计信息
now := time.Now()
cert.LastTransitionAt = &now
cert.LastTransitionBy = actor
cert.LastTransitionActor = actorID
// 保存回滚结果
if err := sm.repository.Update(ctx, *cert); err != nil {
return fmt.Errorf("保存回滚状态失败: %w", err)
}
sm.logger.Info("状态转换回滚成功",
zap.String("certification_id", cert.ID),
zap.String("rollback_to_status", string(originalStatus)))
return nil
}
// createFailureResult 创建失败结果
func (sm *CertificationStateMachine) createFailureResult(
oldStatus, targetStatus enums.CertificationStatus,
message string,
) *StateTransitionResult {
return &StateTransitionResult{
Success: false,
OldStatus: oldStatus,
NewStatus: targetStatus,
Message: message,
TransitionedAt: time.Now(),
Events: []interface{}{},
}
}
// ================ 状态转换快捷方法 ================
// TransitionToInfoSubmitted 转换到已提交企业信息状态
func (sm *CertificationStateMachine) TransitionToInfoSubmitted(
ctx context.Context,
certificationID string,
actor enums.ActorType,
actorID string,
enterpriseInfo interface{},
) (*StateTransitionResult, error) {
req := &StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: enums.StatusInfoSubmitted,
Actor: actor,
ActorID: actorID,
Reason: "用户提交企业信息",
Context: map[string]interface{}{
"enterprise_info": enterpriseInfo,
},
AllowRollback: true,
}
return sm.ExecuteTransition(ctx, req)
}
// TransitionToEnterpriseVerified 转换到已企业认证状态
func (sm *CertificationStateMachine) TransitionToEnterpriseVerified(
ctx context.Context,
certificationID string,
authFlowID string,
) (*StateTransitionResult, error) {
req := &StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: enums.StatusEnterpriseVerified,
Actor: enums.ActorTypeEsign,
ActorID: "esign_system",
Reason: "e签宝企业认证成功",
Context: map[string]interface{}{
"auth_flow_id": authFlowID,
},
AllowRollback: false,
}
return sm.ExecuteTransition(ctx, req)
}
// TransitionToInfoRejected 转换到企业信息被拒绝状态
func (sm *CertificationStateMachine) TransitionToInfoRejected(
ctx context.Context,
certificationID string,
failureReason enums.FailureReason,
failureMessage string,
) (*StateTransitionResult, error) {
req := &StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: enums.StatusInfoRejected,
Actor: enums.ActorTypeEsign,
ActorID: "esign_system",
Reason: "e签宝企业认证失败",
Context: map[string]interface{}{
"failure_reason": failureReason,
"failure_message": failureMessage,
},
AllowRollback: false,
}
return sm.ExecuteTransition(ctx, req)
}
// TransitionToContractApplied 转换到已申请合同状态
func (sm *CertificationStateMachine) TransitionToContractApplied(
ctx context.Context,
certificationID string,
actor enums.ActorType,
actorID string,
) (*StateTransitionResult, error) {
req := &StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: enums.StatusContractApplied,
Actor: actor,
ActorID: actorID,
Reason: "用户申请合同签署",
Context: map[string]interface{}{},
AllowRollback: true,
}
return sm.ExecuteTransition(ctx, req)
}
// TransitionToContractSigned 转换到已签署合同状态(认证完成)
func (sm *CertificationStateMachine) TransitionToContractSigned(
ctx context.Context,
certificationID string,
contractURL string,
) (*StateTransitionResult, error) {
req := &StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: enums.StatusContractSigned,
Actor: enums.ActorTypeEsign,
ActorID: "esign_system",
Reason: "e签宝合同签署成功认证完成",
Context: map[string]interface{}{
"contract_url": contractURL,
},
AllowRollback: false,
}
return sm.ExecuteTransition(ctx, req)
}
// TransitionToContractRejected 转换到合同被拒签状态
func (sm *CertificationStateMachine) TransitionToContractRejected(
ctx context.Context,
certificationID string,
failureReason enums.FailureReason,
failureMessage string,
) (*StateTransitionResult, error) {
req := &StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: enums.StatusContractRejected,
Actor: enums.ActorTypeEsign,
ActorID: "esign_system",
Reason: "合同签署失败",
Context: map[string]interface{}{
"failure_reason": failureReason,
"failure_message": failureMessage,
},
AllowRollback: false,
}
return sm.ExecuteTransition(ctx, req)
}
// TransitionToContractExpired 转换到合同签署超时状态
func (sm *CertificationStateMachine) TransitionToContractExpired(
ctx context.Context,
certificationID string,
failureMessage string,
) (*StateTransitionResult, error) {
req := &StateTransitionRequest{
CertificationID: certificationID,
TargetStatus: enums.StatusContractExpired,
Actor: enums.ActorTypeSystem,
ActorID: "timeout_monitor",
Reason: "合同签署超时",
Context: map[string]interface{}{
"failure_reason": enums.FailureReasonContractExpired,
"failure_message": failureMessage,
},
AllowRollback: false,
}
return sm.ExecuteTransition(ctx, req)
}

View File

@@ -0,0 +1,391 @@
package state_machine
import (
"context"
"encoding/json"
"fmt"
"tyapi-server/internal/domains/certification/enums"
"go.uber.org/zap"
)
// EsignCallbackData e签宝回调数据结构
type EsignCallbackData struct {
// 基础信息
Action string `json:"action"` // 回调动作类型
FlowID string `json:"flow_id"` // 流程ID
AccountID string `json:"account_id"` // 账户ID
Status string `json:"status"` // 状态
Message string `json:"message"` // 消息
Timestamp int64 `json:"timestamp"` // 时间戳
// 扩展数据
Data map[string]interface{} `json:"data,omitempty"` // 扩展数据
OriginalData string `json:"original_data"` // 原始回调数据
}
// EsignCallbackHandler e签宝回调处理器
// 负责处理e签宝的异步回调将外部回调转换为内部状态转换
type EsignCallbackHandler struct {
stateMachine *CertificationStateMachine
logger *zap.Logger
}
// NewEsignCallbackHandler 创建e签宝回调处理器
func NewEsignCallbackHandler(
stateMachine *CertificationStateMachine,
logger *zap.Logger,
) *EsignCallbackHandler {
return &EsignCallbackHandler{
stateMachine: stateMachine,
logger: logger,
}
}
// HandleCallback 处理e签宝回调
func (h *EsignCallbackHandler) HandleCallback(
ctx context.Context,
certificationID string,
callbackData *EsignCallbackData,
) error {
h.logger.Info("接收到e签宝回调",
zap.String("certification_id", certificationID),
zap.String("action", callbackData.Action),
zap.String("flow_id", callbackData.FlowID),
zap.String("status", callbackData.Status))
// 根据动作类型分发处理
switch callbackData.Action {
case "auth_result":
return h.handleAuthResult(ctx, certificationID, callbackData)
case "sign_result":
return h.handleSignResult(ctx, certificationID, callbackData)
case "flow_status":
return h.handleFlowStatus(ctx, certificationID, callbackData)
default:
h.logger.Warn("未知的e签宝回调动作",
zap.String("certification_id", certificationID),
zap.String("action", callbackData.Action))
return fmt.Errorf("未知的回调动作: %s", callbackData.Action)
}
}
// handleAuthResult 处理企业认证结果回调
func (h *EsignCallbackHandler) handleAuthResult(
ctx context.Context,
certificationID string,
callbackData *EsignCallbackData,
) error {
h.logger.Info("处理企业认证结果回调",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("status", callbackData.Status))
switch callbackData.Status {
case "success", "verified", "completed":
// 认证成功
_, err := h.stateMachine.TransitionToEnterpriseVerified(
ctx,
certificationID,
callbackData.FlowID,
)
return err
case "failed", "rejected", "error":
// 认证失败
failureReason := h.parseAuthFailureReason(callbackData)
_, err := h.stateMachine.TransitionToInfoRejected(
ctx,
certificationID,
failureReason,
callbackData.Message,
)
return err
default:
h.logger.Warn("未知的企业认证状态",
zap.String("certification_id", certificationID),
zap.String("status", callbackData.Status))
return fmt.Errorf("未知的认证状态: %s", callbackData.Status)
}
}
// handleSignResult 处理合同签署结果回调
func (h *EsignCallbackHandler) handleSignResult(
ctx context.Context,
certificationID string,
callbackData *EsignCallbackData,
) error {
h.logger.Info("处理合同签署结果回调",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("status", callbackData.Status))
switch callbackData.Status {
case "signed", "completed", "success":
// 签署成功
contractURL := h.extractContractURL(callbackData)
_, err := h.stateMachine.TransitionToContractSigned(
ctx,
certificationID,
contractURL,
)
return err
case "rejected", "refused":
// 用户拒绝签署
_, err := h.stateMachine.TransitionToContractRejected(
ctx,
certificationID,
enums.FailureReasonContractRejectedByUser,
callbackData.Message,
)
return err
case "expired", "timeout":
// 签署超时
_, err := h.stateMachine.TransitionToContractExpired(
ctx,
certificationID,
fmt.Sprintf("合同签署超时: %s", callbackData.Message),
)
return err
case "failed", "error":
// 签署失败
failureReason := h.parseSignFailureReason(callbackData)
_, err := h.stateMachine.TransitionToContractRejected(
ctx,
certificationID,
failureReason,
callbackData.Message,
)
return err
default:
h.logger.Warn("未知的合同签署状态",
zap.String("certification_id", certificationID),
zap.String("status", callbackData.Status))
return fmt.Errorf("未知的签署状态: %s", callbackData.Status)
}
}
// handleFlowStatus 处理流程状态回调
func (h *EsignCallbackHandler) handleFlowStatus(
ctx context.Context,
certificationID string,
callbackData *EsignCallbackData,
) error {
h.logger.Info("处理流程状态回调",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("status", callbackData.Status))
// 流程状态回调主要用于监控和日志记录
// 实际的状态转换由具体的auth_result和sign_result处理
switch callbackData.Status {
case "started", "processing", "in_progress":
h.logger.Info("e签宝流程进行中",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID))
case "paused", "suspended":
h.logger.Warn("e签宝流程被暂停",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("message", callbackData.Message))
case "cancelled", "terminated":
h.logger.Warn("e签宝流程被取消",
zap.String("certification_id", certificationID),
zap.String("flow_id", callbackData.FlowID),
zap.String("message", callbackData.Message))
default:
h.logger.Info("收到其他流程状态",
zap.String("certification_id", certificationID),
zap.String("status", callbackData.Status))
}
return nil
}
// parseAuthFailureReason 解析企业认证失败原因
func (h *EsignCallbackHandler) parseAuthFailureReason(callbackData *EsignCallbackData) enums.FailureReason {
// 根据e签宝返回的错误信息解析失败原因
message := callbackData.Message
// 检查扩展数据中的错误码
if errorCode, exists := callbackData.Data["error_code"]; exists {
switch errorCode {
case "ENTERPRISE_NOT_FOUND", "ORG_NOT_EXISTS":
return enums.FailureReasonEnterpriseNotExists
case "INFO_MISMATCH", "ORG_INFO_ERROR":
return enums.FailureReasonEnterpriseInfoMismatch
case "STATUS_ABNORMAL", "ORG_STATUS_ERROR":
return enums.FailureReasonEnterpriseStatusAbnormal
case "LEGAL_PERSON_ERROR", "LEGAL_REP_ERROR":
return enums.FailureReasonLegalPersonMismatch
case "DOCUMENT_INVALID", "ID_CARD_ERROR":
return enums.FailureReasonInvalidDocument
}
}
// 根据错误消息文本判断
if message != "" {
if h.containsKeywords(message, []string{"企业不存在", "机构不存在", "not found"}) {
return enums.FailureReasonEnterpriseNotExists
}
if h.containsKeywords(message, []string{"信息不匹配", "信息错误", "mismatch"}) {
return enums.FailureReasonEnterpriseInfoMismatch
}
if h.containsKeywords(message, []string{"状态异常", "status abnormal"}) {
return enums.FailureReasonEnterpriseStatusAbnormal
}
if h.containsKeywords(message, []string{"法定代表人", "legal person", "法人"}) {
return enums.FailureReasonLegalPersonMismatch
}
if h.containsKeywords(message, []string{"证件", "身份证", "document", "id card"}) {
return enums.FailureReasonInvalidDocument
}
}
// 默认返回e签宝验证失败
return enums.FailureReasonEsignVerificationFailed
}
// parseSignFailureReason 解析合同签署失败原因
func (h *EsignCallbackHandler) parseSignFailureReason(callbackData *EsignCallbackData) enums.FailureReason {
// 根据e签宝返回的错误信息解析失败原因
message := callbackData.Message
// 检查扩展数据中的错误码
if errorCode, exists := callbackData.Data["error_code"]; exists {
switch errorCode {
case "USER_REJECTED", "SIGN_REJECTED":
return enums.FailureReasonContractRejectedByUser
case "FLOW_EXPIRED", "SIGN_EXPIRED":
return enums.FailureReasonContractExpired
case "FLOW_ERROR", "SIGN_PROCESS_ERROR":
return enums.FailureReasonSignProcessFailed
case "ESIGN_ERROR", "SYSTEM_ERROR":
return enums.FailureReasonEsignFlowError
}
}
// 根据错误消息文本判断
if message != "" {
if h.containsKeywords(message, []string{"拒绝", "rejected", "refused"}) {
return enums.FailureReasonContractRejectedByUser
}
if h.containsKeywords(message, []string{"过期", "超时", "expired", "timeout"}) {
return enums.FailureReasonContractExpired
}
if h.containsKeywords(message, []string{"流程错误", "process error", "flow error"}) {
return enums.FailureReasonSignProcessFailed
}
}
// 默认返回e签宝流程错误
return enums.FailureReasonEsignFlowError
}
// extractContractURL 提取合同URL
func (h *EsignCallbackHandler) extractContractURL(callbackData *EsignCallbackData) string {
// 优先从扩展数据中获取
if contractURL, exists := callbackData.Data["contract_url"]; exists {
if url, ok := contractURL.(string); ok && url != "" {
return url
}
}
if downloadURL, exists := callbackData.Data["download_url"]; exists {
if url, ok := downloadURL.(string); ok && url != "" {
return url
}
}
if fileURL, exists := callbackData.Data["file_url"]; exists {
if url, ok := fileURL.(string); ok && url != "" {
return url
}
}
// 如果没有找到URL返回空字符串
h.logger.Warn("未能从回调数据中提取合同URL",
zap.Any("callback_data", callbackData.Data))
return ""
}
// containsKeywords 检查文本是否包含关键词
func (h *EsignCallbackHandler) containsKeywords(text string, keywords []string) bool {
for _, keyword := range keywords {
if len(text) >= len(keyword) {
for i := 0; i <= len(text)-len(keyword); i++ {
if text[i:i+len(keyword)] == keyword {
return true
}
}
}
}
return false
}
// ValidateCallbackData 验证回调数据
func (h *EsignCallbackHandler) ValidateCallbackData(callbackData *EsignCallbackData) error {
if callbackData == nil {
return fmt.Errorf("回调数据不能为空")
}
if callbackData.Action == "" {
return fmt.Errorf("回调动作不能为空")
}
if callbackData.FlowID == "" {
return fmt.Errorf("流程ID不能为空")
}
if callbackData.Status == "" {
return fmt.Errorf("状态不能为空")
}
return nil
}
// ParseCallbackData 解析原始回调数据
func (h *EsignCallbackHandler) ParseCallbackData(rawData string) (*EsignCallbackData, error) {
var callbackData EsignCallbackData
if err := json.Unmarshal([]byte(rawData), &callbackData); err != nil {
h.logger.Error("解析e签宝回调数据失败", zap.Error(err), zap.String("raw_data", rawData))
return nil, fmt.Errorf("解析回调数据失败: %w", err)
}
// 保存原始数据
callbackData.OriginalData = rawData
// 验证数据完整性
if err := h.ValidateCallbackData(&callbackData); err != nil {
return nil, fmt.Errorf("回调数据验证失败: %w", err)
}
return &callbackData, nil
}
// GetCallbackType 获取回调类型描述
func (h *EsignCallbackHandler) GetCallbackType(action string) string {
types := map[string]string{
"auth_result": "企业认证结果",
"sign_result": "合同签署结果",
"flow_status": "流程状态更新",
}
if typeName, exists := types[action]; exists {
return typeName
}
return "未知类型"
}

View File

@@ -0,0 +1,438 @@
package state_machine
import (
"fmt"
"tyapi-server/internal/domains/certification/enums"
)
// StateConfig 状态配置
type StateConfig struct {
Status enums.CertificationStatus `json:"status"`
Name string `json:"name"`
ProgressPercentage int `json:"progress_percentage"`
IsUserActionRequired bool `json:"is_user_action_required"`
IsSystemAction bool `json:"is_system_action"`
TimestampField string `json:"timestamp_field,omitempty"`
Description string `json:"description"`
NextValidStatuses []enums.CertificationStatus `json:"next_valid_statuses"`
AllowedActors []enums.ActorType `json:"allowed_actors"`
}
// StateTransitionRule 状态转换规则
type StateTransitionRule struct {
FromStatus enums.CertificationStatus `json:"from_status"`
ToStatus enums.CertificationStatus `json:"to_status"`
TransitionName string `json:"transition_name"`
AllowedActors []enums.ActorType `json:"allowed_actors"`
RequiresValidation bool `json:"requires_validation"`
Description string `json:"description"`
BusinessRules []string `json:"business_rules"`
}
// StateConfigManager 状态配置管理器
type StateConfigManager struct {
stateConfigs map[enums.CertificationStatus]*StateConfig
transitionRules map[string]*StateTransitionRule // key: "from_status->to_status"
actorPermissions map[enums.ActorType][]string // actor允许的操作
}
// NewStateConfigManager 创建状态配置管理器
func NewStateConfigManager() *StateConfigManager {
manager := &StateConfigManager{
stateConfigs: make(map[enums.CertificationStatus]*StateConfig),
transitionRules: make(map[string]*StateTransitionRule),
actorPermissions: make(map[enums.ActorType][]string),
}
manager.initializeStateConfigs()
manager.initializeTransitionRules()
manager.initializeActorPermissions()
return manager
}
// initializeStateConfigs 初始化状态配置
func (m *StateConfigManager) initializeStateConfigs() {
configs := []*StateConfig{
{
Status: enums.StatusPending,
Name: "待认证",
ProgressPercentage: 0,
IsUserActionRequired: true,
IsSystemAction: false,
Description: "等待用户提交企业信息",
NextValidStatuses: []enums.CertificationStatus{enums.StatusInfoSubmitted},
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
},
{
Status: enums.StatusInfoSubmitted,
Name: "已提交企业信息",
ProgressPercentage: 25,
IsUserActionRequired: false,
IsSystemAction: true,
TimestampField: "InfoSubmittedAt",
Description: "企业信息已提交等待e签宝验证",
NextValidStatuses: []enums.CertificationStatus{enums.StatusEnterpriseVerified, enums.StatusInfoRejected},
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
},
{
Status: enums.StatusEnterpriseVerified,
Name: "已企业认证",
ProgressPercentage: 50,
IsUserActionRequired: true,
IsSystemAction: false,
TimestampField: "EnterpriseVerifiedAt",
Description: "企业认证完成,用户可申请合同",
NextValidStatuses: []enums.CertificationStatus{enums.StatusContractApplied},
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
},
{
Status: enums.StatusContractApplied,
Name: "已申请签署合同",
ProgressPercentage: 75,
IsUserActionRequired: true,
IsSystemAction: true,
TimestampField: "ContractAppliedAt",
Description: "合同已生成,等待用户签署",
NextValidStatuses: []enums.CertificationStatus{enums.StatusContractSigned, enums.StatusContractRejected, enums.StatusContractExpired},
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
},
{
Status: enums.StatusContractSigned,
Name: "认证完成",
ProgressPercentage: 100,
IsUserActionRequired: false,
IsSystemAction: false,
TimestampField: "ContractSignedAt",
Description: "认证流程已完成",
NextValidStatuses: []enums.CertificationStatus{},
AllowedActors: []enums.ActorType{},
},
// 失败状态
{
Status: enums.StatusInfoRejected,
Name: "企业信息被拒绝",
ProgressPercentage: 25,
IsUserActionRequired: true,
IsSystemAction: false,
Description: "企业信息验证失败,需要重新提交",
NextValidStatuses: []enums.CertificationStatus{enums.StatusInfoSubmitted},
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
},
{
Status: enums.StatusContractRejected,
Name: "合同被拒签",
ProgressPercentage: 75,
IsUserActionRequired: true,
IsSystemAction: false,
Description: "用户拒绝签署合同",
NextValidStatuses: []enums.CertificationStatus{enums.StatusEnterpriseVerified},
AllowedActors: []enums.ActorType{enums.ActorTypeUser, enums.ActorTypeSystem},
},
{
Status: enums.StatusContractExpired,
Name: "合同签署超时",
ProgressPercentage: 75,
IsUserActionRequired: true,
IsSystemAction: false,
Description: "合同签署链接已过期",
NextValidStatuses: []enums.CertificationStatus{enums.StatusEnterpriseVerified},
AllowedActors: []enums.ActorType{enums.ActorTypeUser, enums.ActorTypeSystem},
},
}
for _, config := range configs {
m.stateConfigs[config.Status] = config
}
}
// initializeTransitionRules 初始化状态转换规则
func (m *StateConfigManager) initializeTransitionRules() {
rules := []*StateTransitionRule{
// 用户提交企业信息
{
FromStatus: enums.StatusPending,
ToStatus: enums.StatusInfoSubmitted,
TransitionName: "submit_enterprise_info",
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
RequiresValidation: true,
Description: "用户提交企业信息",
BusinessRules: []string{"enterprise_info_complete", "enterprise_info_valid"},
},
// e签宝企业认证成功
{
FromStatus: enums.StatusInfoSubmitted,
ToStatus: enums.StatusEnterpriseVerified,
TransitionName: "enterprise_verification_success",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "e签宝企业认证成功",
BusinessRules: []string{"auth_flow_id_exists"},
},
// e签宝企业认证失败
{
FromStatus: enums.StatusInfoSubmitted,
ToStatus: enums.StatusInfoRejected,
TransitionName: "enterprise_verification_failed",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "e签宝企业认证失败",
BusinessRules: []string{"failure_reason_provided"},
},
// 用户申请合同
{
FromStatus: enums.StatusEnterpriseVerified,
ToStatus: enums.StatusContractApplied,
TransitionName: "apply_contract",
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
RequiresValidation: true,
Description: "用户申请合同签署",
BusinessRules: []string{"enterprise_verified", "auth_flow_id_exists"},
},
// e签宝合同签署成功
{
FromStatus: enums.StatusContractApplied,
ToStatus: enums.StatusContractSigned,
TransitionName: "contract_sign_success",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "e签宝合同签署成功",
BusinessRules: []string{"contract_info_complete"},
},
// 合同签署失败
{
FromStatus: enums.StatusContractApplied,
ToStatus: enums.StatusContractRejected,
TransitionName: "contract_sign_rejected",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "用户拒绝签署合同",
BusinessRules: []string{"failure_reason_provided"},
},
// 合同签署超时
{
FromStatus: enums.StatusContractApplied,
ToStatus: enums.StatusContractExpired,
TransitionName: "contract_sign_expired",
AllowedActors: []enums.ActorType{enums.ActorTypeEsign, enums.ActorTypeSystem},
RequiresValidation: false,
Description: "合同签署超时",
BusinessRules: []string{"failure_reason_provided"},
},
// 重新提交企业信息
{
FromStatus: enums.StatusInfoRejected,
ToStatus: enums.StatusInfoSubmitted,
TransitionName: "resubmit_enterprise_info",
AllowedActors: []enums.ActorType{enums.ActorTypeUser},
RequiresValidation: true,
Description: "用户重新提交企业信息",
BusinessRules: []string{"enterprise_info_complete", "enterprise_info_valid", "retry_limit_check"},
},
// 从合同失败状态恢复
{
FromStatus: enums.StatusContractRejected,
ToStatus: enums.StatusEnterpriseVerified,
TransitionName: "reset_from_contract_rejected",
AllowedActors: []enums.ActorType{enums.ActorTypeSystem, enums.ActorTypeUser},
RequiresValidation: false,
Description: "从合同拒签状态恢复",
BusinessRules: []string{"retry_limit_check"},
},
{
FromStatus: enums.StatusContractExpired,
ToStatus: enums.StatusEnterpriseVerified,
TransitionName: "reset_from_contract_expired",
AllowedActors: []enums.ActorType{enums.ActorTypeSystem, enums.ActorTypeUser},
RequiresValidation: false,
Description: "从合同超时状态恢复",
BusinessRules: []string{"retry_limit_check"},
},
}
for _, rule := range rules {
key := string(rule.FromStatus) + "->" + string(rule.ToStatus)
m.transitionRules[key] = rule
}
}
// initializeActorPermissions 初始化操作者权限
func (m *StateConfigManager) initializeActorPermissions() {
m.actorPermissions = map[enums.ActorType][]string{
enums.ActorTypeUser: {
"submit_enterprise_info",
"apply_contract",
"view_certification",
"retry_from_failure",
},
enums.ActorTypeSystem: {
"auto_transition",
"system_recovery",
"timeout_handling",
"data_cleanup",
},
enums.ActorTypeEsign: {
"verification_callback",
"sign_callback",
"status_notification",
},
enums.ActorTypeAdmin: {
"manual_intervention",
"force_transition",
"view_all_certifications",
"system_configuration",
},
}
}
// GetStateConfig 获取状态配置
func (m *StateConfigManager) GetStateConfig(status enums.CertificationStatus) *StateConfig {
return m.stateConfigs[status]
}
// GetTransitionRule 获取状态转换规则
func (m *StateConfigManager) GetTransitionRule(fromStatus, toStatus enums.CertificationStatus) *StateTransitionRule {
key := string(fromStatus) + "->" + string(toStatus)
return m.transitionRules[key]
}
// CanTransition 检查是否可以执行状态转换
func (m *StateConfigManager) CanTransition(fromStatus, toStatus enums.CertificationStatus, actor enums.ActorType) (bool, string) {
// 获取转换规则
rule := m.GetTransitionRule(fromStatus, toStatus)
if rule == nil {
return false, "不支持的状态转换"
}
// 检查操作者权限
allowed := false
for _, allowedActor := range rule.AllowedActors {
if actor == allowedActor {
allowed = true
break
}
}
if !allowed {
return false, "操作者无权限执行此转换"
}
return true, ""
}
// GetAllowedTransitions 获取指定状态下允许的转换
func (m *StateConfigManager) GetAllowedTransitions(fromStatus enums.CertificationStatus, actor enums.ActorType) []*StateTransitionRule {
var allowedTransitions []*StateTransitionRule
config := m.GetStateConfig(fromStatus)
if config == nil {
return allowedTransitions
}
for _, toStatus := range config.NextValidStatuses {
if canTransition, _ := m.CanTransition(fromStatus, toStatus, actor); canTransition {
if rule := m.GetTransitionRule(fromStatus, toStatus); rule != nil {
allowedTransitions = append(allowedTransitions, rule)
}
}
}
return allowedTransitions
}
// GetActorPermissions 获取操作者权限
func (m *StateConfigManager) GetActorPermissions(actor enums.ActorType) []string {
if permissions, exists := m.actorPermissions[actor]; exists {
return permissions
}
return []string{}
}
// HasPermission 检查操作者是否有指定权限
func (m *StateConfigManager) HasPermission(actor enums.ActorType, permission string) bool {
permissions := m.GetActorPermissions(actor)
for _, p := range permissions {
if p == permission {
return true
}
}
return false
}
// ValidateBusinessRules 验证业务规则
func (m *StateConfigManager) ValidateBusinessRules(rule *StateTransitionRule, context map[string]interface{}) error {
for _, businessRule := range rule.BusinessRules {
if err := m.validateSingleBusinessRule(businessRule, context); err != nil {
return err
}
}
return nil
}
// validateSingleBusinessRule 验证单个业务规则
func (m *StateConfigManager) validateSingleBusinessRule(ruleName string, context map[string]interface{}) error {
switch ruleName {
case "enterprise_info_complete":
if enterpriseInfo, exists := context["enterprise_info"]; !exists || enterpriseInfo == nil {
return fmt.Errorf("企业信息不能为空")
}
case "enterprise_info_valid":
// 这里可以添加更复杂的企业信息验证逻辑
return nil
case "auth_flow_id_exists":
if authFlowID, exists := context["auth_flow_id"]; !exists || authFlowID == "" {
return fmt.Errorf("认证流程ID不能为空")
}
case "failure_reason_provided":
if reason, exists := context["failure_reason"]; !exists || reason == "" {
return fmt.Errorf("失败原因不能为空")
}
case "enterprise_verified":
if status, exists := context["current_status"]; !exists || status != string(enums.StatusEnterpriseVerified) {
return fmt.Errorf("企业必须先完成认证")
}
case "contract_info_complete":
if contractInfo, exists := context["contract_info"]; !exists || contractInfo == nil {
return fmt.Errorf("合同信息不能为空")
}
case "retry_limit_check":
if retryCount, exists := context["retry_count"]; exists {
if count, ok := retryCount.(int); ok && count >= 3 {
return fmt.Errorf("已达到最大重试次数限制")
}
}
}
return nil
}
// GetStateProgress 获取状态进度信息
func (m *StateConfigManager) GetStateProgress(status enums.CertificationStatus) int {
if config := m.GetStateConfig(status); config != nil {
return config.ProgressPercentage
}
return 0
}
// IsUserActionRequired 检查是否需要用户操作
func (m *StateConfigManager) IsUserActionRequired(status enums.CertificationStatus) bool {
if config := m.GetStateConfig(status); config != nil {
return config.IsUserActionRequired
}
return false
}
// IsSystemAction 检查是否为系统操作状态
func (m *StateConfigManager) IsSystemAction(status enums.CertificationStatus) bool {
if config := m.GetStateConfig(status); config != nil {
return config.IsSystemAction
}
return false
}
// GetTimestampField 获取状态对应的时间戳字段
func (m *StateConfigManager) GetTimestampField(status enums.CertificationStatus) string {
if config := m.GetStateConfig(status); config != nil {
return config.TimestampField
}
return ""
}

View File

@@ -0,0 +1,181 @@
package value_objects
import (
"fmt"
"regexp"
"strings"
)
// EnterpriseInfo 企业信息值对象
// 负责封装和验证企业认证相关的信息
type EnterpriseInfo struct {
CompanyName string `json:"company_name" validate:"required,min=2,max=100"`
UnifiedSocialCode string `json:"unified_social_code" validate:"required,len=18"`
LegalPersonName string `json:"legal_person_name" validate:"required,min=2,max=50"`
LegalPersonID string `json:"legal_person_id" validate:"required,len=18"`
LegalPersonPhone string `json:"legal_person_phone" validate:"required,mobile"`
}
// NewEnterpriseInfo 创建企业信息值对象
func NewEnterpriseInfo(
companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone string,
) (*EnterpriseInfo, error) {
// 清理输入数据
info := &EnterpriseInfo{
CompanyName: strings.TrimSpace(companyName),
UnifiedSocialCode: strings.TrimSpace(unifiedSocialCode),
LegalPersonName: strings.TrimSpace(legalPersonName),
LegalPersonID: strings.TrimSpace(legalPersonID),
LegalPersonPhone: strings.TrimSpace(legalPersonPhone),
}
// 验证数据
if err := info.Validate(); err != nil {
return nil, err
}
return info, nil
}
// Validate 验证企业信息
func (e *EnterpriseInfo) Validate() error {
// 验证公司名称
if e.CompanyName == "" {
return fmt.Errorf("公司名称不能为空")
}
if len(e.CompanyName) < 2 || len(e.CompanyName) > 100 {
return fmt.Errorf("公司名称长度应在2-100个字符之间")
}
// 验证统一社会信用代码
if err := e.validateUnifiedSocialCode(); err != nil {
return err
}
// 验证法人姓名
if e.LegalPersonName == "" {
return fmt.Errorf("法人姓名不能为空")
}
if len(e.LegalPersonName) < 2 || len(e.LegalPersonName) > 50 {
return fmt.Errorf("法人姓名长度应在2-50个字符之间")
}
// 验证法人身份证
if err := e.validateLegalPersonID(); err != nil {
return err
}
// 验证法人手机号
if err := e.validateLegalPersonPhone(); err != nil {
return err
}
return nil
}
// validateUnifiedSocialCode 验证统一社会信用代码
func (e *EnterpriseInfo) validateUnifiedSocialCode() error {
if e.UnifiedSocialCode == "" {
return fmt.Errorf("统一社会信用代码不能为空")
}
if len(e.UnifiedSocialCode) != 18 {
return fmt.Errorf("统一社会信用代码应为18位")
}
// 检查格式:应为数字和大写字母组合
matched, _ := regexp.MatchString(`^[0-9A-Z]{18}$`, e.UnifiedSocialCode)
if !matched {
return fmt.Errorf("统一社会信用代码格式不正确")
}
// TODO: 可以添加更严格的校验算法
return nil
}
// validateLegalPersonID 验证法人身份证号
func (e *EnterpriseInfo) validateLegalPersonID() error {
if e.LegalPersonID == "" {
return fmt.Errorf("法人身份证号不能为空")
}
if len(e.LegalPersonID) != 18 {
return fmt.Errorf("法人身份证号应为18位")
}
// 检查格式前17位为数字最后一位为数字或X
matched, _ := regexp.MatchString(`^\d{17}[\dX]$`, e.LegalPersonID)
if !matched {
return fmt.Errorf("法人身份证号格式不正确")
}
// TODO: 可以添加身份证校验算法
return nil
}
// validateLegalPersonPhone 验证法人手机号
func (e *EnterpriseInfo) validateLegalPersonPhone() error {
if e.LegalPersonPhone == "" {
return fmt.Errorf("法人手机号不能为空")
}
// 检查中国大陆手机号格式
matched, _ := regexp.MatchString(`^1[3-9]\d{9}$`, e.LegalPersonPhone)
if !matched {
return fmt.Errorf("法人手机号格式不正确")
}
return nil
}
// ================ 业务方法 ================
// GetDisplayName 获取显示名称
func (e *EnterpriseInfo) GetDisplayName() string {
return fmt.Sprintf("%s%s", e.CompanyName, e.LegalPersonName)
}
// IsSame 判断是否为同一企业信息
func (e *EnterpriseInfo) IsSame(other *EnterpriseInfo) bool {
if other == nil {
return false
}
return e.UnifiedSocialCode == other.UnifiedSocialCode &&
e.LegalPersonID == other.LegalPersonID
}
// GetMaskedPhone 获取脱敏手机号
func (e *EnterpriseInfo) GetMaskedPhone() string {
if len(e.LegalPersonPhone) != 11 {
return e.LegalPersonPhone
}
return e.LegalPersonPhone[:3] + "****" + e.LegalPersonPhone[7:]
}
// GetMaskedIDNumber 获取脱敏身份证号
func (e *EnterpriseInfo) GetMaskedIDNumber() string {
if len(e.LegalPersonID) != 18 {
return e.LegalPersonID
}
return e.LegalPersonID[:6] + "******" + e.LegalPersonID[14:]
}
// ToMap 转换为Map格式
func (e *EnterpriseInfo) ToMap() map[string]string {
return map[string]string{
"company_name": e.CompanyName,
"unified_social_code": e.UnifiedSocialCode,
"legal_person_name": e.LegalPersonName,
"legal_person_id": e.LegalPersonID,
"legal_person_phone": e.LegalPersonPhone,
}
}
// String 字符串表示
func (e *EnterpriseInfo) String() string {
return fmt.Sprintf("企业信息[公司=%s, 法人=%s, 信用代码=%s]",
e.CompanyName, e.LegalPersonName, e.UnifiedSocialCode)
}

View File

@@ -0,0 +1,370 @@
package certification
import (
"context"
"fmt"
"time"
"go.uber.org/zap"
"gorm.io/gorm"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
"tyapi-server/internal/shared/database"
"tyapi-server/internal/shared/interfaces"
)
// ================ 常量定义 ================
const (
// 表名常量
CertificationsTable = "certifications"
// 缓存时间常量
CacheTTLPrimaryQuery = 30 * time.Minute // 主键查询缓存时间
CacheTTLBusinessQuery = 15 * time.Minute // 业务查询缓存时间
CacheTTLUserQuery = 10 * time.Minute // 用户相关查询缓存时间
CacheTTLWarmupLong = 30 * time.Minute // 预热长期缓存
CacheTTLWarmupMedium = 15 * time.Minute // 预热中期缓存
// 缓存键模式常量
CachePatternTable = "gorm_cache:certifications:*"
CachePatternUser = "certification:user_id:*"
)
// ================ Repository 实现 ================
// GormCertificationCommandRepository 认证命令仓储GORM实现
//
// 特性说明:
// - 基于 CachedBaseRepositoryImpl 实现自动缓存管理
// - 支持多级缓存策略主键查询30分钟业务查询15分钟
// - 自动缓存失效:写操作时自动清理相关缓存
// - 智能缓存选择:根据查询复杂度自动选择缓存策略
// - 内置监控支持:提供缓存统计和性能监控
type GormCertificationCommandRepository struct {
*database.CachedBaseRepositoryImpl
}
// 编译时检查接口实现
var _ repositories.CertificationCommandRepository = (*GormCertificationCommandRepository)(nil)
// NewGormCertificationCommandRepository 创建认证命令仓储
//
// 参数:
// - db: GORM数据库连接实例
// - logger: 日志记录器
//
// 返回:
// - repositories.CertificationCommandRepository: 仓储接口实现
func NewGormCertificationCommandRepository(db *gorm.DB, logger *zap.Logger) repositories.CertificationCommandRepository {
return &GormCertificationCommandRepository{
CachedBaseRepositoryImpl: database.NewCachedBaseRepositoryImpl(db, logger, CertificationsTable),
}
}
// ================ 基础CRUD操作 ================
// Create 创建认证
//
// 业务说明:
// - 创建新的认证申请
// - 自动触发相关缓存失效
//
// 参数:
// - ctx: 上下文
// - cert: 认证实体
//
// 返回:
// - error: 创建失败时的错误信息
func (r *GormCertificationCommandRepository) Create(ctx context.Context, cert entities.Certification) error {
r.GetLogger().Info("创建认证申请",
zap.String("user_id", cert.UserID),
zap.String("status", string(cert.Status)))
return r.CreateEntity(ctx, &cert)
}
// Update 更新认证
//
// 缓存影响:
// - GORM缓存插件会自动失效相关缓存
// - 无需手动管理缓存一致性
//
// 参数:
// - ctx: 上下文
// - cert: 认证实体
//
// 返回:
// - error: 更新失败时的错误信息
func (r *GormCertificationCommandRepository) Update(ctx context.Context, cert entities.Certification) error {
r.GetLogger().Info("更新认证",
zap.String("id", cert.ID),
zap.String("status", string(cert.Status)))
return r.UpdateEntity(ctx, &cert)
}
// Delete 删除认证
//
// 参数:
// - ctx: 上下文
// - id: 认证ID
//
// 返回:
// - error: 删除失败时的错误信息
func (r *GormCertificationCommandRepository) Delete(ctx context.Context, id string) error {
r.GetLogger().Info("删除认证", zap.String("id", id))
return r.DeleteEntity(ctx, id, &entities.Certification{})
}
// ================ 业务特定的更新操作 ================
// UpdateStatus 更新认证状态
//
// 业务说明:
// - 更新认证的状态
// - 自动更新时间戳
//
// 缓存影响:
// - GORM缓存插件会自动失效表相关的缓存
// - 状态更新会影响列表查询和统计结果
//
// 参数:
// - ctx: 上下文
// - id: 认证ID
// - status: 新状态
//
// 返回:
// - error: 更新失败时的错误信息
func (r *GormCertificationCommandRepository) UpdateStatus(ctx context.Context, id string, status enums.CertificationStatus) error {
r.GetLogger().Info("更新认证状态",
zap.String("id", id),
zap.String("status", string(status)))
updates := map[string]interface{}{
"status": status,
"updated_at": time.Now(),
}
return r.GetDB(ctx).Model(&entities.Certification{}).
Where("id = ?", id).
Updates(updates).Error
}
// UpdateAuthFlowID 更新认证流程ID
//
// 业务说明:
// - 记录e签宝企业认证流程ID
// - 用于回调处理和状态跟踪
//
// 参数:
// - ctx: 上下文
// - id: 认证ID
// - authFlowID: 认证流程ID
//
// 返回:
// - error: 更新失败时的错误信息
func (r *GormCertificationCommandRepository) UpdateAuthFlowID(ctx context.Context, id string, authFlowID string) error {
r.GetLogger().Info("更新认证流程ID",
zap.String("id", id),
zap.String("auth_flow_id", authFlowID))
updates := map[string]interface{}{
"auth_flow_id": authFlowID,
"updated_at": time.Now(),
}
return r.GetDB(ctx).Model(&entities.Certification{}).
Where("id = ?", id).
Updates(updates).Error
}
// UpdateContractInfo 更新合同信息
//
// 业务说明:
// - 记录合同相关的ID和URL信息
// - 用于合同管理和用户下载
//
// 参数:
// - ctx: 上下文
// - id: 认证ID
// - contractFileID: 合同文件ID
// - esignFlowID: e签宝流程ID
// - contractURL: 合同URL
// - contractSignURL: 合同签署URL
//
// 返回:
// - error: 更新失败时的错误信息
func (r *GormCertificationCommandRepository) UpdateContractInfo(ctx context.Context, id string, contractFileID, esignFlowID, contractURL, contractSignURL string) error {
r.GetLogger().Info("更新合同信息",
zap.String("id", id),
zap.String("contract_file_id", contractFileID),
zap.String("esign_flow_id", esignFlowID))
updates := map[string]interface{}{
"contract_file_id": contractFileID,
"esign_flow_id": esignFlowID,
"contract_url": contractURL,
"contract_sign_url": contractSignURL,
"updated_at": time.Now(),
}
return r.GetDB(ctx).Model(&entities.Certification{}).
Where("id = ?", id).
Updates(updates).Error
}
// UpdateFailureInfo 更新失败信息
//
// 业务说明:
// - 记录认证失败的原因和详细信息
// - 用于错误分析和用户提示
//
// 参数:
// - ctx: 上下文
// - id: 认证ID
// - reason: 失败原因
// - message: 失败详细信息
//
// 返回:
// - error: 更新失败时的错误信息
func (r *GormCertificationCommandRepository) UpdateFailureInfo(ctx context.Context, id string, reason enums.FailureReason, message string) error {
r.GetLogger().Info("更新失败信息",
zap.String("id", id),
zap.String("reason", string(reason)),
zap.String("message", message))
updates := map[string]interface{}{
"failure_reason": reason,
"failure_message": message,
"updated_at": time.Now(),
}
return r.GetDB(ctx).Model(&entities.Certification{}).
Where("id = ?", id).
Updates(updates).Error
}
// ================ 批量操作 ================
// BatchUpdateStatus 批量更新状态
//
// 业务说明:
// - 批量更新多个认证的状态
// - 适用于管理员批量操作
//
// 参数:
// - ctx: 上下文
// - ids: 认证ID列表
// - status: 新状态
//
// 返回:
// - error: 更新失败时的错误信息
func (r *GormCertificationCommandRepository) BatchUpdateStatus(ctx context.Context, ids []string, status enums.CertificationStatus) error {
if len(ids) == 0 {
return fmt.Errorf("批量更新状态ID列表不能为空")
}
r.GetLogger().Info("批量更新认证状态",
zap.Strings("ids", ids),
zap.String("status", string(status)))
updates := map[string]interface{}{
"status": status,
"updated_at": time.Now(),
}
result := r.GetDB(ctx).Model(&entities.Certification{}).
Where("id IN ?", ids).
Updates(updates)
if result.Error != nil {
return fmt.Errorf("批量更新认证状态失败: %w", result.Error)
}
r.GetLogger().Info("批量更新完成", zap.Int64("affected_rows", result.RowsAffected))
return nil
}
// ================ 事务支持 ================
// WithTx 使用事务
//
// 业务说明:
// - 返回支持事务的仓储实例
// - 用于复杂业务操作的事务一致性保证
//
// 参数:
// - tx: 事务对象
//
// 返回:
// - repositories.CertificationCommandRepository: 支持事务的仓储实例
func (r *GormCertificationCommandRepository) WithTx(tx interfaces.Transaction) repositories.CertificationCommandRepository {
// 获取事务的底层*gorm.DB
txDB := tx.GetDB()
if gormDB, ok := txDB.(*gorm.DB); ok {
return &GormCertificationCommandRepository{
CachedBaseRepositoryImpl: database.NewCachedBaseRepositoryImpl(gormDB, r.GetLogger(), CertificationsTable),
}
}
r.GetLogger().Warn("不支持的事务类型,返回原始仓储")
return r
}
// ================ 缓存管理方法 ================
// WarmupCache 预热认证缓存
//
// 业务说明:
// - 系统启动时预热常用查询的缓存
// - 提升首次访问的响应速度
//
// 预热策略:
// - 活跃认证30分钟长期缓存
// - 最近创建15分钟中期缓存
func (r *GormCertificationCommandRepository) WarmupCache(ctx context.Context) error {
r.GetLogger().Info("开始预热认证缓存")
queries := []database.WarmupQuery{
{
Name: "active_certifications",
TTL: CacheTTLWarmupLong,
Dest: &[]entities.Certification{},
},
{
Name: "recent_certifications",
TTL: CacheTTLWarmupMedium,
Dest: &[]entities.Certification{},
},
}
return r.WarmupCommonQueries(ctx, queries)
}
// RefreshCache 刷新认证缓存
//
// 业务说明:
// - 手动刷新认证相关的所有缓存
// - 适用于数据迁移或批量更新后的缓存清理
func (r *GormCertificationCommandRepository) RefreshCache(ctx context.Context) error {
r.GetLogger().Info("刷新认证缓存")
return r.CachedBaseRepositoryImpl.RefreshCache(ctx, CachePatternTable)
}
// GetCacheStats 获取缓存统计信息
//
// 返回当前Repository的缓存使用统计包括
// - 基础缓存信息(命中率、键数量等)
// - 特定的缓存模式列表
// - 性能指标
func (r *GormCertificationCommandRepository) GetCacheStats() map[string]interface{} {
stats := r.GetCacheInfo()
stats["specific_patterns"] = []string{
CachePatternTable,
CachePatternUser,
}
return stats
}

View File

@@ -0,0 +1,514 @@
package certification
import (
"context"
"fmt"
"strings"
"time"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
"tyapi-server/internal/domains/certification/repositories/queries"
"tyapi-server/internal/shared/database"
"go.uber.org/zap"
"gorm.io/gorm"
)
// ================ 常量定义 ================
const (
// 缓存时间常量
QueryCacheTTLPrimaryQuery = 30 * time.Minute // 主键查询缓存时间
QueryCacheTTLBusinessQuery = 15 * time.Minute // 业务查询缓存时间
QueryCacheTTLUserQuery = 10 * time.Minute // 用户相关查询缓存时间
QueryCacheTTLSearchQuery = 2 * time.Minute // 搜索查询缓存时间
QueryCacheTTLActiveRecords = 5 * time.Minute // 活跃记录查询缓存时间
QueryCacheTTLWarmupLong = 30 * time.Minute // 预热长期缓存
QueryCacheTTLWarmupMedium = 15 * time.Minute // 预热中期缓存
// 缓存键模式常量
QueryCachePatternTable = "gorm_cache:certifications:*"
QueryCachePatternUser = "certification:user_id:*"
)
// ================ Repository 实现 ================
// GormCertificationQueryRepository 认证查询仓储GORM实现
//
// 特性说明:
// - 基于 CachedBaseRepositoryImpl 实现自动缓存管理
// - 支持多级缓存策略主键查询30分钟业务查询15分钟搜索2分钟
// - 自动缓存失效:写操作时自动清理相关缓存
// - 智能缓存选择:根据查询复杂度自动选择缓存策略
// - 内置监控支持:提供缓存统计和性能监控
type GormCertificationQueryRepository struct {
*database.CachedBaseRepositoryImpl
}
// 编译时检查接口实现
var _ repositories.CertificationQueryRepository = (*GormCertificationQueryRepository)(nil)
// NewGormCertificationQueryRepository 创建认证查询仓储
//
// 参数:
// - db: GORM数据库连接实例
// - logger: 日志记录器
//
// 返回:
// - repositories.CertificationQueryRepository: 仓储接口实现
func NewGormCertificationQueryRepository(
db *gorm.DB,
logger *zap.Logger,
) repositories.CertificationQueryRepository {
return &GormCertificationQueryRepository{
CachedBaseRepositoryImpl: database.NewCachedBaseRepositoryImpl(db, logger, CertificationsTable),
}
}
// ================ 基础查询操作 ================
// GetByID 根据ID获取认证
//
// 缓存策略:
// - 使用智能主键查询自动缓存30分钟
// - 主键查询命中率高,适合长期缓存
//
// 参数:
// - ctx: 上下文
// - id: 认证ID
//
// 返回:
// - *entities.Certification: 查询到的认证未找到时返回nil
// - error: 查询失败时的错误信息
func (r *GormCertificationQueryRepository) GetByID(ctx context.Context, id string) (*entities.Certification, error) {
var cert entities.Certification
if err := r.SmartGetByID(ctx, id, &cert); err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("认证记录不存在")
}
return nil, fmt.Errorf("查询认证记录失败: %w", err)
}
return &cert, nil
}
// GetByUserID 根据用户ID获取认证
//
// 缓存策略:
// - 业务查询缓存15分钟
// - 用户查询频率较高,适合中期缓存
//
// 参数:
// - ctx: 上下文
// - userID: 用户ID
//
// 返回:
// - *entities.Certification: 查询到的认证未找到时返回nil
// - error: 查询失败时的错误信息
func (r *GormCertificationQueryRepository) GetByUserID(ctx context.Context, userID string) (*entities.Certification, error) {
var cert entities.Certification
err := r.SmartGetByField(ctx, &cert, "user_id", userID, QueryCacheTTLUserQuery)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("用户尚未创建认证申请")
}
return nil, fmt.Errorf("查询用户认证记录失败: %w", err)
}
return &cert, nil
}
// Exists 检查认证是否存在
func (r *GormCertificationQueryRepository) Exists(ctx context.Context, id string) (bool, error) {
return r.ExistsEntity(ctx, id, &entities.Certification{})
}
// ================ 列表查询 ================
// List 分页列表查询
//
// 缓存策略:
// - 搜索查询短期缓存2分钟避免频繁数据库查询但保证实时性
// - 常规列表:智能缓存(根据查询复杂度自动选择缓存策略)
//
// 参数:
// - ctx: 上下文
// - query: 列表查询条件
//
// 返回:
// - []*entities.Certification: 查询结果列表
// - int64: 总记录数
// - error: 查询失败时的错误信息
func (r *GormCertificationQueryRepository) List(ctx context.Context, query *queries.ListCertificationsQuery) ([]*entities.Certification, int64, error) {
db := r.GetDB(ctx).Model(&entities.Certification{})
// 应用过滤条件
if query.UserID != "" {
db = db.Where("user_id = ?", query.UserID)
}
if query.Status != "" {
db = db.Where("status = ?", query.Status)
}
if len(query.Statuses) > 0 {
db = db.Where("status IN ?", query.Statuses)
}
// 获取总数
var total int64
if err := db.Count(&total).Error; err != nil {
return nil, 0, fmt.Errorf("查询认证总数失败: %w", err)
}
// 应用排序和分页
if query.SortBy != "" {
orderClause := query.SortBy
if query.SortOrder != "" {
orderClause += " " + strings.ToUpper(query.SortOrder)
}
db = db.Order(orderClause)
} else {
db = db.Order("created_at DESC")
}
offset := (query.Page - 1) * query.PageSize
db = db.Offset(offset).Limit(query.PageSize)
// 执行查询
var certifications []*entities.Certification
if err := db.Find(&certifications).Error; err != nil {
return nil, 0, fmt.Errorf("查询认证列表失败: %w", err)
}
return certifications, total, nil
}
// ListByUserIDs 根据用户ID列表查询
func (r *GormCertificationQueryRepository) ListByUserIDs(ctx context.Context, userIDs []string) ([]*entities.Certification, error) {
if len(userIDs) == 0 {
return []*entities.Certification{}, nil
}
var certifications []*entities.Certification
if err := r.GetDB(ctx).Where("user_id IN ?", userIDs).Find(&certifications).Error; err != nil {
return nil, fmt.Errorf("根据用户ID列表查询认证失败: %w", err)
}
return certifications, nil
}
// ListByStatus 根据状态查询
func (r *GormCertificationQueryRepository) ListByStatus(ctx context.Context, status enums.CertificationStatus, limit int) ([]*entities.Certification, error) {
db := r.GetDB(ctx).Where("status = ?", status)
if limit > 0 {
db = db.Limit(limit)
}
var certifications []*entities.Certification
if err := db.Find(&certifications).Error; err != nil {
return nil, fmt.Errorf("根据状态查询认证失败: %w", err)
}
return certifications, nil
}
// ================ 业务查询 ================
// FindByAuthFlowID 根据认证流程ID查询
//
// 缓存策略:
// - 业务查询缓存15分钟
// - 回调查询频率较高
//
// 参数:
// - ctx: 上下文
// - authFlowID: 认证流程ID
//
// 返回:
// - *entities.Certification: 查询到的认证未找到时返回nil
// - error: 查询失败时的错误信息
func (r *GormCertificationQueryRepository) FindByAuthFlowID(ctx context.Context, authFlowID string) (*entities.Certification, error) {
var cert entities.Certification
err := r.SmartGetByField(ctx, &cert, "auth_flow_id", authFlowID, QueryCacheTTLBusinessQuery)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("认证流程不存在")
}
return nil, fmt.Errorf("根据认证流程ID查询失败: %w", err)
}
return &cert, nil
}
// FindByEsignFlowID 根据e签宝流程ID查询
//
// 缓存策略:
// - 业务查询缓存15分钟
// - 回调查询频率较高
//
// 参数:
// - ctx: 上下文
// - esignFlowID: e签宝流程ID
//
// 返回:
// - *entities.Certification: 查询到的认证未找到时返回nil
// - error: 查询失败时的错误信息
func (r *GormCertificationQueryRepository) FindByEsignFlowID(ctx context.Context, esignFlowID string) (*entities.Certification, error) {
var cert entities.Certification
err := r.SmartGetByField(ctx, &cert, "esign_flow_id", esignFlowID, QueryCacheTTLBusinessQuery)
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("e签宝流程不存在")
}
return nil, fmt.Errorf("根据e签宝流程ID查询失败: %w", err)
}
return &cert, nil
}
// ListPendingRetry 查询待重试的认证
//
// 缓存策略:
// - 管理查询,不缓存保证数据实时性
//
// 参数:
// - ctx: 上下文
// - maxRetryCount: 最大重试次数
//
// 返回:
// - []*entities.Certification: 待重试的认证列表
// - error: 查询失败时的错误信息
func (r *GormCertificationQueryRepository) ListPendingRetry(ctx context.Context, maxRetryCount int) ([]*entities.Certification, error) {
var certifications []*entities.Certification
err := r.WithoutCache().GetDB(ctx).
Where("status IN ? AND retry_count < ?",
[]enums.CertificationStatus{
enums.StatusInfoRejected,
enums.StatusContractRejected,
enums.StatusContractExpired,
},
maxRetryCount).
Order("created_at ASC").
Find(&certifications).Error
if err != nil {
return nil, fmt.Errorf("查询待重试认证失败: %w", err)
}
return certifications, nil
}
// GetPendingCertifications 获取待处理认证
func (r *GormCertificationQueryRepository) GetPendingCertifications(ctx context.Context) ([]*entities.Certification, error) {
var certifications []*entities.Certification
err := r.WithoutCache().GetDB(ctx).
Where("status IN ?", []enums.CertificationStatus{
enums.StatusPending,
enums.StatusInfoSubmitted,
}).
Order("created_at ASC").
Find(&certifications).Error
if err != nil {
return nil, fmt.Errorf("查询待处理认证失败: %w", err)
}
return certifications, nil
}
// GetExpiredContracts 获取过期合同
func (r *GormCertificationQueryRepository) GetExpiredContracts(ctx context.Context) ([]*entities.Certification, error) {
var certifications []*entities.Certification
err := r.WithoutCache().GetDB(ctx).
Where("status = ?", enums.StatusContractExpired).
Order("updated_at DESC").
Find(&certifications).Error
if err != nil {
return nil, fmt.Errorf("查询过期合同失败: %w", err)
}
return certifications, nil
}
// GetCertificationsByDateRange 根据日期范围获取认证
func (r *GormCertificationQueryRepository) GetCertificationsByDateRange(ctx context.Context, startDate, endDate time.Time) ([]*entities.Certification, error) {
var certifications []*entities.Certification
err := r.GetDB(ctx).
Where("created_at BETWEEN ? AND ?", startDate, endDate).
Order("created_at DESC").
Find(&certifications).Error
if err != nil {
return nil, fmt.Errorf("根据日期范围查询认证失败: %w", err)
}
return certifications, nil
}
// GetUserActiveCertification 获取用户当前活跃认证
func (r *GormCertificationQueryRepository) GetUserActiveCertification(ctx context.Context, userID string) (*entities.Certification, error) {
var cert entities.Certification
err := r.GetDB(ctx).
Where("user_id = ? AND status NOT IN ?", userID, []enums.CertificationStatus{
enums.StatusContractSigned,
enums.StatusInfoRejected,
enums.StatusContractRejected,
enums.StatusContractExpired,
}).
First(&cert).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("用户没有活跃的认证申请")
}
return nil, fmt.Errorf("查询用户活跃认证失败: %w", err)
}
return &cert, nil
}
// ================ 统计查询 ================
// GetStatistics 获取统计数据
func (r *GormCertificationQueryRepository) GetStatistics(ctx context.Context, period repositories.CertificationTimePeriod) (*repositories.CertificationStatistics, error) {
now := time.Now()
var startDate time.Time
switch period {
case repositories.PeriodDaily:
startDate = now.AddDate(0, 0, -1)
case repositories.PeriodWeekly:
startDate = now.AddDate(0, 0, -7)
case repositories.PeriodMonthly:
startDate = now.AddDate(0, -1, 0)
case repositories.PeriodYearly:
startDate = now.AddDate(-1, 0, 0)
default:
startDate = now.AddDate(0, 0, -7)
}
// 使用短期缓存进行统计查询
var totalCount int64
r.WithShortCache().GetDB(ctx).Model(&entities.Certification{}).
Where("created_at BETWEEN ? AND ?", startDate, now).
Count(&totalCount)
var completedCount int64
r.WithShortCache().GetDB(ctx).Model(&entities.Certification{}).
Where("created_at BETWEEN ? AND ? AND status = ?", startDate, now, enums.StatusContractSigned).
Count(&completedCount)
successRate := float64(0)
if totalCount > 0 {
successRate = float64(completedCount) / float64(totalCount)
}
return &repositories.CertificationStatistics{
Period: period,
StartDate: startDate,
EndDate: now,
TotalCertifications: totalCount,
CompletedCount: completedCount,
SuccessRate: successRate,
StatusDistribution: make(map[enums.CertificationStatus]int64),
FailureDistribution: make(map[enums.FailureReason]int64),
AvgProcessingTime: 24 * time.Hour, // 简化实现
}, nil
}
// CountByStatus 按状态统计
func (r *GormCertificationQueryRepository) CountByStatus(ctx context.Context, status enums.CertificationStatus) (int64, error) {
var count int64
if err := r.WithShortCache().GetDB(ctx).Model(&entities.Certification{}).Where("status = ?", status).Count(&count).Error; err != nil {
return 0, fmt.Errorf("按状态统计认证失败: %w", err)
}
return count, nil
}
// CountByFailureReason 按失败原因统计
func (r *GormCertificationQueryRepository) CountByFailureReason(ctx context.Context, reason enums.FailureReason) (int64, error) {
var count int64
if err := r.WithShortCache().GetDB(ctx).Model(&entities.Certification{}).Where("failure_reason = ?", reason).Count(&count).Error; err != nil {
return 0, fmt.Errorf("按失败原因统计认证失败: %w", err)
}
return count, nil
}
// GetProgressStatistics 获取进度统计
func (r *GormCertificationQueryRepository) GetProgressStatistics(ctx context.Context) (*repositories.CertificationProgressStats, error) {
// 简化实现
return &repositories.CertificationProgressStats{
StatusProgress: make(map[enums.CertificationStatus]int64),
ProgressDistribution: make(map[int]int64),
StageTimeStats: make(map[string]*repositories.CertificationStageTimeInfo),
}, nil
}
// SearchByCompanyName 按公司名搜索
func (r *GormCertificationQueryRepository) SearchByCompanyName(ctx context.Context, companyName string, limit int) ([]*entities.Certification, error) {
// 简化实现,暂时返回空结果
r.GetLogger().Warn("按公司名搜索功能待实现,需要企业信息服务支持")
return []*entities.Certification{}, nil
}
// SearchByLegalPerson 按法人搜索
func (r *GormCertificationQueryRepository) SearchByLegalPerson(ctx context.Context, legalPersonName string, limit int) ([]*entities.Certification, error) {
// 简化实现,暂时返回空结果
r.GetLogger().Warn("按法人搜索功能待实现,需要企业信息服务支持")
return []*entities.Certification{}, nil
}
// InvalidateCache 清除缓存
func (r *GormCertificationQueryRepository) InvalidateCache(ctx context.Context, keys ...string) error {
// 简化实现,暂不处理缓存
return nil
}
// RefreshCache 刷新缓存
func (r *GormCertificationQueryRepository) RefreshCache(ctx context.Context, certificationID string) error {
// 简化实现,暂不处理缓存
return nil
}
// ================ 缓存管理方法 ================
// WarmupCache 预热认证查询缓存
//
// 业务说明:
// - 系统启动时预热常用查询的缓存
// - 提升首次访问的响应速度
//
// 预热策略:
// - 活跃认证30分钟长期缓存
// - 待处理认证15分钟中期缓存
func (r *GormCertificationQueryRepository) WarmupCache(ctx context.Context) error {
r.GetLogger().Info("开始预热认证查询缓存")
queries := []database.WarmupQuery{
{
Name: "active_certifications",
TTL: QueryCacheTTLWarmupLong,
Dest: &[]entities.Certification{},
},
{
Name: "pending_certifications",
TTL: QueryCacheTTLWarmupMedium,
Dest: &[]entities.Certification{},
},
}
return r.WarmupCommonQueries(ctx, queries)
}
// GetCacheStats 获取缓存统计信息
//
// 返回当前Repository的缓存使用统计包括
// - 基础缓存信息(命中率、键数量等)
// - 特定的缓存模式列表
// - 性能指标
func (r *GormCertificationQueryRepository) GetCacheStats() map[string]interface{} {
stats := r.GetCacheInfo()
stats["specific_patterns"] = []string{
QueryCachePatternTable,
QueryCachePatternUser,
}
return stats
}

View File

@@ -1,370 +0,0 @@
package repositories
import (
"context"
"errors"
"time"
"go.uber.org/zap"
"gorm.io/gorm"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/repositories"
"tyapi-server/internal/domains/certification/repositories/queries"
"tyapi-server/internal/shared/interfaces"
)
// GormCertificationRepository GORM认证仓储实现
type GormCertificationRepository struct {
db *gorm.DB
logger *zap.Logger
}
// 编译时检查接口实现
var _ repositories.CertificationRepository = (*GormCertificationRepository)(nil)
// NewGormCertificationRepository 创建GORM认证仓储
func NewGormCertificationRepository(db *gorm.DB, logger *zap.Logger) repositories.CertificationRepository {
return &GormCertificationRepository{
db: db,
logger: logger,
}
}
// ================ 基础CRUD操作 ================
// Create 创建认证申请
func (r *GormCertificationRepository) Create(ctx context.Context, cert entities.Certification) (entities.Certification, error) {
r.logger.Info("创建认证申请", zap.String("user_id", cert.UserID))
err := r.db.WithContext(ctx).Create(&cert).Error
return cert, err
}
// GetByID 根据ID获取认证申请
func (r *GormCertificationRepository) GetByID(ctx context.Context, id string) (entities.Certification, error) {
var cert entities.Certification
err := r.db.WithContext(ctx).Where("id = ?", id).First(&cert).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return entities.Certification{}, gorm.ErrRecordNotFound
}
return entities.Certification{}, err
}
return cert, err
}
// GetByAuthFlowID 根据认证流程ID获取认证申请
func (r *GormCertificationRepository) GetByAuthFlowID(ctx context.Context, authFlowID string) (entities.Certification, error) {
var cert entities.Certification
err := r.db.WithContext(ctx).Where("auth_flow_id = ?", authFlowID).First(&cert).Error
return cert, err
}
// GetByEsignFlowID 根据签署流程ID获取认证申请
func (r *GormCertificationRepository) GetByEsignFlowID(ctx context.Context, esignFlowID string) (entities.Certification, error) {
var cert entities.Certification
err := r.db.WithContext(ctx).Where("esign_flow_id = ?", esignFlowID).First(&cert).Error
return cert, err
}
// Update 更新认证申请
func (r *GormCertificationRepository) Update(ctx context.Context, cert entities.Certification) error {
r.logger.Info("更新认证申请", zap.String("id", cert.ID))
return r.db.WithContext(ctx).Save(&cert).Error
}
// Delete 删除认证申请
func (r *GormCertificationRepository) Delete(ctx context.Context, id string) error {
r.logger.Info("删除认证申请", zap.String("id", id))
return r.db.WithContext(ctx).Delete(&entities.Certification{}, "id = ?", id).Error
}
// SoftDelete 软删除认证申请
func (r *GormCertificationRepository) SoftDelete(ctx context.Context, id string) error {
r.logger.Info("软删除认证申请", zap.String("id", id))
return r.db.WithContext(ctx).Delete(&entities.Certification{}, "id = ?", id).Error
}
// Restore 恢复认证申请
func (r *GormCertificationRepository) Restore(ctx context.Context, id string) error {
r.logger.Info("恢复认证申请", zap.String("id", id))
return r.db.WithContext(ctx).Unscoped().Model(&entities.Certification{}).Where("id = ?", id).Update("deleted_at", nil).Error
}
// Count 统计认证申请数量
func (r *GormCertificationRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
var count int64
query := r.db.WithContext(ctx).Model(&entities.Certification{})
if options.Filters != nil {
for key, value := range options.Filters {
query = query.Where(key+" = ?", value)
}
}
if options.Search != "" {
query = query.Where("user_id LIKE ?", "%"+options.Search+"%")
}
err := query.Count(&count).Error
return count, err
}
// Exists 检查认证申请是否存在
func (r *GormCertificationRepository) Exists(ctx context.Context, id string) (bool, error) {
var count int64
err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("id = ?", id).Count(&count).Error
return count > 0, err
}
// CreateBatch 批量创建认证申请
func (r *GormCertificationRepository) CreateBatch(ctx context.Context, certs []entities.Certification) error {
r.logger.Info("批量创建认证申请", zap.Int("count", len(certs)))
return r.db.WithContext(ctx).Create(&certs).Error
}
// GetByIDs 根据ID列表获取认证申请
func (r *GormCertificationRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.Certification, error) {
var certs []entities.Certification
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&certs).Error
return certs, err
}
// UpdateBatch 批量更新认证申请
func (r *GormCertificationRepository) UpdateBatch(ctx context.Context, certs []entities.Certification) error {
r.logger.Info("批量更新认证申请", zap.Int("count", len(certs)))
return r.db.WithContext(ctx).Save(&certs).Error
}
// DeleteBatch 批量删除认证申请
func (r *GormCertificationRepository) DeleteBatch(ctx context.Context, ids []string) error {
r.logger.Info("批量删除认证申请", zap.Strings("ids", ids))
return r.db.WithContext(ctx).Delete(&entities.Certification{}, "id IN ?", ids).Error
}
// List 获取认证申请列表
func (r *GormCertificationRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.Certification, error) {
var certs []entities.Certification
query := r.db.WithContext(ctx).Model(&entities.Certification{})
if options.Filters != nil {
for key, value := range options.Filters {
query = query.Where(key+" = ?", value)
}
}
if options.Search != "" {
query = query.Where("user_id LIKE ?", "%"+options.Search+"%")
}
if options.Sort != "" {
order := "ASC"
if options.Order != "" {
order = options.Order
}
query = query.Order(options.Sort + " " + order)
}
if options.Page > 0 && options.PageSize > 0 {
offset := (options.Page - 1) * options.PageSize
query = query.Offset(offset).Limit(options.PageSize)
}
return certs, query.Find(&certs).Error
}
// WithTx 使用事务
func (r *GormCertificationRepository) WithTx(tx interface{}) interfaces.Repository[entities.Certification] {
if gormTx, ok := tx.(*gorm.DB); ok {
return &GormCertificationRepository{
db: gormTx,
logger: r.logger,
}
}
return r
}
// ================ 业务方法 ================
// ListCertifications 获取认证申请列表(带分页和筛选)
func (r *GormCertificationRepository) ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) ([]*entities.Certification, int64, error) {
var certs []entities.Certification
var total int64
dbQuery := r.db.WithContext(ctx).Model(&entities.Certification{})
// 应用筛选条件
if query.UserID != "" {
dbQuery = dbQuery.Where("user_id = ?", query.UserID)
}
if query.Status != "" {
dbQuery = dbQuery.Where("status = ?", query.Status)
}
if query.StartDate != "" {
dbQuery = dbQuery.Where("created_at >= ?", query.StartDate)
}
if query.EndDate != "" {
dbQuery = dbQuery.Where("created_at <= ?", query.EndDate)
}
if query.EnterpriseName != "" {
// 简化企业名称查询,暂时不关联企业表
dbQuery = dbQuery.Where("user_id IN (SELECT user_id FROM enterprise_infos WHERE company_name LIKE ?)", "%"+query.EnterpriseName+"%")
}
// 统计总数
if err := dbQuery.Count(&total).Error; err != nil {
return nil, 0, err
}
// 应用分页
if query.Page > 0 && query.PageSize > 0 {
offset := (query.Page - 1) * query.PageSize
dbQuery = dbQuery.Offset(offset).Limit(query.PageSize)
}
// 排序
dbQuery = dbQuery.Order("created_at DESC")
// 执行查询
if err := dbQuery.Find(&certs).Error; err != nil {
return nil, 0, err
}
// 转换为指针切片
var result []*entities.Certification
for i := range certs {
result = append(result, &certs[i])
}
return result, total, nil
}
// GetByUserID 根据用户ID获取认证申请
func (r *GormCertificationRepository) GetByUserID(ctx context.Context, userID string) (*entities.Certification, error) {
var cert entities.Certification
err := r.db.WithContext(ctx).Where("user_id = ?", userID).First(&cert).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, gorm.ErrRecordNotFound
}
return nil, err
}
return &cert, nil
}
// GetByStatus 根据状态获取认证申请列表
func (r *GormCertificationRepository) GetByStatus(ctx context.Context, status string) ([]*entities.Certification, error) {
var certs []entities.Certification
err := r.db.WithContext(ctx).Where("status = ?", status).Find(&certs).Error
if err != nil {
return nil, err
}
var result []*entities.Certification
for i := range certs {
result = append(result, &certs[i])
}
return result, nil
}
// UpdateStatus 更新认证状态
func (r *GormCertificationRepository) UpdateStatus(ctx context.Context, certificationID string, status string) error {
r.logger.Info("更新认证状态",
zap.String("certification_id", certificationID),
zap.String("status", status),
)
updates := map[string]interface{}{
"status": status,
"updated_at": time.Now(),
}
// 根据状态更新时间戳
switch status {
case "info_submitted":
updates["info_submitted_at"] = time.Now()
case "enterprise_verified":
updates["enterprise_verified_at"] = time.Now()
case "contract_applied":
updates["contract_applied_at"] = time.Now()
case "contract_signed":
updates["contract_signed_at"] = time.Now()
case "completed":
updates["completed_at"] = time.Now()
}
return r.db.WithContext(ctx).Model(&entities.Certification{}).
Where("id = ?", certificationID).
Updates(updates).Error
}
// GetPendingCertifications 获取待处理的认证申请
func (r *GormCertificationRepository) GetPendingCertifications(ctx context.Context) ([]*entities.Certification, error) {
return r.GetByStatus(ctx, "pending")
}
// GetStats 获取认证统计信息
func (r *GormCertificationRepository) GetStats(ctx context.Context) (*repositories.CertificationStats, error) {
var stats repositories.CertificationStats
// 总认证数
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).Count(&stats.TotalCertifications).Error; err != nil {
return nil, err
}
// 待处理认证数
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("status = ?", "pending").Count(&stats.PendingCertifications).Error; err != nil {
return nil, err
}
// 已完成认证数
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("status = ?", "completed").Count(&stats.CompletedCertifications).Error; err != nil {
return nil, err
}
// 今日提交数
today := time.Now().Format("2006-01-02")
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).
Where("DATE(created_at) = ?", today).
Count(&stats.TodaySubmissions).Error; err != nil {
return nil, err
}
return &stats, nil
}
// GetStatsByDateRange 根据日期范围获取认证统计信息
func (r *GormCertificationRepository) GetStatsByDateRange(ctx context.Context, startDate, endDate string) (*repositories.CertificationStats, error) {
var stats repositories.CertificationStats
// 总认证数
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).
Where("created_at BETWEEN ? AND ?", startDate, endDate).
Count(&stats.TotalCertifications).Error; err != nil {
return nil, err
}
// 待处理认证数
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).
Where("status = ? AND created_at BETWEEN ? AND ?", "pending", startDate, endDate).
Count(&stats.PendingCertifications).Error; err != nil {
return nil, err
}
// 已完成认证数
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).
Where("status = ? AND created_at BETWEEN ? AND ?", "completed", startDate, endDate).
Count(&stats.CompletedCertifications).Error; err != nil {
return nil, err
}
// 今日提交数
today := time.Now().Format("2006-01-02")
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).
Where("DATE(created_at) = ?", today).
Count(&stats.TodaySubmissions).Error; err != nil {
return nil, err
}
return &stats, nil
}

View File

@@ -1,596 +0,0 @@
package repositories
import (
"context"
"time"
"go.uber.org/zap"
"gorm.io/gorm"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/repositories"
"tyapi-server/internal/domains/certification/repositories/queries"
"tyapi-server/internal/shared/database"
"tyapi-server/internal/shared/interfaces"
)
// ================ 常量定义 ================
const (
// 表名常量
EnterpriseInfoSubmitRecordsTable = "enterprise_info_submit_records"
// 缓存时间常量
CacheTTLPrimaryQuery = 30 * time.Minute // 主键查询缓存时间
CacheTTLBusinessQuery = 15 * time.Minute // 业务查询缓存时间
CacheTTLUserQuery = 10 * time.Minute // 用户相关查询缓存时间
CacheTTLSearchQuery = 2 * time.Minute // 搜索查询缓存时间
CacheTTLActiveRecords = 5 * time.Minute // 活跃记录查询缓存时间
CacheTTLWarmupLong = 30 * time.Minute // 预热长期缓存
CacheTTLWarmupMedium = 15 * time.Minute // 预热中期缓存
// 缓存键模式常量
CachePatternTable = "gorm_cache:enterprise_info_submit_records:*"
CachePatternCertification = "enterprise_info:certification_id:*"
CachePatternUser = "enterprise_info:user_id:*"
// 业务常量
StatusActive = "active"
StatusPending = "pending"
)
// ================ Repository 实现 ================
// GormEnterpriseInfoSubmitRecordRepository 企业信息提交记录GORM仓储实现
//
// 特性说明:
// - 基于 CachedBaseRepositoryImpl 实现自动缓存管理
// - 支持多级缓存策略主键查询30分钟业务查询15分钟搜索2分钟
// - 自动缓存失效:写操作时自动清理相关缓存
// - 智能缓存选择:根据查询复杂度自动选择缓存策略
// - 内置监控支持:提供缓存统计和性能监控
type GormEnterpriseInfoSubmitRecordRepository struct {
*database.CachedBaseRepositoryImpl
}
// 编译时检查接口实现
var _ repositories.EnterpriseInfoSubmitRecordRepository = (*GormEnterpriseInfoSubmitRecordRepository)(nil)
// NewGormEnterpriseInfoSubmitRecordRepository 创建企业信息提交记录GORM仓储
//
// 参数:
// - db: GORM数据库连接实例
// - logger: 日志记录器
//
// 返回:
// - repositories.EnterpriseInfoSubmitRecordRepository: 仓储接口实现
func NewGormEnterpriseInfoSubmitRecordRepository(db *gorm.DB, logger *zap.Logger) repositories.EnterpriseInfoSubmitRecordRepository {
return &GormEnterpriseInfoSubmitRecordRepository{
CachedBaseRepositoryImpl: database.NewCachedBaseRepositoryImpl(db, logger, EnterpriseInfoSubmitRecordsTable),
}
}
// ================ Repository[T] 接口实现 ================
// Create 创建企业信息提交记录
//
// 业务说明:
// - 创建新的企业信息提交记录
// - 自动触发相关缓存失效
//
// 参数:
// - ctx: 上下文
// - record: 要创建的记录实体
//
// 返回:
// - entities.EnterpriseInfoSubmitRecord: 创建后的记录包含生成的ID
// - error: 创建失败时的错误信息
func (r *GormEnterpriseInfoSubmitRecordRepository) Create(ctx context.Context, record entities.EnterpriseInfoSubmitRecord) (entities.EnterpriseInfoSubmitRecord, error) {
r.GetLogger().Info("创建企业信息提交记录",
zap.String("user_id", record.UserID),
zap.String("company_name", record.CompanyName))
err := r.CreateEntity(ctx, &record)
return record, err
}
// GetByID 根据ID获取企业信息提交记录
//
// 缓存策略:
// - 使用智能主键查询自动缓存30分钟
// - 主键查询命中率高,适合长期缓存
//
// 参数:
// - ctx: 上下文
// - id: 记录ID
//
// 返回:
// - entities.EnterpriseInfoSubmitRecord: 查询到的记录
// - error: 查询失败或记录不存在时的错误
func (r *GormEnterpriseInfoSubmitRecordRepository) GetByID(ctx context.Context, id string) (entities.EnterpriseInfoSubmitRecord, error) {
var record entities.EnterpriseInfoSubmitRecord
err := r.SmartGetByID(ctx, id, &record)
return record, err
}
// Update 更新企业信息提交记录
//
// 缓存影响:
// - GORM缓存插件会自动失效相关缓存
// - 无需手动管理缓存一致性
//
// 参数:
// - ctx: 上下文
// - record: 要更新的记录实体
//
// 返回:
// - error: 更新失败时的错误信息
func (r *GormEnterpriseInfoSubmitRecordRepository) Update(ctx context.Context, record entities.EnterpriseInfoSubmitRecord) error {
r.GetLogger().Info("更新企业信息提交记录",
zap.String("id", record.ID),
zap.String("company_name", record.CompanyName))
return r.UpdateEntity(ctx, &record)
}
// CreateBatch 批量创建企业信息提交记录
func (r *GormEnterpriseInfoSubmitRecordRepository) CreateBatch(ctx context.Context, records []entities.EnterpriseInfoSubmitRecord) error {
r.GetLogger().Info("批量创建企业信息提交记录", zap.Int("count", len(records)))
return r.CreateBatchEntity(ctx, &records)
}
// GetByIDs 根据ID列表获取企业信息提交记录
func (r *GormEnterpriseInfoSubmitRecordRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.EnterpriseInfoSubmitRecord, error) {
var records []entities.EnterpriseInfoSubmitRecord
err := r.GetEntitiesByIDs(ctx, ids, &records)
return records, err
}
// UpdateBatch 批量更新企业信息提交记录
func (r *GormEnterpriseInfoSubmitRecordRepository) UpdateBatch(ctx context.Context, records []entities.EnterpriseInfoSubmitRecord) error {
r.GetLogger().Info("批量更新企业信息提交记录", zap.Int("count", len(records)))
return r.UpdateBatchEntity(ctx, &records)
}
// DeleteBatch 批量删除企业信息提交记录
func (r *GormEnterpriseInfoSubmitRecordRepository) DeleteBatch(ctx context.Context, ids []string) error {
r.GetLogger().Info("批量删除企业信息提交记录", zap.Strings("ids", ids))
return r.DeleteBatchEntity(ctx, ids, &entities.EnterpriseInfoSubmitRecord{})
}
// List 获取企业信息提交记录列表
//
// 缓存策略:
// - 搜索查询短期缓存2分钟避免频繁数据库查询但保证实时性
// - 常规列表:智能缓存(根据查询复杂度自动选择缓存策略)
//
// 参数:
// - ctx: 上下文
// - options: 列表查询选项(分页、排序、筛选、搜索等)
//
// 返回:
// - []entities.EnterpriseInfoSubmitRecord: 查询结果列表
// - error: 查询失败时的错误信息
func (r *GormEnterpriseInfoSubmitRecordRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.EnterpriseInfoSubmitRecord, error) {
var records []entities.EnterpriseInfoSubmitRecord
// 搜索查询:使用短期缓存避免频繁数据库查询
if options.Search != "" {
return r.handleSearchQuery(ctx, options)
}
// 常规列表:使用智能缓存策略
err := r.SmartList(ctx, &records, options)
return records, err
}
// ================ BaseRepository 接口实现 ================
// Delete 删除企业信息提交记录
func (r *GormEnterpriseInfoSubmitRecordRepository) Delete(ctx context.Context, id string) error {
return r.DeleteEntity(ctx, id, &entities.EnterpriseInfoSubmitRecord{})
}
// Exists 检查企业信息提交记录是否存在
func (r *GormEnterpriseInfoSubmitRecordRepository) Exists(ctx context.Context, id string) (bool, error) {
return r.ExistsEntity(ctx, id, &entities.EnterpriseInfoSubmitRecord{})
}
// Count 统计企业信息提交记录数量
//
// 缓存策略:
// - 搜索统计:使用自定义搜索逻辑,不缓存保证准确性
// - 常规统计:使用基础实现的缓存策略
func (r *GormEnterpriseInfoSubmitRecordRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
if options.Search != "" {
return r.CountWhere(ctx, &entities.EnterpriseInfoSubmitRecord{},
"company_name LIKE ? OR unified_social_code LIKE ? OR legal_person_name LIKE ?",
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
}
return r.CountWithOptions(ctx, &entities.EnterpriseInfoSubmitRecord{}, options)
}
// SoftDelete 软删除企业信息提交记录
func (r *GormEnterpriseInfoSubmitRecordRepository) SoftDelete(ctx context.Context, id string) error {
return r.SoftDeleteEntity(ctx, id, &entities.EnterpriseInfoSubmitRecord{})
}
// Restore 恢复企业信息提交记录
func (r *GormEnterpriseInfoSubmitRecordRepository) Restore(ctx context.Context, id string) error {
return r.RestoreEntity(ctx, id, &entities.EnterpriseInfoSubmitRecord{})
}
// ================ 业务专用查询方法 ================
// GetByCertificationID 根据认证ID获取企业信息提交记录
//
// 业务说明:
// - 每个认证流程对应一个企业信息提交记录
// - 适用于认证流程中查询企业信息
//
// 缓存策略:
// - 业务查询缓存15分钟
// - 认证ID查询频率较高适合中期缓存
//
// 参数:
// - ctx: 上下文
// - certificationID: 认证流程ID
//
// 返回:
// - *entities.EnterpriseInfoSubmitRecord: 查询到的记录未找到时返回nil
// - error: 查询失败时的错误信息
func (r *GormEnterpriseInfoSubmitRecordRepository) GetByCertificationID(ctx context.Context, certificationID string) (*entities.EnterpriseInfoSubmitRecord, error) {
var record entities.EnterpriseInfoSubmitRecord
err := r.SmartGetByField(ctx, &record, "certification_id", certificationID, CacheTTLBusinessQuery)
if err != nil {
return nil, err
}
return &record, nil
}
// GetByUserID 根据用户ID获取企业信息提交记录列表
//
// 业务说明:
// - 获取某用户的所有企业信息提交记录
// - 按创建时间倒序排列,最新的在前
//
// 缓存策略:
// - 用户相关查询使用中期缓存10分钟
// - 用户查询频率中等,适合中期缓存
//
// 参数:
// - ctx: 上下文
// - userID: 用户ID
//
// 返回:
// - []*entities.EnterpriseInfoSubmitRecord: 查询结果列表
// - error: 查询失败时的错误信息
func (r *GormEnterpriseInfoSubmitRecordRepository) GetByUserID(ctx context.Context, userID string) ([]*entities.EnterpriseInfoSubmitRecord, error) {
var records []*entities.EnterpriseInfoSubmitRecord
err := r.WithMediumCache().GetDB(ctx).
Where("user_id = ?", userID).
Order("created_at DESC").
Find(&records).Error
return records, err
}
// GetLatestByUserID 根据用户ID获取最新的企业信息提交记录
//
// 业务说明:
// - 获取用户最新提交的企业信息记录
// - 适用于用户中心显示最新提交状态
//
// 缓存策略:
// - 最新记录查询缓存15分钟
// - 用户中心访问频率较高
func (r *GormEnterpriseInfoSubmitRecordRepository) GetLatestByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error) {
var record entities.EnterpriseInfoSubmitRecord
err := r.GetDB(ctx).
Where("user_id = ?", userID).
Order("created_at DESC").
First(&record).Error
if err != nil {
return nil, err
}
return &record, nil
}
// ================ 业务状态管理方法 ================
// UpdateStatus 更新企业信息提交记录状态
//
// 业务说明:
// - 更新记录的审核状态和备注信息
// - 适用于管理员审核流程
//
// 缓存影响:
// - GORM缓存插件会自动失效表相关的缓存
// - 状态更新会影响列表查询和统计结果
//
// 参数:
// - ctx: 上下文
// - recordID: 记录ID
// - status: 新状态
// - reason: 状态变更原因或备注
//
// 返回:
// - error: 更新失败时的错误信息
func (r *GormEnterpriseInfoSubmitRecordRepository) UpdateStatus(ctx context.Context, recordID string, status string, reason string) error {
r.GetLogger().Info("更新企业信息提交记录状态",
zap.String("record_id", recordID),
zap.String("status", status),
zap.String("reason", reason))
updates := map[string]interface{}{
"status": status,
"updated_at": time.Now(),
}
if reason != "" {
updates["reason"] = reason
}
return r.GetDB(ctx).Model(&entities.EnterpriseInfoSubmitRecord{}).
Where("id = ?", recordID).
Updates(updates).Error
}
// ================ 高级查询方法 ================
// ListRecords 获取企业信息提交记录列表(带分页和高级筛选)
//
// 业务说明:
// - 管理员后台的高级查询接口
// - 支持多维度筛选和分页
//
// 参数:
// - ctx: 上下文
// - query: 高级查询条件
//
// 返回:
// - []*entities.EnterpriseInfoSubmitRecord: 查询结果
// - int64: 总记录数
// - error: 查询失败时的错误
func (r *GormEnterpriseInfoSubmitRecordRepository) ListRecords(ctx context.Context, query *queries.ListEnterpriseInfoSubmitRecordsQuery) ([]*entities.EnterpriseInfoSubmitRecord, int64, error) {
var records []*entities.EnterpriseInfoSubmitRecord
var total int64
dbQuery := r.GetDB(ctx).Model(&entities.EnterpriseInfoSubmitRecord{})
// 构建查询条件
dbQuery = r.buildQueryConditions(dbQuery, query)
// 统计总数
if err := dbQuery.Count(&total).Error; err != nil {
return nil, 0, err
}
// 应用分页和排序
dbQuery = r.applyPaginationAndSorting(dbQuery, query)
// 查询数据
if err := dbQuery.Find(&records).Error; err != nil {
return nil, 0, err
}
return records, total, nil
}
// ================ 缓存管理方法 ================
// GetActiveRecords 获取活跃记录
//
// 业务说明:
// - 获取状态为活跃的企业信息提交记录
// - 适用于仪表板统计
//
// 缓存策略:
// - 活跃记录查询使用短期缓存5分钟
// - 活跃状态变化较频繁,使用短期缓存
func (r *GormEnterpriseInfoSubmitRecordRepository) GetActiveRecords(ctx context.Context) ([]entities.EnterpriseInfoSubmitRecord, error) {
var records []entities.EnterpriseInfoSubmitRecord
err := r.WithShortCache().FindWithCache(ctx, &records, CacheTTLActiveRecords, "status = ?", StatusActive)
return records, err
}
// GetPendingRecords 获取待审核记录
//
// 业务说明:
// - 获取待审核的企业信息提交记录
// - 适用于管理员工作台
//
// 缓存策略:
// - 禁用缓存,保证数据实时性
// - 待审核状态需要实时准确的数据
func (r *GormEnterpriseInfoSubmitRecordRepository) GetPendingRecords(ctx context.Context) ([]entities.EnterpriseInfoSubmitRecord, error) {
var records []entities.EnterpriseInfoSubmitRecord
db := r.WithoutCache().GetDB(ctx)
err := db.Where("status = ?", StatusPending).
Order("created_at ASC").
Find(&records).Error
return records, err
}
// WarmupCache 预热企业信息提交记录缓存
//
// 业务说明:
// - 系统启动时预热常用查询的缓存
// - 提升首次访问的响应速度
//
// 预热策略:
// - 活跃记录30分钟长期缓存
// - 最近提交15分钟中期缓存
func (r *GormEnterpriseInfoSubmitRecordRepository) WarmupCache(ctx context.Context) error {
r.GetLogger().Info("开始预热企业信息提交记录缓存")
queries := []database.WarmupQuery{
{
Name: "active_records",
TTL: CacheTTLWarmupLong,
Dest: &[]entities.EnterpriseInfoSubmitRecord{},
},
{
Name: "recent_submissions",
TTL: CacheTTLWarmupMedium,
Dest: &[]entities.EnterpriseInfoSubmitRecord{},
},
}
return r.WarmupCommonQueries(ctx, queries)
}
// RefreshRecordCache 刷新记录缓存
//
// 业务说明:
// - 手动刷新企业信息提交记录相关的所有缓存
// - 适用于数据迁移或批量更新后的缓存清理
func (r *GormEnterpriseInfoSubmitRecordRepository) RefreshRecordCache(ctx context.Context) error {
r.GetLogger().Info("刷新企业信息提交记录缓存")
return r.RefreshCache(ctx, CachePatternTable)
}
// GetCacheStats 获取缓存统计信息
//
// 返回当前Repository的缓存使用统计包括
// - 基础缓存信息(命中率、键数量等)
// - 特定的缓存模式列表
// - 性能指标
func (r *GormEnterpriseInfoSubmitRecordRepository) GetCacheStats() map[string]interface{} {
stats := r.GetCacheInfo()
stats["specific_patterns"] = []string{
CachePatternTable,
CachePatternCertification,
CachePatternUser,
}
return stats
}
// ================ 私有辅助方法 ================
// handleSearchQuery 处理搜索查询
//
// 业务逻辑:
// - 支持按企业名称、统一社会信用代码、法定代表人姓名搜索
// - 使用短期缓存避免频繁数据库查询
//
// 参数:
// - ctx: 上下文
// - options: 查询选项
//
// 返回:
// - []entities.EnterpriseInfoSubmitRecord: 搜索结果
// - error: 查询失败时的错误
func (r *GormEnterpriseInfoSubmitRecordRepository) handleSearchQuery(ctx context.Context, options interfaces.ListOptions) ([]entities.EnterpriseInfoSubmitRecord, error) {
var records []entities.EnterpriseInfoSubmitRecord
db := r.GetDB(ctx).
Set("cache:enabled", true).
Set("cache:ttl", CacheTTLSearchQuery).
Model(&entities.EnterpriseInfoSubmitRecord{})
// 应用筛选条件
if options.Filters != nil {
for key, value := range options.Filters {
db = db.Where(key+" = ?", value)
}
}
// 企业信息特定的搜索逻辑
db = db.Where("company_name LIKE ? OR unified_social_code LIKE ? OR legal_person_name LIKE ?",
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
// 应用预加载
for _, include := range options.Include {
db = db.Preload(include)
}
// 应用排序
db = r.applySorting(db, options)
// 应用分页
db = r.applyPagination(db, options)
return records, db.Find(&records).Error
}
// buildQueryConditions 构建查询条件
//
// 参数:
// - db: GORM数据库查询对象
// - query: 查询条件
//
// 返回:
// - *gorm.DB: 应用查询条件后的数据库对象
func (r *GormEnterpriseInfoSubmitRecordRepository) buildQueryConditions(db *gorm.DB, query *queries.ListEnterpriseInfoSubmitRecordsQuery) *gorm.DB {
if query.UserID != "" {
db = db.Where("user_id = ?", query.UserID)
}
if query.Status != "" {
db = db.Where("status = ?", query.Status)
}
if query.StartDate != "" {
db = db.Where("created_at >= ?", query.StartDate)
}
if query.EndDate != "" {
db = db.Where("created_at <= ?", query.EndDate)
}
return db
}
// applyPaginationAndSorting 应用分页和排序
//
// 参数:
// - db: GORM数据库查询对象
// - query: 查询条件
//
// 返回:
// - *gorm.DB: 应用分页和排序后的数据库对象
func (r *GormEnterpriseInfoSubmitRecordRepository) applyPaginationAndSorting(db *gorm.DB, query *queries.ListEnterpriseInfoSubmitRecordsQuery) *gorm.DB {
// 应用分页
offset := (query.Page - 1) * query.PageSize
db = db.Offset(offset).Limit(query.PageSize)
// 默认排序
db = db.Order("created_at DESC")
return db
}
// applySorting 应用排序规则
//
// 参数:
// - db: GORM数据库查询对象
// - options: 查询选项
//
// 返回:
// - *gorm.DB: 应用排序后的数据库对象
func (r *GormEnterpriseInfoSubmitRecordRepository) applySorting(db *gorm.DB, options interfaces.ListOptions) *gorm.DB {
if options.Sort != "" {
order := "ASC"
if options.Order == "desc" || options.Order == "DESC" {
order = "DESC"
}
return db.Order(options.Sort + " " + order)
}
return db.Order("created_at DESC")
}
// applyPagination 应用分页规则
//
// 参数:
// - db: GORM数据库查询对象
// - options: 查询选项
//
// 返回:
// - *gorm.DB: 应用分页后的数据库对象
func (r *GormEnterpriseInfoSubmitRecordRepository) applyPagination(db *gorm.DB, options interfaces.ListOptions) *gorm.DB {
if options.Page > 0 && options.PageSize > 0 {
offset := (options.Page - 1) * options.PageSize
return db.Offset(offset).Limit(options.PageSize)
}
return db
}

View File

@@ -1,315 +0,0 @@
package repositories
import (
"context"
"time"
"go.uber.org/zap"
"gorm.io/gorm"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/repositories"
"tyapi-server/internal/domains/certification/repositories/queries"
"tyapi-server/internal/shared/interfaces"
)
// GormEsignContractGenerateRecordRepository e签宝生成合同记录GORM仓储实现
type GormEsignContractGenerateRecordRepository struct {
db *gorm.DB
logger *zap.Logger
}
// 编译时检查接口实现
var _ repositories.EsignContractGenerateRecordRepository = (*GormEsignContractGenerateRecordRepository)(nil)
// NewGormEsignContractGenerateRecordRepository 创建e签宝生成合同记录GORM仓储
func NewGormEsignContractGenerateRecordRepository(db *gorm.DB, logger *zap.Logger) repositories.EsignContractGenerateRecordRepository {
return &GormEsignContractGenerateRecordRepository{
db: db,
logger: logger,
}
}
// ================ 基础CRUD操作 ================
// Create 创建e签宝生成合同记录
func (r *GormEsignContractGenerateRecordRepository) Create(ctx context.Context, record entities.EsignContractGenerateRecord) (entities.EsignContractGenerateRecord, error) {
r.logger.Info("创建e签宝生成合同记录", zap.String("certification_id", record.CertificationID))
err := r.db.WithContext(ctx).Create(&record).Error
return record, err
}
// GetByID 根据ID获取e签宝生成合同记录
func (r *GormEsignContractGenerateRecordRepository) GetByID(ctx context.Context, id string) (entities.EsignContractGenerateRecord, error) {
var record entities.EsignContractGenerateRecord
err := r.db.WithContext(ctx).Where("id = ?", id).First(&record).Error
return record, err
}
// Update 更新e签宝生成合同记录
func (r *GormEsignContractGenerateRecordRepository) Update(ctx context.Context, record entities.EsignContractGenerateRecord) error {
r.logger.Info("更新e签宝生成合同记录", zap.String("id", record.ID))
return r.db.WithContext(ctx).Save(&record).Error
}
// Delete 删除e签宝生成合同记录
func (r *GormEsignContractGenerateRecordRepository) Delete(ctx context.Context, id string) error {
r.logger.Info("删除e签宝生成合同记录", zap.String("id", id))
return r.db.WithContext(ctx).Delete(&entities.EsignContractGenerateRecord{}, "id = ?", id).Error
}
// SoftDelete 软删除e签宝生成合同记录
func (r *GormEsignContractGenerateRecordRepository) SoftDelete(ctx context.Context, id string) error {
r.logger.Info("软删除e签宝生成合同记录", zap.String("id", id))
return r.db.WithContext(ctx).Delete(&entities.EsignContractGenerateRecord{}, "id = ?", id).Error
}
// Restore 恢复e签宝生成合同记录
func (r *GormEsignContractGenerateRecordRepository) Restore(ctx context.Context, id string) error {
r.logger.Info("恢复e签宝生成合同记录", zap.String("id", id))
return r.db.WithContext(ctx).Unscoped().Model(&entities.EsignContractGenerateRecord{}).Where("id = ?", id).Update("deleted_at", nil).Error
}
// Count 统计e签宝生成合同记录数量
func (r *GormEsignContractGenerateRecordRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
var count int64
query := r.db.WithContext(ctx).Model(&entities.EsignContractGenerateRecord{})
if options.Filters != nil {
for key, value := range options.Filters {
query = query.Where(key+" = ?", value)
}
}
if options.Search != "" {
query = query.Where("contract_name LIKE ?", "%"+options.Search+"%")
}
err := query.Count(&count).Error
return count, err
}
// Exists 检查e签宝生成合同记录是否存在
func (r *GormEsignContractGenerateRecordRepository) Exists(ctx context.Context, id string) (bool, error) {
var count int64
err := r.db.WithContext(ctx).Model(&entities.EsignContractGenerateRecord{}).Where("id = ?", id).Count(&count).Error
return count > 0, err
}
// CreateBatch 批量创建e签宝生成合同记录
func (r *GormEsignContractGenerateRecordRepository) CreateBatch(ctx context.Context, records []entities.EsignContractGenerateRecord) error {
r.logger.Info("批量创建e签宝生成合同记录", zap.Int("count", len(records)))
return r.db.WithContext(ctx).Create(&records).Error
}
// GetByIDs 根据ID列表获取e签宝生成合同记录
func (r *GormEsignContractGenerateRecordRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.EsignContractGenerateRecord, error) {
var records []entities.EsignContractGenerateRecord
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&records).Error
return records, err
}
// UpdateBatch 批量更新e签宝生成合同记录
func (r *GormEsignContractGenerateRecordRepository) UpdateBatch(ctx context.Context, records []entities.EsignContractGenerateRecord) error {
r.logger.Info("批量更新e签宝生成合同记录", zap.Int("count", len(records)))
return r.db.WithContext(ctx).Save(&records).Error
}
// DeleteBatch 批量删除e签宝生成合同记录
func (r *GormEsignContractGenerateRecordRepository) DeleteBatch(ctx context.Context, ids []string) error {
r.logger.Info("批量删除e签宝生成合同记录", zap.Strings("ids", ids))
return r.db.WithContext(ctx).Delete(&entities.EsignContractGenerateRecord{}, "id IN ?", ids).Error
}
// List 获取e签宝生成合同记录列表
func (r *GormEsignContractGenerateRecordRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.EsignContractGenerateRecord, error) {
var records []entities.EsignContractGenerateRecord
query := r.db.WithContext(ctx).Model(&entities.EsignContractGenerateRecord{})
if options.Filters != nil {
for key, value := range options.Filters {
query = query.Where(key+" = ?", value)
}
}
if options.Search != "" {
query = query.Where("contract_name LIKE ?", "%"+options.Search+"%")
}
if options.Sort != "" {
order := "ASC"
if options.Order != "" {
order = options.Order
}
query = query.Order(options.Sort + " " + order)
}
if options.Page > 0 && options.PageSize > 0 {
offset := (options.Page - 1) * options.PageSize
query = query.Offset(offset).Limit(options.PageSize)
}
return records, query.Find(&records).Error
}
// WithTx 使用事务
func (r *GormEsignContractGenerateRecordRepository) WithTx(tx interface{}) interfaces.Repository[entities.EsignContractGenerateRecord] {
if gormTx, ok := tx.(*gorm.DB); ok {
return &GormEsignContractGenerateRecordRepository{
db: gormTx,
logger: r.logger,
}
}
return r
}
// ================ 业务方法 ================
// GetByCertificationID 根据认证ID获取e签宝生成合同记录
func (r *GormEsignContractGenerateRecordRepository) GetByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractGenerateRecord, error) {
var record entities.EsignContractGenerateRecord
err := r.db.WithContext(ctx).Where("certification_id = ?", certificationID).First(&record).Error
if err != nil {
return nil, err
}
return &record, nil
}
// GetByUserID 根据用户ID获取e签宝生成合同记录列表
func (r *GormEsignContractGenerateRecordRepository) GetByUserID(ctx context.Context, userID string) ([]*entities.EsignContractGenerateRecord, error) {
var records []entities.EsignContractGenerateRecord
err := r.db.WithContext(ctx).Where("user_id = ?", userID).Find(&records).Error
if err != nil {
return nil, err
}
var result []*entities.EsignContractGenerateRecord
for i := range records {
result = append(result, &records[i])
}
return result, nil
}
// GetLatestByCertificationID 根据认证ID获取最新的e签宝生成合同记录
func (r *GormEsignContractGenerateRecordRepository) GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractGenerateRecord, error) {
var record entities.EsignContractGenerateRecord
err := r.db.WithContext(ctx).Where("certification_id = ?", certificationID).Order("created_at DESC").First(&record).Error
if err != nil {
return nil, err
}
return &record, nil
}
// ListRecords 获取e签宝生成合同记录列表带分页和筛选
func (r *GormEsignContractGenerateRecordRepository) ListRecords(ctx context.Context, query *queries.ListEsignContractGenerateRecordsQuery) ([]*entities.EsignContractGenerateRecord, int64, error) {
var records []entities.EsignContractGenerateRecord
var total int64
dbQuery := r.db.WithContext(ctx).Model(&entities.EsignContractGenerateRecord{})
// 应用筛选条件
if query.CertificationID != "" {
dbQuery = dbQuery.Where("certification_id = ?", query.CertificationID)
}
if query.UserID != "" {
dbQuery = dbQuery.Where("user_id = ?", query.UserID)
}
if query.Status != "" {
dbQuery = dbQuery.Where("status = ?", query.Status)
}
if query.ContractType != "" {
dbQuery = dbQuery.Where("contract_type = ?", query.ContractType)
}
if query.StartDate != "" {
dbQuery = dbQuery.Where("created_at >= ?", query.StartDate)
}
if query.EndDate != "" {
dbQuery = dbQuery.Where("created_at <= ?", query.EndDate)
}
// 统计总数
if err := dbQuery.Count(&total).Error; err != nil {
return nil, 0, err
}
// 应用分页
if query.Page > 0 && query.PageSize > 0 {
offset := (query.Page - 1) * query.PageSize
dbQuery = dbQuery.Offset(offset).Limit(query.PageSize)
}
// 排序
dbQuery = dbQuery.Order("created_at DESC")
// 执行查询
if err := dbQuery.Find(&records).Error; err != nil {
return nil, 0, err
}
// 转换为指针切片
var result []*entities.EsignContractGenerateRecord
for i := range records {
result = append(result, &records[i])
}
return result, total, nil
}
// UpdateStatus 更新状态
func (r *GormEsignContractGenerateRecordRepository) UpdateStatus(ctx context.Context, recordID string, status string, reason string) error {
r.logger.Info("更新e签宝生成合同记录状态",
zap.String("record_id", recordID),
zap.String("status", status),
)
updates := map[string]interface{}{
"status": status,
"updated_at": time.Now(),
}
// 根据状态设置相应的时间戳
switch status {
case "generating":
// 不需要额外时间戳
case "success":
updates["generated_at"] = time.Now()
case "failed":
updates["failed_at"] = time.Now()
updates["failure_reason"] = reason
}
return r.db.WithContext(ctx).Model(&entities.EsignContractGenerateRecord{}).
Where("id = ?", recordID).
Updates(updates).Error
}
// UpdateSuccessInfo 更新成功信息
func (r *GormEsignContractGenerateRecordRepository) UpdateSuccessInfo(ctx context.Context, recordID, esignFlowID, contractFileID, contractURL string) error {
r.logger.Info("更新e签宝生成合同记录成功信息",
zap.String("record_id", recordID),
zap.String("esign_flow_id", esignFlowID),
)
now := time.Now()
updates := map[string]interface{}{
"status": "success",
"esign_flow_id": esignFlowID,
"contract_file_id": contractFileID,
"contract_url": contractURL,
"generated_at": &now,
"updated_at": now,
}
return r.db.WithContext(ctx).Model(&entities.EsignContractGenerateRecord{}).
Where("id = ?", recordID).
Updates(updates).Error
}
// IncrementRetry 增加重试次数
func (r *GormEsignContractGenerateRecordRepository) IncrementRetry(ctx context.Context, recordID string) error {
r.logger.Info("增加e签宝生成合同记录重试次数", zap.String("record_id", recordID))
return r.db.WithContext(ctx).Model(&entities.EsignContractGenerateRecord{}).
Where("id = ?", recordID).
UpdateColumn("retry_count", gorm.Expr("retry_count + 1")).Error
}

View File

@@ -1,356 +0,0 @@
package repositories
import (
"context"
"time"
"go.uber.org/zap"
"gorm.io/gorm"
"tyapi-server/internal/domains/certification/entities"
"tyapi-server/internal/domains/certification/repositories"
"tyapi-server/internal/domains/certification/repositories/queries"
"tyapi-server/internal/shared/interfaces"
)
// GormEsignContractSignRecordRepository e签宝签署合同记录GORM仓储实现
type GormEsignContractSignRecordRepository struct {
db *gorm.DB
logger *zap.Logger
}
// 编译时检查接口实现
var _ repositories.EsignContractSignRecordRepository = (*GormEsignContractSignRecordRepository)(nil)
// NewGormEsignContractSignRecordRepository 创建e签宝签署合同记录GORM仓储
func NewGormEsignContractSignRecordRepository(db *gorm.DB, logger *zap.Logger) repositories.EsignContractSignRecordRepository {
return &GormEsignContractSignRecordRepository{
db: db,
logger: logger,
}
}
// ================ 基础CRUD操作 ================
// Create 创建e签宝签署合同记录
func (r *GormEsignContractSignRecordRepository) Create(ctx context.Context, record entities.EsignContractSignRecord) (entities.EsignContractSignRecord, error) {
r.logger.Info("创建e签宝签署合同记录", zap.String("certification_id", record.CertificationID))
err := r.db.WithContext(ctx).Create(&record).Error
return record, err
}
// GetByID 根据ID获取e签宝签署合同记录
func (r *GormEsignContractSignRecordRepository) GetByID(ctx context.Context, id string) (entities.EsignContractSignRecord, error) {
var record entities.EsignContractSignRecord
err := r.db.WithContext(ctx).Where("id = ?", id).First(&record).Error
return record, err
}
// Update 更新e签宝签署合同记录
func (r *GormEsignContractSignRecordRepository) Update(ctx context.Context, record entities.EsignContractSignRecord) error {
r.logger.Info("更新e签宝签署合同记录", zap.String("id", record.ID))
return r.db.WithContext(ctx).Save(&record).Error
}
// Delete 删除e签宝签署合同记录
func (r *GormEsignContractSignRecordRepository) Delete(ctx context.Context, id string) error {
r.logger.Info("删除e签宝签署合同记录", zap.String("id", id))
return r.db.WithContext(ctx).Delete(&entities.EsignContractSignRecord{}, "id = ?", id).Error
}
// SoftDelete 软删除e签宝签署合同记录
func (r *GormEsignContractSignRecordRepository) SoftDelete(ctx context.Context, id string) error {
r.logger.Info("软删除e签宝签署合同记录", zap.String("id", id))
return r.db.WithContext(ctx).Delete(&entities.EsignContractSignRecord{}, "id = ?", id).Error
}
// Restore 恢复e签宝签署合同记录
func (r *GormEsignContractSignRecordRepository) Restore(ctx context.Context, id string) error {
r.logger.Info("恢复e签宝签署合同记录", zap.String("id", id))
return r.db.WithContext(ctx).Unscoped().Model(&entities.EsignContractSignRecord{}).Where("id = ?", id).Update("deleted_at", nil).Error
}
// Count 统计e签宝签署合同记录数量
func (r *GormEsignContractSignRecordRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
var count int64
query := r.db.WithContext(ctx).Model(&entities.EsignContractSignRecord{})
if options.Filters != nil {
for key, value := range options.Filters {
query = query.Where(key+" = ?", value)
}
}
if options.Search != "" {
query = query.Where("signer_name LIKE ?", "%"+options.Search+"%")
}
err := query.Count(&count).Error
return count, err
}
// Exists 检查e签宝签署合同记录是否存在
func (r *GormEsignContractSignRecordRepository) Exists(ctx context.Context, id string) (bool, error) {
var count int64
err := r.db.WithContext(ctx).Model(&entities.EsignContractSignRecord{}).Where("id = ?", id).Count(&count).Error
return count > 0, err
}
// CreateBatch 批量创建e签宝签署合同记录
func (r *GormEsignContractSignRecordRepository) CreateBatch(ctx context.Context, records []entities.EsignContractSignRecord) error {
r.logger.Info("批量创建e签宝签署合同记录", zap.Int("count", len(records)))
return r.db.WithContext(ctx).Create(&records).Error
}
// GetByIDs 根据ID列表获取e签宝签署合同记录
func (r *GormEsignContractSignRecordRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.EsignContractSignRecord, error) {
var records []entities.EsignContractSignRecord
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&records).Error
return records, err
}
// UpdateBatch 批量更新e签宝签署合同记录
func (r *GormEsignContractSignRecordRepository) UpdateBatch(ctx context.Context, records []entities.EsignContractSignRecord) error {
r.logger.Info("批量更新e签宝签署合同记录", zap.Int("count", len(records)))
return r.db.WithContext(ctx).Save(&records).Error
}
// DeleteBatch 批量删除e签宝签署合同记录
func (r *GormEsignContractSignRecordRepository) DeleteBatch(ctx context.Context, ids []string) error {
r.logger.Info("批量删除e签宝签署合同记录", zap.Strings("ids", ids))
return r.db.WithContext(ctx).Delete(&entities.EsignContractSignRecord{}, "id IN ?", ids).Error
}
// List 获取e签宝签署合同记录列表
func (r *GormEsignContractSignRecordRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.EsignContractSignRecord, error) {
var records []entities.EsignContractSignRecord
query := r.db.WithContext(ctx).Model(&entities.EsignContractSignRecord{})
if options.Filters != nil {
for key, value := range options.Filters {
query = query.Where(key+" = ?", value)
}
}
if options.Search != "" {
query = query.Where("signer_name LIKE ?", "%"+options.Search+"%")
}
if options.Sort != "" {
order := "ASC"
if options.Order != "" {
order = options.Order
}
query = query.Order(options.Sort + " " + order)
}
if options.Page > 0 && options.PageSize > 0 {
offset := (options.Page - 1) * options.PageSize
query = query.Offset(offset).Limit(options.PageSize)
}
return records, query.Find(&records).Error
}
// WithTx 使用事务
func (r *GormEsignContractSignRecordRepository) WithTx(tx interface{}) interfaces.Repository[entities.EsignContractSignRecord] {
if gormTx, ok := tx.(*gorm.DB); ok {
return &GormEsignContractSignRecordRepository{
db: gormTx,
logger: r.logger,
}
}
return r
}
// ================ 业务方法 ================
// GetByCertificationID 根据认证ID获取e签宝签署合同记录
func (r *GormEsignContractSignRecordRepository) GetByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractSignRecord, error) {
var record entities.EsignContractSignRecord
err := r.db.WithContext(ctx).Where("certification_id = ?", certificationID).First(&record).Error
if err != nil {
return nil, err
}
return &record, nil
}
// GetByUserID 根据用户ID获取e签宝签署合同记录列表
func (r *GormEsignContractSignRecordRepository) GetByUserID(ctx context.Context, userID string) ([]*entities.EsignContractSignRecord, error) {
var records []entities.EsignContractSignRecord
err := r.db.WithContext(ctx).Where("user_id = ?", userID).Find(&records).Error
if err != nil {
return nil, err
}
var result []*entities.EsignContractSignRecord
for i := range records {
result = append(result, &records[i])
}
return result, nil
}
// GetLatestByCertificationID 根据认证ID获取最新的e签宝签署合同记录
func (r *GormEsignContractSignRecordRepository) GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.EsignContractSignRecord, error) {
var record entities.EsignContractSignRecord
err := r.db.WithContext(ctx).Where("certification_id = ?", certificationID).Order("created_at DESC").First(&record).Error
if err != nil {
return nil, err
}
return &record, nil
}
// GetByGenerateRecordID 根据生成记录ID获取e签宝签署合同记录
func (r *GormEsignContractSignRecordRepository) GetByGenerateRecordID(ctx context.Context, generateRecordID string) (*entities.EsignContractSignRecord, error) {
var record entities.EsignContractSignRecord
err := r.db.WithContext(ctx).Where("generate_record_id = ?", generateRecordID).First(&record).Error
if err != nil {
return nil, err
}
return &record, nil
}
// ListRecords 获取e签宝签署合同记录列表带分页和筛选
func (r *GormEsignContractSignRecordRepository) ListRecords(ctx context.Context, query *queries.ListEsignContractSignRecordsQuery) ([]*entities.EsignContractSignRecord, int64, error) {
var records []entities.EsignContractSignRecord
var total int64
dbQuery := r.db.WithContext(ctx).Model(&entities.EsignContractSignRecord{})
// 应用筛选条件
if query.CertificationID != "" {
dbQuery = dbQuery.Where("certification_id = ?", query.CertificationID)
}
if query.UserID != "" {
dbQuery = dbQuery.Where("user_id = ?", query.UserID)
}
if query.Status != "" {
dbQuery = dbQuery.Where("status = ?", query.Status)
}
if query.SignerName != "" {
dbQuery = dbQuery.Where("signer_name LIKE ?", "%"+query.SignerName+"%")
}
if query.StartDate != "" {
dbQuery = dbQuery.Where("created_at >= ?", query.StartDate)
}
if query.EndDate != "" {
dbQuery = dbQuery.Where("created_at <= ?", query.EndDate)
}
// 统计总数
if err := dbQuery.Count(&total).Error; err != nil {
return nil, 0, err
}
// 应用分页
if query.Page > 0 && query.PageSize > 0 {
offset := (query.Page - 1) * query.PageSize
dbQuery = dbQuery.Offset(offset).Limit(query.PageSize)
}
// 排序
dbQuery = dbQuery.Order("created_at DESC")
// 执行查询
if err := dbQuery.Find(&records).Error; err != nil {
return nil, 0, err
}
// 转换为指针切片
var result []*entities.EsignContractSignRecord
for i := range records {
result = append(result, &records[i])
}
return result, total, nil
}
// UpdateStatus 更新状态
func (r *GormEsignContractSignRecordRepository) UpdateStatus(ctx context.Context, recordID string, status string, reason string) error {
r.logger.Info("更新e签宝签署合同记录状态",
zap.String("record_id", recordID),
zap.String("status", status),
)
updates := map[string]interface{}{
"status": status,
"updated_at": time.Now(),
}
// 根据状态设置相应的时间戳
switch status {
case "signing":
// 不需要额外时间戳
case "success":
updates["signed_at"] = time.Now()
case "failed":
updates["failed_at"] = time.Now()
updates["failure_reason"] = reason
case "expired":
updates["expired_at"] = time.Now()
}
return r.db.WithContext(ctx).Model(&entities.EsignContractSignRecord{}).
Where("id = ?", recordID).
Updates(updates).Error
}
// UpdateSuccessInfo 更新成功信息
func (r *GormEsignContractSignRecordRepository) UpdateSuccessInfo(ctx context.Context, recordID, signedFileURL string) error {
r.logger.Info("更新e签宝签署合同记录成功信息",
zap.String("record_id", recordID),
)
now := time.Now()
updates := map[string]interface{}{
"status": "success",
"signed_file_url": signedFileURL,
"signed_at": &now,
"updated_at": now,
}
return r.db.WithContext(ctx).Model(&entities.EsignContractSignRecord{}).
Where("id = ?", recordID).
Updates(updates).Error
}
// SetSignURL 设置签署链接
func (r *GormEsignContractSignRecordRepository) SetSignURL(ctx context.Context, recordID, signURL string) error {
r.logger.Info("设置e签宝签署合同记录签署链接",
zap.String("record_id", recordID),
)
updates := map[string]interface{}{
"sign_url": signURL,
"updated_at": time.Now(),
}
return r.db.WithContext(ctx).Model(&entities.EsignContractSignRecord{}).
Where("id = ?", recordID).
Updates(updates).Error
}
// IncrementRetry 增加重试次数
func (r *GormEsignContractSignRecordRepository) IncrementRetry(ctx context.Context, recordID string) error {
r.logger.Info("增加e签宝签署合同记录重试次数", zap.String("record_id", recordID))
return r.db.WithContext(ctx).Model(&entities.EsignContractSignRecord{}).
Where("id = ?", recordID).
UpdateColumn("retry_count", gorm.Expr("retry_count + 1")).Error
}
// MarkExpiredRecords 标记过期的记录
func (r *GormEsignContractSignRecordRepository) MarkExpiredRecords(ctx context.Context) error {
r.logger.Info("标记过期的e签宝签署合同记录")
now := time.Now()
updates := map[string]interface{}{
"status": "expired",
"expired_at": &now,
"updated_at": now,
}
return r.db.WithContext(ctx).Model(&entities.EsignContractSignRecord{}).
Where("status = ? AND expired_at < ?", "pending", now).
Updates(updates).Error
}

View File

@@ -0,0 +1,291 @@
package events
import (
"context"
"time"
"go.uber.org/zap"
"tyapi-server/internal/shared/interfaces"
)
// ================ 常量定义 ================
const (
// 事件类型
EventTypeCertificationCreated = "certification.created"
EventTypeEnterpriseInfoSubmitted = "certification.enterprise_info_submitted"
EventTypeEnterpriseVerificationCompleted = "certification.enterprise_verification_completed"
EventTypeContractGenerated = "certification.contract_generated"
EventTypeContractSigned = "certification.contract_signed"
EventTypeCertificationCompleted = "certification.completed"
EventTypeCertificationFailed = "certification.failed"
EventTypeStatusTransitioned = "certification.status_transitioned"
// 重试配置
MaxRetries = 3
RetryDelay = 5 * time.Second
)
// ================ 事件结构 ================
// CertificationEventData 认证事件数据结构
type CertificationEventData struct {
EventType string `json:"event_type"`
CertificationID string `json:"certification_id"`
UserID string `json:"user_id"`
Data map[string]interface{} `json:"data"`
Timestamp time.Time `json:"timestamp"`
Version string `json:"version"`
}
// ================ 事件发布器实现 ================
// CertificationEventPublisher 认证事件发布器实现
//
// 职责:
// - 发布认证域相关的事件
// - 支持异步发布和重试机制
// - 提供事件持久化能力
// - 集成监控和日志
type CertificationEventPublisher struct {
eventBus interfaces.EventBus
logger *zap.Logger
}
// NewCertificationEventPublisher 创建认证事件发布器
func NewCertificationEventPublisher(
eventBus interfaces.EventBus,
logger *zap.Logger,
) *CertificationEventPublisher {
return &CertificationEventPublisher{
eventBus: eventBus,
logger: logger,
}
}
// ================ 事件发布方法 ================
// PublishCertificationCreated 发布认证创建事件
func (p *CertificationEventPublisher) PublishCertificationCreated(
ctx context.Context,
certificationID, userID string,
data map[string]interface{},
) error {
eventData := &CertificationEventData{
EventType: EventTypeCertificationCreated,
CertificationID: certificationID,
UserID: userID,
Data: data,
Timestamp: time.Now(),
Version: "1.0",
}
return p.publishEventData(ctx, eventData)
}
// PublishEnterpriseInfoSubmitted 发布企业信息提交事件
func (p *CertificationEventPublisher) PublishEnterpriseInfoSubmitted(
ctx context.Context,
certificationID, userID string,
data map[string]interface{},
) error {
eventData := &CertificationEventData{
EventType: EventTypeEnterpriseInfoSubmitted,
CertificationID: certificationID,
UserID: userID,
Data: data,
Timestamp: time.Now(),
Version: "1.0",
}
return p.publishEventData(ctx, eventData)
}
// PublishEnterpriseVerificationCompleted 发布企业认证完成事件
func (p *CertificationEventPublisher) PublishEnterpriseVerificationCompleted(
ctx context.Context,
certificationID, userID string,
data map[string]interface{},
) error {
eventData := &CertificationEventData{
EventType: EventTypeEnterpriseVerificationCompleted,
CertificationID: certificationID,
UserID: userID,
Data: data,
Timestamp: time.Now(),
Version: "1.0",
}
return p.publishEventData(ctx, eventData)
}
// PublishContractGenerated 发布合同生成事件
func (p *CertificationEventPublisher) PublishContractGenerated(
ctx context.Context,
certificationID, userID string,
data map[string]interface{},
) error {
eventData := &CertificationEventData{
EventType: EventTypeContractGenerated,
CertificationID: certificationID,
UserID: userID,
Data: data,
Timestamp: time.Now(),
Version: "1.0",
}
return p.publishEventData(ctx, eventData)
}
// PublishContractSigned 发布合同签署事件
func (p *CertificationEventPublisher) PublishContractSigned(
ctx context.Context,
certificationID, userID string,
data map[string]interface{},
) error {
eventData := &CertificationEventData{
EventType: EventTypeContractSigned,
CertificationID: certificationID,
UserID: userID,
Data: data,
Timestamp: time.Now(),
Version: "1.0",
}
return p.publishEventData(ctx, eventData)
}
// PublishCertificationCompleted 发布认证完成事件
func (p *CertificationEventPublisher) PublishCertificationCompleted(
ctx context.Context,
certificationID, userID string,
data map[string]interface{},
) error {
eventData := &CertificationEventData{
EventType: EventTypeCertificationCompleted,
CertificationID: certificationID,
UserID: userID,
Data: data,
Timestamp: time.Now(),
Version: "1.0",
}
return p.publishEventData(ctx, eventData)
}
// PublishCertificationFailed 发布认证失败事件
func (p *CertificationEventPublisher) PublishCertificationFailed(
ctx context.Context,
certificationID, userID string,
data map[string]interface{},
) error {
eventData := &CertificationEventData{
EventType: EventTypeCertificationFailed,
CertificationID: certificationID,
UserID: userID,
Data: data,
Timestamp: time.Now(),
Version: "1.0",
}
return p.publishEventData(ctx, eventData)
}
// PublishStatusTransitioned 发布状态转换事件
func (p *CertificationEventPublisher) PublishStatusTransitioned(
ctx context.Context,
certificationID, userID string,
data map[string]interface{},
) error {
eventData := &CertificationEventData{
EventType: EventTypeStatusTransitioned,
CertificationID: certificationID,
UserID: userID,
Data: data,
Timestamp: time.Now(),
Version: "1.0",
}
return p.publishEventData(ctx, eventData)
}
// ================ 内部实现 ================
// publishEventData 发布事件数据(带重试机制)
func (p *CertificationEventPublisher) publishEventData(ctx context.Context, eventData *CertificationEventData) error {
p.logger.Info("发布认证事件",
zap.String("event_type", eventData.EventType),
zap.String("certification_id", eventData.CertificationID),
zap.Time("timestamp", eventData.Timestamp))
// 尝试发布事件,带重试机制
var lastErr error
for attempt := 0; attempt <= MaxRetries; attempt++ {
if attempt > 0 {
// 指数退避重试
delay := time.Duration(attempt) * RetryDelay
p.logger.Warn("事件发布重试",
zap.String("event_type", eventData.EventType),
zap.Int("attempt", attempt),
zap.Duration("delay", delay))
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(delay):
// 继续重试
}
}
// 简化的事件发布:直接记录日志
p.logger.Info("模拟事件发布",
zap.String("event_type", eventData.EventType),
zap.String("certification_id", eventData.CertificationID),
zap.Any("data", eventData.Data))
// TODO: 这里可以集成真正的事件总线
// if err := p.eventBus.Publish(ctx, eventData); err != nil {
// lastErr = err
// continue
// }
// 发布成功
p.logger.Info("事件发布成功",
zap.String("event_type", eventData.EventType),
zap.String("certification_id", eventData.CertificationID))
return nil
}
// 理论上不会到达这里,因为简化实现总是成功
return lastErr
}
// ================ 事件处理器注册 ================
// RegisterEventHandlers 注册事件处理器
func (p *CertificationEventPublisher) RegisterEventHandlers() error {
// TODO: 注册具体的事件处理器
// 例如:发送通知、更新统计数据、触发后续流程等
p.logger.Info("认证事件处理器已注册")
return nil
}
// ================ 工具方法 ================
// CreateEventData 创建事件数据
func CreateEventData(eventType, certificationID, userID string, data map[string]interface{}) map[string]interface{} {
if data == nil {
data = make(map[string]interface{})
}
return map[string]interface{}{
"event_type": eventType,
"certification_id": certificationID,
"user_id": userID,
"data": data,
"timestamp": time.Now(),
"version": "1.0",
}
}

View File

@@ -0,0 +1,295 @@
package esign
import (
"context"
"fmt"
"time"
"go.uber.org/zap"
"tyapi-server/internal/domains/certification/enums"
"tyapi-server/internal/domains/certification/repositories"
"tyapi-server/internal/domains/certification/value_objects"
"tyapi-server/internal/shared/esign"
)
// ================ 常量定义 ================
const (
// 企业认证超时时间
EnterpriseAuthTimeout = 30 * time.Minute
// 合同签署超时时间
ContractSignTimeout = 7 * 24 * time.Hour // 7天
// 回调重试次数
MaxCallbackRetries = 3
)
// ================ 服务实现 ================
// CertificationEsignService 认证e签宝服务实现
//
// 业务职责:
// - 处理企业认证流程
// - 处理合同生成和签署
// - 处理e签宝回调
// - 管理认证状态更新
type CertificationEsignService struct {
esignClient *esign.Client
commandRepo repositories.CertificationCommandRepository
queryRepo repositories.CertificationQueryRepository
logger *zap.Logger
}
// NewCertificationEsignService 创建认证e签宝服务
func NewCertificationEsignService(
esignClient *esign.Client,
commandRepo repositories.CertificationCommandRepository,
queryRepo repositories.CertificationQueryRepository,
logger *zap.Logger,
) *CertificationEsignService {
return &CertificationEsignService{
esignClient: esignClient,
commandRepo: commandRepo,
queryRepo: queryRepo,
logger: logger,
}
}
// ================ 企业认证流程 ================
// StartEnterpriseAuth 开始企业认证
//
// 业务流程:
// 1. 调用e签宝企业认证API
// 2. 更新认证记录的auth_flow_id
// 3. 更新状态为企业认证中
//
// 参数:
// - ctx: 上下文
// - certificationID: 认证ID
// - enterpriseInfo: 企业信息
//
// 返回:
// - authURL: 认证URL
// - error: 错误信息
func (s *CertificationEsignService) StartEnterpriseAuth(
ctx context.Context,
certificationID string,
enterpriseInfo *value_objects.EnterpriseInfo,
) (string, error) {
s.logger.Info("开始企业认证",
zap.String("certification_id", certificationID),
zap.String("company_name", enterpriseInfo.CompanyName))
// TODO: 实现e签宝企业认证API调用
// 暂时使用模拟响应
authFlowID := fmt.Sprintf("auth_%s_%d", certificationID, time.Now().Unix())
authURL := fmt.Sprintf("https://esign.example.com/auth/%s", authFlowID)
s.logger.Info("模拟调用e签宝企业认证API",
zap.String("auth_flow_id", authFlowID),
zap.String("auth_url", authURL))
// 更新认证记录
if err := s.commandRepo.UpdateAuthFlowID(ctx, certificationID, authFlowID); err != nil {
s.logger.Error("更新认证流程ID失败", zap.Error(err))
return "", fmt.Errorf("更新认证流程ID失败: %w", err)
}
s.logger.Info("企业认证启动成功",
zap.String("certification_id", certificationID),
zap.String("auth_flow_id", authFlowID))
return authURL, nil
}
// HandleEnterpriseAuthCallback 处理企业认证回调
//
// 业务流程:
// 1. 根据回调信息查找认证记录
// 2. 根据回调状态更新认证状态
// 3. 如果成功,继续合同生成流程
//
// 参数:
// - ctx: 上下文
// - authFlowID: 认证流程ID
// - success: 是否成功
// - message: 回调消息
//
// 返回:
// - error: 错误信息
func (s *CertificationEsignService) HandleEnterpriseAuthCallback(
ctx context.Context,
authFlowID string,
success bool,
message string,
) error {
s.logger.Info("处理企业认证回调",
zap.String("auth_flow_id", authFlowID),
zap.Bool("success", success))
// 查找认证记录
cert, err := s.queryRepo.FindByAuthFlowID(ctx, authFlowID)
if err != nil {
s.logger.Error("根据认证流程ID查找认证记录失败", zap.Error(err))
return fmt.Errorf("查找认证记录失败: %w", err)
}
if success {
// 企业认证成功,更新状态
if err := s.commandRepo.UpdateStatus(ctx, cert.ID, enums.StatusEnterpriseVerified); err != nil {
s.logger.Error("更新认证状态失败", zap.Error(err))
return fmt.Errorf("更新认证状态失败: %w", err)
}
s.logger.Info("企业认证成功", zap.String("certification_id", cert.ID))
} else {
// 企业认证失败,更新状态
if err := s.commandRepo.UpdateStatus(ctx, cert.ID, enums.StatusInfoRejected); err != nil {
s.logger.Error("更新认证状态失败", zap.Error(err))
return fmt.Errorf("更新认证状态失败: %w", err)
}
s.logger.Info("企业认证失败", zap.String("certification_id", cert.ID), zap.String("reason", message))
}
return nil
}
// ================ 合同管理流程 ================
// GenerateContract 生成认证合同
//
// 业务流程:
// 1. 调用e签宝合同生成API
// 2. 更新认证记录的合同信息
// 3. 更新状态为合同已生成
//
// 参数:
// - ctx: 上下文
// - certificationID: 认证ID
//
// 返回:
// - contractSignURL: 合同签署URL
// - error: 错误信息
func (s *CertificationEsignService) GenerateContract(
ctx context.Context,
certificationID string,
) (string, error) {
s.logger.Info("生成认证合同", zap.String("certification_id", certificationID))
// TODO: 实现e签宝合同生成API调用
// 暂时使用模拟响应
contractFileID := fmt.Sprintf("contract_%s_%d", certificationID, time.Now().Unix())
esignFlowID := fmt.Sprintf("flow_%s_%d", certificationID, time.Now().Unix())
contractURL := fmt.Sprintf("https://esign.example.com/contract/%s", contractFileID)
contractSignURL := fmt.Sprintf("https://esign.example.com/sign/%s", esignFlowID)
s.logger.Info("模拟调用e签宝合同生成API",
zap.String("contract_file_id", contractFileID),
zap.String("esign_flow_id", esignFlowID))
// 更新认证记录
if err := s.commandRepo.UpdateContractInfo(
ctx,
certificationID,
contractFileID,
esignFlowID,
contractURL,
contractSignURL,
); err != nil {
s.logger.Error("更新合同信息失败", zap.Error(err))
return "", fmt.Errorf("更新合同信息失败: %w", err)
}
// 更新状态
if err := s.commandRepo.UpdateStatus(ctx, certificationID, enums.StatusContractApplied); err != nil {
s.logger.Error("更新认证状态失败", zap.Error(err))
return "", fmt.Errorf("更新认证状态失败: %w", err)
}
s.logger.Info("认证合同生成成功",
zap.String("certification_id", certificationID),
zap.String("contract_file_id", contractFileID))
return contractSignURL, nil
}
// HandleContractSignCallback 处理合同签署回调
//
// 业务流程:
// 1. 根据回调信息查找认证记录
// 2. 根据回调状态更新认证状态
// 3. 如果成功,认证流程完成
//
// 参数:
// - ctx: 上下文
// - esignFlowID: e签宝流程ID
// - success: 是否成功
// - signedFileURL: 已签署文件URL
//
// 返回:
// - error: 错误信息
func (s *CertificationEsignService) HandleContractSignCallback(
ctx context.Context,
esignFlowID string,
success bool,
signedFileURL string,
) error {
s.logger.Info("处理合同签署回调",
zap.String("esign_flow_id", esignFlowID),
zap.Bool("success", success))
// 查找认证记录
cert, err := s.queryRepo.FindByEsignFlowID(ctx, esignFlowID)
if err != nil {
s.logger.Error("根据e签宝流程ID查找认证记录失败", zap.Error(err))
return fmt.Errorf("查找认证记录失败: %w", err)
}
if success {
// 合同签署成功,认证完成
if err := s.commandRepo.UpdateStatus(ctx, cert.ID, enums.StatusContractSigned); err != nil {
s.logger.Error("更新认证状态失败", zap.Error(err))
return fmt.Errorf("更新认证状态失败: %w", err)
}
s.logger.Info("认证流程完成", zap.String("certification_id", cert.ID))
} else {
// 合同签署失败
if err := s.commandRepo.UpdateStatus(ctx, cert.ID, enums.StatusContractRejected); err != nil {
s.logger.Error("更新认证状态失败", zap.Error(err))
return fmt.Errorf("更新认证状态失败: %w", err)
}
s.logger.Info("合同签署失败", zap.String("certification_id", cert.ID))
}
return nil
}
// ================ 辅助方法 ================
// GetContractSignURL 获取合同签署URL
//
// 参数:
// - ctx: 上下文
// - certificationID: 认证ID
//
// 返回:
// - signURL: 签署URL
// - error: 错误信息
func (s *CertificationEsignService) GetContractSignURL(ctx context.Context, certificationID string) (string, error) {
cert, err := s.queryRepo.GetByID(ctx, certificationID)
if err != nil {
return "", fmt.Errorf("获取认证信息失败: %w", err)
}
if cert.ContractSignURL == "" {
return "", fmt.Errorf("合同签署URL尚未生成")
}
return cert.ContractSignURL, nil
}

View File

@@ -1,10 +1,6 @@
package handlers
import (
"bytes"
"io"
"time"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
@@ -12,157 +8,150 @@ import (
"tyapi-server/internal/application/certification/dto/commands"
"tyapi-server/internal/application/certification/dto/queries"
"tyapi-server/internal/shared/interfaces"
"tyapi-server/internal/shared/middleware"
)
// CertificationHandler 认证处理器
// 负责处理HTTP请求参数验证调用应用服务返回HTTP响应
// CertificationHandler 认证HTTP处理器
type CertificationHandler struct {
certAppService certification.CertificationApplicationService
esignCallbackService certification.EsignCallbackApplicationService
response interfaces.ResponseBuilder
validator interfaces.RequestValidator
logger *zap.Logger
appService certification.CertificationApplicationService
response interfaces.ResponseBuilder
validator interfaces.RequestValidator
logger *zap.Logger
jwtAuth *middleware.JWTAuthMiddleware
}
// NewCertificationHandler 创建认证处理器
func NewCertificationHandler(
certAppService certification.CertificationApplicationService,
esignCallbackService certification.EsignCallbackApplicationService,
appService certification.CertificationApplicationService,
response interfaces.ResponseBuilder,
validator interfaces.RequestValidator,
logger *zap.Logger,
jwtAuth *middleware.JWTAuthMiddleware,
) *CertificationHandler {
return &CertificationHandler{
certAppService: certAppService,
esignCallbackService: esignCallbackService,
response: response,
validator: validator,
logger: logger,
appService: appService,
response: response,
validator: validator,
logger: logger,
jwtAuth: jwtAuth,
}
}
// GetCertificationStatus 获取认证状态
// @Summary 获取认证状态
// @Description 获取当前用户的认证状态信息,包括认证进度、当前状态等
// @Tags 企业认证
// ================ 认证申请管理 ================
// CreateCertification 创建认证申请
// @Summary 创建认证申请
// @Description 为用户创建企业认证申请
// @Tags 认证管理
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {object} map[string]interface{} "获取认证状态成功"
// @Failure 401 {object} map[string]interface{} "用户未登录"
// @Param request body commands.CreateCertificationCommand true "创建认证申请请求"
// @Success 201 {object} responses.CertificationResponse "认证申请创建成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/certification/status [get]
func (h *CertificationHandler) GetCertificationStatus(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
// @Router /api/v1/certifications [post]
func (h *CertificationHandler) CreateCertification(c *gin.Context) {
var cmd commands.CreateCertificationCommand
cmd.UserID = h.getCurrentUserID(c)
if cmd.UserID == "" {
h.response.Unauthorized(c, "用户未登录")
return
}
query := &queries.GetCertificationStatusQuery{
UserID: userID,
}
result, err := h.certAppService.GetCertificationStatus(c.Request.Context(), query)
result, err := h.appService.CreateCertification(c.Request.Context(), &cmd)
if err != nil {
h.logger.Error("获取认证状态失败", zap.Error(err))
h.logger.Error("创建认证申请失败", zap.Error(err), zap.String("user_id", cmd.UserID))
h.response.BadRequest(c, err.Error())
return
}
h.response.Success(c, result, "获取认证状态成功")
h.response.Created(c, result, "认证申请创建成功")
}
// GetCertificationDetails 获取认证详情
// GetCertification 获取认证详情
// @Summary 获取认证详情
// @Description 获取当前用户的详细认证信息,包括企业信息、认证记录等
// @Tags 企业认证
// @Description 根据认证ID获取认证详情
// @Tags 认证管理
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {object} map[string]interface{} "获取认证详情成功"
// @Failure 401 {object} map[string]interface{} "用户未登录"
// @Param id path string true "认证ID"
// @Success 200 {object} responses.CertificationResponse "获取认证详情成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 404 {object} map[string]interface{} "认证记录不存在"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/certification/details [get]
func (h *CertificationHandler) GetCertificationDetails(c *gin.Context) {
userID := c.GetString("user_id")
// @Router /api/v1/certifications/{id} [get]
func (h *CertificationHandler) GetCertification(c *gin.Context) {
userID := h.getCurrentUserID(c)
if userID == "" {
h.response.Unauthorized(c, "用户未登录")
return
}
query := &queries.GetCertificationDetailsQuery{
UserID: userID,
certificationID := c.Param("id")
if certificationID == "" {
h.response.BadRequest(c, "认证ID不能为空")
return
}
result, err := h.certAppService.GetCertificationDetails(c.Request.Context(), query)
query := &queries.GetCertificationQuery{
CertificationID: certificationID,
UserID: userID,
}
result, err := h.appService.GetCertification(c.Request.Context(), query)
if err != nil {
h.logger.Error("获取认证详情失败", zap.Error(err))
h.response.BadRequest(c, err.Error())
h.logger.Error("获取认证详情失败", zap.Error(err), zap.String("certification_id", certificationID))
h.response.NotFound(c, "认证记录不存在")
return
}
h.response.Success(c, result, "获取认证详情成功")
}
// GetCertificationProgress 获取认证进度
// @Summary 获取认证进度
// @Description 获取当前用户的认证进度百分比和下一步操作提示
// @Tags 企业认证
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {object} map[string]interface{} "获取认证进度成功"
// @Failure 401 {object} map[string]interface{} "用户未登录"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/certification/progress [get]
func (h *CertificationHandler) GetCertificationProgress(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.response.Unauthorized(c, "用户未登录")
return
}
result, err := h.certAppService.GetCertificationProgress(c.Request.Context(), userID)
if err != nil {
h.logger.Error("获取认证进度失败", zap.Error(err))
h.response.BadRequest(c, err.Error())
return
}
h.response.Success(c, result, "获取认证进度成功")
}
// ================ 企业信息管理 ================
// SubmitEnterpriseInfo 提交企业信息
// @Summary 提交企业信息
// @Description 提交企业四要素信息(企业名称、统一社会信用代码、法定代表人姓名、法定代表人身份证),完成企业信息验证。如果用户没有认证申请,系统会自动创建
// @Tags 企业认证
// @Description 提交企业认证所需的企业信息
// @Tags 认证管理
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body commands.SubmitEnterpriseInfoCommand true "企业信息提交请求"
// @Success 200 {object} map[string]interface{} "企业信息提交成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误或验证码无效"
// @Failure 401 {object} map[string]interface{} "用户未登录"
// @Param id path string true "认证ID"
// @Param request body commands.SubmitEnterpriseInfoCommand true "提交企业信息请求"
// @Success 200 {object} responses.CertificationResponse "企业信息提交成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 404 {object} map[string]interface{} "认证记录不存在"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/certification/submit-enterprise-info [post]
// @Router /api/v1/certifications/{id}/enterprise-info [post]
func (h *CertificationHandler) SubmitEnterpriseInfo(c *gin.Context) {
userID := c.GetString("user_id")
userID := h.getCurrentUserID(c)
if userID == "" {
h.response.Unauthorized(c, "用户未登录")
return
}
certificationID := c.Param("id")
if certificationID == "" {
h.response.BadRequest(c, "认证ID不能为空")
return
}
var cmd commands.SubmitEnterpriseInfoCommand
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
return
}
cmd.CertificationID = certificationID
cmd.UserID = userID
result, err := h.certAppService.SubmitEnterpriseInfo(c.Request.Context(), &cmd)
result, err := h.appService.SubmitEnterpriseInfo(c.Request.Context(), &cmd)
if err != nil {
h.logger.Error("提交企业信息失败", zap.Error(err))
h.logger.Error("提交企业信息失败", zap.Error(err), zap.String("certification_id", certificationID))
h.response.BadRequest(c, err.Error())
return
}
@@ -170,57 +159,38 @@ func (h *CertificationHandler) SubmitEnterpriseInfo(c *gin.Context) {
h.response.Success(c, result, "企业信息提交成功")
}
// GetEnterpriseAuthURL 获取企业认证链接
// @Summary 获取企业认证链接
// @Description 获取e签宝企业认证链接用户可通过该链接完成企业认证
// @Tags 企业认证
// ================ 合同管理 ================
// ApplyContract 申请合同签署
// @Summary 申请合同签署
// @Description 申请企业认证合同签署
// @Tags 认证管理
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {object} map[string]interface{} "获取企业认证链接成功"
// @Failure 401 {object} map[string]interface{} "用户未登录"
// @Failure 400 {object} map[string]interface{} "企业信息未提交或认证状态异常"
// @Param request body commands.ApplyContractCommand true "申请合同请求"
// @Success 200 {object} responses.ContractSignUrlResponse "合同申请成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 404 {object} map[string]interface{} "认证记录不存在"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/certification/enterprise-auth-url [get]
func (h *CertificationHandler) GetEnterpriseAuthURL(c *gin.Context) {
userID := c.GetString("user_id")
if userID == "" {
h.response.Unauthorized(c, "用户未登录")
return
}
result, err := h.certAppService.GetEnterpriseAuthURL(c.Request.Context(), userID)
if err != nil {
h.logger.Error("获取企业认证链接失败", zap.Error(err))
h.response.BadRequest(c, err.Error())
return
}
h.response.Success(c, result, "获取企业认证链接成功")
}
// ApplyContract 申请合同
// @Summary 申请合同
// @Description 为企业认证用户申请合同,生成合同文档
// @Tags 企业认证
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {object} map[string]interface{} "合同申请成功"
// @Failure 401 {object} map[string]interface{} "用户未登录"
// @Failure 400 {object} map[string]interface{} "企业认证未完成或合同申请失败"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/certification/apply-contract [post]
// @Router /api/v1/certifications/apply-contract [post]
func (h *CertificationHandler) ApplyContract(c *gin.Context) {
userID := c.GetString("user_id")
userID := h.getCurrentUserID(c)
if userID == "" {
h.response.Unauthorized(c, "用户未登录")
return
}
result, err := h.certAppService.ApplyContract(c.Request.Context(), userID)
var cmd commands.ApplyContractCommand
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
return
}
cmd.UserID = userID
result, err := h.appService.ApplyContract(c.Request.Context(), &cmd)
if err != nil {
h.logger.Error("申请合同失败", zap.Error(err))
h.logger.Error("申请合同失败", zap.Error(err), zap.String("certification_id", cmd.CertificationID))
h.response.BadRequest(c, err.Error())
return
}
@@ -228,161 +198,282 @@ func (h *CertificationHandler) ApplyContract(c *gin.Context) {
h.response.Success(c, result, "合同申请成功")
}
// GetContractSignURL 获取合同签署链接
// @Summary 获取合同签署链接
// @Description 获取e签宝合同签署链接用户可通过该链接完成合同签署
// @Tags 企业认证
// ================ 重试操作 ================
// RetryOperation 重试操作
// @Summary 重试操作
// @Description 重试失败的企业认证或合同申请操作
// @Tags 认证管理
// @Accept json
// @Produce json
// @Security Bearer
// @Success 200 {object} map[string]interface{} "获取合同签署链接成功"
// @Failure 401 {object} map[string]interface{} "用户未登录"
// @Failure 400 {object} map[string]interface{} "合同未申请或签署状态异常"
// @Param request body commands.RetryOperationCommand true "重试操作请求"
// @Success 200 {object} responses.CertificationResponse "重试操作成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 404 {object} map[string]interface{} "认证记录不存在"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/certification/contract-sign-url [get]
func (h *CertificationHandler) GetContractSignURL(c *gin.Context) {
userID := c.GetString("user_id")
// @Router /api/v1/certifications/retry [post]
func (h *CertificationHandler) RetryOperation(c *gin.Context) {
userID := h.getCurrentUserID(c)
if userID == "" {
h.response.Unauthorized(c, "用户未登录")
return
}
cmd := &commands.GetContractSignURLCommand{
UserID: userID,
var cmd commands.RetryOperationCommand
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
return
}
cmd.UserID = userID
result, err := h.certAppService.GetContractSignURL(c.Request.Context(), cmd)
result, err := h.appService.RetryOperation(c.Request.Context(), &cmd)
if err != nil {
h.logger.Error("获取合同签署链接失败", zap.Error(err))
h.logger.Error("重试操作失败", zap.Error(err), zap.String("certification_id", cmd.CertificationID))
h.response.BadRequest(c, err.Error())
return
}
h.response.Success(c, result, "获取合同签署链接成功")
h.response.Success(c, result, "重试操作成功")
}
// EsignCallback e签宝回调
// @Summary e签宝回调接口
// @Description 接收e签宝认证和签署的回调通知
// @Tags 企业认证
// ================ 查询操作 ================
// GetUserCertifications 获取用户认证列表
// @Summary 获取用户认证列表
// @Description 获取当前用户的认证申请列表
// @Tags 认证管理
// @Accept json
// @Produce json
// @Success 200 {object} map[string]interface{} "回调处理成功"
// @Failure 400 {object} map[string]interface{} "回调参数错误"
// @Security Bearer
// @Param status query string false "认证状态"
// @Param include_completed query bool false "是否包含已完成"
// @Param include_failed query bool false "是否包含失败"
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(10)
// @Success 200 {object} responses.CertificationListResponse "获取用户认证列表成功"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/certification/esign-callback [post]
func (h *CertificationHandler) EsignCallback(c *gin.Context) {
// 记录请求基本信息
h.logger.Info("收到e签宝回调请求",
zap.String("method", c.Request.Method),
zap.String("url", c.Request.URL.String()),
zap.String("remote_addr", c.ClientIP()),
zap.String("user_agent", c.GetHeader("User-Agent")),
)
// 记录所有请求头
headers := make(map[string]string)
for key, values := range c.Request.Header {
if len(values) > 0 {
headers[key] = values[0]
}
}
h.logger.Info("回调请求头信息", zap.Any("headers", headers))
// 记录URL查询参数
queryParams := make(map[string]string)
for key, values := range c.Request.URL.Query() {
if len(values) > 0 {
queryParams[key] = values[0]
}
}
if len(queryParams) > 0 {
h.logger.Info("回调URL查询参数", zap.Any("query_params", queryParams))
// @Router /api/v1/certifications/user [get]
func (h *CertificationHandler) GetUserCertifications(c *gin.Context) {
userID := h.getCurrentUserID(c)
if userID == "" {
h.response.Unauthorized(c, "用户未登录")
return
}
// 读取并记录请求体
var requestBody interface{}
var callbackData map[string]interface{}
if c.Request.Body != nil {
// 读取请求体
bodyBytes, err := c.GetRawData()
if err != nil {
h.logger.Error("读取回调请求体失败", zap.Error(err))
h.response.BadRequest(c, "读取请求体失败")
return
}
var query queries.GetUserCertificationsQuery
if err := h.validator.BindAndValidate(c, &query); err != nil {
return
}
query.UserID = userID
// 尝试解析为JSON
if err := c.ShouldBindJSON(&callbackData); err == nil {
requestBody = callbackData
} else {
// 如果不是JSON记录原始字符串
requestBody = string(bodyBytes)
h.logger.Error("回调请求体不是有效的JSON格式", zap.Error(err))
h.response.BadRequest(c, "请求体格式错误")
return
}
h.logger.Info("回调请求体内容", zap.Any("body", requestBody))
// 重新设置请求体,以便后续处理
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
result, err := h.appService.GetUserCertifications(c.Request.Context(), &query)
if err != nil {
h.logger.Error("获取用户认证列表失败", zap.Error(err), zap.String("user_id", userID))
h.response.BadRequest(c, err.Error())
return
}
// 记录Content-Type
contentType := c.GetHeader("Content-Type")
h.logger.Info("回调请求Content-Type", zap.String("content_type", contentType))
// 记录Content-Length
contentLength := c.GetHeader("Content-Length")
if contentLength != "" {
h.logger.Info("回调请求Content-Length", zap.String("content_length", contentLength))
}
// 记录时间戳
h.logger.Info("回调请求时间",
zap.Time("request_time", time.Now()),
zap.String("request_id", c.GetHeader("X-Request-ID")),
)
// 记录完整的请求信息摘要
h.logger.Info("e签宝回调完整信息摘要",
zap.String("method", c.Request.Method),
zap.String("url", c.Request.URL.String()),
zap.String("client_ip", c.ClientIP()),
zap.String("content_type", contentType),
zap.Any("headers", headers),
zap.Any("query_params", queryParams),
zap.Any("body", requestBody),
)
// 处理回调数据
if callbackData != nil {
// 构建请求头映射
headers := make(map[string]string)
for key, values := range c.Request.Header {
if len(values) > 0 {
headers[key] = values[0]
}
}
// 构建查询参数映射
queryParams := make(map[string]string)
for key, values := range c.Request.URL.Query() {
if len(values) > 0 {
queryParams[key] = values[0]
}
}
if err := h.esignCallbackService.HandleCallback(c.Request.Context(), callbackData, headers, queryParams); err != nil {
h.logger.Error("处理e签宝回调失败", zap.Error(err))
h.response.BadRequest(c, "回调处理失败: "+err.Error())
return
}
}
// 返回成功响应
c.JSON(200, map[string]interface{}{
"code": "200",
"msg": "success",
})
h.response.Success(c, result, "获取用户认证列表成功")
}
// ListCertifications 获取认证列表(管理员)
// @Summary 获取认证列表
// @Description 管理员获取认证申请列表
// @Tags 认证管理
// @Accept json
// @Produce json
// @Security Bearer
// @Param page query int false "页码" default(1)
// @Param page_size query int false "每页数量" default(10)
// @Param sort_by query string false "排序字段"
// @Param sort_order query string false "排序方向" Enums(asc, desc)
// @Param status query string false "认证状态"
// @Param user_id query string false "用户ID"
// @Param company_name query string false "公司名称"
// @Param legal_person_name query string false "法人姓名"
// @Param search_keyword query string false "搜索关键词"
// @Success 200 {object} responses.CertificationListResponse "获取认证列表成功"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 403 {object} map[string]interface{} "权限不足"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/certifications [get]
func (h *CertificationHandler) ListCertifications(c *gin.Context) {
userID := h.getCurrentUserID(c)
if userID == "" {
h.response.Unauthorized(c, "用户未登录")
return
}
var query queries.ListCertificationsQuery
if err := h.validator.BindAndValidate(c, &query); err != nil {
return
}
result, err := h.appService.ListCertifications(c.Request.Context(), &query)
if err != nil {
h.logger.Error("获取认证列表失败", zap.Error(err))
h.response.BadRequest(c, err.Error())
return
}
h.response.Success(c, result, "获取认证列表成功")
}
// GetCertificationStatistics 获取认证统计
// @Summary 获取认证统计
// @Description 获取认证相关的统计数据
// @Tags 认证管理
// @Accept json
// @Produce json
// @Security Bearer
// @Param start_date query string true "开始日期" format(date)
// @Param end_date query string true "结束日期" format(date)
// @Param period query string false "统计周期" Enums(daily, weekly, monthly, yearly) default(daily)
// @Param group_by query []string false "分组字段"
// @Param user_ids query []string false "用户ID列表"
// @Param statuses query []string false "状态列表"
// @Success 200 {object} responses.CertificationStatisticsResponse "获取认证统计成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/certifications/statistics [get]
func (h *CertificationHandler) GetCertificationStatistics(c *gin.Context) {
userID := h.getCurrentUserID(c)
if userID == "" {
h.response.Unauthorized(c, "用户未登录")
return
}
var query queries.GetCertificationStatisticsQuery
if err := h.validator.BindAndValidate(c, &query); err != nil {
return
}
result, err := h.appService.GetCertificationStatistics(c.Request.Context(), &query)
if err != nil {
h.logger.Error("获取认证统计失败", zap.Error(err))
h.response.BadRequest(c, err.Error())
return
}
h.response.Success(c, result, "获取认证统计成功")
}
// ================ 回调处理 ================
// HandleEsignCallback 处理e签宝回调
// @Summary 处理e签宝回调
// @Description 处理e签宝的企业认证和合同签署回调
// @Tags 认证管理
// @Accept json
// @Produce json
// @Param request body commands.EsignCallbackCommand true "e签宝回调数据"
// @Success 200 {object} responses.CallbackResponse "回调处理成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/certifications/callbacks [post]
func (h *CertificationHandler) HandleEsignCallback(c *gin.Context) {
var cmd commands.EsignCallbackCommand
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
return
}
result, err := h.appService.HandleEsignCallback(c.Request.Context(), &cmd)
if err != nil {
h.logger.Error("处理e签宝回调失败", zap.Error(err), zap.String("certification_id", cmd.CertificationID))
h.response.BadRequest(c, err.Error())
return
}
h.response.Success(c, result, "回调处理成功")
}
// ================ 管理员操作 ================
// ForceTransitionStatus 强制状态转换(管理员)
// @Summary 强制状态转换
// @Description 管理员强制转换认证状态
// @Tags 认证管理
// @Accept json
// @Produce json
// @Security Bearer
// @Param request body commands.ForceTransitionStatusCommand true "强制状态转换请求"
// @Success 200 {object} responses.CertificationResponse "状态转换成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 403 {object} map[string]interface{} "权限不足"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/certifications/force-transition [post]
func (h *CertificationHandler) ForceTransitionStatus(c *gin.Context) {
adminID := h.getCurrentUserID(c)
if adminID == "" {
h.response.Unauthorized(c, "用户未登录")
return
}
var cmd commands.ForceTransitionStatusCommand
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
return
}
cmd.AdminID = adminID
result, err := h.appService.ForceTransitionStatus(c.Request.Context(), &cmd)
if err != nil {
h.logger.Error("强制状态转换失败", zap.Error(err), zap.String("certification_id", cmd.CertificationID))
h.response.BadRequest(c, err.Error())
return
}
h.response.Success(c, result, "状态转换成功")
}
// GetSystemMonitoring 获取系统监控数据
// @Summary 获取系统监控数据
// @Description 获取认证系统的监控数据
// @Tags 认证管理
// @Accept json
// @Produce json
// @Security Bearer
// @Param time_range query string false "时间范围" Enums(1h, 6h, 24h, 7d, 30d) default(24h)
// @Param metrics query []string false "监控指标"
// @Success 200 {object} responses.SystemMonitoringResponse "获取系统监控数据成功"
// @Failure 400 {object} map[string]interface{} "请求参数错误"
// @Failure 401 {object} map[string]interface{} "未认证"
// @Failure 403 {object} map[string]interface{} "权限不足"
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
// @Router /api/v1/certifications/monitoring [get]
func (h *CertificationHandler) GetSystemMonitoring(c *gin.Context) {
userID := h.getCurrentUserID(c)
if userID == "" {
h.response.Unauthorized(c, "用户未登录")
return
}
var query queries.GetSystemMonitoringQuery
if err := h.validator.BindAndValidate(c, &query); err != nil {
return
}
result, err := h.appService.GetSystemMonitoring(c.Request.Context(), &query)
if err != nil {
h.logger.Error("获取系统监控数据失败", zap.Error(err))
h.response.BadRequest(c, err.Error())
return
}
h.response.Success(c, result, "获取系统监控数据成功")
}
// ================ 辅助方法 ================
// getCurrentUserID 获取当前用户ID
func (h *CertificationHandler) getCurrentUserID(c *gin.Context) string {
if userID, exists := c.Get("user_id"); exists {
if id, ok := userID.(string); ok {
return id
}
}
return ""
}

View File

@@ -1,58 +1,100 @@
package routes
import (
"tyapi-server/internal/infrastructure/http/handlers"
sharedhttp "tyapi-server/internal/shared/http"
"tyapi-server/internal/shared/middleware"
"go.uber.org/zap"
"tyapi-server/internal/infrastructure/http/handlers"
"tyapi-server/internal/shared/http"
"tyapi-server/internal/shared/middleware"
)
// CertificationRoutes 认证路由注册器
// CertificationRoutes 认证路由
type CertificationRoutes struct {
handler *handlers.CertificationHandler
authMiddleware *middleware.JWTAuthMiddleware
logger *zap.Logger
handler *handlers.CertificationHandler
router *http.GinRouter
logger *zap.Logger
auth *middleware.JWTAuthMiddleware
optional *middleware.OptionalAuthMiddleware
}
// NewCertificationRoutes 创建认证路由注册器
// NewCertificationRoutes 创建认证路由
func NewCertificationRoutes(
handler *handlers.CertificationHandler,
authMiddleware *middleware.JWTAuthMiddleware,
router *http.GinRouter,
logger *zap.Logger,
auth *middleware.JWTAuthMiddleware,
optional *middleware.OptionalAuthMiddleware,
) *CertificationRoutes {
return &CertificationRoutes{
handler: handler,
authMiddleware: authMiddleware,
logger: logger,
handler: handler,
router: router,
logger: logger,
auth: auth,
optional: optional,
}
}
// Register 注册认证相关路由
func (r *CertificationRoutes) Register(router *sharedhttp.GinRouter) {
// 认证相关路由组
engine := router.GetEngine()
certificationGroup := engine.Group("/api/v1/certification")
certificationGroup.Use(r.authMiddleware.Handle())
// Register 注册认证路由
func (r *CertificationRoutes) Register(router *http.GinRouter) {
// 认证管理路由组
certificationGroup := router.GetEngine().Group("/api/v1/certifications")
{
// 认证状态查询
certificationGroup.GET("/status", r.handler.GetCertificationStatus) // 获取认证状态
certificationGroup.GET("/details", r.handler.GetCertificationDetails) // 获取认证详情
certificationGroup.GET("/progress", r.handler.GetCertificationProgress) // 获取认证进度
// 需要认证的路由
authGroup := certificationGroup.Group("")
authGroup.Use(r.auth.Handle())
{
authGroup.GET("/user", r.handler.GetUserCertifications) // 获取用户认证列表
authGroup.GET("", r.handler.ListCertifications) // 查询认证列表(管理员)
authGroup.GET("/statistics", r.handler.GetCertificationStatistics) // 获取认证统计
// 企业信息管理
certificationGroup.POST("/submit-enterprise-info", r.handler.SubmitEnterpriseInfo) // 提交企业信息(自动创建认证申请)
// 1. 获取认证详情
authGroup.GET("/:id", r.handler.GetCertification)
// 企业认证
certificationGroup.GET("/enterprise-auth-url", r.handler.GetEnterpriseAuthURL) // 获取企业认证链接
// 2. 提交企业信息
authGroup.POST("/:id/enterprise-info", r.handler.SubmitEnterpriseInfo)
// 合同管理
certificationGroup.POST("/apply-contract", r.handler.ApplyContract) // 申请合同
certificationGroup.GET("/contract-sign-url", r.handler.GetContractSignURL) // 获取合同签署链接
// 合同管理
authGroup.POST("/apply-contract", r.handler.ApplyContract) // 申请合同签署
// 重试操作
authGroup.POST("/retry", r.handler.RetryOperation) // 重试操作
// 管理员操作
authGroup.POST("/force-transition", r.handler.ForceTransitionStatus) // 强制状态转换
authGroup.GET("/monitoring", r.handler.GetSystemMonitoring) // 获取系统监控数据
}
// 回调路由(不需要认证,但需要验证签名)
callbackGroup := certificationGroup.Group("/callbacks")
{
callbackGroup.POST("", r.handler.HandleEsignCallback) // e签宝回调统一处理企业认证和合同签署回调
}
}
callbackGroup := engine.Group("/api/v1/certification")
// e签宝回调
callbackGroup.POST("/esign-callback", r.handler.EsignCallback) // e签宝回调
r.logger.Info("认证路由注册完成")
}
// GetRoutes 获取路由信息(用于调试)
func (r *CertificationRoutes) GetRoutes() []RouteInfo {
return []RouteInfo{
{Method: "POST", Path: "/api/v1/certifications", Handler: "CreateCertification", Auth: true},
{Method: "GET", Path: "/api/v1/certifications/:id", Handler: "GetCertification", Auth: true},
{Method: "GET", Path: "/api/v1/certifications/user", Handler: "GetUserCertifications", Auth: true},
{Method: "GET", Path: "/api/v1/certifications", Handler: "ListCertifications", Auth: true},
{Method: "GET", Path: "/api/v1/certifications/statistics", Handler: "GetCertificationStatistics", Auth: true},
{Method: "POST", Path: "/api/v1/certifications/:id/enterprise-info", Handler: "SubmitEnterpriseInfo", Auth: true},
{Method: "POST", Path: "/api/v1/certifications/apply-contract", Handler: "ApplyContract", Auth: true},
{Method: "POST", Path: "/api/v1/certifications/retry", Handler: "RetryOperation", Auth: true},
{Method: "POST", Path: "/api/v1/certifications/force-transition", Handler: "ForceTransitionStatus", Auth: true},
{Method: "GET", Path: "/api/v1/certifications/monitoring", Handler: "GetSystemMonitoring", Auth: true},
{Method: "POST", Path: "/api/v1/certifications/callbacks", Handler: "HandleEsignCallback", Auth: false},
}
}
// RouteInfo 路由信息
type RouteInfo struct {
Method string
Path string
Handler string
Auth bool
}