temp
This commit is contained in:
348
docs/Certification域DDD重构规范.md
Normal file
348
docs/Certification域DDD重构规范.md
Normal 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
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 流程2:e签宝企业认证回调
|
||||||
|
```
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 流程4:e签宝合同签署回调
|
||||||
|
```
|
||||||
|
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域的重构实施,确保重构后的代码具有良好的可维护性、可扩展性和健壮性。
|
||||||
@@ -9,25 +9,49 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// CertificationApplicationService 认证应用服务接口
|
// CertificationApplicationService 认证应用服务接口
|
||||||
|
// 负责用例协调,提供精简的应用层接口
|
||||||
type CertificationApplicationService interface {
|
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)
|
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)
|
RetryOperation(ctx context.Context, cmd *commands.RetryOperationCommand) (*responses.CertificationResponse, error)
|
||||||
GetContractSignURL(ctx context.Context, cmd *commands.GetContractSignURLCommand) (*responses.ContractSignURLResponse, 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签宝回调
|
// 处理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)
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,21 +1,50 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
// SubmitEnterpriseInfoCommand 提交企业信息命令
|
import (
|
||||||
// 用于用户提交企业四要素信息,完成企业信息验证
|
"tyapi-server/internal/domains/certification/entities/value_objects"
|
||||||
// 如果用户没有认证申请,系统会自动创建
|
"tyapi-server/internal/domains/certification/enums"
|
||||||
type SubmitEnterpriseInfoCommand struct {
|
)
|
||||||
UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"`
|
|
||||||
CompanyName string `json:"company_name" binding:"required,min=2,max=100" comment:"企业名称,如:北京科技有限公司"`
|
// CreateCertificationCommand 创建认证申请命令
|
||||||
UnifiedSocialCode string `json:"unified_social_code" binding:"required,social_credit_code" comment:"统一社会信用代码,18位企业唯一标识,如:91110000123456789X"`
|
type CreateCertificationCommand struct {
|
||||||
LegalPersonName string `json:"legal_person_name" binding:"required,min=2,max=20" comment:"法定代表人姓名,如:张三"`
|
UserID string `json:"-"`
|
||||||
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:"验证码"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CompleteContractSignCommand 完成合同签署命令
|
// ApplyContractCommand 申请合同命令
|
||||||
// 用于用户完成合同签署,提交合同URL
|
type ApplyContractCommand struct {
|
||||||
type CompleteContractSignCommand struct {
|
CertificationID string `json:"certification_id" validate:"required"`
|
||||||
UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"`
|
UserID string `json:"user_id" validate:"required"`
|
||||||
ContractURL string `json:"contract_url" binding:"required,url,min=10,max=500" comment:"合同签署后的URL地址"`
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,185 @@
|
|||||||
package queries
|
package queries
|
||||||
|
|
||||||
// GetCertificationStatusQuery 获取认证状态查询
|
import (
|
||||||
// 用于查询用户当前认证申请的进度状态
|
"time"
|
||||||
type GetCertificationStatusQuery struct {
|
|
||||||
UserID string `json:"user_id" binding:"required,uuid" comment:"用户唯一标识,用于查询该用户的认证申请状态"`
|
"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 获取认证详情查询
|
// GetUserCertificationsQuery 获取用户认证列表查询
|
||||||
// 用于查询用户认证申请的详细信息,包括所有相关记录
|
type GetUserCertificationsQuery struct {
|
||||||
type GetCertificationDetailsQuery struct {
|
UserID string `json:"user_id" validate:"required"`
|
||||||
UserID string `json:"user_id" binding:"required,uuid" comment:"用户唯一标识,用于查询该用户的认证申请详细信息"`
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,45 +1,235 @@
|
|||||||
package responses
|
package responses
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/certification/entities/value_objects"
|
||||||
"tyapi-server/internal/domains/certification/enums"
|
"tyapi-server/internal/domains/certification/enums"
|
||||||
|
"tyapi-server/internal/domains/certification/repositories"
|
||||||
|
"tyapi-server/internal/domains/certification/services/state_machine"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CertificationResponse 认证响应
|
// CertificationResponse 认证响应
|
||||||
type CertificationResponse struct {
|
type CertificationResponse struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
Status enums.CertificationStatus `json:"status"`
|
Status enums.CertificationStatus `json:"status"`
|
||||||
StatusName string `json:"status_name"`
|
StatusName string `json:"status_name"`
|
||||||
Progress int `json:"progress"`
|
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"`
|
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info,omitempty"`
|
||||||
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty"`
|
|
||||||
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty"`
|
// 合同信息
|
||||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
ContractInfo *value_objects.ContractInfo `json:"contract_info,omitempty"`
|
||||||
Enterprise *EnterpriseInfoResponse `json:"enterprise,omitempty"`
|
|
||||||
ContractURL string `json:"contract_url,omitempty"`
|
// 时间戳
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_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 企业信息响应
|
// CertificationListResponse 认证列表响应
|
||||||
type EnterpriseInfoResponse struct {
|
type CertificationListResponse struct {
|
||||||
ID string `json:"id"`
|
Items []*CertificationResponse `json:"items"`
|
||||||
CompanyName string `json:"company_name"`
|
Total int64 `json:"total"`
|
||||||
UnifiedSocialCode string `json:"unified_social_code"`
|
Page int `json:"page"`
|
||||||
LegalPersonName string `json:"legal_person_name"`
|
PageSize int `json:"page_size"`
|
||||||
LegalPersonID string `json:"legal_person_id"`
|
TotalPages int `json:"total_pages"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnterpriseAuthURLResponse 企业认证链接响应
|
// ContractSignUrlResponse 合同签署URL响应
|
||||||
type EnterpriseAuthURLResponse struct {
|
type ContractSignUrlResponse struct {
|
||||||
EsignFlowID string `json:"esign_flow_id"` // e签宝认证流程ID
|
CertificationID string `json:"certification_id"`
|
||||||
AuthURL string `json:"auth_url"` // 认证链接
|
ContractSignURL string `json:"contract_sign_url"`
|
||||||
ShortURL string `json:"short_url"` // 短链接
|
ContractURL string `json:"contract_url,omitempty"`
|
||||||
ExpireAt string `json:"expire_at"` // 过期时间
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,6 @@ import (
|
|||||||
"tyapi-server/internal/application/user"
|
"tyapi-server/internal/application/user"
|
||||||
"tyapi-server/internal/config"
|
"tyapi-server/internal/config"
|
||||||
domain_certification_repo "tyapi-server/internal/domains/certification/repositories"
|
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"
|
domain_finance_repo "tyapi-server/internal/domains/finance/repositories"
|
||||||
finance_service "tyapi-server/internal/domains/finance/services"
|
finance_service "tyapi-server/internal/domains/finance/services"
|
||||||
domain_product_repo "tyapi-server/internal/domains/product/repositories"
|
domain_product_repo "tyapi-server/internal/domains/product/repositories"
|
||||||
@@ -251,25 +250,15 @@ func NewContainer() *Container {
|
|||||||
|
|
||||||
// 仓储层 - 认证域
|
// 仓储层 - 认证域
|
||||||
fx.Provide(
|
fx.Provide(
|
||||||
// 认证申请仓储
|
// 认证命令仓储
|
||||||
fx.Annotate(
|
fx.Annotate(
|
||||||
certification_repo.NewGormCertificationRepository,
|
certification_repo.NewGormCertificationCommandRepository,
|
||||||
fx.As(new(domain_certification_repo.CertificationRepository)),
|
fx.As(new(domain_certification_repo.CertificationCommandRepository)),
|
||||||
),
|
),
|
||||||
// 企业信息提交记录仓储
|
// 认证查询仓储
|
||||||
fx.Annotate(
|
fx.Annotate(
|
||||||
certification_repo.NewGormEnterpriseInfoSubmitRecordRepository,
|
certification_repo.NewGormCertificationQueryRepository,
|
||||||
fx.As(new(domain_certification_repo.EnterpriseInfoSubmitRecordRepository)),
|
fx.As(new(domain_certification_repo.CertificationQueryRepository)),
|
||||||
),
|
|
||||||
// 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)),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -314,11 +303,7 @@ func NewContainer() *Container {
|
|||||||
user_service.NewEnterpriseService,
|
user_service.NewEnterpriseService,
|
||||||
product_service.NewProductManagementService,
|
product_service.NewProductManagementService,
|
||||||
product_service.NewProductSubscriptionService,
|
product_service.NewProductSubscriptionService,
|
||||||
certification_service.NewCertificationManagementService,
|
// 认证域的领域服务已经整合到应用服务中
|
||||||
certification_service.NewCertificationWorkflowService,
|
|
||||||
certification_service.NewCertificationStateMachine,
|
|
||||||
certification_service.NewEnterpriseInfoSubmitRecordService,
|
|
||||||
certification_service.NewCertificationEsignService,
|
|
||||||
finance_service.NewFinanceService,
|
finance_service.NewFinanceService,
|
||||||
),
|
),
|
||||||
|
|
||||||
@@ -334,11 +319,6 @@ func NewContainer() *Container {
|
|||||||
certification.NewCertificationApplicationService,
|
certification.NewCertificationApplicationService,
|
||||||
fx.As(new(certification.CertificationApplicationService)),
|
fx.As(new(certification.CertificationApplicationService)),
|
||||||
),
|
),
|
||||||
// e签宝回调应用服务 - 绑定到接口
|
|
||||||
fx.Annotate(
|
|
||||||
certification.NewEsignCallbackApplicationService,
|
|
||||||
fx.As(new(certification.EsignCallbackApplicationService)),
|
|
||||||
),
|
|
||||||
// 财务应用服务 - 绑定到接口
|
// 财务应用服务 - 绑定到接口
|
||||||
fx.Annotate(
|
fx.Annotate(
|
||||||
finance.NewFinanceApplicationService,
|
finance.NewFinanceApplicationService,
|
||||||
|
|||||||
@@ -1,43 +1,55 @@
|
|||||||
package entities
|
package entities
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"tyapi-server/internal/domains/certification/entities/value_objects"
|
||||||
"tyapi-server/internal/domains/certification/enums"
|
"tyapi-server/internal/domains/certification/enums"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Certification 认证申请实体
|
// Certification 认证聚合根
|
||||||
// 这是企业认证流程的核心实体,负责管理整个认证申请的生命周期
|
// 这是企业认证流程的核心聚合根,封装了完整的认证业务逻辑和状态管理
|
||||||
type Certification struct {
|
type Certification struct {
|
||||||
// 基础信息
|
// === 基础信息 ===
|
||||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"认证申请唯一标识"`
|
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"认证申请唯一标识"`
|
||||||
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"申请用户ID"`
|
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:"当前认证状态"`
|
Status enums.CertificationStatus `gorm:"type:varchar(50);not null;index" json:"status" comment:"当前认证状态"`
|
||||||
|
|
||||||
// 流程节点时间戳 - 记录每个关键步骤的完成时间
|
// === 流程时间戳 - 记录每个关键步骤的完成时间 ===
|
||||||
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
|
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
|
||||||
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty" comment:"企业认证完成时间"`
|
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty" comment:"企业认证完成时间"`
|
||||||
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty" comment:"合同申请时间"`
|
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty" comment:"合同申请时间"`
|
||||||
ContractSignedAt *time.Time `json:"contract_signed_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"`
|
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"`
|
EsignFlowID string `gorm:"type:varchar(500)" json:"esign_flow_id,omitempty" comment:"签署流程ID"`
|
||||||
ContractURL string `gorm:"type:varchar(500)" json:"contract_url,omitempty" comment:"合同文件访问链接"`
|
ContractURL string `gorm:"type:varchar(500)" json:"contract_url,omitempty" comment:"合同文件访问链接"`
|
||||||
ContractSignURL string `gorm:"type:varchar(500)" json:"contract_sign_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:"创建时间"`
|
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
|
||||||
|
|
||||||
|
// === 领域事件 (不持久化) ===
|
||||||
|
domainEvents []interface{} `gorm:"-" json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TableName 指定数据库表名
|
// TableName 指定数据库表名
|
||||||
@@ -50,98 +62,622 @@ func (c *Certification) BeforeCreate(tx *gorm.DB) error {
|
|||||||
if c.ID == "" {
|
if c.ID == "" {
|
||||||
c.ID = uuid.New().String()
|
c.ID = uuid.New().String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置初始状态
|
||||||
|
if c.Status == "" {
|
||||||
|
c.Status = enums.StatusPending
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
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)
|
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 {
|
func (c *Certification) IsFinalStatus() bool {
|
||||||
return enums.IsFinalStatus(c.Status)
|
return enums.IsFinalStatus(c.Status)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStatusCategory 获取状态分类
|
// IsCompleted 是否已完成认证
|
||||||
func (c *Certification) GetStatusCategory() string {
|
func (c *Certification) IsCompleted() bool {
|
||||||
return enums.GetStatusCategory(c.Status)
|
return c.Status == enums.StatusContractSigned
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNextValidStatuses 获取下一个有效状态
|
// GetNextValidStatuses 获取下一个有效状态
|
||||||
func (c *Certification) GetNextValidStatuses() []enums.CertificationStatus {
|
func (c *Certification) GetNextValidStatuses() []enums.CertificationStatus {
|
||||||
nextStatusMap := map[enums.CertificationStatus][]enums.CertificationStatus{
|
return enums.GetNextValidStatuses(c.Status)
|
||||||
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{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanTransitionTo 检查是否可以转换到指定状态
|
// GetFailureInfo 获取失败信息
|
||||||
func (c *Certification) CanTransitionTo(targetStatus enums.CertificationStatus, isUser bool) (bool, string) {
|
func (c *Certification) GetFailureInfo() (enums.FailureReason, string) {
|
||||||
nextStatuses := c.GetNextValidStatuses()
|
return c.FailureReason, c.FailureMessage
|
||||||
|
}
|
||||||
|
|
||||||
for _, nextStatus := range nextStatuses {
|
// ================ 业务规则验证 ================
|
||||||
if nextStatus == targetStatus {
|
|
||||||
// 检查权限
|
// ValidateBusinessRules 验证业务规则
|
||||||
if isUser && !c.IsUserActionRequired() {
|
func (c *Certification) ValidateBusinessRules() error {
|
||||||
return false, "当前状态不需要用户操作"
|
// 基础验证
|
||||||
}
|
if c.UserID == "" {
|
||||||
return true, ""
|
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"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import (
|
|||||||
|
|
||||||
// EnterpriseInfoSubmitRecord 企业信息提交记录
|
// EnterpriseInfoSubmitRecord 企业信息提交记录
|
||||||
type EnterpriseInfoSubmitRecord struct {
|
type EnterpriseInfoSubmitRecord struct {
|
||||||
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
|
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
|
||||||
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
|
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
|
||||||
|
|
||||||
// 企业信息
|
// 企业信息
|
||||||
CompanyName string `json:"company_name" gorm:"type:varchar(200);not null"`
|
CompanyName string `json:"company_name" gorm:"type:varchar(200);not null"`
|
||||||
UnifiedSocialCode string `json:"unified_social_code" gorm:"type:varchar(50);not null;index"`
|
UnifiedSocialCode string `json:"unified_social_code" gorm:"type:varchar(50);not null;index"`
|
||||||
@@ -19,12 +19,12 @@ type EnterpriseInfoSubmitRecord struct {
|
|||||||
LegalPersonID string `json:"legal_person_id" gorm:"type:varchar(50);not null"`
|
LegalPersonID string `json:"legal_person_id" gorm:"type:varchar(50);not null"`
|
||||||
LegalPersonPhone string `json:"legal_person_phone" 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
|
Status string `json:"status" gorm:"type:varchar(20);not null;default:'submitted'"` // submitted, verified, failed
|
||||||
SubmitAt time.Time `json:"submit_at" gorm:"not null"`
|
SubmitAt time.Time `json:"submit_at" gorm:"not null"`
|
||||||
VerifiedAt *time.Time `json:"verified_at"`
|
VerifiedAt *time.Time `json:"verified_at"`
|
||||||
FailedAt *time.Time `json:"failed_at"`
|
FailedAt *time.Time `json:"failed_at"`
|
||||||
FailureReason string `json:"failure_reason" gorm:"type:text"`
|
FailureReason string `json:"failure_reason" gorm:"type:text"`
|
||||||
|
|
||||||
// 系统字段
|
// 系统字段
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"not null"`
|
CreatedAt time.Time `json:"created_at" gorm:"not null"`
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
|
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
|
||||||
@@ -80,4 +80,4 @@ func (r *EnterpriseInfoSubmitRecord) IsVerified() bool {
|
|||||||
// IsFailed 检查是否验证失败
|
// IsFailed 检查是否验证失败
|
||||||
func (r *EnterpriseInfoSubmitRecord) IsFailed() bool {
|
func (r *EnterpriseInfoSubmitRecord) IsFailed() bool {
|
||||||
return r.Status == "failed"
|
return r.Status == "failed"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
215
internal/domains/certification/enums/actor_type.go
Normal file
215
internal/domains/certification/enums/actor_type.go
Normal 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 + ")"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,23 +4,50 @@ package enums
|
|||||||
type CertificationStatus string
|
type CertificationStatus string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// 主流程状态
|
// === 主流程状态 ===
|
||||||
StatusPending CertificationStatus = "pending" // 待认证
|
StatusPending CertificationStatus = "pending" // 待认证
|
||||||
StatusInfoSubmitted CertificationStatus = "info_submitted" // 已提交企业信息
|
StatusInfoSubmitted CertificationStatus = "info_submitted" // 已提交企业信息
|
||||||
StatusEnterpriseVerified CertificationStatus = "enterprise_verified" // 已企业认证
|
StatusEnterpriseVerified CertificationStatus = "enterprise_verified" // 已企业认证
|
||||||
StatusContractApplied CertificationStatus = "contract_applied" // 已申请签署合同
|
StatusContractApplied CertificationStatus = "contract_applied" // 已申请签署合同
|
||||||
StatusContractSigned CertificationStatus = "contract_signed" // 已签署合同
|
StatusContractSigned CertificationStatus = "contract_signed" // 已签署合同(认证完成)
|
||||||
StatusCompleted CertificationStatus = "completed" // 认证完成
|
|
||||||
|
// === 失败状态 ===
|
||||||
|
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 检查状态是否有效
|
// IsValidStatus 检查状态是否有效
|
||||||
func IsValidStatus(status CertificationStatus) bool {
|
func IsValidStatus(status CertificationStatus) bool {
|
||||||
validStatuses := []CertificationStatus{
|
for _, validStatus := range AllStatuses {
|
||||||
StatusPending, StatusInfoSubmitted, StatusEnterpriseVerified,
|
|
||||||
StatusContractApplied, StatusContractSigned, StatusCompleted,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, validStatus := range validStatuses {
|
|
||||||
if status == validStatus {
|
if status == validStatus {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -31,12 +58,14 @@ func IsValidStatus(status CertificationStatus) bool {
|
|||||||
// GetStatusName 获取状态的中文名称
|
// GetStatusName 获取状态的中文名称
|
||||||
func GetStatusName(status CertificationStatus) string {
|
func GetStatusName(status CertificationStatus) string {
|
||||||
statusNames := map[CertificationStatus]string{
|
statusNames := map[CertificationStatus]string{
|
||||||
StatusPending: "待认证",
|
StatusPending: "待认证",
|
||||||
StatusInfoSubmitted: "已提交企业信息",
|
StatusInfoSubmitted: "已提交企业信息",
|
||||||
StatusEnterpriseVerified: "已企业认证",
|
StatusEnterpriseVerified: "已企业认证",
|
||||||
StatusContractApplied: "已申请合同",
|
StatusContractApplied: "已申请签署合同",
|
||||||
StatusContractSigned: "已签署合同",
|
StatusContractSigned: "认证完成",
|
||||||
StatusCompleted: "认证完成",
|
StatusInfoRejected: "企业信息被拒绝",
|
||||||
|
StatusContractRejected: "合同被拒签",
|
||||||
|
StatusContractExpired: "合同签署超时",
|
||||||
}
|
}
|
||||||
|
|
||||||
if name, exists := statusNames[status]; exists {
|
if name, exists := statusNames[status]; exists {
|
||||||
@@ -47,32 +76,51 @@ func GetStatusName(status CertificationStatus) string {
|
|||||||
|
|
||||||
// IsFinalStatus 判断是否为最终状态
|
// IsFinalStatus 判断是否为最终状态
|
||||||
func IsFinalStatus(status CertificationStatus) bool {
|
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 获取状态分类
|
// GetStatusCategory 获取状态分类
|
||||||
func GetStatusCategory(status CertificationStatus) string {
|
func GetStatusCategory(status CertificationStatus) string {
|
||||||
switch status {
|
if IsMainFlowStatus(status) {
|
||||||
case StatusPending:
|
return "主流程"
|
||||||
return "initial"
|
|
||||||
case StatusInfoSubmitted, StatusEnterpriseVerified, StatusContractApplied, StatusContractSigned:
|
|
||||||
return "processing"
|
|
||||||
case StatusCompleted:
|
|
||||||
return "completed"
|
|
||||||
default:
|
|
||||||
return "unknown"
|
|
||||||
}
|
}
|
||||||
|
if IsFailureStatus(status) {
|
||||||
|
return "失败状态"
|
||||||
|
}
|
||||||
|
return "未知"
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStatusPriority 获取状态优先级(用于排序)
|
// GetStatusPriority 获取状态优先级(用于排序)
|
||||||
func GetStatusPriority(status CertificationStatus) int {
|
func GetStatusPriority(status CertificationStatus) int {
|
||||||
priorities := map[CertificationStatus]int{
|
priorities := map[CertificationStatus]int{
|
||||||
StatusPending: 0,
|
StatusPending: 1,
|
||||||
StatusInfoSubmitted: 1,
|
StatusInfoSubmitted: 2,
|
||||||
StatusEnterpriseVerified: 2,
|
StatusEnterpriseVerified: 3,
|
||||||
StatusContractApplied: 3,
|
StatusContractApplied: 4,
|
||||||
StatusContractSigned: 4,
|
StatusContractSigned: 5,
|
||||||
StatusCompleted: 5,
|
StatusInfoRejected: 6,
|
||||||
|
StatusContractRejected: 7,
|
||||||
|
StatusContractExpired: 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
if priority, exists := priorities[status]; exists {
|
if priority, exists := priorities[status]; exists {
|
||||||
@@ -80,3 +128,131 @@ func GetStatusPriority(status CertificationStatus) int {
|
|||||||
}
|
}
|
||||||
return 999
|
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 "未知转换"
|
||||||
|
}
|
||||||
|
|||||||
270
internal/domains/certification/enums/failure_reason.go
Normal file
270
internal/domains/certification/enums/failure_reason.go
Normal 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 "请重新尝试操作"
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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"`
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,62 +1,279 @@
|
|||||||
package queries
|
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 {
|
type ListCertificationsQuery struct {
|
||||||
Page int `json:"page"`
|
// 分页参数
|
||||||
PageSize int `json:"page_size"`
|
Page int `json:"page" validate:"min=1"`
|
||||||
UserID string `json:"user_id"`
|
PageSize int `json:"page_size" validate:"min=1,max=100"`
|
||||||
Status enums.CertificationStatus `json:"status"`
|
|
||||||
StartDate string `json:"start_date"`
|
// 排序参数
|
||||||
EndDate string `json:"end_date"`
|
SortBy string `json:"sort_by"` // 排序字段: created_at, updated_at, status, progress
|
||||||
EnterpriseName string `json:"enterprise_name"`
|
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 企业信息列表查询参数
|
// DefaultValues 设置默认值
|
||||||
type ListEnterprisesQuery struct {
|
func (q *ListCertificationsQuery) DefaultValues() {
|
||||||
Page int `json:"page"`
|
if q.Page <= 0 {
|
||||||
PageSize int `json:"page_size"`
|
q.Page = 1
|
||||||
UserID string `json:"user_id"`
|
}
|
||||||
EnterpriseName string `json:"enterprise_name"`
|
if q.PageSize <= 0 {
|
||||||
LicenseNumber string `json:"license_number"`
|
q.PageSize = 20
|
||||||
LegalPersonName string `json:"legal_person_name"`
|
}
|
||||||
StartDate string `json:"start_date"`
|
if q.SortBy == "" {
|
||||||
EndDate string `json:"end_date"`
|
q.SortBy = "created_at"
|
||||||
|
}
|
||||||
|
if q.SortOrder == "" {
|
||||||
|
q.SortOrder = "desc"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListEnterpriseInfoSubmitRecordsQuery 企业信息提交记录列表查询参数
|
// GetOffset 计算分页偏移量
|
||||||
type ListEnterpriseInfoSubmitRecordsQuery struct {
|
func (q *ListCertificationsQuery) GetOffset() int {
|
||||||
Page int `json:"page"`
|
return (q.Page - 1) * q.PageSize
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListEsignContractGenerateRecordsQuery e签宝生成合同记录列表查询参数
|
// GetLimit 获取查询限制数量
|
||||||
type ListEsignContractGenerateRecordsQuery struct {
|
func (q *ListCertificationsQuery) GetLimit() int {
|
||||||
Page int `json:"page"`
|
return q.PageSize
|
||||||
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"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListEsignContractSignRecordsQuery e签宝签署合同记录列表查询参数
|
// HasTimeFilter 检查是否有时间过滤条件
|
||||||
type ListEsignContractSignRecordsQuery struct {
|
func (q *ListCertificationsQuery) HasTimeFilter() bool {
|
||||||
Page int `json:"page"`
|
return q.CreatedAfter != nil || q.CreatedBefore != nil ||
|
||||||
PageSize int `json:"page_size"`
|
q.UpdatedAfter != nil || q.UpdatedBefore != nil
|
||||||
CertificationID string `json:"certification_id"`
|
}
|
||||||
UserID string `json:"user_id"`
|
|
||||||
Status string `json:"status"`
|
// HasStatusFilter 检查是否有状态过滤条件
|
||||||
SignerName string `json:"signer_name"`
|
func (q *ListCertificationsQuery) HasStatusFilter() bool {
|
||||||
StartDate string `json:"start_date"`
|
return q.Status != "" || len(q.Statuses) > 0
|
||||||
EndDate string `json:"end_date"`
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -97,18 +97,9 @@ func (manager *CertificationStateManager) initStateConfigs() {
|
|||||||
IsAdminActionRequired: false,
|
IsAdminActionRequired: false,
|
||||||
TimestampField: "ContractSignedAt",
|
TimestampField: "ContractSignedAt",
|
||||||
Description: "合同已签署",
|
Description: "合同已签署",
|
||||||
NextValidStatuses: []enums.CertificationStatus{enums.StatusCompleted},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Status: enums.StatusCompleted,
|
|
||||||
Name: "认证完成",
|
|
||||||
ProgressPercentage: 100,
|
|
||||||
IsUserActionRequired: false,
|
|
||||||
IsAdminActionRequired: false,
|
|
||||||
TimestampField: "CompletedAt",
|
|
||||||
Description: "认证流程已完成",
|
|
||||||
NextValidStatuses: []enums.CertificationStatus{},
|
NextValidStatuses: []enums.CertificationStatus{},
|
||||||
},
|
},
|
||||||
|
// 已完成状态已合并到StatusContractSigned中
|
||||||
}
|
}
|
||||||
|
|
||||||
// 转换配置
|
// 转换配置
|
||||||
@@ -168,17 +159,7 @@ func (manager *CertificationStateManager) initStateConfigs() {
|
|||||||
RequiresValidation: true,
|
RequiresValidation: true,
|
||||||
Description: "用户签署合同",
|
Description: "用户签署合同",
|
||||||
},
|
},
|
||||||
// 完成认证
|
// 合同签署即为认证完成,无需额外状态转换
|
||||||
{
|
|
||||||
From: enums.StatusContractSigned,
|
|
||||||
To: enums.StatusCompleted,
|
|
||||||
Action: "complete",
|
|
||||||
ActionName: "完成认证",
|
|
||||||
AllowUser: false,
|
|
||||||
AllowAdmin: false,
|
|
||||||
RequiresValidation: false,
|
|
||||||
Description: "系统自动完成认证",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建映射
|
// 构建映射
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -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 "未知类型"
|
||||||
|
}
|
||||||
@@ -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 ""
|
||||||
|
}
|
||||||
181
internal/domains/certification/value_objects/enterprise_info.go
Normal file
181
internal/domains/certification/value_objects/enterprise_info.go
Normal 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)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
291
internal/infrastructure/events/certification_event_publisher.go
Normal file
291
internal/infrastructure/events/certification_event_publisher.go
Normal 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",
|
||||||
|
}
|
||||||
|
}
|
||||||
295
internal/infrastructure/external/esign/certification_esign_service.go
vendored
Normal file
295
internal/infrastructure/external/esign/certification_esign_service.go
vendored
Normal 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
|
||||||
|
}
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
@@ -12,157 +8,150 @@ import (
|
|||||||
"tyapi-server/internal/application/certification/dto/commands"
|
"tyapi-server/internal/application/certification/dto/commands"
|
||||||
"tyapi-server/internal/application/certification/dto/queries"
|
"tyapi-server/internal/application/certification/dto/queries"
|
||||||
"tyapi-server/internal/shared/interfaces"
|
"tyapi-server/internal/shared/interfaces"
|
||||||
|
"tyapi-server/internal/shared/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CertificationHandler 认证处理器
|
// CertificationHandler 认证HTTP处理器
|
||||||
// 负责处理HTTP请求,参数验证,调用应用服务,返回HTTP响应
|
|
||||||
type CertificationHandler struct {
|
type CertificationHandler struct {
|
||||||
certAppService certification.CertificationApplicationService
|
appService certification.CertificationApplicationService
|
||||||
esignCallbackService certification.EsignCallbackApplicationService
|
response interfaces.ResponseBuilder
|
||||||
response interfaces.ResponseBuilder
|
validator interfaces.RequestValidator
|
||||||
validator interfaces.RequestValidator
|
logger *zap.Logger
|
||||||
logger *zap.Logger
|
jwtAuth *middleware.JWTAuthMiddleware
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCertificationHandler 创建认证处理器
|
// NewCertificationHandler 创建认证处理器
|
||||||
func NewCertificationHandler(
|
func NewCertificationHandler(
|
||||||
certAppService certification.CertificationApplicationService,
|
appService certification.CertificationApplicationService,
|
||||||
esignCallbackService certification.EsignCallbackApplicationService,
|
|
||||||
response interfaces.ResponseBuilder,
|
response interfaces.ResponseBuilder,
|
||||||
validator interfaces.RequestValidator,
|
validator interfaces.RequestValidator,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
|
jwtAuth *middleware.JWTAuthMiddleware,
|
||||||
) *CertificationHandler {
|
) *CertificationHandler {
|
||||||
return &CertificationHandler{
|
return &CertificationHandler{
|
||||||
certAppService: certAppService,
|
appService: appService,
|
||||||
esignCallbackService: esignCallbackService,
|
response: response,
|
||||||
response: response,
|
validator: validator,
|
||||||
validator: validator,
|
logger: logger,
|
||||||
logger: logger,
|
jwtAuth: jwtAuth,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCertificationStatus 获取认证状态
|
// ================ 认证申请管理 ================
|
||||||
// @Summary 获取认证状态
|
|
||||||
// @Description 获取当前用户的认证状态信息,包括认证进度、当前状态等
|
// CreateCertification 创建认证申请
|
||||||
// @Tags 企业认证
|
// @Summary 创建认证申请
|
||||||
|
// @Description 为用户创建企业认证申请
|
||||||
|
// @Tags 认证管理
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Success 200 {object} map[string]interface{} "获取认证状态成功"
|
// @Param request body commands.CreateCertificationCommand true "创建认证申请请求"
|
||||||
// @Failure 401 {object} map[string]interface{} "用户未登录"
|
// @Success 201 {object} responses.CertificationResponse "认证申请创建成功"
|
||||||
|
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||||
|
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
// @Router /api/v1/certification/status [get]
|
// @Router /api/v1/certifications [post]
|
||||||
func (h *CertificationHandler) GetCertificationStatus(c *gin.Context) {
|
func (h *CertificationHandler) CreateCertification(c *gin.Context) {
|
||||||
userID := c.GetString("user_id")
|
var cmd commands.CreateCertificationCommand
|
||||||
if userID == "" {
|
cmd.UserID = h.getCurrentUserID(c)
|
||||||
|
if cmd.UserID == "" {
|
||||||
h.response.Unauthorized(c, "用户未登录")
|
h.response.Unauthorized(c, "用户未登录")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
query := &queries.GetCertificationStatusQuery{
|
result, err := h.appService.CreateCertification(c.Request.Context(), &cmd)
|
||||||
UserID: userID,
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := h.certAppService.GetCertificationStatus(c.Request.Context(), query)
|
|
||||||
if err != nil {
|
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())
|
h.response.BadRequest(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.response.Success(c, result, "获取认证状态成功")
|
h.response.Created(c, result, "认证申请创建成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCertificationDetails 获取认证详情
|
// GetCertification 获取认证详情
|
||||||
// @Summary 获取认证详情
|
// @Summary 获取认证详情
|
||||||
// @Description 获取当前用户的详细认证信息,包括企业信息、认证记录等
|
// @Description 根据认证ID获取认证详情
|
||||||
// @Tags 企业认证
|
// @Tags 认证管理
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Success 200 {object} map[string]interface{} "获取认证详情成功"
|
// @Param id path string true "认证ID"
|
||||||
// @Failure 401 {object} map[string]interface{} "用户未登录"
|
// @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{} "服务器内部错误"
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
// @Router /api/v1/certification/details [get]
|
// @Router /api/v1/certifications/{id} [get]
|
||||||
func (h *CertificationHandler) GetCertificationDetails(c *gin.Context) {
|
func (h *CertificationHandler) GetCertification(c *gin.Context) {
|
||||||
userID := c.GetString("user_id")
|
userID := h.getCurrentUserID(c)
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
h.response.Unauthorized(c, "用户未登录")
|
h.response.Unauthorized(c, "用户未登录")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
query := &queries.GetCertificationDetailsQuery{
|
certificationID := c.Param("id")
|
||||||
UserID: userID,
|
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 {
|
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())
|
h.response.NotFound(c, "认证记录不存在")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.response.Success(c, result, "获取认证详情成功")
|
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 提交企业信息
|
// SubmitEnterpriseInfo 提交企业信息
|
||||||
// @Summary 提交企业信息
|
// @Summary 提交企业信息
|
||||||
// @Description 提交企业四要素信息(企业名称、统一社会信用代码、法定代表人姓名、法定代表人身份证),完成企业信息验证。如果用户没有认证申请,系统会自动创建
|
// @Description 提交企业认证所需的企业信息
|
||||||
// @Tags 企业认证
|
// @Tags 认证管理
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Param request body commands.SubmitEnterpriseInfoCommand true "企业信息提交请求"
|
// @Param id path string true "认证ID"
|
||||||
// @Success 200 {object} map[string]interface{} "企业信息提交成功"
|
// @Param request body commands.SubmitEnterpriseInfoCommand true "提交企业信息请求"
|
||||||
// @Failure 400 {object} map[string]interface{} "请求参数错误或验证码无效"
|
// @Success 200 {object} responses.CertificationResponse "企业信息提交成功"
|
||||||
// @Failure 401 {object} map[string]interface{} "用户未登录"
|
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||||
|
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||||
|
// @Failure 404 {object} map[string]interface{} "认证记录不存在"
|
||||||
// @Failure 500 {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) {
|
func (h *CertificationHandler) SubmitEnterpriseInfo(c *gin.Context) {
|
||||||
userID := c.GetString("user_id")
|
userID := h.getCurrentUserID(c)
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
h.response.Unauthorized(c, "用户未登录")
|
h.response.Unauthorized(c, "用户未登录")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
certificationID := c.Param("id")
|
||||||
|
if certificationID == "" {
|
||||||
|
h.response.BadRequest(c, "认证ID不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var cmd commands.SubmitEnterpriseInfoCommand
|
var cmd commands.SubmitEnterpriseInfoCommand
|
||||||
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
if err := h.validator.BindAndValidate(c, &cmd); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
cmd.CertificationID = certificationID
|
||||||
cmd.UserID = userID
|
cmd.UserID = userID
|
||||||
|
|
||||||
result, err := h.certAppService.SubmitEnterpriseInfo(c.Request.Context(), &cmd)
|
result, err := h.appService.SubmitEnterpriseInfo(c.Request.Context(), &cmd)
|
||||||
if err != nil {
|
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())
|
h.response.BadRequest(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -170,57 +159,38 @@ func (h *CertificationHandler) SubmitEnterpriseInfo(c *gin.Context) {
|
|||||||
h.response.Success(c, result, "企业信息提交成功")
|
h.response.Success(c, result, "企业信息提交成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEnterpriseAuthURL 获取企业认证链接
|
// ================ 合同管理 ================
|
||||||
// @Summary 获取企业认证链接
|
|
||||||
// @Description 获取e签宝企业认证链接,用户可通过该链接完成企业认证
|
// ApplyContract 申请合同签署
|
||||||
// @Tags 企业认证
|
// @Summary 申请合同签署
|
||||||
|
// @Description 申请企业认证合同签署
|
||||||
|
// @Tags 认证管理
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Success 200 {object} map[string]interface{} "获取企业认证链接成功"
|
// @Param request body commands.ApplyContractCommand true "申请合同请求"
|
||||||
// @Failure 401 {object} map[string]interface{} "用户未登录"
|
// @Success 200 {object} responses.ContractSignUrlResponse "合同申请成功"
|
||||||
// @Failure 400 {object} map[string]interface{} "企业信息未提交或认证状态异常"
|
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||||
|
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||||
|
// @Failure 404 {object} map[string]interface{} "认证记录不存在"
|
||||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
// @Router /api/v1/certification/enterprise-auth-url [get]
|
// @Router /api/v1/certifications/apply-contract [post]
|
||||||
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]
|
|
||||||
func (h *CertificationHandler) ApplyContract(c *gin.Context) {
|
func (h *CertificationHandler) ApplyContract(c *gin.Context) {
|
||||||
userID := c.GetString("user_id")
|
userID := h.getCurrentUserID(c)
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
h.response.Unauthorized(c, "用户未登录")
|
h.response.Unauthorized(c, "用户未登录")
|
||||||
return
|
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 {
|
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())
|
h.response.BadRequest(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -228,161 +198,282 @@ func (h *CertificationHandler) ApplyContract(c *gin.Context) {
|
|||||||
h.response.Success(c, result, "合同申请成功")
|
h.response.Success(c, result, "合同申请成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetContractSignURL 获取合同签署链接
|
// ================ 重试操作 ================
|
||||||
// @Summary 获取合同签署链接
|
|
||||||
// @Description 获取e签宝合同签署链接,用户可通过该链接完成合同签署
|
// RetryOperation 重试操作
|
||||||
// @Tags 企业认证
|
// @Summary 重试操作
|
||||||
|
// @Description 重试失败的企业认证或合同申请操作
|
||||||
|
// @Tags 认证管理
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security Bearer
|
// @Security Bearer
|
||||||
// @Success 200 {object} map[string]interface{} "获取合同签署链接成功"
|
// @Param request body commands.RetryOperationCommand true "重试操作请求"
|
||||||
// @Failure 401 {object} map[string]interface{} "用户未登录"
|
// @Success 200 {object} responses.CertificationResponse "重试操作成功"
|
||||||
// @Failure 400 {object} map[string]interface{} "合同未申请或签署状态异常"
|
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||||
|
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||||
|
// @Failure 404 {object} map[string]interface{} "认证记录不存在"
|
||||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
// @Router /api/v1/certification/contract-sign-url [get]
|
// @Router /api/v1/certifications/retry [post]
|
||||||
func (h *CertificationHandler) GetContractSignURL(c *gin.Context) {
|
func (h *CertificationHandler) RetryOperation(c *gin.Context) {
|
||||||
userID := c.GetString("user_id")
|
userID := h.getCurrentUserID(c)
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
h.response.Unauthorized(c, "用户未登录")
|
h.response.Unauthorized(c, "用户未登录")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := &commands.GetContractSignURLCommand{
|
var cmd commands.RetryOperationCommand
|
||||||
UserID: userID,
|
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 {
|
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())
|
h.response.BadRequest(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.response.Success(c, result, "获取合同签署链接成功")
|
h.response.Success(c, result, "重试操作成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
// EsignCallback e签宝回调
|
// ================ 查询操作 ================
|
||||||
// @Summary e签宝回调接口
|
|
||||||
// @Description 接收e签宝认证和签署的回调通知
|
// GetUserCertifications 获取用户认证列表
|
||||||
// @Tags 企业认证
|
// @Summary 获取用户认证列表
|
||||||
|
// @Description 获取当前用户的认证申请列表
|
||||||
|
// @Tags 认证管理
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 200 {object} map[string]interface{} "回调处理成功"
|
// @Security Bearer
|
||||||
// @Failure 400 {object} map[string]interface{} "回调参数错误"
|
// @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{} "服务器内部错误"
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
// @Router /api/v1/certification/esign-callback [post]
|
// @Router /api/v1/certifications/user [get]
|
||||||
func (h *CertificationHandler) EsignCallback(c *gin.Context) {
|
func (h *CertificationHandler) GetUserCertifications(c *gin.Context) {
|
||||||
// 记录请求基本信息
|
userID := h.getCurrentUserID(c)
|
||||||
h.logger.Info("收到e签宝回调请求",
|
if userID == "" {
|
||||||
zap.String("method", c.Request.Method),
|
h.response.Unauthorized(c, "用户未登录")
|
||||||
zap.String("url", c.Request.URL.String()),
|
return
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取并记录请求体
|
var query queries.GetUserCertificationsQuery
|
||||||
var requestBody interface{}
|
if err := h.validator.BindAndValidate(c, &query); err != nil {
|
||||||
var callbackData map[string]interface{}
|
return
|
||||||
if c.Request.Body != nil {
|
}
|
||||||
// 读取请求体
|
query.UserID = userID
|
||||||
bodyBytes, err := c.GetRawData()
|
|
||||||
if err != nil {
|
|
||||||
h.logger.Error("读取回调请求体失败", zap.Error(err))
|
|
||||||
h.response.BadRequest(c, "读取请求体失败")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试解析为JSON
|
result, err := h.appService.GetUserCertifications(c.Request.Context(), &query)
|
||||||
if err := c.ShouldBindJSON(&callbackData); err == nil {
|
if err != nil {
|
||||||
requestBody = callbackData
|
h.logger.Error("获取用户认证列表失败", zap.Error(err), zap.String("user_id", userID))
|
||||||
} else {
|
h.response.BadRequest(c, err.Error())
|
||||||
// 如果不是JSON,记录原始字符串
|
return
|
||||||
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))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 记录Content-Type
|
h.response.Success(c, result, "获取用户认证列表成功")
|
||||||
contentType := c.GetHeader("Content-Type")
|
}
|
||||||
h.logger.Info("回调请求Content-Type", zap.String("content_type", contentType))
|
|
||||||
|
// ListCertifications 获取认证列表(管理员)
|
||||||
// 记录Content-Length
|
// @Summary 获取认证列表
|
||||||
contentLength := c.GetHeader("Content-Length")
|
// @Description 管理员获取认证申请列表
|
||||||
if contentLength != "" {
|
// @Tags 认证管理
|
||||||
h.logger.Info("回调请求Content-Length", zap.String("content_length", contentLength))
|
// @Accept json
|
||||||
}
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
// 记录时间戳
|
// @Param page query int false "页码" default(1)
|
||||||
h.logger.Info("回调请求时间",
|
// @Param page_size query int false "每页数量" default(10)
|
||||||
zap.Time("request_time", time.Now()),
|
// @Param sort_by query string false "排序字段"
|
||||||
zap.String("request_id", c.GetHeader("X-Request-ID")),
|
// @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 "公司名称"
|
||||||
h.logger.Info("e签宝回调完整信息摘要",
|
// @Param legal_person_name query string false "法人姓名"
|
||||||
zap.String("method", c.Request.Method),
|
// @Param search_keyword query string false "搜索关键词"
|
||||||
zap.String("url", c.Request.URL.String()),
|
// @Success 200 {object} responses.CertificationListResponse "获取认证列表成功"
|
||||||
zap.String("client_ip", c.ClientIP()),
|
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||||
zap.String("content_type", contentType),
|
// @Failure 403 {object} map[string]interface{} "权限不足"
|
||||||
zap.Any("headers", headers),
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
zap.Any("query_params", queryParams),
|
// @Router /api/v1/certifications [get]
|
||||||
zap.Any("body", requestBody),
|
func (h *CertificationHandler) ListCertifications(c *gin.Context) {
|
||||||
)
|
userID := h.getCurrentUserID(c)
|
||||||
|
if userID == "" {
|
||||||
// 处理回调数据
|
h.response.Unauthorized(c, "用户未登录")
|
||||||
if callbackData != nil {
|
return
|
||||||
// 构建请求头映射
|
}
|
||||||
headers := make(map[string]string)
|
|
||||||
for key, values := range c.Request.Header {
|
var query queries.ListCertificationsQuery
|
||||||
if len(values) > 0 {
|
if err := h.validator.BindAndValidate(c, &query); err != nil {
|
||||||
headers[key] = values[0]
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
result, err := h.appService.ListCertifications(c.Request.Context(), &query)
|
||||||
// 构建查询参数映射
|
if err != nil {
|
||||||
queryParams := make(map[string]string)
|
h.logger.Error("获取认证列表失败", zap.Error(err))
|
||||||
for key, values := range c.Request.URL.Query() {
|
h.response.BadRequest(c, err.Error())
|
||||||
if len(values) > 0 {
|
return
|
||||||
queryParams[key] = values[0]
|
}
|
||||||
}
|
|
||||||
}
|
h.response.Success(c, result, "获取认证列表成功")
|
||||||
|
}
|
||||||
if err := h.esignCallbackService.HandleCallback(c.Request.Context(), callbackData, headers, queryParams); err != nil {
|
|
||||||
h.logger.Error("处理e签宝回调失败", zap.Error(err))
|
// GetCertificationStatistics 获取认证统计
|
||||||
h.response.BadRequest(c, "回调处理失败: "+err.Error())
|
// @Summary 获取认证统计
|
||||||
return
|
// @Description 获取认证相关的统计数据
|
||||||
}
|
// @Tags 认证管理
|
||||||
}
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
// 返回成功响应
|
// @Security Bearer
|
||||||
c.JSON(200, map[string]interface{}{
|
// @Param start_date query string true "开始日期" format(date)
|
||||||
"code": "200",
|
// @Param end_date query string true "结束日期" format(date)
|
||||||
"msg": "success",
|
// @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 ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,58 +1,100 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"tyapi-server/internal/infrastructure/http/handlers"
|
|
||||||
sharedhttp "tyapi-server/internal/shared/http"
|
|
||||||
"tyapi-server/internal/shared/middleware"
|
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"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 {
|
type CertificationRoutes struct {
|
||||||
handler *handlers.CertificationHandler
|
handler *handlers.CertificationHandler
|
||||||
authMiddleware *middleware.JWTAuthMiddleware
|
router *http.GinRouter
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
|
auth *middleware.JWTAuthMiddleware
|
||||||
|
optional *middleware.OptionalAuthMiddleware
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCertificationRoutes 创建认证路由注册器
|
// NewCertificationRoutes 创建认证路由
|
||||||
func NewCertificationRoutes(
|
func NewCertificationRoutes(
|
||||||
handler *handlers.CertificationHandler,
|
handler *handlers.CertificationHandler,
|
||||||
authMiddleware *middleware.JWTAuthMiddleware,
|
router *http.GinRouter,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
|
auth *middleware.JWTAuthMiddleware,
|
||||||
|
optional *middleware.OptionalAuthMiddleware,
|
||||||
) *CertificationRoutes {
|
) *CertificationRoutes {
|
||||||
return &CertificationRoutes{
|
return &CertificationRoutes{
|
||||||
handler: handler,
|
handler: handler,
|
||||||
authMiddleware: authMiddleware,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
auth: auth,
|
||||||
|
optional: optional,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register 注册认证相关路由
|
// Register 注册认证路由
|
||||||
func (r *CertificationRoutes) Register(router *sharedhttp.GinRouter) {
|
func (r *CertificationRoutes) Register(router *http.GinRouter) {
|
||||||
// 认证相关路由组
|
// 认证管理路由组
|
||||||
engine := router.GetEngine()
|
certificationGroup := router.GetEngine().Group("/api/v1/certifications")
|
||||||
certificationGroup := engine.Group("/api/v1/certification")
|
|
||||||
certificationGroup.Use(r.authMiddleware.Handle())
|
|
||||||
{
|
{
|
||||||
// 认证状态查询
|
// 需要认证的路由
|
||||||
certificationGroup.GET("/status", r.handler.GetCertificationStatus) // 获取认证状态
|
authGroup := certificationGroup.Group("")
|
||||||
certificationGroup.GET("/details", r.handler.GetCertificationDetails) // 获取认证详情
|
authGroup.Use(r.auth.Handle())
|
||||||
certificationGroup.GET("/progress", r.handler.GetCertificationProgress) // 获取认证进度
|
{
|
||||||
|
authGroup.GET("/user", r.handler.GetUserCertifications) // 获取用户认证列表
|
||||||
|
authGroup.GET("", r.handler.ListCertifications) // 查询认证列表(管理员)
|
||||||
|
authGroup.GET("/statistics", r.handler.GetCertificationStatistics) // 获取认证统计
|
||||||
|
|
||||||
// 企业信息管理
|
// 1. 获取认证详情
|
||||||
certificationGroup.POST("/submit-enterprise-info", r.handler.SubmitEnterpriseInfo) // 提交企业信息(自动创建认证申请)
|
authGroup.GET("/:id", r.handler.GetCertification)
|
||||||
|
|
||||||
// 企业认证
|
// 2. 提交企业信息
|
||||||
certificationGroup.GET("/enterprise-auth-url", r.handler.GetEnterpriseAuthURL) // 获取企业认证链接
|
authGroup.POST("/:id/enterprise-info", r.handler.SubmitEnterpriseInfo)
|
||||||
|
|
||||||
// 合同管理
|
// 合同管理
|
||||||
certificationGroup.POST("/apply-contract", r.handler.ApplyContract) // 申请合同
|
authGroup.POST("/apply-contract", r.handler.ApplyContract) // 申请合同签署
|
||||||
certificationGroup.GET("/contract-sign-url", r.handler.GetContractSignURL) // 获取合同签署链接
|
|
||||||
|
// 重试操作
|
||||||
|
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("认证路由注册完成")
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user