temp
This commit is contained in:
@@ -19,8 +19,9 @@ import (
|
||||
// 财务域实体
|
||||
financeEntities "tyapi-server/internal/domains/finance/entities"
|
||||
|
||||
// 管理员域实体
|
||||
adminEntities "tyapi-server/internal/domains/admin/entities"
|
||||
// 产品域实体
|
||||
productEntities "tyapi-server/internal/domains/product/entities"
|
||||
|
||||
"tyapi-server/internal/infrastructure/database"
|
||||
)
|
||||
|
||||
@@ -204,23 +205,23 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
|
||||
// 用户域
|
||||
&entities.User{},
|
||||
&entities.SMSCode{},
|
||||
&entities.EnterpriseInfo{},
|
||||
|
||||
// 认证域
|
||||
&certEntities.Certification{},
|
||||
&certEntities.LicenseUploadRecord{},
|
||||
&certEntities.FaceVerifyRecord{},
|
||||
&certEntities.ContractRecord{},
|
||||
&certEntities.NotificationRecord{},
|
||||
|
||||
// 用户域 - 企业信息
|
||||
&entities.EnterpriseInfo{},
|
||||
&certEntities.EnterpriseInfoSubmitRecord{},
|
||||
&certEntities.EsignContractGenerateRecord{},
|
||||
&certEntities.EsignContractSignRecord{},
|
||||
|
||||
// 财务域
|
||||
&financeEntities.Wallet{},
|
||||
&financeEntities.UserSecrets{},
|
||||
|
||||
// 管理员域
|
||||
&adminEntities.Admin{},
|
||||
// 产品域
|
||||
&productEntities.Product{},
|
||||
&productEntities.ProductCategory{},
|
||||
&productEntities.Subscription{},
|
||||
&productEntities.ProductDocumentation{},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tyapi-server/internal/application/admin/dto/commands"
|
||||
"tyapi-server/internal/application/admin/dto/queries"
|
||||
"tyapi-server/internal/application/admin/dto/responses"
|
||||
)
|
||||
|
||||
// AdminApplicationService 管理员应用服务接口
|
||||
type AdminApplicationService interface {
|
||||
Login(ctx context.Context, cmd *commands.AdminLoginCommand) (*responses.AdminLoginResponse, error)
|
||||
CreateAdmin(ctx context.Context, cmd *commands.CreateAdminCommand) error
|
||||
UpdateAdmin(ctx context.Context, cmd *commands.UpdateAdminCommand) error
|
||||
ChangePassword(ctx context.Context, cmd *commands.ChangeAdminPasswordCommand) error
|
||||
ListAdmins(ctx context.Context, query *queries.ListAdminsQuery) (*responses.AdminListResponse, error)
|
||||
GetAdminByID(ctx context.Context, query *queries.GetAdminInfoQuery) (*responses.AdminInfoResponse, error)
|
||||
DeleteAdmin(ctx context.Context, cmd *commands.DeleteAdminCommand) error
|
||||
GetAdminStats(ctx context.Context) (*responses.AdminStatsResponse, error)
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"tyapi-server/internal/application/admin/dto/commands"
|
||||
"tyapi-server/internal/application/admin/dto/queries"
|
||||
"tyapi-server/internal/application/admin/dto/responses"
|
||||
"tyapi-server/internal/domains/admin/entities"
|
||||
"tyapi-server/internal/domains/admin/repositories"
|
||||
)
|
||||
|
||||
// AdminApplicationServiceImpl 管理员应用服务实现
|
||||
type AdminApplicationServiceImpl struct {
|
||||
adminRepo repositories.AdminRepository
|
||||
loginLogRepo repositories.AdminLoginLogRepository
|
||||
operationLogRepo repositories.AdminOperationLogRepository
|
||||
permissionRepo repositories.AdminPermissionRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewAdminApplicationService 创建管理员应用服务
|
||||
func NewAdminApplicationService(
|
||||
adminRepo repositories.AdminRepository,
|
||||
loginLogRepo repositories.AdminLoginLogRepository,
|
||||
operationLogRepo repositories.AdminOperationLogRepository,
|
||||
permissionRepo repositories.AdminPermissionRepository,
|
||||
logger *zap.Logger,
|
||||
) AdminApplicationService {
|
||||
return &AdminApplicationServiceImpl{
|
||||
adminRepo: adminRepo,
|
||||
loginLogRepo: loginLogRepo,
|
||||
operationLogRepo: operationLogRepo,
|
||||
permissionRepo: permissionRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AdminApplicationServiceImpl) Login(ctx context.Context, cmd *commands.AdminLoginCommand) (*responses.AdminLoginResponse, error) {
|
||||
s.logger.Info("管理员登录", zap.String("username", cmd.Username))
|
||||
|
||||
admin, err := s.adminRepo.FindByUsername(ctx, cmd.Username)
|
||||
if err != nil {
|
||||
s.logger.Warn("管理员登录失败:用户不存在", zap.String("username", cmd.Username))
|
||||
return nil, fmt.Errorf("用户名或密码错误")
|
||||
}
|
||||
|
||||
if !admin.IsActive {
|
||||
s.logger.Warn("管理员登录失败:账户已禁用", zap.String("username", cmd.Username))
|
||||
return nil, fmt.Errorf("账户已被禁用,请联系管理员")
|
||||
}
|
||||
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(admin.Password), []byte(cmd.Password)); err != nil {
|
||||
s.logger.Warn("管理员登录失败:密码错误", zap.String("username", cmd.Username))
|
||||
return nil, fmt.Errorf("用户名或密码错误")
|
||||
}
|
||||
|
||||
if err := s.adminRepo.UpdateLoginStats(ctx, admin.ID); err != nil {
|
||||
s.logger.Error("更新登录统计失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// This part would ideally be in a separate auth service or helper
|
||||
token, expiresAt, err := s.generateJWTToken(admin)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成令牌失败: %w", err)
|
||||
}
|
||||
|
||||
permissions, err := s.getAdminPermissions(ctx, admin)
|
||||
if err != nil {
|
||||
s.logger.Error("获取管理员权限失败", zap.Error(err))
|
||||
permissions = []string{}
|
||||
}
|
||||
|
||||
adminInfo := responses.AdminInfoResponse{
|
||||
ID: admin.ID,
|
||||
Username: admin.Username,
|
||||
Email: admin.Email,
|
||||
Phone: admin.Phone,
|
||||
RealName: admin.RealName,
|
||||
Role: admin.Role,
|
||||
IsActive: admin.IsActive,
|
||||
LastLoginAt: admin.LastLoginAt,
|
||||
LoginCount: admin.LoginCount,
|
||||
Permissions: permissions,
|
||||
CreatedAt: admin.CreatedAt,
|
||||
}
|
||||
|
||||
s.logger.Info("管理员登录成功", zap.String("username", cmd.Username))
|
||||
return &responses.AdminLoginResponse{
|
||||
Token: token,
|
||||
ExpiresAt: expiresAt,
|
||||
Admin: adminInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AdminApplicationServiceImpl) CreateAdmin(ctx context.Context, cmd *commands.CreateAdminCommand) error {
|
||||
// ... implementation ...
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AdminApplicationServiceImpl) UpdateAdmin(ctx context.Context, cmd *commands.UpdateAdminCommand) error {
|
||||
// ... implementation ...
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AdminApplicationServiceImpl) ChangePassword(ctx context.Context, cmd *commands.ChangeAdminPasswordCommand) error {
|
||||
// ... implementation ...
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AdminApplicationServiceImpl) ListAdmins(ctx context.Context, query *queries.ListAdminsQuery) (*responses.AdminListResponse, error) {
|
||||
// ... implementation ...
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *AdminApplicationServiceImpl) GetAdminByID(ctx context.Context, query *queries.GetAdminInfoQuery) (*responses.AdminInfoResponse, error) {
|
||||
// ... implementation ...
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (s *AdminApplicationServiceImpl) DeleteAdmin(ctx context.Context, cmd *commands.DeleteAdminCommand) error {
|
||||
// ... implementation ...
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *AdminApplicationServiceImpl) GetAdminStats(ctx context.Context) (*responses.AdminStatsResponse, error) {
|
||||
// ... implementation ...
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Private helper methods from old service
|
||||
func (s *AdminApplicationServiceImpl) getAdminPermissions(ctx context.Context, admin *entities.Admin) ([]string, error) {
|
||||
rolePermissions, err := s.adminRepo.GetPermissionsByRole(ctx, admin.Role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
permissions := make([]string, 0, len(rolePermissions))
|
||||
for _, perm := range rolePermissions {
|
||||
permissions = append(permissions, perm.Code)
|
||||
}
|
||||
|
||||
if admin.Permissions != "" {
|
||||
var customPermissions []string
|
||||
if err := json.Unmarshal([]byte(admin.Permissions), &customPermissions); err == nil {
|
||||
permissions = append(permissions, customPermissions...)
|
||||
}
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func (s *AdminApplicationServiceImpl) generateJWTToken(admin *entities.Admin) (string, time.Time, error) {
|
||||
// This should be handled by a dedicated auth service
|
||||
token := fmt.Sprintf("admin_token_%s_%d", admin.ID, time.Now().Unix())
|
||||
expiresAt := time.Now().Add(24 * time.Hour)
|
||||
return token, expiresAt, nil
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
package commands
|
||||
|
||||
// AdminLoginCommand 管理员登录命令
|
||||
type AdminLoginCommand struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
// CreateAdminCommand 创建管理员命令
|
||||
type CreateAdminCommand struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
Email string `json:"email" binding:"required,email"`
|
||||
Phone string `json:"phone"`
|
||||
RealName string `json:"real_name" binding:"required"`
|
||||
Role string `json:"role" binding:"required"`
|
||||
Permissions []string `json:"permissions"`
|
||||
OperatorID string `json:"-"`
|
||||
}
|
||||
|
||||
// UpdateAdminCommand 更新管理员命令
|
||||
type UpdateAdminCommand struct {
|
||||
AdminID string `json:"-"`
|
||||
Email string `json:"email" binding:"email"`
|
||||
Phone string `json:"phone"`
|
||||
RealName string `json:"real_name"`
|
||||
Role string `json:"role"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
Permissions []string `json:"permissions"`
|
||||
OperatorID string `json:"-"`
|
||||
}
|
||||
|
||||
// ChangeAdminPasswordCommand 修改密码命令
|
||||
type ChangeAdminPasswordCommand struct {
|
||||
AdminID string `json:"-"`
|
||||
OldPassword string `json:"old_password" binding:"required"`
|
||||
NewPassword string `json:"new_password" binding:"required"`
|
||||
}
|
||||
|
||||
// DeleteAdminCommand 删除管理员命令
|
||||
type DeleteAdminCommand struct {
|
||||
AdminID string `json:"-"`
|
||||
OperatorID string `json:"-"`
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package queries
|
||||
|
||||
// ListAdminsQuery 获取管理员列表查询
|
||||
type ListAdminsQuery struct {
|
||||
Page int `form:"page" binding:"min=1"`
|
||||
PageSize int `form:"page_size" binding:"min=1,max=100"`
|
||||
Username string `form:"username"`
|
||||
Email string `form:"email"`
|
||||
Role string `form:"role"`
|
||||
IsActive *bool `form:"is_active"`
|
||||
}
|
||||
|
||||
// GetAdminInfoQuery 获取管理员信息查询
|
||||
type GetAdminInfoQuery struct {
|
||||
AdminID string `uri:"id" binding:"required"`
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/admin/entities"
|
||||
)
|
||||
|
||||
// AdminLoginResponse 管理员登录响应
|
||||
type AdminLoginResponse struct {
|
||||
Token string `json:"token"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
Admin AdminInfoResponse `json:"admin"`
|
||||
}
|
||||
|
||||
// AdminInfoResponse 管理员信息响应
|
||||
type AdminInfoResponse struct {
|
||||
ID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
RealName string `json:"real_name"`
|
||||
Role entities.AdminRole `json:"role"`
|
||||
IsActive bool `json:"is_active"`
|
||||
LastLoginAt *time.Time `json:"last_login_at"`
|
||||
LoginCount int `json:"login_count"`
|
||||
Permissions []string `json:"permissions"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// AdminListResponse 管理员列表响应
|
||||
type AdminListResponse struct {
|
||||
Total int64 `json:"total"`
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
Admins []AdminInfoResponse `json:"admins"`
|
||||
}
|
||||
|
||||
// AdminStatsResponse 管理员统计响应
|
||||
type AdminStatsResponse struct {
|
||||
TotalAdmins int64 `json:"total_admins"`
|
||||
ActiveAdmins int64 `json:"active_admins"`
|
||||
TodayLogins int64 `json:"today_logins"`
|
||||
TotalOperations int64 `json:"total_operations"`
|
||||
}
|
||||
@@ -10,39 +10,24 @@ import (
|
||||
|
||||
// CertificationApplicationService 认证应用服务接口
|
||||
type CertificationApplicationService interface {
|
||||
// 认证申请管理
|
||||
CreateCertification(ctx context.Context, cmd *commands.CreateCertificationCommand) (*responses.CertificationResponse, error)
|
||||
// 认证状态查询
|
||||
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)
|
||||
|
||||
// 企业信息管理
|
||||
CreateEnterpriseInfo(ctx context.Context, cmd *commands.CreateEnterpriseInfoCommand) (*responses.EnterpriseInfoResponse, error)
|
||||
SubmitEnterpriseInfo(ctx context.Context, cmd *commands.SubmitEnterpriseInfoCommand) (*responses.EnterpriseInfoResponse, error)
|
||||
SubmitEnterpriseInfo(ctx context.Context, cmd *commands.SubmitEnterpriseInfoCommand) (*responses.CertificationResponse, error)
|
||||
|
||||
// 营业执照上传
|
||||
UploadLicense(ctx context.Context, cmd *commands.UploadLicenseCommand) (*responses.UploadLicenseResponse, error)
|
||||
GetLicenseOCRResult(ctx context.Context, recordID string) (*responses.UploadLicenseResponse, error)
|
||||
|
||||
// UploadBusinessLicense 上传营业执照并同步OCR识别
|
||||
UploadBusinessLicense(ctx context.Context, userID string, fileBytes []byte, fileName string) (*responses.UploadLicenseResponse, error)
|
||||
|
||||
// 人脸识别
|
||||
InitiateFaceVerify(ctx context.Context, cmd *commands.InitiateFaceVerifyCommand) (*responses.FaceVerifyResponse, error)
|
||||
CompleteFaceVerify(ctx context.Context, faceVerifyID string, isSuccess bool) error
|
||||
RetryFaceVerify(ctx context.Context, userID string) (*responses.FaceVerifyResponse, error)
|
||||
// 企业认证
|
||||
GetEnterpriseAuthURL(ctx context.Context, userID string) (*responses.EnterpriseAuthURLResponse, error)
|
||||
|
||||
// 合同管理
|
||||
ApplyContract(ctx context.Context, userID string) (*responses.CertificationResponse, error)
|
||||
ApproveContract(ctx context.Context, certificationID, adminID, signingURL, approvalNotes string) error
|
||||
RejectContract(ctx context.Context, certificationID, adminID, rejectReason string) error
|
||||
CompleteContractSign(ctx context.Context, certificationID, contractURL string) error
|
||||
RetryContractSign(ctx context.Context, userID string) (*responses.CertificationResponse, error)
|
||||
|
||||
// 认证完成
|
||||
CompleteCertification(ctx context.Context, certificationID string) error
|
||||
|
||||
// 重试和重启
|
||||
RetryStep(ctx context.Context, cmd *commands.RetryStepCommand) error
|
||||
RestartCertification(ctx context.Context, certificationID string) error
|
||||
GetContractSignURL(ctx context.Context, cmd *commands.GetContractSignURLCommand) (*responses.ContractSignURLResponse, error)
|
||||
}
|
||||
|
||||
// EsignCallbackApplicationService e签宝回调应用服务接口
|
||||
type EsignCallbackApplicationService interface {
|
||||
// 处理e签宝回调
|
||||
HandleCallback(ctx context.Context, callbackData map[string]interface{}, headers map[string]string, queryParams map[string]string) error
|
||||
}
|
||||
|
||||
@@ -2,112 +2,74 @@ package certification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/application/certification/dto/commands"
|
||||
"tyapi-server/internal/application/certification/dto/queries"
|
||||
"tyapi-server/internal/application/certification/dto/responses"
|
||||
"tyapi-server/internal/domains/certification/entities"
|
||||
"tyapi-server/internal/domains/certification/repositories"
|
||||
"tyapi-server/internal/domains/certification/enums"
|
||||
"tyapi-server/internal/domains/certification/services"
|
||||
user_entities "tyapi-server/internal/domains/user/entities"
|
||||
user_repositories "tyapi-server/internal/domains/user/repositories"
|
||||
"tyapi-server/internal/shared/ocr"
|
||||
"tyapi-server/internal/shared/storage"
|
||||
user_services "tyapi-server/internal/domains/user/services"
|
||||
"tyapi-server/internal/shared/database"
|
||||
esign_service "tyapi-server/internal/shared/esign"
|
||||
)
|
||||
|
||||
// CertificationApplicationServiceImpl 认证应用服务实现
|
||||
// 负责业务流程编排、事务管理、数据转换,不直接操作仓库
|
||||
type CertificationApplicationServiceImpl struct {
|
||||
certRepo repositories.CertificationRepository
|
||||
licenseRepo repositories.LicenseUploadRecordRepository
|
||||
faceVerifyRepo repositories.FaceVerifyRecordRepository
|
||||
contractRepo repositories.ContractRecordRepository
|
||||
certService *services.CertificationService
|
||||
stateMachine *services.CertificationStateMachine
|
||||
storageService storage.StorageService
|
||||
ocrService ocr.OCRService
|
||||
enterpriseInfoRepo user_repositories.EnterpriseInfoRepository
|
||||
logger *zap.Logger
|
||||
certManagementService *services.CertificationManagementService
|
||||
certWorkflowService *services.CertificationWorkflowService
|
||||
certificationEsignService *services.CertificationEsignService
|
||||
enterpriseService *user_services.EnterpriseService
|
||||
esignService *esign_service.Client
|
||||
enterpriseRecordService *services.EnterpriseInfoSubmitRecordService
|
||||
smsCodeService *user_services.SMSCodeService
|
||||
txManager *database.TransactionManager
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCertificationApplicationService 创建认证应用服务
|
||||
func NewCertificationApplicationService(
|
||||
certRepo repositories.CertificationRepository,
|
||||
licenseRepo repositories.LicenseUploadRecordRepository,
|
||||
faceVerifyRepo repositories.FaceVerifyRecordRepository,
|
||||
contractRepo repositories.ContractRecordRepository,
|
||||
certService *services.CertificationService,
|
||||
stateMachine *services.CertificationStateMachine,
|
||||
storageService storage.StorageService,
|
||||
ocrService ocr.OCRService,
|
||||
enterpriseInfoRepo user_repositories.EnterpriseInfoRepository,
|
||||
certManagementService *services.CertificationManagementService,
|
||||
certWorkflowService *services.CertificationWorkflowService,
|
||||
certificationEsignService *services.CertificationEsignService,
|
||||
enterpriseService *user_services.EnterpriseService,
|
||||
esignService *esign_service.Client,
|
||||
enterpriseRecordService *services.EnterpriseInfoSubmitRecordService,
|
||||
smsCodeService *user_services.SMSCodeService,
|
||||
txManager *database.TransactionManager,
|
||||
logger *zap.Logger,
|
||||
) CertificationApplicationService {
|
||||
return &CertificationApplicationServiceImpl{
|
||||
certRepo: certRepo,
|
||||
licenseRepo: licenseRepo,
|
||||
faceVerifyRepo: faceVerifyRepo,
|
||||
contractRepo: contractRepo,
|
||||
certService: certService,
|
||||
stateMachine: stateMachine,
|
||||
storageService: storageService,
|
||||
ocrService: ocrService,
|
||||
enterpriseInfoRepo: enterpriseInfoRepo,
|
||||
logger: logger,
|
||||
certManagementService: certManagementService,
|
||||
certWorkflowService: certWorkflowService,
|
||||
certificationEsignService: certificationEsignService,
|
||||
enterpriseService: enterpriseService,
|
||||
esignService: esignService,
|
||||
enterpriseRecordService: enterpriseRecordService,
|
||||
smsCodeService: smsCodeService,
|
||||
txManager: txManager,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCertification 创建认证申请
|
||||
func (s *CertificationApplicationServiceImpl) CreateCertification(ctx context.Context, cmd *commands.CreateCertificationCommand) (*responses.CertificationResponse, error) {
|
||||
// 使用领域服务创建认证申请
|
||||
certification, err := s.certService.CreateCertification(ctx, cmd.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// SubmitEnterpriseInfo 提交企业信息
|
||||
// 业务流程:1. 验证企业信息 2. 创建或获取认证申请 3. 使用事务执行状态转换和创建记录
|
||||
func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(ctx context.Context, cmd *commands.SubmitEnterpriseInfoCommand) (*responses.CertificationResponse, error) {
|
||||
// 1. 验证短信验证码
|
||||
if err := s.smsCodeService.VerifyCode(ctx, cmd.LegalPersonPhone, cmd.VerificationCode, user_entities.SMSSceneCertification); err != nil {
|
||||
return nil, fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
response := &responses.CertificationResponse{
|
||||
ID: certification.ID,
|
||||
UserID: certification.UserID,
|
||||
Status: certification.Status,
|
||||
StatusName: string(certification.Status),
|
||||
Progress: certification.GetProgressPercentage(),
|
||||
IsUserActionRequired: certification.IsUserActionRequired(),
|
||||
IsAdminActionRequired: certification.IsAdminActionRequired(),
|
||||
InfoSubmittedAt: certification.InfoSubmittedAt,
|
||||
FaceVerifiedAt: certification.FaceVerifiedAt,
|
||||
ContractAppliedAt: certification.ContractAppliedAt,
|
||||
ContractApprovedAt: certification.ContractApprovedAt,
|
||||
ContractSignedAt: certification.ContractSignedAt,
|
||||
CompletedAt: certification.CompletedAt,
|
||||
ContractURL: certification.ContractURL,
|
||||
SigningURL: certification.SigningURL,
|
||||
RejectReason: certification.RejectReason,
|
||||
CreatedAt: certification.CreatedAt,
|
||||
UpdatedAt: certification.UpdatedAt,
|
||||
}
|
||||
|
||||
s.logger.Info("认证申请创建成功",
|
||||
zap.String("certification_id", certification.ID),
|
||||
zap.String("user_id", cmd.UserID),
|
||||
)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// CreateEnterpriseInfo 创建企业信息
|
||||
func (s *CertificationApplicationServiceImpl) CreateEnterpriseInfo(ctx context.Context, cmd *commands.CreateEnterpriseInfoCommand) (*responses.EnterpriseInfoResponse, error) {
|
||||
// 检查用户是否已有企业信息
|
||||
existingInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, cmd.UserID)
|
||||
if err == nil && existingInfo != nil {
|
||||
return nil, fmt.Errorf("用户已有企业信息")
|
||||
}
|
||||
|
||||
// 检查统一社会信用代码是否已存在
|
||||
exists, err := s.enterpriseInfoRepo.CheckUnifiedSocialCodeExists(ctx, cmd.UnifiedSocialCode, "")
|
||||
// 2. 验证企业信息(检查统一社会信用代码是否已存在)
|
||||
exists, err := s.enterpriseService.CheckUnifiedSocialCodeExists(ctx, cmd.UnifiedSocialCode, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("检查企业信息失败: %w", err)
|
||||
}
|
||||
@@ -115,494 +77,379 @@ func (s *CertificationApplicationServiceImpl) CreateEnterpriseInfo(ctx context.C
|
||||
return nil, fmt.Errorf("统一社会信用代码已存在")
|
||||
}
|
||||
|
||||
// 创建企业信息
|
||||
enterpriseInfo := &user_entities.EnterpriseInfo{
|
||||
UserID: cmd.UserID,
|
||||
CompanyName: cmd.CompanyName,
|
||||
UnifiedSocialCode: cmd.UnifiedSocialCode,
|
||||
LegalPersonName: cmd.LegalPersonName,
|
||||
LegalPersonID: cmd.LegalPersonID,
|
||||
// 3. 获取或创建认证申请
|
||||
certification, err := s.certManagementService.GetCertificationByUserID(ctx, cmd.UserID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
certification, err = s.certManagementService.CreateCertification(ctx, cmd.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建认证申请失败: %w", err)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("获取认证申请失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
createdEnterpriseInfo, err := s.enterpriseInfoRepo.Create(ctx, *enterpriseInfo)
|
||||
if err != nil {
|
||||
s.logger.Error("创建企业信息失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建企业信息失败: %w", err)
|
||||
// 4. 创建记录
|
||||
if certification.Status != enums.StatusPending && certification.Status != enums.StatusInfoSubmitted {
|
||||
return nil, fmt.Errorf("当前状态不允许提交企业信息")
|
||||
}
|
||||
s.logger.Info("企业信息创建成功",
|
||||
_, err = s.enterpriseRecordService.CreateEnterpriseInfoSubmitRecord(ctx, cmd.UserID, cmd.CompanyName, cmd.UnifiedSocialCode, cmd.LegalPersonName, cmd.LegalPersonID, cmd.LegalPersonPhone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("企业信息提交失败: %w", err)
|
||||
}
|
||||
s.logger.Info("企业信息提交成功",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("enterprise_id", enterpriseInfo.ID),
|
||||
zap.String("certification_id", certification.ID),
|
||||
zap.String("company_name", cmd.CompanyName),
|
||||
)
|
||||
|
||||
return &responses.EnterpriseInfoResponse{
|
||||
ID: createdEnterpriseInfo.ID,
|
||||
CompanyName: enterpriseInfo.CompanyName,
|
||||
UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode,
|
||||
LegalPersonName: enterpriseInfo.LegalPersonName,
|
||||
LegalPersonID: enterpriseInfo.LegalPersonID,
|
||||
IsOCRVerified: enterpriseInfo.IsOCRVerified,
|
||||
IsFaceVerified: enterpriseInfo.IsFaceVerified,
|
||||
CreatedAt: enterpriseInfo.CreatedAt,
|
||||
UpdatedAt: enterpriseInfo.UpdatedAt,
|
||||
}, nil
|
||||
// 转换状态
|
||||
err = s.certWorkflowService.SubmitEnterpriseInfo(ctx, certification.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("转换状态失败: %w", err)
|
||||
}
|
||||
|
||||
// 5. 检查企业是否已经认证
|
||||
hasCertification, err := s.certManagementService.CheckCertification(ctx, cmd.CompanyName, cmd.UnifiedSocialCode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("检查企业认证状态失败: %w", err)
|
||||
}
|
||||
if hasCertification {
|
||||
// 如果企业已经认证,则直接完成认证
|
||||
err = s.completeEnterpriseAuth(ctx, certification)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("完成企业认证失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 重新获取认证申请数据
|
||||
certification, err = s.certManagementService.GetCertificationByID(ctx, certification.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取认证申请失败: %w", err)
|
||||
}
|
||||
return s.buildCertificationResponse(certification), nil
|
||||
}
|
||||
|
||||
// UploadLicense 上传营业执照
|
||||
func (s *CertificationApplicationServiceImpl) UploadLicense(ctx context.Context, cmd *commands.UploadLicenseCommand) (*responses.UploadLicenseResponse, error) {
|
||||
// 1. 业务规则验证 - 调用领域服务
|
||||
if err := s.certService.ValidateLicenseUpload(ctx, cmd.UserID, cmd.FileName, cmd.FileSize); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. 上传文件到存储服务
|
||||
uploadResult, err := s.storageService.UploadFile(ctx, cmd.FileBytes, cmd.FileName)
|
||||
if err != nil {
|
||||
s.logger.Error("上传营业执照失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("上传营业执照失败: %w", err)
|
||||
}
|
||||
|
||||
// 3. 创建营业执照上传记录 - 调用领域服务
|
||||
licenseRecord, err := s.certService.CreateLicenseUploadRecord(ctx, cmd.UserID, cmd.FileName, cmd.FileSize, uploadResult)
|
||||
if err != nil {
|
||||
s.logger.Error("创建营业执照记录失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建营业执照记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 4. 异步处理OCR识别 - 使用任务队列或后台任务
|
||||
go s.processOCRAsync(ctx, licenseRecord.ID, cmd.FileBytes)
|
||||
|
||||
s.logger.Info("营业执照上传成功",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("license_id", licenseRecord.ID),
|
||||
zap.String("file_url", uploadResult.URL),
|
||||
)
|
||||
|
||||
// 5. 构建响应
|
||||
response := &responses.UploadLicenseResponse{
|
||||
UploadRecordID: licenseRecord.ID,
|
||||
FileURL: uploadResult.URL,
|
||||
OCRProcessed: false,
|
||||
OCRSuccess: false,
|
||||
}
|
||||
|
||||
// 6. 如果OCR处理很快完成,尝试获取结果
|
||||
// 这里可以添加一个简单的轮询机制,或者使用WebSocket推送结果
|
||||
// 暂时返回基础信息,前端可以通过查询接口获取OCR结果
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// UploadBusinessLicense 上传营业执照并同步OCR识别
|
||||
func (s *CertificationApplicationServiceImpl) UploadBusinessLicense(ctx context.Context, userID string, fileBytes []byte, fileName string) (*responses.UploadLicenseResponse, error) {
|
||||
s.logger.Info("开始处理营业执照上传",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("file_name", fileName),
|
||||
)
|
||||
|
||||
// 调用领域服务进行上传和OCR识别
|
||||
uploadRecord, ocrResult, err := s.certService.UploadBusinessLicense(ctx, userID, fileBytes, fileName)
|
||||
if err != nil {
|
||||
s.logger.Error("营业执照上传失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
response := &responses.UploadLicenseResponse{
|
||||
UploadRecordID: uploadRecord.ID,
|
||||
FileURL: uploadRecord.FileURL,
|
||||
OCRProcessed: uploadRecord.OCRProcessed,
|
||||
OCRSuccess: uploadRecord.OCRSuccess,
|
||||
OCRConfidence: uploadRecord.OCRConfidence,
|
||||
OCRErrorMessage: uploadRecord.OCRErrorMessage,
|
||||
}
|
||||
|
||||
// 如果OCR成功,添加识别结果
|
||||
if ocrResult != nil && uploadRecord.OCRSuccess {
|
||||
response.EnterpriseName = ocrResult.CompanyName
|
||||
response.CreditCode = ocrResult.UnifiedSocialCode
|
||||
response.LegalPerson = ocrResult.LegalPersonName
|
||||
}
|
||||
|
||||
s.logger.Info("营业执照上传完成",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("upload_record_id", uploadRecord.ID),
|
||||
zap.Bool("ocr_success", uploadRecord.OCRSuccess),
|
||||
)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// SubmitEnterpriseInfo 提交企业信息
|
||||
func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(ctx context.Context, cmd *commands.SubmitEnterpriseInfoCommand) (*responses.EnterpriseInfoResponse, error) {
|
||||
// 根据用户ID获取认证申请
|
||||
certification, err := s.certRepo.GetByUserID(ctx, cmd.UserID)
|
||||
// GetEnterpriseAuthURL 获取企业认证链接
|
||||
// 业务流程:1. 获取认证申请 2. 获取企业信息提交记录 3. 生成e签宝认证文件 4. 返回认证链接
|
||||
func (s *CertificationApplicationServiceImpl) GetEnterpriseAuthURL(ctx context.Context, userID string) (*responses.EnterpriseAuthURLResponse, error) {
|
||||
// 1. 获取认证申请
|
||||
certification, err := s.certManagementService.GetCertificationByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户尚未创建认证申请: %w", err)
|
||||
}
|
||||
|
||||
// 设置认证ID
|
||||
cmd.CertificationID = certification.ID
|
||||
// 2. 检查认证状态
|
||||
if certification.Status != enums.StatusInfoSubmitted {
|
||||
return nil, fmt.Errorf("当前状态不允许进行企业认证")
|
||||
}
|
||||
|
||||
// 调用领域服务提交企业信息
|
||||
if err := s.certService.SubmitEnterpriseInfo(ctx, certification.ID); err != nil {
|
||||
// 3. 获取企业信息提交记录
|
||||
enterpriseRecord, err := s.enterpriseRecordService.GetLatestByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
// 4. 生成e签宝认证文件
|
||||
authReq := &esign_service.EnterpriseAuthRequest{
|
||||
CompanyName: enterpriseRecord.CompanyName,
|
||||
UnifiedSocialCode: enterpriseRecord.UnifiedSocialCode,
|
||||
LegalPersonName: enterpriseRecord.LegalPersonName,
|
||||
LegalPersonID: enterpriseRecord.LegalPersonID,
|
||||
TransactorName: enterpriseRecord.LegalPersonName,
|
||||
TransactorMobile: enterpriseRecord.LegalPersonPhone,
|
||||
TransactorID: enterpriseRecord.LegalPersonID,
|
||||
}
|
||||
|
||||
authResp, err := s.esignService.GenerateEnterpriseAuth(authReq)
|
||||
if err != nil {
|
||||
s.logger.Error("生成企业认证文件失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("certification_id", certification.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("生成企业认证文件失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("获取企业认证链接成功",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("certification_id", certification.ID),
|
||||
zap.String("esign_flow_id", authResp.AuthFlowID),
|
||||
)
|
||||
|
||||
return &responses.EnterpriseAuthURLResponse{
|
||||
AuthURL: authResp.AuthURL,
|
||||
ShortURL: authResp.AuthShortURL,
|
||||
ExpireAt: time.Now().AddDate(0, 0, 7).Format(time.RFC3339),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ApplyContract 申请合同文件
|
||||
// 业务流程:1. 获取认证申请 2. 获取企业信息 3. 生成e签宝合同文件
|
||||
func (s *CertificationApplicationServiceImpl) ApplyContract(ctx context.Context, userID string) (*responses.CertificationResponse, error) {
|
||||
// 1. 获取认证申请
|
||||
certification, err := s.certManagementService.GetCertificationByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户尚未创建认证申请: %w", err)
|
||||
}
|
||||
|
||||
// 2. 获取企业信息
|
||||
enterpriseInfo, err := s.enterpriseService.GetEnterpriseInfo(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
// 4. 生成e签宝合同
|
||||
components := map[string]string{
|
||||
"JFQY": "海南学宇思网络科技有限公司",
|
||||
"JFFR": "刘福思",
|
||||
"YFQY": enterpriseInfo.CompanyName,
|
||||
"YFFR": enterpriseInfo.LegalPersonName,
|
||||
"QDRQ": time.Now().Format("2006-01-02"),
|
||||
}
|
||||
_, err = s.certificationEsignService.FillTemplate(ctx, certification, components)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("生成e签宝合同失败: %w", err)
|
||||
}
|
||||
// 6. 重新获取更新后的认证申请数据
|
||||
updatedCertification, err := s.certManagementService.GetCertificationByID(ctx, certification.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.buildCertificationResponse(updatedCertification), nil
|
||||
}
|
||||
|
||||
// GetContractSignURL 获取合同签署链接
|
||||
// 业务流程:1. 获取认证申请 2. 获取企业信息 3. 获取签署链接
|
||||
func (s *CertificationApplicationServiceImpl) GetContractSignURL(ctx context.Context, cmd *commands.GetContractSignURLCommand) (*responses.ContractSignURLResponse, error) {
|
||||
// 1. 获取认证申请
|
||||
certification, err := s.certManagementService.GetCertificationByUserID(ctx, cmd.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户尚未创建认证申请: %w", err)
|
||||
}
|
||||
|
||||
// 2. 检查认证状态
|
||||
if certification.Status != enums.StatusContractApplied {
|
||||
return nil, fmt.Errorf("当前状态不允许获取签署链接")
|
||||
}
|
||||
|
||||
// 3. 获取企业信息
|
||||
enterpriseInfo, err := s.enterpriseService.GetEnterpriseInfo(ctx, cmd.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
if certification.ContractFileID == "" {
|
||||
return nil, fmt.Errorf("请先申请合同文件")
|
||||
}
|
||||
|
||||
// 5. 发起签署
|
||||
signRecord, err := s.certificationEsignService.InitiateSign(ctx, certification, enterpriseInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取签署链接失败: %w", err)
|
||||
}
|
||||
// 转换状态
|
||||
err = s.certWorkflowService.ApplyContract(ctx, certification.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("转换状态失败: %w", err)
|
||||
}
|
||||
// 6. 计算过期时间(7天后)
|
||||
expireAt := time.Now().AddDate(0, 0, 7).Format(time.RFC3339)
|
||||
s.logger.Info("获取签署链接成功",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("certification_id", certification.ID),
|
||||
zap.String("sign_flow_id", signRecord.EsignFlowID),
|
||||
)
|
||||
|
||||
return &responses.ContractSignURLResponse{
|
||||
SignURL: signRecord.SignURL,
|
||||
ShortURL: signRecord.SignShortURL,
|
||||
SignFlowID: signRecord.EsignFlowID,
|
||||
ExpireAt: expireAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CompleteContractSign 完成合同签署
|
||||
// 业务流程:1. 获取认证申请 2. 完成合同签署 3. 自动完成认证
|
||||
func (s *CertificationApplicationServiceImpl) CompleteContractSign(ctx context.Context, cmd *commands.CompleteContractSignCommand) error {
|
||||
// 1. 获取认证申请
|
||||
certification, err := s.certManagementService.GetCertificationByUserID(ctx, cmd.UserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户尚未创建认证申请: %w", err)
|
||||
}
|
||||
if certification.Status != enums.StatusContractApplied {
|
||||
return fmt.Errorf("当前状态不允许完成合同签署")
|
||||
}
|
||||
// 2. 完成合同签署(状态转换)
|
||||
if err := s.certWorkflowService.CompleteContractSign(ctx, certification.ID, cmd.ContractURL); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 重新获取认证申请
|
||||
updatedCertification, err := s.certManagementService.GetCertificationByID(ctx, certification.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. 如果合同已签署,自动完成认证
|
||||
if updatedCertification.Status == enums.StatusContractSigned {
|
||||
if err := s.certWorkflowService.CompleteCertification(ctx, certification.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("合同签署完成",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("certification_id", certification.ID),
|
||||
zap.String("contract_url", cmd.ContractURL),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCertificationStatus 获取认证状态
|
||||
// 业务流程:1. 获取认证申请 2. 构建响应数据
|
||||
func (s *CertificationApplicationServiceImpl) GetCertificationStatus(ctx context.Context, query *queries.GetCertificationStatusQuery) (*responses.CertificationResponse, error) {
|
||||
// 1. 获取认证申请
|
||||
certification, err := s.certManagementService.GetCertificationByUserID(ctx, query.UserID)
|
||||
if err != nil {
|
||||
// 如果用户没有认证申请,返回一个表示未开始的状态
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return &responses.CertificationResponse{
|
||||
ID: "",
|
||||
UserID: query.UserID,
|
||||
Status: "not_started",
|
||||
StatusName: "未开始认证",
|
||||
Progress: 0,
|
||||
IsUserActionRequired: true,
|
||||
InfoSubmittedAt: nil,
|
||||
EnterpriseVerifiedAt: nil,
|
||||
ContractAppliedAt: nil,
|
||||
ContractSignedAt: nil,
|
||||
CompletedAt: nil,
|
||||
ContractURL: "",
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建企业信息
|
||||
enterpriseInfo := &user_entities.EnterpriseInfo{
|
||||
UserID: cmd.UserID,
|
||||
CompanyName: cmd.CompanyName,
|
||||
UnifiedSocialCode: cmd.UnifiedSocialCode,
|
||||
LegalPersonName: cmd.LegalPersonName,
|
||||
LegalPersonID: cmd.LegalPersonID,
|
||||
}
|
||||
// 2. 构建响应
|
||||
return s.buildCertificationResponse(certification), nil
|
||||
}
|
||||
|
||||
*enterpriseInfo, err = s.enterpriseInfoRepo.Create(ctx, *enterpriseInfo)
|
||||
// GetCertificationDetails 获取认证详情
|
||||
// 业务流程:1. 获取认证申请 2. 获取企业信息 3. 构建响应数据
|
||||
func (s *CertificationApplicationServiceImpl) GetCertificationDetails(ctx context.Context, query *queries.GetCertificationDetailsQuery) (*responses.CertificationResponse, error) {
|
||||
// 1. 获取认证申请
|
||||
certification, err := s.certManagementService.GetCertificationByUserID(ctx, query.UserID)
|
||||
if err != nil {
|
||||
s.logger.Error("创建企业信息失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建企业信息失败: %w", err)
|
||||
// 如果用户没有认证申请,返回错误
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fmt.Errorf("用户尚未创建认证申请")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("企业信息提交成功",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("certification_id", certification.ID),
|
||||
zap.String("enterprise_id", enterpriseInfo.ID),
|
||||
)
|
||||
// 2. 构建响应
|
||||
response := s.buildCertificationResponse(certification)
|
||||
|
||||
// 3. 添加企业信息
|
||||
if certification.UserID != "" {
|
||||
enterpriseInfo, err := s.enterpriseService.GetEnterpriseInfo(ctx, certification.UserID)
|
||||
if err == nil && enterpriseInfo != nil {
|
||||
response.Enterprise = s.buildEnterpriseInfoResponse(enterpriseInfo)
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetCertificationProgress 获取认证进度
|
||||
// 业务流程:1. 获取认证申请 2. 获取进度信息
|
||||
func (s *CertificationApplicationServiceImpl) GetCertificationProgress(ctx context.Context, userID string) (map[string]interface{}, error) {
|
||||
// 1. 获取认证申请
|
||||
certification, err := s.certManagementService.GetCertificationByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
// 如果用户没有认证申请,返回未开始状态
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return map[string]interface{}{
|
||||
"certification_id": "",
|
||||
"user_id": userID,
|
||||
"current_status": "not_started",
|
||||
"status_name": "未开始认证",
|
||||
"progress_percentage": 0,
|
||||
"is_user_action_required": true,
|
||||
"next_valid_statuses": []string{"pending"},
|
||||
"message": "用户尚未开始认证流程",
|
||||
"created_at": nil,
|
||||
"updated_at": nil,
|
||||
}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. 获取认证进度
|
||||
return s.certManagementService.GetCertificationProgress(ctx, certification.ID)
|
||||
}
|
||||
|
||||
// buildCertificationResponse 构建认证响应
|
||||
func (s *CertificationApplicationServiceImpl) buildCertificationResponse(certification *entities.Certification) *responses.CertificationResponse {
|
||||
return &responses.CertificationResponse{
|
||||
ID: certification.ID,
|
||||
UserID: certification.UserID,
|
||||
Status: certification.Status,
|
||||
StatusName: certification.GetStatusName(),
|
||||
Progress: certification.GetProgressPercentage(),
|
||||
IsUserActionRequired: certification.IsUserActionRequired(),
|
||||
InfoSubmittedAt: certification.InfoSubmittedAt,
|
||||
EnterpriseVerifiedAt: certification.EnterpriseVerifiedAt,
|
||||
ContractAppliedAt: certification.ContractAppliedAt,
|
||||
ContractSignedAt: certification.ContractSignedAt,
|
||||
CompletedAt: certification.CompletedAt,
|
||||
ContractURL: certification.ContractURL,
|
||||
CreatedAt: certification.CreatedAt,
|
||||
UpdatedAt: certification.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// buildEnterpriseInfoResponse 构建企业信息响应
|
||||
func (s *CertificationApplicationServiceImpl) buildEnterpriseInfoResponse(enterpriseInfo *user_entities.EnterpriseInfo) *responses.EnterpriseInfoResponse {
|
||||
return &responses.EnterpriseInfoResponse{
|
||||
ID: enterpriseInfo.ID,
|
||||
CompanyName: enterpriseInfo.CompanyName,
|
||||
UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode,
|
||||
LegalPersonName: enterpriseInfo.LegalPersonName,
|
||||
LegalPersonID: enterpriseInfo.LegalPersonID,
|
||||
IsOCRVerified: enterpriseInfo.IsOCRVerified,
|
||||
IsFaceVerified: enterpriseInfo.IsFaceVerified,
|
||||
CreatedAt: enterpriseInfo.CreatedAt,
|
||||
UpdatedAt: enterpriseInfo.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// InitiateFaceVerify 发起人脸识别验证
|
||||
func (s *CertificationApplicationServiceImpl) InitiateFaceVerify(ctx context.Context, cmd *commands.InitiateFaceVerifyCommand) (*responses.FaceVerifyResponse, error) {
|
||||
// 根据用户ID获取认证申请 - 这里需要从Handler传入用户ID
|
||||
// 由于cmd中没有UserID字段,我们需要修改Handler的调用方式
|
||||
// 暂时使用certificationID来获取认证申请
|
||||
certification, err := s.certRepo.GetByID(ctx, cmd.CertificationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 调用领域服务发起人脸识别
|
||||
faceVerifyRecord, err := s.certService.InitiateFaceVerify(ctx, certification.ID, cmd.RealName, cmd.IDCardNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 构建验证URL(这里应该根据实际的人脸识别服务生成)
|
||||
verifyURL := fmt.Sprintf("/api/certification/face-verify/%s?return_url=%s", faceVerifyRecord.ID, cmd.ReturnURL)
|
||||
|
||||
s.logger.Info("人脸识别验证发起成功",
|
||||
zap.String("certification_id", certification.ID),
|
||||
zap.String("face_verify_id", faceVerifyRecord.ID),
|
||||
)
|
||||
|
||||
return &responses.FaceVerifyResponse{
|
||||
CertifyID: faceVerifyRecord.ID,
|
||||
VerifyURL: verifyURL,
|
||||
ExpiresAt: faceVerifyRecord.ExpiresAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ApplyContract 申请合同
|
||||
func (s *CertificationApplicationServiceImpl) ApplyContract(ctx context.Context, userID string) (*responses.CertificationResponse, error) {
|
||||
// 根据用户ID获取认证申请
|
||||
certification, err := s.certRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户尚未创建认证申请: %w", err)
|
||||
}
|
||||
|
||||
// 调用领域服务申请合同
|
||||
if err := s.certService.ApplyContract(ctx, certification.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 重新获取更新后的认证申请
|
||||
updatedCertification, err := s.certRepo.GetByID(ctx, certification.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("合同申请成功",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("certification_id", certification.ID),
|
||||
)
|
||||
|
||||
return s.buildCertificationResponse(&updatedCertification), nil
|
||||
}
|
||||
|
||||
// GetCertificationStatus 获取认证状态
|
||||
func (s *CertificationApplicationServiceImpl) GetCertificationStatus(ctx context.Context, query *queries.GetCertificationStatusQuery) (*responses.CertificationResponse, error) {
|
||||
// 根据用户ID获取认证申请
|
||||
certification, err := s.certRepo.GetByUserID(ctx, query.UserID)
|
||||
if err != nil {
|
||||
// 如果用户没有认证申请,返回一个表示未开始的状态
|
||||
if err.Error() == "认证申请不存在" || err.Error() == "record not found" {
|
||||
return &responses.CertificationResponse{
|
||||
ID: "",
|
||||
UserID: query.UserID,
|
||||
Status: "not_started",
|
||||
StatusName: "未开始认证",
|
||||
Progress: 0,
|
||||
IsUserActionRequired: true,
|
||||
IsAdminActionRequired: false,
|
||||
InfoSubmittedAt: nil,
|
||||
FaceVerifiedAt: nil,
|
||||
ContractAppliedAt: nil,
|
||||
ContractApprovedAt: nil,
|
||||
ContractSignedAt: nil,
|
||||
CompletedAt: nil,
|
||||
ContractURL: "",
|
||||
SigningURL: "",
|
||||
RejectReason: "",
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
}, nil
|
||||
// 企业认证成功后操作
|
||||
func (s *CertificationApplicationServiceImpl) 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)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
response := s.buildCertificationResponse(certification)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetCertificationDetails 获取认证详情
|
||||
func (s *CertificationApplicationServiceImpl) GetCertificationDetails(ctx context.Context, query *queries.GetCertificationDetailsQuery) (*responses.CertificationResponse, error) {
|
||||
// 根据用户ID获取认证申请
|
||||
certification, err := s.certRepo.GetByUserID(ctx, query.UserID)
|
||||
if err != nil {
|
||||
// 如果用户没有认证申请,返回错误
|
||||
if err.Error() == "认证申请不存在" || err.Error() == "record not found" {
|
||||
return nil, fmt.Errorf("用户尚未创建认证申请")
|
||||
// 2. 转换状态
|
||||
if err := s.certWorkflowService.CompleteEnterpriseVerification(txCtx, certification.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取认证申请详细信息
|
||||
certificationWithDetails, err := s.certService.GetCertificationWithDetails(ctx, certification.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
response := s.buildCertificationResponse(certificationWithDetails)
|
||||
|
||||
// 添加企业信息
|
||||
if certification.UserID != "" {
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, certification.UserID)
|
||||
if err == nil && enterpriseInfo != nil {
|
||||
response.Enterprise = &responses.EnterpriseInfoResponse{
|
||||
ID: enterpriseInfo.ID,
|
||||
CompanyName: enterpriseInfo.CompanyName,
|
||||
UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode,
|
||||
LegalPersonName: enterpriseInfo.LegalPersonName,
|
||||
LegalPersonID: enterpriseInfo.LegalPersonID,
|
||||
IsOCRVerified: enterpriseInfo.IsOCRVerified,
|
||||
IsFaceVerified: enterpriseInfo.IsFaceVerified,
|
||||
CreatedAt: enterpriseInfo.CreatedAt,
|
||||
UpdatedAt: enterpriseInfo.UpdatedAt,
|
||||
}
|
||||
// 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 response, nil
|
||||
}
|
||||
|
||||
// CompleteFaceVerify 完成人脸识别验证
|
||||
func (s *CertificationApplicationServiceImpl) CompleteFaceVerify(ctx context.Context, faceVerifyID string, isSuccess bool) error {
|
||||
return s.certService.CompleteFaceVerify(ctx, faceVerifyID, isSuccess)
|
||||
}
|
||||
|
||||
// ApproveContract 管理员审核合同
|
||||
func (s *CertificationApplicationServiceImpl) ApproveContract(ctx context.Context, certificationID, adminID, signingURL, approvalNotes string) error {
|
||||
return s.certService.ApproveContract(ctx, certificationID, adminID, signingURL, approvalNotes)
|
||||
}
|
||||
|
||||
// RejectContract 管理员拒绝合同
|
||||
func (s *CertificationApplicationServiceImpl) RejectContract(ctx context.Context, certificationID, adminID, rejectReason string) error {
|
||||
return s.certService.RejectContract(ctx, certificationID, adminID, rejectReason)
|
||||
}
|
||||
|
||||
// CompleteContractSign 完成合同签署
|
||||
func (s *CertificationApplicationServiceImpl) CompleteContractSign(ctx context.Context, certificationID, contractURL string) error {
|
||||
return s.certService.CompleteContractSign(ctx, certificationID, contractURL)
|
||||
}
|
||||
|
||||
// CompleteCertification 完成认证
|
||||
func (s *CertificationApplicationServiceImpl) CompleteCertification(ctx context.Context, certificationID string) error {
|
||||
return s.certService.CompleteCertification(ctx, certificationID)
|
||||
}
|
||||
|
||||
// RetryStep 重试认证步骤
|
||||
func (s *CertificationApplicationServiceImpl) RetryStep(ctx context.Context, cmd *commands.RetryStepCommand) error {
|
||||
switch cmd.Step {
|
||||
case "face_verify":
|
||||
return s.certService.RetryFaceVerify(ctx, cmd.CertificationID)
|
||||
case "restart":
|
||||
return s.certService.RestartCertification(ctx, cmd.CertificationID)
|
||||
default:
|
||||
return fmt.Errorf("不支持的重试步骤: %s", cmd.Step)
|
||||
}
|
||||
}
|
||||
|
||||
// GetCertificationProgress 获取认证进度
|
||||
func (s *CertificationApplicationServiceImpl) GetCertificationProgress(ctx context.Context, userID string) (map[string]interface{}, error) {
|
||||
// 根据用户ID获取认证申请
|
||||
certification, err := s.certRepo.GetByUserID(ctx, userID)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
// 如果用户没有认证申请,返回未开始状态
|
||||
if err.Error() == "认证申请不存在" || err.Error() == "record not found" {
|
||||
return map[string]interface{}{
|
||||
"certification_id": "",
|
||||
"user_id": userID,
|
||||
"current_status": "not_started",
|
||||
"status_name": "未开始认证",
|
||||
"progress_percentage": 0,
|
||||
"is_user_action_required": true,
|
||||
"is_admin_action_required": false,
|
||||
"next_valid_statuses": []string{"pending"},
|
||||
"message": "用户尚未开始认证流程",
|
||||
"created_at": nil,
|
||||
"updated_at": nil,
|
||||
}, nil
|
||||
}
|
||||
return nil, err
|
||||
return fmt.Errorf("完成企业认证失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取认证进度
|
||||
return s.certService.GetCertificationProgress(ctx, certification.ID)
|
||||
}
|
||||
|
||||
// RetryFaceVerify 重试人脸识别
|
||||
func (s *CertificationApplicationServiceImpl) RetryFaceVerify(ctx context.Context, userID string) (*responses.FaceVerifyResponse, error) {
|
||||
// 根据用户ID获取认证申请
|
||||
certification, err := s.certRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户尚未创建认证申请: %w", err)
|
||||
}
|
||||
|
||||
// 调用领域服务重试人脸识别
|
||||
if err := s.certService.RetryFaceVerify(ctx, certification.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 重新发起人脸识别
|
||||
faceVerifyRecord, err := s.certService.InitiateFaceVerify(ctx, certification.ID, "", "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 构建验证URL
|
||||
verifyURL := fmt.Sprintf("/api/certification/face-verify/%s", faceVerifyRecord.ID)
|
||||
|
||||
return &responses.FaceVerifyResponse{
|
||||
CertifyID: faceVerifyRecord.ID,
|
||||
VerifyURL: verifyURL,
|
||||
ExpiresAt: faceVerifyRecord.ExpiresAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// RetryContractSign 重试合同签署
|
||||
func (s *CertificationApplicationServiceImpl) RetryContractSign(ctx context.Context, userID string) (*responses.CertificationResponse, error) {
|
||||
// 根据用户ID获取认证申请
|
||||
certification, err := s.certRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户尚未创建认证申请: %w", err)
|
||||
}
|
||||
|
||||
// 重新获取更新后的认证申请
|
||||
updatedCertification, err := s.certRepo.GetByID(ctx, certification.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.logger.Info("合同签署重试准备完成",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("certification_id", certification.ID),
|
||||
)
|
||||
|
||||
return s.buildCertificationResponse(&updatedCertification), nil
|
||||
}
|
||||
|
||||
// RestartCertification 重新开始认证
|
||||
func (s *CertificationApplicationServiceImpl) RestartCertification(ctx context.Context, certificationID string) error {
|
||||
return s.certService.RestartCertification(ctx, certificationID)
|
||||
}
|
||||
|
||||
// buildCertificationResponse 构建认证响应
|
||||
func (s *CertificationApplicationServiceImpl) buildCertificationResponse(certification *entities.Certification) *responses.CertificationResponse {
|
||||
return &responses.CertificationResponse{
|
||||
ID: certification.ID,
|
||||
UserID: certification.UserID,
|
||||
Status: certification.Status,
|
||||
StatusName: string(certification.Status),
|
||||
Progress: certification.GetProgressPercentage(),
|
||||
IsUserActionRequired: certification.IsUserActionRequired(),
|
||||
IsAdminActionRequired: certification.IsAdminActionRequired(),
|
||||
InfoSubmittedAt: certification.InfoSubmittedAt,
|
||||
FaceVerifiedAt: certification.FaceVerifiedAt,
|
||||
ContractAppliedAt: certification.ContractAppliedAt,
|
||||
ContractApprovedAt: certification.ContractApprovedAt,
|
||||
ContractSignedAt: certification.ContractSignedAt,
|
||||
CompletedAt: certification.CompletedAt,
|
||||
ContractURL: certification.ContractURL,
|
||||
SigningURL: certification.SigningURL,
|
||||
RejectReason: certification.RejectReason,
|
||||
CreatedAt: certification.CreatedAt,
|
||||
UpdatedAt: certification.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// processOCRAsync 异步处理OCR识别
|
||||
func (s *CertificationApplicationServiceImpl) processOCRAsync(ctx context.Context, licenseID string, fileBytes []byte) {
|
||||
// 调用领域服务处理OCR识别
|
||||
if err := s.certService.ProcessOCRAsync(ctx, licenseID, fileBytes); err != nil {
|
||||
s.logger.Error("OCR处理失败",
|
||||
zap.String("license_id", licenseID),
|
||||
zap.Error(err),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// GetLicenseOCRResult 获取营业执照OCR识别结果
|
||||
func (s *CertificationApplicationServiceImpl) GetLicenseOCRResult(ctx context.Context, recordID string) (*responses.UploadLicenseResponse, error) {
|
||||
// 获取营业执照上传记录
|
||||
licenseRecord, err := s.licenseRepo.GetByID(ctx, recordID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取营业执照记录失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取营业执照记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
response := &responses.UploadLicenseResponse{
|
||||
UploadRecordID: licenseRecord.ID,
|
||||
FileURL: licenseRecord.FileURL,
|
||||
OCRProcessed: licenseRecord.OCRProcessed,
|
||||
OCRSuccess: licenseRecord.OCRSuccess,
|
||||
OCRConfidence: licenseRecord.OCRConfidence,
|
||||
OCRErrorMessage: licenseRecord.OCRErrorMessage,
|
||||
}
|
||||
|
||||
// 如果OCR成功,解析OCR结果
|
||||
if licenseRecord.OCRSuccess && licenseRecord.OCRRawData != "" {
|
||||
// 这里可以解析OCR原始数据,提取企业信息
|
||||
// 简化处理,直接返回原始数据中的关键信息
|
||||
// 实际项目中可以使用JSON解析
|
||||
response.EnterpriseName = "已识别" // 从OCR数据中提取
|
||||
response.CreditCode = "已识别" // 从OCR数据中提取
|
||||
response.LegalPerson = "已识别" // 从OCR数据中提取
|
||||
}
|
||||
|
||||
return response, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,62 +1,21 @@
|
||||
package commands
|
||||
|
||||
// CreateCertificationCommand 创建认证申请命令
|
||||
// 用于用户发起企业认证流程的初始请求
|
||||
type CreateCertificationCommand struct {
|
||||
UserID string `json:"user_id" binding:"required" comment:"用户唯一标识,从JWT token获取"`
|
||||
}
|
||||
|
||||
// UploadLicenseCommand 上传营业执照命令
|
||||
// 用于处理营业执照文件上传的业务逻辑
|
||||
type UploadLicenseCommand struct {
|
||||
UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"`
|
||||
FileBytes []byte `json:"-" comment:"营业执照文件的二进制内容,从multipart/form-data获取"`
|
||||
FileName string `json:"-" comment:"营业执照文件的原始文件名,从multipart/form-data获取"`
|
||||
FileSize int64 `json:"-" comment:"营业执照文件的大小(字节),从multipart/form-data获取"`
|
||||
}
|
||||
|
||||
// SubmitEnterpriseInfoCommand 提交企业信息命令
|
||||
// 用于用户提交企业四要素信息,完成企业信息验证
|
||||
// 如果用户没有认证申请,系统会自动创建
|
||||
type SubmitEnterpriseInfoCommand struct {
|
||||
UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"`
|
||||
CertificationID string `json:"-" comment:"认证申请唯一标识,从URL路径获取,不在JSON中暴露"`
|
||||
CompanyName string `json:"company_name" binding:"required" comment:"企业名称,如:北京科技有限公司"`
|
||||
UnifiedSocialCode string `json:"unified_social_code" binding:"required" comment:"统一社会信用代码,18位企业唯一标识,如:91110000123456789X"`
|
||||
LegalPersonName string `json:"legal_person_name" binding:"required" comment:"法定代表人姓名,如:张三"`
|
||||
LegalPersonID string `json:"legal_person_id" binding:"required" comment:"法定代表人身份证号码,18位,如:110101199001011234"`
|
||||
LicenseUploadRecordID string `json:"license_upload_record_id" binding:"required" comment:"营业执照上传记录唯一标识,关联已上传的营业执照文件"`
|
||||
}
|
||||
|
||||
// InitiateFaceVerifyCommand 初始化人脸识别命令
|
||||
// 用于发起人脸识别验证流程,验证法定代表人身份
|
||||
type InitiateFaceVerifyCommand struct {
|
||||
CertificationID string `json:"-" comment:"认证申请唯一标识,从URL路径获取,不在JSON中暴露"`
|
||||
RealName string `json:"real_name" binding:"required" comment:"真实姓名,必须与营业执照上的法定代表人姓名一致"`
|
||||
IDCardNumber string `json:"id_card_number" binding:"required" comment:"身份证号码,18位,用于人脸识别身份验证"`
|
||||
ReturnURL string `json:"return_url" binding:"required" comment:"人脸识别完成后的回调地址,用于跳转回应用"`
|
||||
}
|
||||
|
||||
// ApplyContractCommand 申请合同命令
|
||||
// 用于用户申请电子合同,进入合同签署流程
|
||||
type ApplyContractCommand struct {
|
||||
CertificationID string `json:"-" comment:"认证申请唯一标识,从URL路径获取,不在JSON中暴露"`
|
||||
}
|
||||
|
||||
// RetryStepCommand 重试认证步骤命令
|
||||
// 用于用户重试失败的认证步骤,如人脸识别失败后的重试
|
||||
type RetryStepCommand struct {
|
||||
UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"`
|
||||
CertificationID string `json:"-" comment:"认证申请唯一标识,从URL路径获取,不在JSON中暴露"`
|
||||
Step string `json:"step" binding:"required" comment:"重试的步骤名称,如:face_verify(人脸识别)、contract_sign(合同签署)"`
|
||||
}
|
||||
|
||||
// CreateEnterpriseInfoCommand 创建企业信息命令
|
||||
// 用于创建企业基本信息,通常在企业认证流程中使用
|
||||
// @Description 创建企业信息请求参数
|
||||
type CreateEnterpriseInfoCommand struct {
|
||||
UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"`
|
||||
CompanyName string `json:"company_name" binding:"required" example:"示例企业有限公司" comment:"企业名称,如:示例企业有限公司"`
|
||||
UnifiedSocialCode string `json:"unified_social_code" binding:"required" example:"91110000123456789X" comment:"统一社会信用代码,18位企业唯一标识,如:91110000123456789X"`
|
||||
LegalPersonName string `json:"legal_person_name" binding:"required" example:"张三" comment:"法定代表人姓名,如:张三"`
|
||||
LegalPersonID string `json:"legal_person_id" binding:"required" example:"110101199001011234" comment:"法定代表人身份证号码,18位,如:110101199001011234"`
|
||||
CompanyName string `json:"company_name" binding:"required,min=2,max=100" comment:"企业名称,如:北京科技有限公司"`
|
||||
UnifiedSocialCode string `json:"unified_social_code" binding:"required,social_credit_code" comment:"统一社会信用代码,18位企业唯一标识,如:91110000123456789X"`
|
||||
LegalPersonName string `json:"legal_person_name" binding:"required,min=2,max=20" comment:"法定代表人姓名,如:张三"`
|
||||
LegalPersonID string `json:"legal_person_id" binding:"required,id_card" comment:"法定代表人身份证号码,18位,如:110101199001011234"`
|
||||
LegalPersonPhone string `json:"legal_person_phone" binding:"required,phone" comment:"法定代表人手机号,11位,如:13800138000"`
|
||||
VerificationCode string `json:"verification_code" binding:"required,len=6" comment:"验证码"`
|
||||
}
|
||||
|
||||
// CompleteContractSignCommand 完成合同签署命令
|
||||
// 用于用户完成合同签署,提交合同URL
|
||||
type CompleteContractSignCommand struct {
|
||||
UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"`
|
||||
ContractURL string `json:"contract_url" binding:"required,url,min=10,max=500" comment:"合同签署后的URL地址"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package commands
|
||||
|
||||
// GetContractSignURLCommand 获取合同签署链接命令
|
||||
type GetContractSignURLCommand struct {
|
||||
UserID string `json:"user_id" binding:"required,uuid" comment:"用户ID"`
|
||||
}
|
||||
@@ -3,11 +3,11 @@ package queries
|
||||
// GetCertificationStatusQuery 获取认证状态查询
|
||||
// 用于查询用户当前认证申请的进度状态
|
||||
type GetCertificationStatusQuery struct {
|
||||
UserID string `json:"user_id" binding:"required" comment:"用户唯一标识,用于查询该用户的认证申请状态"`
|
||||
UserID string `json:"user_id" binding:"required,uuid" comment:"用户唯一标识,用于查询该用户的认证申请状态"`
|
||||
}
|
||||
|
||||
// GetCertificationDetailsQuery 获取认证详情查询
|
||||
// 用于查询用户认证申请的详细信息,包括所有相关记录
|
||||
type GetCertificationDetailsQuery struct {
|
||||
UserID string `json:"user_id" binding:"required" comment:"用户唯一标识,用于查询该用户的认证申请详细信息"`
|
||||
UserID string `json:"user_id" binding:"required,uuid" comment:"用户唯一标识,用于查询该用户的认证申请详细信息"`
|
||||
}
|
||||
|
||||
@@ -8,59 +8,38 @@ import (
|
||||
|
||||
// CertificationResponse 认证响应
|
||||
type CertificationResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
StatusName string `json:"status_name"`
|
||||
Progress int `json:"progress"`
|
||||
IsUserActionRequired bool `json:"is_user_action_required"`
|
||||
IsAdminActionRequired bool `json:"is_admin_action_required"`
|
||||
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty"`
|
||||
FaceVerifiedAt *time.Time `json:"face_verified_at,omitempty"`
|
||||
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty"`
|
||||
ContractApprovedAt *time.Time `json:"contract_approved_at,omitempty"`
|
||||
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||
Enterprise *EnterpriseInfoResponse `json:"enterprise,omitempty"`
|
||||
ContractURL string `json:"contract_url,omitempty"`
|
||||
SigningURL string `json:"signing_url,omitempty"`
|
||||
RejectReason string `json:"reject_reason,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
StatusName string `json:"status_name"`
|
||||
Progress int `json:"progress"`
|
||||
IsUserActionRequired bool `json:"is_user_action_required"`
|
||||
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty"`
|
||||
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty"`
|
||||
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty"`
|
||||
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||
Enterprise *EnterpriseInfoResponse `json:"enterprise,omitempty"`
|
||||
ContractURL string `json:"contract_url,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// EnterpriseInfoResponse 企业信息响应
|
||||
type EnterpriseInfoResponse struct {
|
||||
ID string `json:"id"`
|
||||
CertificationID string `json:"certification_id"`
|
||||
CompanyName string `json:"company_name"`
|
||||
UnifiedSocialCode string `json:"unified_social_code"`
|
||||
LegalPersonName string `json:"legal_person_name"`
|
||||
LegalPersonID string `json:"legal_person_id"`
|
||||
LicenseUploadRecordID string `json:"license_upload_record_id"`
|
||||
IsOCRVerified bool `json:"is_ocr_verified"`
|
||||
IsFaceVerified bool `json:"is_face_verified"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
ID string `json:"id"`
|
||||
CompanyName string `json:"company_name"`
|
||||
UnifiedSocialCode string `json:"unified_social_code"`
|
||||
LegalPersonName string `json:"legal_person_name"`
|
||||
LegalPersonID string `json:"legal_person_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// UploadLicenseResponse 上传营业执照响应
|
||||
type UploadLicenseResponse struct {
|
||||
UploadRecordID string `json:"upload_record_id"`
|
||||
FileURL string `json:"file_url"`
|
||||
OCRProcessed bool `json:"ocr_processed"`
|
||||
OCRSuccess bool `json:"ocr_success"`
|
||||
// OCR识别结果(如果成功)
|
||||
EnterpriseName string `json:"enterprise_name,omitempty"`
|
||||
CreditCode string `json:"credit_code,omitempty"`
|
||||
LegalPerson string `json:"legal_person,omitempty"`
|
||||
OCRConfidence float64 `json:"ocr_confidence,omitempty"`
|
||||
OCRErrorMessage string `json:"ocr_error_message,omitempty"`
|
||||
}
|
||||
|
||||
// FaceVerifyResponse 人脸识别响应
|
||||
type FaceVerifyResponse struct {
|
||||
CertifyID string `json:"certify_id"`
|
||||
VerifyURL string `json:"verify_url"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
// EnterpriseAuthURLResponse 企业认证链接响应
|
||||
type EnterpriseAuthURLResponse struct {
|
||||
EsignFlowID string `json:"esign_flow_id"` // e签宝认证流程ID
|
||||
AuthURL string `json:"auth_url"` // 认证链接
|
||||
ShortURL string `json:"short_url"` // 短链接
|
||||
ExpireAt string `json:"expire_at"` // 过期时间
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package responses
|
||||
|
||||
// ContractSignURLResponse 合同签署链接响应
|
||||
type ContractSignURLResponse struct {
|
||||
SignURL string `json:"sign_url"` // 签署链接
|
||||
ShortURL string `json:"short_url"` // 短链接
|
||||
SignFlowID string `json:"sign_flow_id"` // 签署流程ID
|
||||
ExpireAt string `json:"expire_at"` // 过期时间
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
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
|
||||
}
|
||||
@@ -8,62 +8,62 @@ import (
|
||||
|
||||
// CreateWalletCommand 创建钱包命令
|
||||
type CreateWalletCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
}
|
||||
|
||||
// UpdateWalletCommand 更新钱包命令
|
||||
type UpdateWalletCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
Balance decimal.Decimal `json:"balance"`
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
Balance decimal.Decimal `json:"balance" binding:"omitempty"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
}
|
||||
|
||||
// RechargeWalletCommand 充值钱包命令
|
||||
type RechargeWalletCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required"`
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
|
||||
}
|
||||
|
||||
// RechargeCommand 充值命令
|
||||
type RechargeCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required"`
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
|
||||
}
|
||||
|
||||
// WithdrawWalletCommand 提现钱包命令
|
||||
type WithdrawWalletCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required"`
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
|
||||
}
|
||||
|
||||
// WithdrawCommand 提现命令
|
||||
type WithdrawCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required"`
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
|
||||
}
|
||||
|
||||
// CreateUserSecretsCommand 创建用户密钥命令
|
||||
type CreateUserSecretsCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
ExpiresAt *time.Time `json:"expires_at" binding:"omitempty"`
|
||||
}
|
||||
|
||||
// RegenerateAccessKeyCommand 重新生成访问密钥命令
|
||||
type RegenerateAccessKeyCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
ExpiresAt *time.Time `json:"expires_at" binding:"omitempty"`
|
||||
}
|
||||
|
||||
// DeactivateUserSecretsCommand 停用用户密钥命令
|
||||
type DeactivateUserSecretsCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
}
|
||||
|
||||
// WalletTransactionCommand 钱包交易命令
|
||||
type WalletTransactionCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
FromUserID string `json:"from_user_id" binding:"required"`
|
||||
ToUserID string `json:"to_user_id" binding:"required"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required"`
|
||||
Notes string `json:"notes"`
|
||||
UserID string `json:"user_id" binding:"required,uuid"`
|
||||
FromUserID string `json:"from_user_id" binding:"required,uuid"`
|
||||
ToUserID string `json:"to_user_id" binding:"required,uuid"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required,gt=0"`
|
||||
Notes string `json:"notes" binding:"omitempty,max=200"`
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
"tyapi-server/internal/application/product/dto/queries"
|
||||
@@ -9,7 +10,6 @@ import (
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -30,114 +30,92 @@ func NewCategoryApplicationService(
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCategory 创建分类
|
||||
func (s *CategoryApplicationServiceImpl) CreateCategory(ctx context.Context, cmd *commands.CreateCategoryCommand) error {
|
||||
// 1. 参数验证
|
||||
if err := s.validateCreateCategory(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 检查父分类是否存在
|
||||
if cmd.ParentID != nil && *cmd.ParentID != "" {
|
||||
_, err := s.categoryRepo.GetByID(ctx, *cmd.ParentID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("父分类不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 验证分类编号唯一性
|
||||
if err := s.validateCategoryCode(cmd.Code, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
// 3. 创建分类实体
|
||||
category := entities.ProductCategory{
|
||||
category := &entities.ProductCategory{
|
||||
Name: cmd.Name,
|
||||
Code: cmd.Code,
|
||||
Description: cmd.Description,
|
||||
ParentID: cmd.ParentID,
|
||||
Level: cmd.Level,
|
||||
Sort: cmd.Sort,
|
||||
IsEnabled: cmd.IsEnabled,
|
||||
IsVisible: cmd.IsVisible,
|
||||
}
|
||||
|
||||
|
||||
// 4. 保存到仓储
|
||||
_, err := s.categoryRepo.Create(ctx, category)
|
||||
createdCategory, err := s.categoryRepo.Create(ctx, *category)
|
||||
if err != nil {
|
||||
s.logger.Error("创建分类失败", zap.Error(err))
|
||||
s.logger.Error("创建分类失败", zap.Error(err), zap.String("code", cmd.Code))
|
||||
return fmt.Errorf("创建分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("创建分类成功", zap.String("name", cmd.Name))
|
||||
|
||||
s.logger.Info("创建分类成功", zap.String("id", createdCategory.ID), zap.String("code", cmd.Code))
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateCategory 更新分类
|
||||
func (s *CategoryApplicationServiceImpl) UpdateCategory(ctx context.Context, cmd *commands.UpdateCategoryCommand) error {
|
||||
// 1. 获取现有分类
|
||||
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
// 1. 参数验证
|
||||
if err := s.validateUpdateCategory(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 获取现有分类
|
||||
existingCategory, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 更新字段
|
||||
if cmd.Name != "" {
|
||||
category.Name = cmd.Name
|
||||
|
||||
// 3. 验证分类编号唯一性(排除当前分类)
|
||||
if err := s.validateCategoryCode(cmd.Code, cmd.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
if cmd.Code != "" {
|
||||
category.Code = cmd.Code
|
||||
}
|
||||
if cmd.Description != "" {
|
||||
category.Description = cmd.Description
|
||||
}
|
||||
if cmd.ParentID != nil {
|
||||
category.ParentID = cmd.ParentID
|
||||
}
|
||||
if cmd.Level > 0 {
|
||||
category.Level = cmd.Level
|
||||
}
|
||||
if cmd.Sort > 0 {
|
||||
category.Sort = cmd.Sort
|
||||
}
|
||||
if cmd.IsEnabled != nil {
|
||||
category.IsEnabled = *cmd.IsEnabled
|
||||
}
|
||||
if cmd.IsVisible != nil {
|
||||
category.IsVisible = *cmd.IsVisible
|
||||
}
|
||||
|
||||
// 3. 保存到仓储
|
||||
if err := s.categoryRepo.Update(ctx, category); err != nil {
|
||||
s.logger.Error("更新分类失败", zap.Error(err))
|
||||
|
||||
// 4. 更新分类信息
|
||||
existingCategory.Name = cmd.Name
|
||||
existingCategory.Code = cmd.Code
|
||||
existingCategory.Description = cmd.Description
|
||||
existingCategory.Sort = cmd.Sort
|
||||
existingCategory.IsEnabled = cmd.IsEnabled
|
||||
existingCategory.IsVisible = cmd.IsVisible
|
||||
|
||||
// 5. 保存到仓储
|
||||
if err := s.categoryRepo.Update(ctx, existingCategory); err != nil {
|
||||
s.logger.Error("更新分类失败", zap.Error(err), zap.String("id", cmd.ID))
|
||||
return fmt.Errorf("更新分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("更新分类成功", zap.String("id", cmd.ID))
|
||||
|
||||
s.logger.Info("更新分类成功", zap.String("id", cmd.ID), zap.String("code", cmd.Code))
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteCategory 删除分类
|
||||
func (s *CategoryApplicationServiceImpl) DeleteCategory(ctx context.Context, cmd *commands.DeleteCategoryCommand) error {
|
||||
// 1. 检查分类是否存在
|
||||
_, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
existingCategory, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 检查是否有子分类
|
||||
children, err := s.categoryRepo.FindByParentID(ctx, &cmd.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("检查子分类失败", zap.Error(err))
|
||||
return fmt.Errorf("检查子分类失败: %w", err)
|
||||
}
|
||||
if len(children) > 0 {
|
||||
return fmt.Errorf("分类下有子分类,无法删除")
|
||||
}
|
||||
|
||||
|
||||
// 2. 检查是否有产品(可选,根据业务需求决定)
|
||||
// 这里可以添加检查逻辑,如果有产品则不允许删除
|
||||
|
||||
// 3. 删除分类
|
||||
if err := s.categoryRepo.Delete(ctx, cmd.ID); err != nil {
|
||||
s.logger.Error("删除分类失败", zap.Error(err))
|
||||
s.logger.Error("删除分类失败", zap.Error(err), zap.String("id", cmd.ID))
|
||||
return fmt.Errorf("删除分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("删除分类成功", zap.String("id", cmd.ID))
|
||||
|
||||
s.logger.Info("删除分类成功", zap.String("id", cmd.ID), zap.String("code", existingCategory.Code))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -158,16 +136,6 @@ func (s *CategoryApplicationServiceImpl) GetCategoryByID(ctx context.Context, qu
|
||||
|
||||
// 转换为响应对象
|
||||
response := s.convertToCategoryInfoResponse(&category)
|
||||
|
||||
// 加载父分类信息
|
||||
if category.ParentID != nil && *category.ParentID != "" {
|
||||
parent, err := s.categoryRepo.GetByID(ctx, *category.ParentID)
|
||||
if err == nil {
|
||||
parentResponse := s.convertToCategoryInfoResponse(&parent)
|
||||
response.Parent = parentResponse
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
@@ -177,8 +145,6 @@ func (s *CategoryApplicationServiceImpl) ListCategories(ctx context.Context, que
|
||||
repoQuery := &repoQueries.ListCategoriesQuery{
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
ParentID: query.ParentID,
|
||||
Level: query.Level,
|
||||
IsEnabled: query.IsEnabled,
|
||||
IsVisible: query.IsVisible,
|
||||
SortBy: query.SortBy,
|
||||
@@ -206,212 +172,13 @@ func (s *CategoryApplicationServiceImpl) ListCategories(ctx context.Context, que
|
||||
}, nil
|
||||
}
|
||||
|
||||
// EnableCategory 启用分类
|
||||
func (s *CategoryApplicationServiceImpl) EnableCategory(ctx context.Context, cmd *commands.EnableCategoryCommand) error {
|
||||
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
category.IsEnabled = true
|
||||
|
||||
if err := s.categoryRepo.Update(ctx, category); err != nil {
|
||||
s.logger.Error("启用分类失败", zap.Error(err))
|
||||
return fmt.Errorf("启用分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("启用分类成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisableCategory 禁用分类
|
||||
func (s *CategoryApplicationServiceImpl) DisableCategory(ctx context.Context, cmd *commands.DisableCategoryCommand) error {
|
||||
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
category.IsEnabled = false
|
||||
|
||||
if err := s.categoryRepo.Update(ctx, category); err != nil {
|
||||
s.logger.Error("禁用分类失败", zap.Error(err))
|
||||
return fmt.Errorf("禁用分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("禁用分类成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShowCategory 显示分类
|
||||
func (s *CategoryApplicationServiceImpl) ShowCategory(ctx context.Context, cmd *commands.ShowCategoryCommand) error {
|
||||
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
category.IsVisible = true
|
||||
|
||||
if err := s.categoryRepo.Update(ctx, category); err != nil {
|
||||
s.logger.Error("显示分类失败", zap.Error(err))
|
||||
return fmt.Errorf("显示分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("显示分类成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// HideCategory 隐藏分类
|
||||
func (s *CategoryApplicationServiceImpl) HideCategory(ctx context.Context, cmd *commands.HideCategoryCommand) error {
|
||||
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
category.IsVisible = false
|
||||
|
||||
if err := s.categoryRepo.Update(ctx, category); err != nil {
|
||||
s.logger.Error("隐藏分类失败", zap.Error(err))
|
||||
return fmt.Errorf("隐藏分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("隐藏分类成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveCategory 移动分类
|
||||
func (s *CategoryApplicationServiceImpl) MoveCategory(ctx context.Context, cmd *commands.MoveCategoryCommand) error {
|
||||
category, err := s.categoryRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("分类不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查目标父分类是否存在
|
||||
if cmd.ParentID != nil && *cmd.ParentID != "" {
|
||||
_, err := s.categoryRepo.GetByID(ctx, *cmd.ParentID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("目标父分类不存在: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
category.ParentID = cmd.ParentID
|
||||
if cmd.Sort > 0 {
|
||||
category.Sort = cmd.Sort
|
||||
}
|
||||
|
||||
if err := s.categoryRepo.Update(ctx, category); err != nil {
|
||||
s.logger.Error("移动分类失败", zap.Error(err))
|
||||
return fmt.Errorf("移动分类失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("移动分类成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCategoryTree 获取分类树
|
||||
func (s *CategoryApplicationServiceImpl) GetCategoryTree(ctx context.Context, query *queries.GetCategoryTreeQuery) (*responses.CategoryTreeResponse, error) {
|
||||
categories, err := s.categoryRepo.GetCategoryTree(ctx)
|
||||
if err != nil {
|
||||
s.logger.Error("获取分类树失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取分类树失败: %w", err)
|
||||
}
|
||||
|
||||
// 构建树形结构
|
||||
tree := s.buildCategoryTree(categories, query.IncludeDisabled, query.IncludeHidden)
|
||||
|
||||
return &responses.CategoryTreeResponse{
|
||||
Categories: tree,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCategoriesByLevel 根据层级获取分类
|
||||
func (s *CategoryApplicationServiceImpl) GetCategoriesByLevel(ctx context.Context, query *queries.GetCategoriesByLevelQuery) ([]*responses.CategoryInfoResponse, error) {
|
||||
categories, err := s.categoryRepo.FindCategoriesByLevel(ctx, query.Level)
|
||||
if err != nil {
|
||||
s.logger.Error("根据层级获取分类失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("根据层级获取分类失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]*responses.CategoryInfoResponse, len(categories))
|
||||
for i, category := range categories {
|
||||
items[i] = s.convertToCategoryInfoResponse(category)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// GetCategoryPath 获取分类路径
|
||||
func (s *CategoryApplicationServiceImpl) GetCategoryPath(ctx context.Context, query *queries.GetCategoryPathQuery) (*responses.CategoryPathResponse, error) {
|
||||
path, err := s.buildCategoryPath(ctx, query.CategoryID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取分类路径失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取分类路径失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为正确的类型
|
||||
pathItems := make([]responses.CategorySimpleResponse, len(path))
|
||||
for i, item := range path {
|
||||
pathItems[i] = *item
|
||||
}
|
||||
|
||||
return &responses.CategoryPathResponse{
|
||||
Path: pathItems,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetCategoryStats 获取分类统计
|
||||
func (s *CategoryApplicationServiceImpl) GetCategoryStats(ctx context.Context) (*responses.CategoryStatsResponse, error) {
|
||||
// 使用正确的CountOptions
|
||||
total, err := s.categoryRepo.Count(ctx, interfaces.CountOptions{})
|
||||
if err != nil {
|
||||
s.logger.Error("获取分类总数失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取分类总数失败: %w", err)
|
||||
}
|
||||
|
||||
enabled, err := s.categoryRepo.Count(ctx, interfaces.CountOptions{
|
||||
Filters: map[string]interface{}{"is_enabled": true},
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Error("获取启用分类数失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取启用分类数失败: %w", err)
|
||||
}
|
||||
|
||||
visible, err := s.categoryRepo.Count(ctx, interfaces.CountOptions{
|
||||
Filters: map[string]interface{}{"is_visible": true},
|
||||
})
|
||||
if err != nil {
|
||||
s.logger.Error("获取可见分类数失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取可见分类数失败: %w", err)
|
||||
}
|
||||
|
||||
return &responses.CategoryStatsResponse{
|
||||
TotalCategories: total,
|
||||
EnabledCategories: enabled,
|
||||
VisibleCategories: visible,
|
||||
RootCategories: total - enabled, // 简化计算,实际应该查询根分类数量
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 私有方法
|
||||
|
||||
func (s *CategoryApplicationServiceImpl) validateCreateCategory(cmd *commands.CreateCategoryCommand) error {
|
||||
if cmd.Name == "" {
|
||||
return fmt.Errorf("分类名称不能为空")
|
||||
}
|
||||
if cmd.Level <= 0 {
|
||||
return fmt.Errorf("分类层级必须大于0")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertToCategoryInfoResponse 转换为分类信息响应
|
||||
func (s *CategoryApplicationServiceImpl) convertToCategoryInfoResponse(category *entities.ProductCategory) *responses.CategoryInfoResponse {
|
||||
return &responses.CategoryInfoResponse{
|
||||
ID: category.ID,
|
||||
Name: category.Name,
|
||||
Code: category.Code,
|
||||
Description: category.Description,
|
||||
ParentID: category.ParentID,
|
||||
Level: category.Level,
|
||||
Sort: category.Sort,
|
||||
IsEnabled: category.IsEnabled,
|
||||
IsVisible: category.IsVisible,
|
||||
@@ -420,76 +187,50 @@ func (s *CategoryApplicationServiceImpl) convertToCategoryInfoResponse(category
|
||||
}
|
||||
}
|
||||
|
||||
// convertToCategorySimpleResponse 转换为分类简单信息响应
|
||||
func (s *CategoryApplicationServiceImpl) convertToCategorySimpleResponse(category *entities.ProductCategory) *responses.CategorySimpleResponse {
|
||||
return &responses.CategorySimpleResponse{
|
||||
ID: category.ID,
|
||||
Name: category.Name,
|
||||
Code: category.Code,
|
||||
ParentID: category.ParentID,
|
||||
Level: category.Level,
|
||||
ID: category.ID,
|
||||
Name: category.Name,
|
||||
Code: category.Code,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CategoryApplicationServiceImpl) buildCategoryTree(categories []*entities.ProductCategory, includeDisabled, includeHidden bool) []responses.CategoryInfoResponse {
|
||||
// 构建ID到分类的映射
|
||||
categoryMap := make(map[string]*entities.ProductCategory)
|
||||
for _, category := range categories {
|
||||
// 根据过滤条件决定是否包含
|
||||
if !includeDisabled && !category.IsEnabled {
|
||||
continue
|
||||
}
|
||||
if !includeHidden && !category.IsVisible {
|
||||
continue
|
||||
}
|
||||
categoryMap[category.ID] = category
|
||||
// validateCreateCategory 验证创建分类参数
|
||||
func (s *CategoryApplicationServiceImpl) validateCreateCategory(cmd *commands.CreateCategoryCommand) error {
|
||||
if cmd.Name == "" {
|
||||
return errors.New("分类名称不能为空")
|
||||
}
|
||||
|
||||
// 构建树形结构
|
||||
var roots []responses.CategoryInfoResponse
|
||||
for _, category := range categoryMap {
|
||||
if category.ParentID == nil || *category.ParentID == "" {
|
||||
// 根节点
|
||||
root := *s.convertToCategoryInfoResponse(category)
|
||||
root.Children = s.findChildren(category.ID, categoryMap)
|
||||
roots = append(roots, root)
|
||||
}
|
||||
if cmd.Code == "" {
|
||||
return errors.New("分类编号不能为空")
|
||||
}
|
||||
|
||||
return roots
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CategoryApplicationServiceImpl) findChildren(parentID string, categoryMap map[string]*entities.ProductCategory) []responses.CategoryInfoResponse {
|
||||
var children []responses.CategoryInfoResponse
|
||||
for _, category := range categoryMap {
|
||||
if category.ParentID != nil && *category.ParentID == parentID {
|
||||
child := *s.convertToCategoryInfoResponse(category)
|
||||
child.Children = s.findChildren(category.ID, categoryMap)
|
||||
children = append(children, child)
|
||||
}
|
||||
// validateUpdateCategory 验证更新分类参数
|
||||
func (s *CategoryApplicationServiceImpl) validateUpdateCategory(cmd *commands.UpdateCategoryCommand) error {
|
||||
if cmd.ID == "" {
|
||||
return errors.New("分类ID不能为空")
|
||||
}
|
||||
return children
|
||||
if cmd.Name == "" {
|
||||
return errors.New("分类名称不能为空")
|
||||
}
|
||||
if cmd.Code == "" {
|
||||
return errors.New("分类编号不能为空")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CategoryApplicationServiceImpl) buildCategoryPath(ctx context.Context, categoryID string) ([]*responses.CategorySimpleResponse, error) {
|
||||
var path []*responses.CategorySimpleResponse
|
||||
|
||||
currentID := categoryID
|
||||
for currentID != "" {
|
||||
category, err := s.categoryRepo.GetByID(ctx, currentID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取分类失败: %w", err)
|
||||
}
|
||||
|
||||
path = append([]*responses.CategorySimpleResponse{
|
||||
s.convertToCategorySimpleResponse(&category),
|
||||
}, path...)
|
||||
|
||||
if category.ParentID != nil {
|
||||
currentID = *category.ParentID
|
||||
} else {
|
||||
currentID = ""
|
||||
}
|
||||
// validateCategoryCode 验证分类编号唯一性
|
||||
func (s *CategoryApplicationServiceImpl) validateCategoryCode(code, excludeID string) error {
|
||||
if code == "" {
|
||||
return errors.New("分类编号不能为空")
|
||||
}
|
||||
|
||||
return path, nil
|
||||
existingCategory, err := s.categoryRepo.FindByCode(context.Background(), code)
|
||||
if err == nil && existingCategory != nil && existingCategory.ID != excludeID {
|
||||
return errors.New("分类编号已存在")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -2,57 +2,26 @@ package commands
|
||||
|
||||
// CreateCategoryCommand 创建分类命令
|
||||
type CreateCategoryCommand struct {
|
||||
Name string `json:"name" binding:"required" comment:"分类名称"`
|
||||
Code string `json:"code" binding:"required" comment:"分类编号"`
|
||||
Description string `json:"description" comment:"分类描述"`
|
||||
ParentID *string `json:"parent_id" comment:"父分类ID"`
|
||||
Level int `json:"level" binding:"min=1" comment:"分类层级"`
|
||||
Sort int `json:"sort" comment:"排序"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
Name string `json:"name" binding:"required,min=2,max=50" comment:"分类名称"`
|
||||
Code string `json:"code" binding:"required,product_code" comment:"分类编号"`
|
||||
Description string `json:"description" binding:"omitempty,max=200" comment:"分类描述"`
|
||||
Sort int `json:"sort" binding:"min=0,max=9999" comment:"排序"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
}
|
||||
|
||||
// UpdateCategoryCommand 更新分类命令
|
||||
type UpdateCategoryCommand struct {
|
||||
ID string `json:"-"`
|
||||
Name string `json:"name" comment:"分类名称"`
|
||||
Code string `json:"code" comment:"分类编号"`
|
||||
Description string `json:"description" comment:"分类描述"`
|
||||
ParentID *string `json:"parent_id" comment:"父分类ID"`
|
||||
Level int `json:"level" binding:"min=1" comment:"分类层级"`
|
||||
Sort int `json:"sort" comment:"排序"`
|
||||
IsEnabled *bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible *bool `json:"is_visible" comment:"是否展示"`
|
||||
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"分类ID"`
|
||||
Name string `json:"name" binding:"required,min=2,max=50" comment:"分类名称"`
|
||||
Code string `json:"code" binding:"required,product_code" comment:"分类编号"`
|
||||
Description string `json:"description" binding:"omitempty,max=200" comment:"分类描述"`
|
||||
Sort int `json:"sort" binding:"min=0,max=9999" comment:"排序"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
}
|
||||
|
||||
// DeleteCategoryCommand 删除分类命令
|
||||
type DeleteCategoryCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// EnableCategoryCommand 启用分类命令
|
||||
type EnableCategoryCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// DisableCategoryCommand 禁用分类命令
|
||||
type DisableCategoryCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// ShowCategoryCommand 显示分类命令
|
||||
type ShowCategoryCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// HideCategoryCommand 隐藏分类命令
|
||||
type HideCategoryCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// MoveCategoryCommand 移动分类命令
|
||||
type MoveCategoryCommand struct {
|
||||
ID string `json:"-"`
|
||||
ParentID *string `json:"parent_id" comment:"新的父分类ID"`
|
||||
Sort int `json:"sort" comment:"新的排序"`
|
||||
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"分类ID"`
|
||||
}
|
||||
@@ -2,70 +2,42 @@ package commands
|
||||
|
||||
// CreateProductCommand 创建产品命令
|
||||
type CreateProductCommand struct {
|
||||
Name string `json:"name" binding:"required" comment:"产品名称"`
|
||||
Code string `json:"code" binding:"required" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Content string `json:"content" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" binding:"required" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" binding:"min=0" comment:"产品价格"`
|
||||
Name string `json:"name" binding:"required,min=2,max=100" comment:"产品名称"`
|
||||
Code string `json:"code" binding:"required,product_code" comment:"产品编号"`
|
||||
Description string `json:"description" binding:"omitempty,max=500" comment:"产品描述"`
|
||||
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" comment:"SEO描述"`
|
||||
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
|
||||
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
|
||||
SEOKeywords string `json:"seo_keywords" binding:"omitempty,max=200" comment:"SEO关键词"`
|
||||
}
|
||||
|
||||
// UpdateProductCommand 更新产品命令
|
||||
type UpdateProductCommand struct {
|
||||
ID string `json:"-"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Content string `json:"content" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" binding:"min=0" comment:"产品价格"`
|
||||
IsEnabled *bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible *bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage *bool `json:"is_package" comment:"是否组合包"`
|
||||
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"产品ID"`
|
||||
Name string `json:"name" binding:"required,min=2,max=100" comment:"产品名称"`
|
||||
Code string `json:"code" binding:"required,product_code" comment:"产品编号"`
|
||||
Description string `json:"description" binding:"omitempty,max=500" comment:"产品描述"`
|
||||
Content string `json:"content" binding:"omitempty,max=5000" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" binding:"required,uuid" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" binding:"price,min=0" comment:"产品价格"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" comment:"SEO描述"`
|
||||
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
|
||||
SEOTitle string `json:"seo_title" binding:"omitempty,max=100" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" binding:"omitempty,max=200" comment:"SEO描述"`
|
||||
SEOKeywords string `json:"seo_keywords" binding:"omitempty,max=200" comment:"SEO关键词"`
|
||||
}
|
||||
|
||||
// DeleteProductCommand 删除产品命令
|
||||
type DeleteProductCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// EnableProductCommand 启用产品命令
|
||||
type EnableProductCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// DisableProductCommand 禁用产品命令
|
||||
type DisableProductCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// ShowProductCommand 显示产品命令
|
||||
type ShowProductCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// HideProductCommand 隐藏产品命令
|
||||
type HideProductCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// UpdateProductSEOCommand 更新产品SEO信息命令
|
||||
type UpdateProductSEOCommand struct {
|
||||
ID string `json:"-"`
|
||||
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" comment:"SEO描述"`
|
||||
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
|
||||
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"产品ID"`
|
||||
}
|
||||
@@ -2,56 +2,12 @@ package commands
|
||||
|
||||
// CreateSubscriptionCommand 创建订阅命令
|
||||
type CreateSubscriptionCommand struct {
|
||||
UserID string `json:"user_id" binding:"required" comment:"用户ID"`
|
||||
ProductID string `json:"product_id" binding:"required" comment:"产品ID"`
|
||||
Price float64 `json:"price" binding:"min=0" comment:"订阅价格"`
|
||||
APILimit int64 `json:"api_limit" comment:"API调用限制"`
|
||||
AutoRenew bool `json:"auto_renew" comment:"是否自动续费"`
|
||||
Duration string `json:"duration" comment:"订阅时长"`
|
||||
UserID string `json:"-" comment:"用户ID"`
|
||||
ProductID string `json:"-" uri:"id" binding:"required,uuid" comment:"产品ID"`
|
||||
}
|
||||
|
||||
// UpdateSubscriptionCommand 更新订阅命令
|
||||
type UpdateSubscriptionCommand struct {
|
||||
ID string `json:"-"`
|
||||
Price float64 `json:"price" binding:"min=0" comment:"订阅价格"`
|
||||
APILimit int64 `json:"api_limit" comment:"API调用限制"`
|
||||
AutoRenew *bool `json:"auto_renew" comment:"是否自动续费"`
|
||||
}
|
||||
|
||||
// CancelSubscriptionCommand 取消订阅命令
|
||||
type CancelSubscriptionCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// RenewSubscriptionCommand 续费订阅命令
|
||||
type RenewSubscriptionCommand struct {
|
||||
ID string `json:"-"`
|
||||
Duration string `json:"duration" binding:"required" comment:"续费时长"`
|
||||
}
|
||||
|
||||
// ActivateSubscriptionCommand 激活订阅命令
|
||||
type ActivateSubscriptionCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// DeactivateSubscriptionCommand 停用订阅命令
|
||||
type DeactivateSubscriptionCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// UpdateAPIUsageCommand 更新API使用量命令
|
||||
type UpdateAPIUsageCommand struct {
|
||||
ID string `json:"-"`
|
||||
APIUsed int64 `json:"api_used" binding:"min=0" comment:"API使用量"`
|
||||
}
|
||||
|
||||
// ResetAPIUsageCommand 重置API使用量命令
|
||||
type ResetAPIUsageCommand struct {
|
||||
ID string `json:"-"`
|
||||
}
|
||||
|
||||
// SetAPILimitCommand 设置API限制命令
|
||||
type SetAPILimitCommand struct {
|
||||
ID string `json:"-"`
|
||||
APILimit int64 `json:"api_limit" binding:"min=0" comment:"API调用限制"`
|
||||
// UpdateSubscriptionPriceCommand 更新订阅价格命令
|
||||
type UpdateSubscriptionPriceCommand struct {
|
||||
ID string `json:"-" uri:"id" binding:"required,uuid" comment:"订阅ID"`
|
||||
Price float64 `json:"price" binding:"price,min=0" comment:"订阅价格"`
|
||||
}
|
||||
@@ -2,34 +2,16 @@ package queries
|
||||
|
||||
// ListCategoriesQuery 分类列表查询
|
||||
type ListCategoriesQuery struct {
|
||||
Page int `form:"page" binding:"min=1" comment:"页码"`
|
||||
PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
|
||||
ParentID *string `form:"parent_id" comment:"父分类ID"`
|
||||
Level *int `form:"level" comment:"分类层级"`
|
||||
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
|
||||
IsVisible *bool `form:"is_visible" comment:"是否展示"`
|
||||
SortBy string `form:"sort_by" comment:"排序字段"`
|
||||
SortOrder string `form:"sort_order" comment:"排序方向"`
|
||||
Page int `form:"page" binding:"omitempty,min=1" comment:"页码"`
|
||||
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100" comment:"每页数量"`
|
||||
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
|
||||
IsVisible *bool `form:"is_visible" comment:"是否展示"`
|
||||
SortBy string `form:"sort_by" binding:"omitempty,oneof=name code sort created_at updated_at" comment:"排序字段"`
|
||||
SortOrder string `form:"sort_order" binding:"omitempty,sort_order" comment:"排序方向"`
|
||||
}
|
||||
|
||||
// GetCategoryQuery 获取分类详情查询
|
||||
type GetCategoryQuery struct {
|
||||
ID string `uri:"id" comment:"分类ID"`
|
||||
Code string `form:"code" comment:"分类编号"`
|
||||
}
|
||||
|
||||
// GetCategoryTreeQuery 获取分类树查询
|
||||
type GetCategoryTreeQuery struct {
|
||||
IncludeDisabled bool `form:"include_disabled" comment:"是否包含禁用分类"`
|
||||
IncludeHidden bool `form:"include_hidden" comment:"是否包含隐藏分类"`
|
||||
}
|
||||
|
||||
// GetCategoriesByLevelQuery 根据层级获取分类查询
|
||||
type GetCategoriesByLevelQuery struct {
|
||||
Level int `form:"level" binding:"min=1" comment:"分类层级"`
|
||||
}
|
||||
|
||||
// GetCategoryPathQuery 获取分类路径查询
|
||||
type GetCategoryPathQuery struct {
|
||||
CategoryID string `uri:"category_id" binding:"required" comment:"分类ID"`
|
||||
ID string `uri:"id" binding:"omitempty,uuid" comment:"分类ID"`
|
||||
Code string `form:"code" binding:"omitempty,product_code" comment:"分类编号"`
|
||||
}
|
||||
@@ -2,43 +2,43 @@ package queries
|
||||
|
||||
// ListProductsQuery 产品列表查询
|
||||
type ListProductsQuery struct {
|
||||
Page int `form:"page" binding:"min=1" comment:"页码"`
|
||||
PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
|
||||
Keyword string `form:"keyword" comment:"搜索关键词"`
|
||||
CategoryID string `form:"category_id" comment:"分类ID"`
|
||||
MinPrice *float64 `form:"min_price" comment:"最低价格"`
|
||||
MaxPrice *float64 `form:"max_price" comment:"最高价格"`
|
||||
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
|
||||
IsVisible *bool `form:"is_visible" comment:"是否展示"`
|
||||
IsPackage *bool `form:"is_package" comment:"是否组合包"`
|
||||
SortBy string `form:"sort_by" comment:"排序字段"`
|
||||
SortOrder string `form:"sort_order" comment:"排序方向"`
|
||||
Page int `form:"page" binding:"omitempty,min=1" comment:"页码"`
|
||||
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100" comment:"每页数量"`
|
||||
Keyword string `form:"keyword" binding:"omitempty,max=100" comment:"搜索关键词"`
|
||||
CategoryID string `form:"category_id" binding:"omitempty,uuid" comment:"分类ID"`
|
||||
MinPrice *float64 `form:"min_price" binding:"omitempty,min=0" comment:"最低价格"`
|
||||
MaxPrice *float64 `form:"max_price" binding:"omitempty,min=0" comment:"最高价格"`
|
||||
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
|
||||
IsVisible *bool `form:"is_visible" comment:"是否展示"`
|
||||
IsPackage *bool `form:"is_package" comment:"是否组合包"`
|
||||
SortBy string `form:"sort_by" binding:"omitempty,oneof=name code price created_at updated_at" comment:"排序字段"`
|
||||
SortOrder string `form:"sort_order" binding:"omitempty,sort_order" comment:"排序方向"`
|
||||
}
|
||||
|
||||
// SearchProductsQuery 产品搜索查询
|
||||
type SearchProductsQuery struct {
|
||||
Page int `form:"page" binding:"min=1" comment:"页码"`
|
||||
PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
|
||||
Keyword string `form:"keyword" comment:"搜索关键词"`
|
||||
CategoryID string `form:"category_id" comment:"分类ID"`
|
||||
MinPrice *float64 `form:"min_price" comment:"最低价格"`
|
||||
MaxPrice *float64 `form:"max_price" comment:"最高价格"`
|
||||
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
|
||||
IsVisible *bool `form:"is_visible" comment:"是否展示"`
|
||||
IsPackage *bool `form:"is_package" comment:"是否组合包"`
|
||||
SortBy string `form:"sort_by" comment:"排序字段"`
|
||||
SortOrder string `form:"sort_order" comment:"排序方向"`
|
||||
Page int `form:"page" binding:"omitempty,min=1" comment:"页码"`
|
||||
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100" comment:"每页数量"`
|
||||
Keyword string `form:"keyword" binding:"omitempty,max=100" comment:"搜索关键词"`
|
||||
CategoryID string `form:"category_id" binding:"omitempty,uuid" comment:"分类ID"`
|
||||
MinPrice *float64 `form:"min_price" binding:"omitempty,min=0" comment:"最低价格"`
|
||||
MaxPrice *float64 `form:"max_price" binding:"omitempty,min=0" comment:"最高价格"`
|
||||
IsEnabled *bool `form:"is_enabled" comment:"是否启用"`
|
||||
IsVisible *bool `form:"is_visible" comment:"是否展示"`
|
||||
IsPackage *bool `form:"is_package" comment:"是否组合包"`
|
||||
SortBy string `form:"sort_by" binding:"omitempty,oneof=name code price created_at updated_at" comment:"排序字段"`
|
||||
SortOrder string `form:"sort_order" binding:"omitempty,sort_order" comment:"排序方向"`
|
||||
}
|
||||
|
||||
// GetProductQuery 获取产品详情查询
|
||||
type GetProductQuery struct {
|
||||
ID string `uri:"id" comment:"产品ID"`
|
||||
Code string `form:"code" comment:"产品编号"`
|
||||
ID string `uri:"id" binding:"omitempty,uuid" comment:"产品ID"`
|
||||
Code string `form:"code" binding:"omitempty,product_code" comment:"产品编号"`
|
||||
}
|
||||
|
||||
// GetProductsByIDsQuery 根据ID列表获取产品查询
|
||||
type GetProductsByIDsQuery struct {
|
||||
IDs []string `form:"ids" binding:"required" comment:"产品ID列表"`
|
||||
IDs []string `form:"ids" binding:"required,dive,uuid" comment:"产品ID列表"`
|
||||
}
|
||||
|
||||
// GetSubscribableProductsQuery 获取可订阅产品查询
|
||||
|
||||
@@ -1,36 +1,31 @@
|
||||
package queries
|
||||
|
||||
import "tyapi-server/internal/domains/product/entities"
|
||||
|
||||
// ListSubscriptionsQuery 订阅列表查询
|
||||
type ListSubscriptionsQuery struct {
|
||||
Page int `form:"page" binding:"min=1" comment:"页码"`
|
||||
PageSize int `form:"page_size" binding:"min=1,max=100" comment:"每页数量"`
|
||||
UserID string `form:"user_id" comment:"用户ID"`
|
||||
ProductID string `form:"product_id" comment:"产品ID"`
|
||||
Status entities.SubscriptionStatus `form:"status" comment:"订阅状态"`
|
||||
SortBy string `form:"sort_by" comment:"排序字段"`
|
||||
SortOrder string `form:"sort_order" comment:"排序方向"`
|
||||
Page int `form:"page" binding:"omitempty,min=1" comment:"页码"`
|
||||
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100" comment:"每页数量"`
|
||||
UserID string `form:"-" comment:"用户ID"`
|
||||
Keyword string `form:"keyword" binding:"omitempty,max=100" comment:"搜索关键词"`
|
||||
SortBy string `form:"sort_by" binding:"omitempty,oneof=created_at updated_at price" comment:"排序字段"`
|
||||
SortOrder string `form:"sort_order" binding:"omitempty,sort_order" comment:"排序方向"`
|
||||
}
|
||||
|
||||
// GetSubscriptionQuery 获取订阅详情查询
|
||||
type GetSubscriptionQuery struct {
|
||||
ID string `uri:"id" binding:"required" comment:"订阅ID"`
|
||||
ID string `uri:"id" binding:"required,uuid" comment:"订阅ID"`
|
||||
}
|
||||
|
||||
// GetUserSubscriptionsQuery 获取用户订阅查询
|
||||
type GetUserSubscriptionsQuery struct {
|
||||
UserID string `form:"user_id" binding:"required" comment:"用户ID"`
|
||||
Status *entities.SubscriptionStatus `form:"status" comment:"订阅状态"`
|
||||
UserID string `form:"user_id" binding:"required,uuid" comment:"用户ID"`
|
||||
}
|
||||
|
||||
// GetProductSubscriptionsQuery 获取产品订阅查询
|
||||
type GetProductSubscriptionsQuery struct {
|
||||
ProductID string `form:"product_id" binding:"required" comment:"产品ID"`
|
||||
Status *entities.SubscriptionStatus `form:"status" comment:"订阅状态"`
|
||||
ProductID string `form:"product_id" binding:"required,uuid" comment:"产品ID"`
|
||||
}
|
||||
|
||||
// GetActiveSubscriptionsQuery 获取活跃订阅查询
|
||||
type GetActiveSubscriptionsQuery struct {
|
||||
UserID string `form:"user_id" comment:"用户ID"`
|
||||
UserID string `form:"user_id" binding:"omitempty,uuid" comment:"用户ID"`
|
||||
}
|
||||
|
||||
@@ -8,16 +8,10 @@ type CategoryInfoResponse struct {
|
||||
Name string `json:"name" comment:"分类名称"`
|
||||
Code string `json:"code" comment:"分类编号"`
|
||||
Description string `json:"description" comment:"分类描述"`
|
||||
ParentID *string `json:"parent_id" comment:"父分类ID"`
|
||||
Level int `json:"level" comment:"分类层级"`
|
||||
Sort int `json:"sort" comment:"排序"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
|
||||
// 关联信息
|
||||
Parent *CategoryInfoResponse `json:"parent,omitempty" comment:"父分类"`
|
||||
Children []CategoryInfoResponse `json:"children,omitempty" comment:"子分类"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
@@ -30,29 +24,9 @@ type CategoryListResponse struct {
|
||||
Items []CategoryInfoResponse `json:"items" comment:"分类列表"`
|
||||
}
|
||||
|
||||
// CategoryTreeResponse 分类树响应
|
||||
type CategoryTreeResponse struct {
|
||||
Categories []CategoryInfoResponse `json:"categories" comment:"分类树"`
|
||||
}
|
||||
|
||||
// CategorySimpleResponse 分类简单信息响应
|
||||
type CategorySimpleResponse struct {
|
||||
ID string `json:"id" comment:"分类ID"`
|
||||
Name string `json:"name" comment:"分类名称"`
|
||||
Code string `json:"code" comment:"分类编号"`
|
||||
ParentID *string `json:"parent_id" comment:"父分类ID"`
|
||||
Level int `json:"level" comment:"分类层级"`
|
||||
}
|
||||
|
||||
// CategoryPathResponse 分类路径响应
|
||||
type CategoryPathResponse struct {
|
||||
Path []CategorySimpleResponse `json:"path" comment:"分类路径"`
|
||||
}
|
||||
|
||||
// CategoryStatsResponse 分类统计响应
|
||||
type CategoryStatsResponse struct {
|
||||
TotalCategories int64 `json:"total_categories" comment:"分类总数"`
|
||||
RootCategories int64 `json:"root_categories" comment:"根分类数"`
|
||||
EnabledCategories int64 `json:"enabled_categories" comment:"启用分类数"`
|
||||
VisibleCategories int64 `json:"visible_categories" comment:"可见分类数"`
|
||||
ID string `json:"id" comment:"分类ID"`
|
||||
Name string `json:"name" comment:"分类名称"`
|
||||
Code string `json:"code" comment:"分类编号"`
|
||||
}
|
||||
@@ -4,59 +4,60 @@ import "time"
|
||||
|
||||
// ProductInfoResponse 产品详情响应
|
||||
type ProductInfoResponse struct {
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Content string `json:"content" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Content string `json:"content" comment:"产品内容"`
|
||||
CategoryID string `json:"category_id" comment:"产品分类ID"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
IsEnabled bool `json:"is_enabled" comment:"是否启用"`
|
||||
IsVisible bool `json:"is_visible" comment:"是否展示"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
|
||||
// SEO信息
|
||||
SEOTitle string `json:"seo_title" comment:"SEO标题"`
|
||||
SEODescription string `json:"seo_description" comment:"SEO描述"`
|
||||
SEOKeywords string `json:"seo_keywords" comment:"SEO关键词"`
|
||||
|
||||
|
||||
// 关联信息
|
||||
Category *CategoryInfoResponse `json:"category,omitempty" comment:"分类信息"`
|
||||
|
||||
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
}
|
||||
|
||||
// ProductListResponse 产品列表响应
|
||||
type ProductListResponse struct {
|
||||
Total int64 `json:"total" comment:"总数"`
|
||||
Page int `json:"page" comment:"页码"`
|
||||
Size int `json:"size" comment:"每页数量"`
|
||||
Total int64 `json:"total" comment:"总数"`
|
||||
Page int `json:"page" comment:"页码"`
|
||||
Size int `json:"size" comment:"每页数量"`
|
||||
Items []ProductInfoResponse `json:"items" comment:"产品列表"`
|
||||
}
|
||||
|
||||
// ProductSearchResponse 产品搜索响应
|
||||
type ProductSearchResponse struct {
|
||||
Total int64 `json:"total" comment:"总数"`
|
||||
Page int `json:"page" comment:"页码"`
|
||||
Size int `json:"size" comment:"每页数量"`
|
||||
Total int64 `json:"total" comment:"总数"`
|
||||
Page int `json:"page" comment:"页码"`
|
||||
Size int `json:"size" comment:"每页数量"`
|
||||
Items []ProductInfoResponse `json:"items" comment:"产品列表"`
|
||||
}
|
||||
|
||||
// ProductSimpleResponse 产品简单信息响应
|
||||
type ProductSimpleResponse struct {
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
ID string `json:"id" comment:"产品ID"`
|
||||
Name string `json:"name" comment:"产品名称"`
|
||||
Code string `json:"code" comment:"产品编号"`
|
||||
Description string `json:"description" comment:"产品简介"`
|
||||
Category *CategorySimpleResponse `json:"category,omitempty" comment:"分类信息"`
|
||||
Price float64 `json:"price" comment:"产品价格"`
|
||||
IsPackage bool `json:"is_package" comment:"是否组合包"`
|
||||
}
|
||||
|
||||
// ProductStatsResponse 产品统计响应
|
||||
type ProductStatsResponse struct {
|
||||
TotalProducts int64 `json:"total_products" comment:"产品总数"`
|
||||
EnabledProducts int64 `json:"enabled_products" comment:"启用产品数"`
|
||||
VisibleProducts int64 `json:"visible_products" comment:"可见产品数"`
|
||||
PackageProducts int64 `json:"package_products" comment:"组合包产品数"`
|
||||
}
|
||||
TotalProducts int64 `json:"total_products" comment:"产品总数"`
|
||||
EnabledProducts int64 `json:"enabled_products" comment:"启用产品数"`
|
||||
VisibleProducts int64 `json:"visible_products" comment:"可见产品数"`
|
||||
PackageProducts int64 `json:"package_products" comment:"组合包产品数"`
|
||||
}
|
||||
|
||||
@@ -13,19 +13,11 @@ type ProductApplicationService interface {
|
||||
CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) error
|
||||
UpdateProduct(ctx context.Context, cmd *commands.UpdateProductCommand) error
|
||||
DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error
|
||||
|
||||
GetProductByID(ctx context.Context, query *queries.GetProductQuery) (*responses.ProductInfoResponse, error)
|
||||
ListProducts(ctx context.Context, query *queries.ListProductsQuery) (*responses.ProductListResponse, error)
|
||||
GetProductsByIDs(ctx context.Context, query *queries.GetProductsByIDsQuery) ([]*responses.ProductInfoResponse, error)
|
||||
|
||||
// 产品状态管理
|
||||
EnableProduct(ctx context.Context, cmd *commands.EnableProductCommand) error
|
||||
DisableProduct(ctx context.Context, cmd *commands.DisableProductCommand) error
|
||||
ShowProduct(ctx context.Context, cmd *commands.ShowProductCommand) error
|
||||
HideProduct(ctx context.Context, cmd *commands.HideProductCommand) error
|
||||
|
||||
// SEO管理
|
||||
UpdateProductSEO(ctx context.Context, cmd *commands.UpdateProductSEOCommand) error
|
||||
|
||||
|
||||
// 业务查询
|
||||
GetSubscribableProducts(ctx context.Context, query *queries.GetSubscribableProductsQuery) ([]*responses.ProductInfoResponse, error)
|
||||
GetProductStats(ctx context.Context) (*responses.ProductStatsResponse, error)
|
||||
@@ -37,42 +29,26 @@ type CategoryApplicationService interface {
|
||||
CreateCategory(ctx context.Context, cmd *commands.CreateCategoryCommand) error
|
||||
UpdateCategory(ctx context.Context, cmd *commands.UpdateCategoryCommand) error
|
||||
DeleteCategory(ctx context.Context, cmd *commands.DeleteCategoryCommand) error
|
||||
|
||||
GetCategoryByID(ctx context.Context, query *queries.GetCategoryQuery) (*responses.CategoryInfoResponse, error)
|
||||
ListCategories(ctx context.Context, query *queries.ListCategoriesQuery) (*responses.CategoryListResponse, error)
|
||||
|
||||
// 分类状态管理
|
||||
EnableCategory(ctx context.Context, cmd *commands.EnableCategoryCommand) error
|
||||
DisableCategory(ctx context.Context, cmd *commands.DisableCategoryCommand) error
|
||||
ShowCategory(ctx context.Context, cmd *commands.ShowCategoryCommand) error
|
||||
HideCategory(ctx context.Context, cmd *commands.HideCategoryCommand) error
|
||||
|
||||
// 分类结构管理
|
||||
MoveCategory(ctx context.Context, cmd *commands.MoveCategoryCommand) error
|
||||
GetCategoryTree(ctx context.Context, query *queries.GetCategoryTreeQuery) (*responses.CategoryTreeResponse, error)
|
||||
GetCategoriesByLevel(ctx context.Context, query *queries.GetCategoriesByLevelQuery) ([]*responses.CategoryInfoResponse, error)
|
||||
GetCategoryPath(ctx context.Context, query *queries.GetCategoryPathQuery) (*responses.CategoryPathResponse, error)
|
||||
|
||||
// 统计
|
||||
GetCategoryStats(ctx context.Context) (*responses.CategoryStatsResponse, error)
|
||||
}
|
||||
|
||||
// SubscriptionApplicationService 订阅应用服务接口
|
||||
type SubscriptionApplicationService interface {
|
||||
// 订阅管理
|
||||
UpdateSubscriptionPrice(ctx context.Context, cmd *commands.UpdateSubscriptionPriceCommand) error
|
||||
|
||||
// 订阅管理
|
||||
CreateSubscription(ctx context.Context, cmd *commands.CreateSubscriptionCommand) error
|
||||
UpdateSubscription(ctx context.Context, cmd *commands.UpdateSubscriptionCommand) error
|
||||
GetSubscriptionByID(ctx context.Context, query *queries.GetSubscriptionQuery) (*responses.SubscriptionInfoResponse, error)
|
||||
ListSubscriptions(ctx context.Context, query *queries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, error)
|
||||
|
||||
// API使用管理
|
||||
UpdateAPIUsage(ctx context.Context, cmd *commands.UpdateAPIUsageCommand) error
|
||||
ResetAPIUsage(ctx context.Context, cmd *commands.ResetAPIUsageCommand) error
|
||||
|
||||
|
||||
// 业务查询
|
||||
GetUserSubscriptions(ctx context.Context, query *queries.GetUserSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error)
|
||||
GetProductSubscriptions(ctx context.Context, query *queries.GetProductSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error)
|
||||
GetSubscriptionUsage(ctx context.Context, subscriptionID string) (*responses.SubscriptionUsageResponse, error)
|
||||
|
||||
|
||||
// 统计
|
||||
GetSubscriptionStats(ctx context.Context) (*responses.SubscriptionStatsResponse, error)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,201 +2,114 @@ package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
appQueries "tyapi-server/internal/application/product/dto/queries"
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
|
||||
"tyapi-server/internal/domains/product/services"
|
||||
|
||||
"go.uber.org/zap"
|
||||
product_service "tyapi-server/internal/domains/product/services"
|
||||
)
|
||||
|
||||
// ProductApplicationServiceImpl 产品应用服务实现
|
||||
// 负责业务流程编排、事务管理、数据转换,不直接操作仓库
|
||||
type ProductApplicationServiceImpl struct {
|
||||
productRepo repositories.ProductRepository
|
||||
categoryRepo repositories.ProductCategoryRepository
|
||||
subscriptionRepo repositories.SubscriptionRepository
|
||||
productService *services.ProductService
|
||||
logger *zap.Logger
|
||||
productManagementService *product_service.ProductManagementService
|
||||
productSubscriptionService *product_service.ProductSubscriptionService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewProductApplicationService 创建产品应用服务
|
||||
func NewProductApplicationService(
|
||||
productRepo repositories.ProductRepository,
|
||||
categoryRepo repositories.ProductCategoryRepository,
|
||||
subscriptionRepo repositories.SubscriptionRepository,
|
||||
productService *services.ProductService,
|
||||
productManagementService *product_service.ProductManagementService,
|
||||
productSubscriptionService *product_service.ProductSubscriptionService,
|
||||
logger *zap.Logger,
|
||||
) ProductApplicationService {
|
||||
return &ProductApplicationServiceImpl{
|
||||
productRepo: productRepo,
|
||||
categoryRepo: categoryRepo,
|
||||
subscriptionRepo: subscriptionRepo,
|
||||
productService: productService,
|
||||
logger: logger,
|
||||
productManagementService: productManagementService,
|
||||
productSubscriptionService: productSubscriptionService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateProduct 创建产品
|
||||
// 业务流程:1. 构建产品实体 2. 创建产品
|
||||
func (s *ProductApplicationServiceImpl) CreateProduct(ctx context.Context, cmd *commands.CreateProductCommand) error {
|
||||
// 1. 参数验证
|
||||
if err := s.validateCreateProduct(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 验证产品编号唯一性
|
||||
if err := s.productService.ValidateProductCode(cmd.Code, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 创建产品实体
|
||||
// 1. 构建产品实体
|
||||
product := &entities.Product{
|
||||
Name: cmd.Name,
|
||||
Code: cmd.Code,
|
||||
Description: cmd.Description,
|
||||
Content: cmd.Content,
|
||||
CategoryID: cmd.CategoryID,
|
||||
Price: cmd.Price,
|
||||
IsEnabled: cmd.IsEnabled,
|
||||
IsVisible: cmd.IsVisible,
|
||||
IsPackage: cmd.IsPackage,
|
||||
Name: cmd.Name,
|
||||
Code: cmd.Code,
|
||||
Description: cmd.Description,
|
||||
Content: cmd.Content,
|
||||
CategoryID: cmd.CategoryID,
|
||||
Price: cmd.Price,
|
||||
IsEnabled: cmd.IsEnabled,
|
||||
IsVisible: cmd.IsVisible,
|
||||
IsPackage: cmd.IsPackage,
|
||||
SEOTitle: cmd.SEOTitle,
|
||||
SEODescription: cmd.SEODescription,
|
||||
SEOKeywords: cmd.SEOKeywords,
|
||||
}
|
||||
|
||||
// 4. 设置SEO信息
|
||||
product.UpdateSEO(cmd.SEOTitle, cmd.SEODescription, cmd.SEOKeywords)
|
||||
|
||||
// 5. 调用领域服务验证
|
||||
if err := s.productService.ValidateProduct(product); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 6. 保存到仓储
|
||||
if _, err := s.productRepo.Create(ctx, *product); err != nil {
|
||||
s.logger.Error("创建产品失败", zap.Error(err))
|
||||
return fmt.Errorf("创建产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("创建产品成功", zap.String("id", product.ID), zap.String("name", product.Name))
|
||||
return nil
|
||||
// 2. 创建产品
|
||||
_, err := s.productManagementService.CreateProduct(ctx, product)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateProduct 更新产品
|
||||
// 业务流程:1. 获取现有产品 2. 更新产品信息 3. 保存产品
|
||||
func (s *ProductApplicationServiceImpl) UpdateProduct(ctx context.Context, cmd *commands.UpdateProductCommand) error {
|
||||
// 1. 获取现有产品
|
||||
existingProduct, err := s.productRepo.GetByID(ctx, cmd.ID)
|
||||
existingProduct, err := s.productManagementService.GetProductByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 参数验证
|
||||
if err := s.validateUpdateProduct(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 验证产品编号唯一性(如果修改了编号)
|
||||
if cmd.Code != "" && cmd.Code != existingProduct.Code {
|
||||
if err := s.productService.ValidateProductCode(cmd.Code, cmd.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
existingProduct.Code = cmd.Code
|
||||
}
|
||||
// 2. 更新产品信息
|
||||
existingProduct.Name = cmd.Name
|
||||
existingProduct.Code = cmd.Code
|
||||
existingProduct.Description = cmd.Description
|
||||
existingProduct.Content = cmd.Content
|
||||
existingProduct.CategoryID = cmd.CategoryID
|
||||
existingProduct.Price = cmd.Price
|
||||
existingProduct.IsEnabled = cmd.IsEnabled
|
||||
existingProduct.IsVisible = cmd.IsVisible
|
||||
existingProduct.IsPackage = cmd.IsPackage
|
||||
existingProduct.SEOTitle = cmd.SEOTitle
|
||||
existingProduct.SEODescription = cmd.SEODescription
|
||||
existingProduct.SEOKeywords = cmd.SEOKeywords
|
||||
|
||||
// 4. 更新字段
|
||||
if cmd.Name != "" {
|
||||
existingProduct.Name = cmd.Name
|
||||
}
|
||||
if cmd.Description != "" {
|
||||
existingProduct.Description = cmd.Description
|
||||
}
|
||||
if cmd.Content != "" {
|
||||
existingProduct.Content = cmd.Content
|
||||
}
|
||||
if cmd.CategoryID != "" {
|
||||
existingProduct.CategoryID = cmd.CategoryID
|
||||
}
|
||||
if cmd.Price >= 0 {
|
||||
existingProduct.Price = cmd.Price
|
||||
}
|
||||
if cmd.IsEnabled != nil {
|
||||
existingProduct.IsEnabled = *cmd.IsEnabled
|
||||
}
|
||||
if cmd.IsVisible != nil {
|
||||
existingProduct.IsVisible = *cmd.IsVisible
|
||||
}
|
||||
if cmd.IsPackage != nil {
|
||||
existingProduct.IsPackage = *cmd.IsPackage
|
||||
}
|
||||
|
||||
// 5. 更新SEO信息
|
||||
if cmd.SEOTitle != "" || cmd.SEODescription != "" || cmd.SEOKeywords != "" {
|
||||
existingProduct.UpdateSEO(cmd.SEOTitle, cmd.SEODescription, cmd.SEOKeywords)
|
||||
}
|
||||
|
||||
// 6. 调用领域服务验证
|
||||
if err := s.productService.ValidateProduct(&existingProduct); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 7. 保存到仓储
|
||||
if err := s.productRepo.Update(ctx, existingProduct); err != nil {
|
||||
s.logger.Error("更新产品失败", zap.Error(err))
|
||||
return fmt.Errorf("更新产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("更新产品成功", zap.String("id", existingProduct.ID))
|
||||
return nil
|
||||
// 3. 保存产品
|
||||
return s.productManagementService.UpdateProduct(ctx, existingProduct)
|
||||
}
|
||||
|
||||
// DeleteProduct 删除产品
|
||||
// 业务流程:1. 删除产品
|
||||
func (s *ProductApplicationServiceImpl) DeleteProduct(ctx context.Context, cmd *commands.DeleteProductCommand) error {
|
||||
// 1. 检查产品是否存在
|
||||
product, err := s.productRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 检查是否有活跃订阅
|
||||
subscriptions, err := s.subscriptionRepo.FindByProductID(ctx, cmd.ID)
|
||||
if err == nil && len(subscriptions) > 0 {
|
||||
return errors.New("产品存在订阅,无法删除")
|
||||
}
|
||||
|
||||
// 3. 删除产品
|
||||
if err := s.productRepo.Delete(ctx, cmd.ID); err != nil {
|
||||
s.logger.Error("删除产品失败", zap.Error(err))
|
||||
return fmt.Errorf("删除产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("删除产品成功", zap.String("id", cmd.ID), zap.String("name", product.Name))
|
||||
return nil
|
||||
return s.productManagementService.DeleteProduct(ctx, cmd.ID)
|
||||
}
|
||||
|
||||
// ListProducts 获取产品列表
|
||||
// 业务流程:1. 获取产品列表 2. 构建响应数据
|
||||
func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, query *appQueries.ListProductsQuery) (*responses.ProductListResponse, error) {
|
||||
// 构建仓储查询
|
||||
repoQuery := &repoQueries.ListProductsQuery{
|
||||
Keyword: query.Keyword,
|
||||
CategoryID: query.CategoryID,
|
||||
MinPrice: query.MinPrice,
|
||||
MaxPrice: query.MaxPrice,
|
||||
IsEnabled: query.IsEnabled,
|
||||
IsVisible: query.IsVisible,
|
||||
IsPackage: query.IsPackage,
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
SortBy: query.SortBy,
|
||||
SortOrder: query.SortOrder,
|
||||
// 根据查询条件获取产品列表
|
||||
var products []*entities.Product
|
||||
var err error
|
||||
|
||||
if query.CategoryID != "" {
|
||||
products, err = s.productManagementService.GetProductsByCategory(ctx, query.CategoryID)
|
||||
} else if query.IsVisible != nil && *query.IsVisible {
|
||||
products, err = s.productManagementService.GetVisibleProducts(ctx)
|
||||
} else if query.IsEnabled != nil && *query.IsEnabled {
|
||||
products, err = s.productManagementService.GetEnabledProducts(ctx)
|
||||
} else {
|
||||
// 默认获取可见产品
|
||||
products, err = s.productManagementService.GetVisibleProducts(ctx)
|
||||
}
|
||||
|
||||
// 调用仓储
|
||||
products, total, err := s.productRepo.ListProducts(ctx, repoQuery)
|
||||
if err != nil {
|
||||
s.logger.Error("获取产品列表失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取产品列表失败: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
@@ -206,7 +119,7 @@ func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, query
|
||||
}
|
||||
|
||||
return &responses.ProductListResponse{
|
||||
Total: total,
|
||||
Total: int64(len(items)),
|
||||
Page: query.Page,
|
||||
Size: query.PageSize,
|
||||
Items: items,
|
||||
@@ -214,169 +127,55 @@ func (s *ProductApplicationServiceImpl) ListProducts(ctx context.Context, query
|
||||
}
|
||||
|
||||
// GetProductsByIDs 根据ID列表获取产品
|
||||
// 业务流程:1. 获取产品列表 2. 构建响应数据
|
||||
func (s *ProductApplicationServiceImpl) GetProductsByIDs(ctx context.Context, query *appQueries.GetProductsByIDsQuery) ([]*responses.ProductInfoResponse, error) {
|
||||
products, err := s.productRepo.GetByIDs(ctx, query.IDs)
|
||||
if err != nil {
|
||||
s.logger.Error("获取产品列表失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取产品列表失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]*responses.ProductInfoResponse, len(products))
|
||||
for i := range products {
|
||||
items[i] = s.convertToProductInfoResponse(&products[i])
|
||||
}
|
||||
|
||||
return items, nil
|
||||
// 这里需要扩展领域服务来支持批量获取
|
||||
// 暂时返回空列表
|
||||
return []*responses.ProductInfoResponse{}, nil
|
||||
}
|
||||
|
||||
// GetSubscribableProducts 获取可订阅的产品
|
||||
// GetSubscribableProducts 获取可订阅产品列表
|
||||
// 业务流程:1. 获取启用产品 2. 过滤可订阅产品 3. 构建响应数据
|
||||
func (s *ProductApplicationServiceImpl) GetSubscribableProducts(ctx context.Context, query *appQueries.GetSubscribableProductsQuery) ([]*responses.ProductInfoResponse, error) {
|
||||
products, err := s.productRepo.FindSubscribableProducts(ctx, query.UserID)
|
||||
products, err := s.productManagementService.GetEnabledProducts(ctx)
|
||||
if err != nil {
|
||||
s.logger.Error("获取可订阅产品失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取可订阅产品失败: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 过滤可订阅的产品
|
||||
var subscribableProducts []*entities.Product
|
||||
for _, product := range products {
|
||||
if product.CanBeSubscribed() {
|
||||
subscribableProducts = append(subscribableProducts, product)
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]*responses.ProductInfoResponse, len(products))
|
||||
for i := range products {
|
||||
items[i] = s.convertToProductInfoResponse(products[i])
|
||||
items := make([]*responses.ProductInfoResponse, len(subscribableProducts))
|
||||
for i := range subscribableProducts {
|
||||
items[i] = s.convertToProductInfoResponse(subscribableProducts[i])
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// GetProductByID 根据ID获取产品
|
||||
// 业务流程:1. 获取产品信息 2. 构建响应数据
|
||||
func (s *ProductApplicationServiceImpl) GetProductByID(ctx context.Context, query *appQueries.GetProductQuery) (*responses.ProductInfoResponse, error) {
|
||||
var product *entities.Product
|
||||
var err error
|
||||
|
||||
if query.ID != "" {
|
||||
p, err := s.productRepo.GetByID(ctx, query.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
product = &p
|
||||
} else if query.Code != "" {
|
||||
product, err = s.productRepo.FindByCode(ctx, query.Code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
} else {
|
||||
return nil, errors.New("产品ID或编号不能为空")
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
response := s.convertToProductInfoResponse(product)
|
||||
|
||||
// 加载分类信息
|
||||
if product.CategoryID != "" {
|
||||
category, err := s.categoryRepo.GetByID(ctx, product.CategoryID)
|
||||
if err == nil {
|
||||
response.Category = s.convertToCategoryInfoResponse(&category)
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// EnableProduct 启用产品
|
||||
func (s *ProductApplicationServiceImpl) EnableProduct(ctx context.Context, cmd *commands.EnableProductCommand) error {
|
||||
product, err := s.productRepo.GetByID(ctx, cmd.ID)
|
||||
product, err := s.productManagementService.GetProductWithCategory(ctx, query.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
product.Enable()
|
||||
|
||||
if err := s.productRepo.Update(ctx, product); err != nil {
|
||||
s.logger.Error("启用产品失败", zap.Error(err))
|
||||
return fmt.Errorf("启用产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("启用产品成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
return s.convertToProductInfoResponse(product), nil
|
||||
}
|
||||
|
||||
// DisableProduct 禁用产品
|
||||
func (s *ProductApplicationServiceImpl) DisableProduct(ctx context.Context, cmd *commands.DisableProductCommand) error {
|
||||
product, err := s.productRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
product.Disable()
|
||||
|
||||
if err := s.productRepo.Update(ctx, product); err != nil {
|
||||
s.logger.Error("禁用产品失败", zap.Error(err))
|
||||
return fmt.Errorf("禁用产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("禁用产品成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShowProduct 显示产品
|
||||
func (s *ProductApplicationServiceImpl) ShowProduct(ctx context.Context, cmd *commands.ShowProductCommand) error {
|
||||
product, err := s.productRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
product.Show()
|
||||
|
||||
if err := s.productRepo.Update(ctx, product); err != nil {
|
||||
s.logger.Error("显示产品失败", zap.Error(err))
|
||||
return fmt.Errorf("显示产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("显示产品成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// HideProduct 隐藏产品
|
||||
func (s *ProductApplicationServiceImpl) HideProduct(ctx context.Context, cmd *commands.HideProductCommand) error {
|
||||
product, err := s.productRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
product.Hide()
|
||||
|
||||
if err := s.productRepo.Update(ctx, product); err != nil {
|
||||
s.logger.Error("隐藏产品失败", zap.Error(err))
|
||||
return fmt.Errorf("隐藏产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("隐藏产品成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateProductSEO 更新产品SEO信息
|
||||
func (s *ProductApplicationServiceImpl) UpdateProductSEO(ctx context.Context, cmd *commands.UpdateProductSEOCommand) error {
|
||||
product, err := s.productRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
product.UpdateSEO(cmd.SEOTitle, cmd.SEODescription, cmd.SEOKeywords)
|
||||
|
||||
if err := s.productRepo.Update(ctx, product); err != nil {
|
||||
s.logger.Error("更新产品SEO失败", zap.Error(err))
|
||||
return fmt.Errorf("更新产品SEO失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("更新产品SEO成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProductStats 获取产品统计
|
||||
// GetProductStats 获取产品统计信息
|
||||
// 业务流程:1. 获取产品统计 2. 构建响应数据
|
||||
func (s *ProductApplicationServiceImpl) GetProductStats(ctx context.Context) (*responses.ProductStatsResponse, error) {
|
||||
stats, err := s.productService.GetProductStats()
|
||||
stats, err := s.productSubscriptionService.GetProductStats(ctx)
|
||||
if err != nil {
|
||||
s.logger.Error("获取产品统计失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取产品统计失败: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &responses.ProductStatsResponse{
|
||||
@@ -387,66 +186,42 @@ func (s *ProductApplicationServiceImpl) GetProductStats(ctx context.Context) (*r
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 私有方法
|
||||
|
||||
func (s *ProductApplicationServiceImpl) validateCreateProduct(cmd *commands.CreateProductCommand) error {
|
||||
if cmd.Name == "" {
|
||||
return errors.New("产品名称不能为空")
|
||||
}
|
||||
if cmd.Code == "" {
|
||||
return errors.New("产品编号不能为空")
|
||||
}
|
||||
if cmd.CategoryID == "" {
|
||||
return errors.New("产品分类不能为空")
|
||||
}
|
||||
if cmd.Price < 0 {
|
||||
return errors.New("产品价格不能为负数")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ProductApplicationServiceImpl) validateUpdateProduct(cmd *commands.UpdateProductCommand) error {
|
||||
if cmd.ID == "" {
|
||||
return errors.New("产品ID不能为空")
|
||||
}
|
||||
if cmd.Price < 0 {
|
||||
return errors.New("产品价格不能为负数")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// convertToProductInfoResponse 转换为产品信息响应
|
||||
func (s *ProductApplicationServiceImpl) convertToProductInfoResponse(product *entities.Product) *responses.ProductInfoResponse {
|
||||
return &responses.ProductInfoResponse{
|
||||
ID: product.ID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
Price: product.Price,
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsVisible: product.IsVisible,
|
||||
IsPackage: product.IsPackage,
|
||||
SEOTitle: product.SEOTitle,
|
||||
response := &responses.ProductInfoResponse{
|
||||
ID: product.ID,
|
||||
Name: product.Name,
|
||||
Code: product.Code,
|
||||
Description: product.Description,
|
||||
Content: product.Content,
|
||||
CategoryID: product.CategoryID,
|
||||
Price: product.Price,
|
||||
IsEnabled: product.IsEnabled,
|
||||
IsVisible: product.IsVisible,
|
||||
IsPackage: product.IsPackage,
|
||||
SEOTitle: product.SEOTitle,
|
||||
SEODescription: product.SEODescription,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
SEOKeywords: product.SEOKeywords,
|
||||
CreatedAt: product.CreatedAt,
|
||||
UpdatedAt: product.UpdatedAt,
|
||||
}
|
||||
|
||||
// 添加分类信息
|
||||
if product.Category != nil {
|
||||
response.Category = s.convertToCategoryInfoResponse(product.Category)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// convertToCategoryInfoResponse 转换为分类信息响应
|
||||
func (s *ProductApplicationServiceImpl) convertToCategoryInfoResponse(category *entities.ProductCategory) *responses.CategoryInfoResponse {
|
||||
return &responses.CategoryInfoResponse{
|
||||
ID: category.ID,
|
||||
Name: category.Name,
|
||||
Code: category.Code,
|
||||
Description: category.Description,
|
||||
ParentID: category.ParentID,
|
||||
Level: category.Level,
|
||||
Sort: category.Sort,
|
||||
IsEnabled: category.IsEnabled,
|
||||
IsVisible: category.IsVisible,
|
||||
CreatedAt: category.CreatedAt,
|
||||
UpdatedAt: category.UpdatedAt,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,346 +2,149 @@ package product
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/application/product/dto/commands"
|
||||
appQueries "tyapi-server/internal/application/product/dto/queries"
|
||||
"tyapi-server/internal/application/product/dto/responses"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
repoQueries "tyapi-server/internal/domains/product/repositories/queries"
|
||||
"tyapi-server/internal/domains/product/services"
|
||||
|
||||
"go.uber.org/zap"
|
||||
product_service "tyapi-server/internal/domains/product/services"
|
||||
)
|
||||
|
||||
// SubscriptionApplicationServiceImpl 订阅应用服务实现
|
||||
// 负责业务流程编排、事务管理、数据转换,不直接操作仓库
|
||||
type SubscriptionApplicationServiceImpl struct {
|
||||
subscriptionRepo repositories.SubscriptionRepository
|
||||
productRepo repositories.ProductRepository
|
||||
productService *services.ProductService
|
||||
logger *zap.Logger
|
||||
productSubscriptionService *product_service.ProductSubscriptionService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewSubscriptionApplicationService 创建订阅应用服务
|
||||
func NewSubscriptionApplicationService(
|
||||
subscriptionRepo repositories.SubscriptionRepository,
|
||||
productRepo repositories.ProductRepository,
|
||||
productService *services.ProductService,
|
||||
productSubscriptionService *product_service.ProductSubscriptionService,
|
||||
logger *zap.Logger,
|
||||
) SubscriptionApplicationService {
|
||||
return &SubscriptionApplicationServiceImpl{
|
||||
subscriptionRepo: subscriptionRepo,
|
||||
productRepo: productRepo,
|
||||
productService: productService,
|
||||
logger: logger,
|
||||
productSubscriptionService: productSubscriptionService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateSubscriptionPrice 更新订阅价格
|
||||
// 业务流程:1. 获取订阅 2. 更新价格 3. 保存订阅
|
||||
func (s *SubscriptionApplicationServiceImpl) UpdateSubscriptionPrice(ctx context.Context, cmd *commands.UpdateSubscriptionPriceCommand) error {
|
||||
// 1. 获取现有订阅
|
||||
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 更新订阅价格
|
||||
subscription.Price = cmd.Price
|
||||
|
||||
// 3. 保存订阅
|
||||
// 这里需要扩展领域服务来支持更新操作
|
||||
// 暂时返回错误
|
||||
return fmt.Errorf("更新订阅价格功能暂未实现")
|
||||
}
|
||||
|
||||
// CreateSubscription 创建订阅
|
||||
// 业务流程:1. 创建订阅
|
||||
func (s *SubscriptionApplicationServiceImpl) CreateSubscription(ctx context.Context, cmd *commands.CreateSubscriptionCommand) error {
|
||||
// 1. 参数验证
|
||||
if err := s.validateCreateSubscription(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 检查产品是否存在且可订阅
|
||||
canSubscribe, err := s.productService.CanUserSubscribeProduct(cmd.UserID, cmd.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !canSubscribe {
|
||||
return errors.New("产品不可订阅或用户已有活跃订阅")
|
||||
}
|
||||
|
||||
// 3. 检查产品是否存在
|
||||
_, err = s.productRepo.GetByID(ctx, cmd.ProductID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
// 5. 创建订阅实体
|
||||
subscription := entities.Subscription{
|
||||
UserID: cmd.UserID,
|
||||
ProductID: cmd.ProductID,
|
||||
Status: entities.SubscriptionStatusActive,
|
||||
Price: cmd.Price,
|
||||
APIUsed: 0,
|
||||
}
|
||||
|
||||
// 6. 保存到仓储
|
||||
createdSubscription, err := s.subscriptionRepo.Create(ctx, subscription)
|
||||
if err != nil {
|
||||
s.logger.Error("创建订阅失败", zap.Error(err))
|
||||
return fmt.Errorf("创建订阅失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("创建订阅成功", zap.String("id", createdSubscription.ID), zap.String("user_id", cmd.UserID), zap.String("product_id", cmd.ProductID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateSubscription 更新订阅
|
||||
func (s *SubscriptionApplicationServiceImpl) UpdateSubscription(ctx context.Context, cmd *commands.UpdateSubscriptionCommand) error {
|
||||
// 1. 获取现有订阅
|
||||
subscription, err := s.subscriptionRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("订阅不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 更新字段
|
||||
if cmd.Price >= 0 {
|
||||
subscription.Price = cmd.Price
|
||||
}
|
||||
|
||||
// 3. 保存到仓储
|
||||
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
|
||||
s.logger.Error("更新订阅失败", zap.Error(err))
|
||||
return fmt.Errorf("更新订阅失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("更新订阅成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
_, err := s.productSubscriptionService.CreateSubscription(ctx, cmd.UserID, cmd.ProductID)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSubscriptionByID 根据ID获取订阅
|
||||
// 业务流程:1. 获取订阅信息 2. 构建响应数据
|
||||
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionByID(ctx context.Context, query *appQueries.GetSubscriptionQuery) (*responses.SubscriptionInfoResponse, error) {
|
||||
subscription, err := s.subscriptionRepo.GetByID(ctx, query.ID)
|
||||
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, query.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("订阅不存在: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
response := s.convertToSubscriptionInfoResponse(&subscription)
|
||||
|
||||
// 加载产品信息
|
||||
product, err := s.productRepo.GetByID(ctx, subscription.ProductID)
|
||||
if err == nil {
|
||||
response.Product = s.convertToProductSimpleResponse(&product)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
return s.convertToSubscriptionInfoResponse(subscription), nil
|
||||
}
|
||||
|
||||
// ListSubscriptions 获取订阅列表
|
||||
// 业务流程:1. 获取订阅列表 2. 构建响应数据
|
||||
func (s *SubscriptionApplicationServiceImpl) ListSubscriptions(ctx context.Context, query *appQueries.ListSubscriptionsQuery) (*responses.SubscriptionListResponse, error) {
|
||||
// 构建仓储查询
|
||||
repoQuery := &repoQueries.ListSubscriptionsQuery{
|
||||
Page: query.Page,
|
||||
PageSize: query.PageSize,
|
||||
UserID: query.UserID,
|
||||
ProductID: query.ProductID,
|
||||
Status: query.Status,
|
||||
SortBy: query.SortBy,
|
||||
SortOrder: query.SortOrder,
|
||||
}
|
||||
|
||||
// 调用仓储
|
||||
subscriptions, total, err := s.subscriptionRepo.ListSubscriptions(ctx, repoQuery)
|
||||
if err != nil {
|
||||
s.logger.Error("获取订阅列表失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取订阅列表失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]responses.SubscriptionInfoResponse, len(subscriptions))
|
||||
for i, subscription := range subscriptions {
|
||||
items[i] = *s.convertToSubscriptionInfoResponse(subscription)
|
||||
}
|
||||
|
||||
// 这里需要扩展领域服务来支持列表查询
|
||||
// 暂时返回空列表
|
||||
return &responses.SubscriptionListResponse{
|
||||
Total: total,
|
||||
Total: 0,
|
||||
Page: query.Page,
|
||||
Size: query.PageSize,
|
||||
Items: items,
|
||||
Items: []responses.SubscriptionInfoResponse{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
// UpdateAPIUsage 更新API使用量
|
||||
func (s *SubscriptionApplicationServiceImpl) UpdateAPIUsage(ctx context.Context, cmd *commands.UpdateAPIUsageCommand) error {
|
||||
subscription, err := s.subscriptionRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("订阅不存在: %w", err)
|
||||
}
|
||||
|
||||
subscription.IncrementAPIUsage(cmd.APIUsed)
|
||||
|
||||
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
|
||||
s.logger.Error("更新API使用量失败", zap.Error(err))
|
||||
return fmt.Errorf("更新API使用量失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("更新API使用量成功", zap.String("id", cmd.ID), zap.Int64("api_used", cmd.APIUsed))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetAPIUsage 重置API使用量
|
||||
func (s *SubscriptionApplicationServiceImpl) ResetAPIUsage(ctx context.Context, cmd *commands.ResetAPIUsageCommand) error {
|
||||
subscription, err := s.subscriptionRepo.GetByID(ctx, cmd.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("订阅不存在: %w", err)
|
||||
}
|
||||
|
||||
subscription.ResetAPIUsage()
|
||||
|
||||
if err := s.subscriptionRepo.Update(ctx, subscription); err != nil {
|
||||
s.logger.Error("重置API使用量失败", zap.Error(err))
|
||||
return fmt.Errorf("重置API使用量失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("重置API使用量成功", zap.String("id", cmd.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// GetUserSubscriptions 获取用户订阅
|
||||
// 业务流程:1. 获取用户订阅 2. 构建响应数据
|
||||
func (s *SubscriptionApplicationServiceImpl) GetUserSubscriptions(ctx context.Context, query *appQueries.GetUserSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error) {
|
||||
subscriptions, err := s.subscriptionRepo.FindByUserID(ctx, query.UserID)
|
||||
subscriptions, err := s.productSubscriptionService.GetUserSubscriptions(ctx, query.UserID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户订阅失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取用户订阅失败: %w", err)
|
||||
}
|
||||
|
||||
// 过滤状态
|
||||
if query.Status != nil {
|
||||
filtered := make([]*entities.Subscription, 0)
|
||||
for _, sub := range subscriptions {
|
||||
if sub.Status == *query.Status {
|
||||
filtered = append(filtered, sub)
|
||||
}
|
||||
}
|
||||
subscriptions = filtered
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]*responses.SubscriptionInfoResponse, len(subscriptions))
|
||||
for i, subscription := range subscriptions {
|
||||
items[i] = s.convertToSubscriptionInfoResponse(subscription)
|
||||
for i := range subscriptions {
|
||||
items[i] = s.convertToSubscriptionInfoResponse(subscriptions[i])
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// GetProductSubscriptions 获取产品订阅
|
||||
// 业务流程:1. 获取产品订阅 2. 构建响应数据
|
||||
func (s *SubscriptionApplicationServiceImpl) GetProductSubscriptions(ctx context.Context, query *appQueries.GetProductSubscriptionsQuery) ([]*responses.SubscriptionInfoResponse, error) {
|
||||
subscriptions, err := s.subscriptionRepo.FindByProductID(ctx, query.ProductID)
|
||||
if err != nil {
|
||||
s.logger.Error("获取产品订阅失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取产品订阅失败: %w", err)
|
||||
}
|
||||
|
||||
// 过滤状态
|
||||
if query.Status != nil {
|
||||
filtered := make([]*entities.Subscription, 0)
|
||||
for _, sub := range subscriptions {
|
||||
if sub.Status == *query.Status {
|
||||
filtered = append(filtered, sub)
|
||||
}
|
||||
}
|
||||
subscriptions = filtered
|
||||
}
|
||||
|
||||
// 转换为响应对象
|
||||
items := make([]*responses.SubscriptionInfoResponse, len(subscriptions))
|
||||
for i, subscription := range subscriptions {
|
||||
items[i] = s.convertToSubscriptionInfoResponse(subscription)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
// 这里需要扩展领域服务来支持按产品查询订阅
|
||||
// 暂时返回空列表
|
||||
return []*responses.SubscriptionInfoResponse{}, nil
|
||||
}
|
||||
|
||||
// GetSubscriptionUsage 获取订阅使用情况
|
||||
// 业务流程:1. 获取订阅使用情况 2. 构建响应数据
|
||||
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionUsage(ctx context.Context, subscriptionID string) (*responses.SubscriptionUsageResponse, error) {
|
||||
subscription, err := s.subscriptionRepo.GetByID(ctx, subscriptionID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("订阅不存在: %w", err)
|
||||
}
|
||||
|
||||
return &responses.SubscriptionUsageResponse{
|
||||
ID: subscription.ID,
|
||||
ProductID: subscription.ProductID,
|
||||
APIUsed: subscription.APIUsed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetSubscriptionStats 获取订阅统计
|
||||
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionStats(ctx context.Context) (*responses.SubscriptionStatsResponse, error) {
|
||||
// 获取各种状态的订阅数量
|
||||
total, err := s.subscriptionRepo.CountActive(ctx)
|
||||
if err != nil {
|
||||
s.logger.Error("获取订阅统计失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取订阅统计失败: %w", err)
|
||||
}
|
||||
|
||||
// TODO: 计算总收入,需要从订单系统获取
|
||||
totalRevenue := 0.0
|
||||
|
||||
return &responses.SubscriptionStatsResponse{
|
||||
TotalSubscriptions: total,
|
||||
TotalRevenue: totalRevenue,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 私有方法
|
||||
|
||||
func (s *SubscriptionApplicationServiceImpl) validateCreateSubscription(cmd *commands.CreateSubscriptionCommand) error {
|
||||
if cmd.UserID == "" {
|
||||
return errors.New("用户ID不能为空")
|
||||
}
|
||||
if cmd.ProductID == "" {
|
||||
return errors.New("产品ID不能为空")
|
||||
}
|
||||
if cmd.Price < 0 {
|
||||
return errors.New("订阅价格不能为负数")
|
||||
}
|
||||
if cmd.APILimit < 0 {
|
||||
return errors.New("API调用限制不能为负数")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SubscriptionApplicationServiceImpl) calculateEndDate(duration string) (*time.Time, error) {
|
||||
if duration == "" {
|
||||
return nil, nil // 永久订阅
|
||||
}
|
||||
|
||||
d, err := s.parseDuration(duration)
|
||||
subscription, err := s.productSubscriptionService.GetSubscriptionByID(ctx, subscriptionID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
endDate := time.Now().Add(d)
|
||||
return &endDate, nil
|
||||
return &responses.SubscriptionUsageResponse{
|
||||
ID: subscription.ID,
|
||||
ProductID: subscription.ProductID,
|
||||
APIUsed: subscription.APIUsed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SubscriptionApplicationServiceImpl) parseDuration(duration string) (time.Duration, error) {
|
||||
switch duration {
|
||||
case "7d":
|
||||
return 7 * 24 * time.Hour, nil
|
||||
case "30d":
|
||||
return 30 * 24 * time.Hour, nil
|
||||
case "90d":
|
||||
return 90 * 24 * time.Hour, nil
|
||||
case "365d":
|
||||
return 365 * 24 * time.Hour, nil
|
||||
default:
|
||||
return time.ParseDuration(duration)
|
||||
}
|
||||
// GetSubscriptionStats 获取订阅统计信息
|
||||
// 业务流程:1. 获取订阅统计 2. 构建响应数据
|
||||
func (s *SubscriptionApplicationServiceImpl) GetSubscriptionStats(ctx context.Context) (*responses.SubscriptionStatsResponse, error) {
|
||||
// 这里需要扩展领域服务来支持统计功能
|
||||
// 暂时返回默认值
|
||||
return &responses.SubscriptionStatsResponse{
|
||||
TotalSubscriptions: 0,
|
||||
TotalRevenue: 0,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// convertToSubscriptionInfoResponse 转换为订阅信息响应
|
||||
func (s *SubscriptionApplicationServiceImpl) convertToSubscriptionInfoResponse(subscription *entities.Subscription) *responses.SubscriptionInfoResponse {
|
||||
return &responses.SubscriptionInfoResponse{
|
||||
ID: subscription.ID,
|
||||
UserID: subscription.UserID,
|
||||
ProductID: subscription.ProductID,
|
||||
Price: subscription.Price,
|
||||
APIUsed: subscription.APIUsed,
|
||||
CreatedAt: subscription.CreatedAt,
|
||||
UpdatedAt: subscription.UpdatedAt,
|
||||
ID: subscription.ID,
|
||||
UserID: subscription.UserID,
|
||||
ProductID: subscription.ProductID,
|
||||
Price: subscription.Price,
|
||||
APIUsed: subscription.APIUsed,
|
||||
CreatedAt: subscription.CreatedAt,
|
||||
UpdatedAt: subscription.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// convertToProductSimpleResponse 转换为产品简单信息响应
|
||||
func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleResponse(product *entities.Product) *responses.ProductSimpleResponse {
|
||||
return &responses.ProductSimpleResponse{
|
||||
ID: product.ID,
|
||||
@@ -351,4 +154,12 @@ func (s *SubscriptionApplicationServiceImpl) convertToProductSimpleResponse(prod
|
||||
Price: product.Price,
|
||||
IsPackage: product.IsPackage,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// convertToCategorySimpleResponse 转换为分类简单信息响应
|
||||
func (s *SubscriptionApplicationServiceImpl) convertToCategorySimpleResponse(category *entities.ProductCategory) *responses.CategorySimpleResponse {
|
||||
return &responses.CategorySimpleResponse{
|
||||
ID: category.ID,
|
||||
Name: category.Name,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,23 +3,23 @@ package commands
|
||||
// RegisterUserCommand 用户注册命令
|
||||
// @Description 用户注册请求参数
|
||||
type RegisterUserCommand struct {
|
||||
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
|
||||
Password string `json:"password" binding:"required,min=6,max=128" example:"password123"`
|
||||
ConfirmPassword string `json:"confirm_password" binding:"required,eqfield=Password" example:"password123"`
|
||||
Phone string `json:"phone" binding:"required,phone" example:"13800138000"`
|
||||
Password string `json:"password" binding:"required,strong_password" example:"Password123"`
|
||||
ConfirmPassword string `json:"confirm_password" binding:"required,eqfield=Password" example:"Password123"`
|
||||
Code string `json:"code" binding:"required,len=6" example:"123456"`
|
||||
}
|
||||
|
||||
// LoginWithPasswordCommand 密码登录命令
|
||||
// @Description 使用密码进行用户登录请求参数
|
||||
type LoginWithPasswordCommand struct {
|
||||
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
|
||||
Password string `json:"password" binding:"required" example:"password123"`
|
||||
Phone string `json:"phone" binding:"required,phone" example:"13800138000"`
|
||||
Password string `json:"password" binding:"required,min=6,max=128" example:"Password123"`
|
||||
}
|
||||
|
||||
// LoginWithSMSCommand 短信验证码登录命令
|
||||
// @Description 使用短信验证码进行用户登录请求参数
|
||||
type LoginWithSMSCommand struct {
|
||||
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
|
||||
Phone string `json:"phone" binding:"required,phone" example:"13800138000"`
|
||||
Code string `json:"code" binding:"required,len=6" example:"123456"`
|
||||
}
|
||||
|
||||
@@ -27,40 +27,41 @@ type LoginWithSMSCommand struct {
|
||||
// @Description 修改用户密码请求参数
|
||||
type ChangePasswordCommand struct {
|
||||
UserID string `json:"-"`
|
||||
OldPassword string `json:"old_password" binding:"required" example:"oldpassword123"`
|
||||
NewPassword string `json:"new_password" binding:"required,min=6,max=128" example:"newpassword123"`
|
||||
ConfirmNewPassword string `json:"confirm_new_password" binding:"required,eqfield=NewPassword" example:"newpassword123"`
|
||||
OldPassword string `json:"old_password" binding:"required,min=6,max=128" example:"OldPassword123"`
|
||||
NewPassword string `json:"new_password" binding:"required,strong_password" example:"NewPassword123"`
|
||||
ConfirmNewPassword string `json:"confirm_new_password" binding:"required,eqfield=NewPassword" example:"NewPassword123"`
|
||||
Code string `json:"code" binding:"required,len=6" example:"123456"`
|
||||
}
|
||||
|
||||
// ResetPasswordCommand 重置密码命令
|
||||
// @Description 重置用户密码请求参数(忘记密码时使用)
|
||||
type ResetPasswordCommand struct {
|
||||
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
|
||||
NewPassword string `json:"new_password" binding:"required,min=6,max=128" example:"newpassword123"`
|
||||
ConfirmNewPassword string `json:"confirm_new_password" binding:"required,eqfield=NewPassword" example:"newpassword123"`
|
||||
Phone string `json:"phone" binding:"required,phone" example:"13800138000"`
|
||||
NewPassword string `json:"new_password" binding:"required,strong_password" example:"NewPassword123"`
|
||||
ConfirmNewPassword string `json:"confirm_new_password" binding:"required,eqfield=NewPassword" example:"NewPassword123"`
|
||||
Code string `json:"code" binding:"required,len=6" example:"123456"`
|
||||
}
|
||||
|
||||
// SendCodeCommand 发送验证码命令
|
||||
// @Description 发送短信验证码请求参数
|
||||
type SendCodeCommand struct {
|
||||
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
|
||||
Scene string `json:"scene" binding:"required,oneof=register login change_password reset_password bind unbind" example:"register"`
|
||||
Phone string `json:"phone" binding:"required,phone" example:"13800138000"`
|
||||
Scene string `json:"scene" binding:"required,oneof=register login change_password reset_password bind unbind certification" example:"register"`
|
||||
}
|
||||
|
||||
// UpdateProfileCommand 更新用户信息命令
|
||||
// @Description 更新用户基本信息请求参数
|
||||
type UpdateProfileCommand struct {
|
||||
UserID string `json:"-"`
|
||||
Phone string `json:"phone" binding:"omitempty,len=11" example:"13800138000"`
|
||||
// 可以在这里添加更多用户信息字段,如昵称、头像等
|
||||
UserID string `json:"-"`
|
||||
Phone string `json:"phone" binding:"omitempty,phone" example:"13800138000"`
|
||||
DisplayName string `json:"display_name" binding:"omitempty,min=2,max=50" example:"用户昵称"`
|
||||
Email string `json:"email" binding:"omitempty,email" example:"user@example.com"`
|
||||
}
|
||||
|
||||
// VerifyCodeCommand 验证验证码命令
|
||||
// @Description 验证短信验证码请求参数
|
||||
type VerifyCodeCommand struct {
|
||||
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
|
||||
Phone string `json:"phone" binding:"required,phone" example:"13800138000"`
|
||||
Code string `json:"code" binding:"required,len=6" example:"123456"`
|
||||
Scene string `json:"scene" binding:"required,oneof=register login change_password reset_password bind unbind" example:"register"`
|
||||
Scene string `json:"scene" binding:"required,oneof=register login change_password reset_password bind unbind certification" example:"register"`
|
||||
}
|
||||
|
||||
@@ -42,6 +42,12 @@ type LoginUserResponse struct {
|
||||
type UserProfileResponse struct {
|
||||
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
|
||||
Phone string `json:"phone" example:"13800138000"`
|
||||
Username string `json:"username,omitempty" example:"admin"`
|
||||
UserType string `json:"user_type" example:"user"`
|
||||
IsActive bool `json:"is_active" example:"true"`
|
||||
LastLoginAt *time.Time `json:"last_login_at,omitempty" example:"2024-01-01T00:00:00Z"`
|
||||
LoginCount int `json:"login_count" example:"10"`
|
||||
Permissions []string `json:"permissions,omitempty" example:"['user:read','user:write']"`
|
||||
EnterpriseInfo *EnterpriseInfoResponse `json:"enterprise_info,omitempty"`
|
||||
IsCertified bool `json:"is_certified" example:"false"`
|
||||
CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"`
|
||||
|
||||
@@ -11,62 +11,59 @@ import (
|
||||
"tyapi-server/internal/application/user/dto/responses"
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/events"
|
||||
"tyapi-server/internal/domains/user/repositories"
|
||||
user_service "tyapi-server/internal/domains/user/services"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
"tyapi-server/internal/shared/middleware"
|
||||
)
|
||||
|
||||
// UserApplicationServiceImpl 用户应用服务实现
|
||||
// 负责业务流程编排、事务管理、数据转换,不直接操作仓库
|
||||
type UserApplicationServiceImpl struct {
|
||||
userRepo repositories.UserRepository
|
||||
enterpriseInfoRepo repositories.EnterpriseInfoRepository
|
||||
smsCodeService *user_service.SMSCodeService
|
||||
eventBus interfaces.EventBus
|
||||
jwtAuth *middleware.JWTAuthMiddleware
|
||||
logger *zap.Logger
|
||||
userManagementService *user_service.UserManagementService
|
||||
userAuthService *user_service.UserAuthService
|
||||
smsCodeService *user_service.SMSCodeService
|
||||
enterpriseService *user_service.EnterpriseService
|
||||
eventBus interfaces.EventBus
|
||||
jwtAuth *middleware.JWTAuthMiddleware
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewUserApplicationService 创建用户应用服务
|
||||
func NewUserApplicationService(
|
||||
userRepo repositories.UserRepository,
|
||||
enterpriseInfoRepo repositories.EnterpriseInfoRepository,
|
||||
userManagementService *user_service.UserManagementService,
|
||||
userAuthService *user_service.UserAuthService,
|
||||
smsCodeService *user_service.SMSCodeService,
|
||||
enterpriseService *user_service.EnterpriseService,
|
||||
eventBus interfaces.EventBus,
|
||||
jwtAuth *middleware.JWTAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
) UserApplicationService {
|
||||
return &UserApplicationServiceImpl{
|
||||
userRepo: userRepo,
|
||||
enterpriseInfoRepo: enterpriseInfoRepo,
|
||||
smsCodeService: smsCodeService,
|
||||
eventBus: eventBus,
|
||||
jwtAuth: jwtAuth,
|
||||
logger: logger,
|
||||
userManagementService: userManagementService,
|
||||
userAuthService: userAuthService,
|
||||
smsCodeService: smsCodeService,
|
||||
enterpriseService: enterpriseService,
|
||||
eventBus: eventBus,
|
||||
jwtAuth: jwtAuth,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
// 业务流程:1. 验证短信验证码 2. 创建用户 3. 发布注册事件
|
||||
func (s *UserApplicationServiceImpl) Register(ctx context.Context, cmd *commands.RegisterUserCommand) (*responses.RegisterUserResponse, error) {
|
||||
// 1. 验证短信验证码
|
||||
if err := s.smsCodeService.VerifyCode(ctx, cmd.Phone, cmd.Code, entities.SMSSceneRegister); err != nil {
|
||||
return nil, fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
|
||||
if _, err := s.userRepo.GetByPhone(ctx, cmd.Phone); err == nil {
|
||||
return nil, fmt.Errorf("手机号已存在")
|
||||
}
|
||||
|
||||
user, err := entities.NewUser(cmd.Phone, cmd.Password)
|
||||
// 2. 创建用户
|
||||
user, err := s.userManagementService.CreateUser(ctx, cmd.Phone, cmd.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建用户失败: %w", err)
|
||||
}
|
||||
|
||||
createdUser, err := s.userRepo.Create(ctx, *user)
|
||||
if err != nil {
|
||||
s.logger.Error("创建用户失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建用户失败: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3. 发布用户注册事件
|
||||
event := events.NewUserRegisteredEvent(user, "")
|
||||
if err := s.eventBus.Publish(ctx, event); err != nil {
|
||||
s.logger.Warn("发布用户注册事件失败", zap.Error(err))
|
||||
@@ -75,35 +72,63 @@ func (s *UserApplicationServiceImpl) Register(ctx context.Context, cmd *commands
|
||||
s.logger.Info("用户注册成功", zap.String("user_id", user.ID), zap.String("phone", user.Phone))
|
||||
|
||||
return &responses.RegisterUserResponse{
|
||||
ID: createdUser.ID,
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LoginWithPassword 密码登录
|
||||
// 业务流程:1. 验证用户密码 2. 生成访问令牌 3. 更新登录统计 4. 获取用户权限
|
||||
func (s *UserApplicationServiceImpl) LoginWithPassword(ctx context.Context, cmd *commands.LoginWithPasswordCommand) (*responses.LoginUserResponse, error) {
|
||||
user, err := s.userRepo.GetByPhone(ctx, cmd.Phone)
|
||||
// 1. 验证用户密码
|
||||
user, err := s.userAuthService.ValidatePassword(ctx, cmd.Phone, cmd.Password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户名或密码错误")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !user.CanLogin() {
|
||||
return nil, fmt.Errorf("用户状态异常,无法登录")
|
||||
}
|
||||
|
||||
if !user.CheckPassword(cmd.Password) {
|
||||
return nil, fmt.Errorf("用户名或密码错误")
|
||||
}
|
||||
|
||||
accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone)
|
||||
// 2. 生成包含用户类型的token
|
||||
accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone, user.UserType)
|
||||
if err != nil {
|
||||
s.logger.Error("生成令牌失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("生成访问令牌失败")
|
||||
}
|
||||
|
||||
userProfile, err := s.GetUserProfile(ctx, user.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取用户信息失败: %w", err)
|
||||
// 3. 如果是管理员,更新登录统计
|
||||
if user.IsAdmin() {
|
||||
if err := s.userManagementService.UpdateLoginStats(ctx, user.ID); err != nil {
|
||||
s.logger.Error("更新登录统计失败", zap.Error(err))
|
||||
}
|
||||
// 重新获取用户信息以获取最新的登录统计
|
||||
updatedUser, err := s.userManagementService.GetUserByID(ctx, user.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("重新获取用户信息失败", zap.Error(err))
|
||||
} else {
|
||||
user = updatedUser
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 获取用户权限(仅管理员)
|
||||
var permissions []string
|
||||
if user.IsAdmin() {
|
||||
permissions, err = s.userAuthService.GetUserPermissions(ctx, user)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户权限失败", zap.Error(err))
|
||||
permissions = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 构建用户信息
|
||||
userProfile := &responses.UserProfileResponse{
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
Username: user.Username,
|
||||
UserType: user.UserType,
|
||||
IsActive: user.Active,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
LoginCount: user.LoginCount,
|
||||
Permissions: permissions,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
|
||||
return &responses.LoginUserResponse{
|
||||
@@ -116,140 +141,163 @@ func (s *UserApplicationServiceImpl) LoginWithPassword(ctx context.Context, cmd
|
||||
}
|
||||
|
||||
// LoginWithSMS 短信验证码登录
|
||||
// 业务流程:1. 验证短信验证码 2. 验证用户登录状态 3. 生成访问令牌 4. 更新登录统计 5. 获取用户权限
|
||||
func (s *UserApplicationServiceImpl) LoginWithSMS(ctx context.Context, cmd *commands.LoginWithSMSCommand) (*responses.LoginUserResponse, error) {
|
||||
// 1. 验证短信验证码
|
||||
if err := s.smsCodeService.VerifyCode(ctx, cmd.Phone, cmd.Code, entities.SMSSceneLogin); err != nil {
|
||||
return nil, fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
|
||||
user, err := s.userRepo.GetByPhone(ctx, cmd.Phone)
|
||||
// 2. 验证用户登录状态
|
||||
user, err := s.userAuthService.ValidateUserLogin(ctx, cmd.Phone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !user.CanLogin() {
|
||||
return nil, fmt.Errorf("用户状态异常,无法登录")
|
||||
}
|
||||
|
||||
accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone)
|
||||
// 3. 生成包含用户类型的token
|
||||
accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone, user.UserType)
|
||||
if err != nil {
|
||||
s.logger.Error("生成令牌失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("生成访问令牌失败")
|
||||
}
|
||||
|
||||
userProfile, err := s.GetUserProfile(ctx, user.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取用户信息失败: %w", err)
|
||||
// 4. 如果是管理员,更新登录统计
|
||||
if user.IsAdmin() {
|
||||
if err := s.userManagementService.UpdateLoginStats(ctx, user.ID); err != nil {
|
||||
s.logger.Error("更新登录统计失败", zap.Error(err))
|
||||
}
|
||||
// 重新获取用户信息以获取最新的登录统计
|
||||
updatedUser, err := s.userManagementService.GetUserByID(ctx, user.ID)
|
||||
if err != nil {
|
||||
s.logger.Error("重新获取用户信息失败", zap.Error(err))
|
||||
} else {
|
||||
user = updatedUser
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 获取用户权限(仅管理员)
|
||||
var permissions []string
|
||||
if user.IsAdmin() {
|
||||
permissions, err = s.userAuthService.GetUserPermissions(ctx, user)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户权限失败", zap.Error(err))
|
||||
permissions = []string{}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 构建用户信息
|
||||
userProfile := &responses.UserProfileResponse{
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
Username: user.Username,
|
||||
UserType: user.UserType,
|
||||
IsActive: user.Active,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
LoginCount: user.LoginCount,
|
||||
Permissions: permissions,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
|
||||
return &responses.LoginUserResponse{
|
||||
User: userProfile,
|
||||
AccessToken: accessToken,
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: 86400, // 24h
|
||||
ExpiresIn: int64(s.jwtAuth.GetExpiresIn().Seconds()), // 168h
|
||||
LoginMethod: "sms",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SendSMS 发送短信验证码
|
||||
// 业务流程:1. 发送短信验证码
|
||||
func (s *UserApplicationServiceImpl) SendSMS(ctx context.Context, cmd *commands.SendCodeCommand) error {
|
||||
return s.smsCodeService.SendCode(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), "", "")
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
// 业务流程:1. 修改用户密码
|
||||
func (s *UserApplicationServiceImpl) ChangePassword(ctx context.Context, cmd *commands.ChangePasswordCommand) error {
|
||||
user, err := s.userRepo.GetByID(ctx, cmd.UserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
if err := s.smsCodeService.VerifyCode(ctx, user.Phone, cmd.Code, entities.SMSSceneChangePassword); err != nil {
|
||||
return fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
|
||||
if err := user.ChangePassword(cmd.OldPassword, cmd.NewPassword, cmd.ConfirmNewPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.userRepo.Update(ctx, user); err != nil {
|
||||
return fmt.Errorf("密码更新失败: %w", err)
|
||||
}
|
||||
|
||||
event := events.NewUserPasswordChangedEvent(user.ID, user.Phone, "")
|
||||
if err := s.eventBus.Publish(ctx, event); err != nil {
|
||||
s.logger.Warn("发布密码修改事件失败", zap.Error(err))
|
||||
}
|
||||
|
||||
s.logger.Info("密码修改成功", zap.String("user_id", cmd.UserID))
|
||||
return nil
|
||||
return s.userAuthService.ChangePassword(ctx, cmd.UserID, cmd.OldPassword, cmd.NewPassword)
|
||||
}
|
||||
|
||||
// ResetPassword 重置密码
|
||||
// 业务流程:1. 验证短信验证码 2. 重置用户密码
|
||||
func (s *UserApplicationServiceImpl) ResetPassword(ctx context.Context, cmd *commands.ResetPasswordCommand) error {
|
||||
user, err := s.userRepo.GetByPhone(ctx, cmd.Phone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在")
|
||||
}
|
||||
|
||||
// 1. 验证短信验证码
|
||||
if err := s.smsCodeService.VerifyCode(ctx, cmd.Phone, cmd.Code, entities.SMSSceneResetPassword); err != nil {
|
||||
return fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
|
||||
if err := user.ResetPassword(cmd.NewPassword, cmd.ConfirmNewPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.userRepo.Update(ctx, *user); err != nil {
|
||||
return fmt.Errorf("密码更新失败: %w", err)
|
||||
}
|
||||
|
||||
event := events.NewUserPasswordChangedEvent(user.ID, user.Phone, "")
|
||||
if err := s.eventBus.Publish(ctx, event); err != nil {
|
||||
s.logger.Warn("发布密码重置事件失败", zap.Error(err))
|
||||
}
|
||||
|
||||
s.logger.Info("密码重置成功", zap.String("user_id", user.ID), zap.String("phone", user.Phone))
|
||||
return nil
|
||||
// 2. 重置用户密码
|
||||
return s.userAuthService.ResetPassword(ctx, cmd.Phone, cmd.NewPassword)
|
||||
}
|
||||
|
||||
// GetUserProfile 获取用户信息
|
||||
// GetUserProfile 获取用户资料
|
||||
// 业务流程:1. 获取用户信息 2. 获取企业信息 3. 构建响应数据
|
||||
func (s *UserApplicationServiceImpl) GetUserProfile(ctx context.Context, userID string) (*responses.UserProfileResponse, error) {
|
||||
if userID == "" {
|
||||
return nil, fmt.Errorf("用户ID不能为空")
|
||||
}
|
||||
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
// 1. 获取用户信息(包含企业信息)
|
||||
user, err := s.enterpriseService.GetUserWithEnterpriseInfo(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
response := &responses.UserProfileResponse{
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
|
||||
// 获取企业信息(如果存在)
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
s.logger.Debug("用户暂无企业信息", zap.String("user_id", userID))
|
||||
response.IsCertified = false
|
||||
} else {
|
||||
response.EnterpriseInfo = &responses.EnterpriseInfoResponse{
|
||||
ID: enterpriseInfo.ID,
|
||||
CompanyName: enterpriseInfo.CompanyName,
|
||||
UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode,
|
||||
LegalPersonName: enterpriseInfo.LegalPersonName,
|
||||
LegalPersonID: enterpriseInfo.LegalPersonID,
|
||||
IsOCRVerified: enterpriseInfo.IsOCRVerified,
|
||||
IsFaceVerified: enterpriseInfo.IsFaceVerified,
|
||||
IsCertified: enterpriseInfo.IsCertified,
|
||||
CertifiedAt: enterpriseInfo.CertifiedAt,
|
||||
CreatedAt: enterpriseInfo.CreatedAt,
|
||||
UpdatedAt: enterpriseInfo.UpdatedAt,
|
||||
// 2. 获取用户权限(仅管理员)
|
||||
var permissions []string
|
||||
if user.IsAdmin() {
|
||||
permissions, err = s.userAuthService.GetUserPermissions(ctx, user)
|
||||
if err != nil {
|
||||
s.logger.Error("获取用户权限失败", zap.Error(err))
|
||||
permissions = []string{}
|
||||
}
|
||||
response.IsCertified = enterpriseInfo.IsCertified
|
||||
}
|
||||
|
||||
return response, nil
|
||||
// 3. 构建用户信息
|
||||
userProfile := &responses.UserProfileResponse{
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
Username: user.Username,
|
||||
UserType: user.UserType,
|
||||
IsActive: user.Active,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
LoginCount: user.LoginCount,
|
||||
Permissions: permissions,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
|
||||
// 4. 添加企业信息
|
||||
if user.EnterpriseInfo != nil {
|
||||
userProfile.EnterpriseInfo = &responses.EnterpriseInfoResponse{
|
||||
ID: user.EnterpriseInfo.ID,
|
||||
CompanyName: user.EnterpriseInfo.CompanyName,
|
||||
UnifiedSocialCode: user.EnterpriseInfo.UnifiedSocialCode,
|
||||
LegalPersonName: user.EnterpriseInfo.LegalPersonName,
|
||||
LegalPersonID: user.EnterpriseInfo.LegalPersonID,
|
||||
CreatedAt: user.EnterpriseInfo.CreatedAt,
|
||||
UpdatedAt: user.EnterpriseInfo.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
return userProfile, nil
|
||||
}
|
||||
|
||||
// GetUser 获取用户信息
|
||||
// 业务流程:1. 获取用户信息 2. 构建响应数据
|
||||
func (s *UserApplicationServiceImpl) GetUser(ctx context.Context, query *queries.GetUserQuery) (*responses.UserProfileResponse, error) {
|
||||
// ... implementation
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
user, err := s.userManagementService.GetUserByID(ctx, query.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &responses.UserProfileResponse{
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
Username: user.Username,
|
||||
UserType: user.UserType,
|
||||
IsActive: user.Active,
|
||||
LastLoginAt: user.LastLoginAt,
|
||||
LoginCount: user.LoginCount,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ type Config struct {
|
||||
Development DevelopmentConfig `mapstructure:"development"`
|
||||
App AppConfig `mapstructure:"app"`
|
||||
WechatWork WechatWorkConfig `mapstructure:"wechat_work"`
|
||||
Esign EsignConfig `mapstructure:"esign"`
|
||||
}
|
||||
|
||||
// ServerConfig HTTP服务器配置
|
||||
@@ -210,3 +211,44 @@ type OCRConfig struct {
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
}
|
||||
|
||||
// EsignConfig e签宝配置
|
||||
type EsignConfig struct {
|
||||
AppID string `mapstructure:"app_id"` // 应用ID
|
||||
AppSecret string `mapstructure:"app_secret"` // 应用密钥
|
||||
ServerURL string `mapstructure:"server_url"` // 服务器URL
|
||||
TemplateID string `mapstructure:"template_id"` // 模板ID
|
||||
|
||||
Contract ContractConfig `mapstructure:"contract"` // 合同配置
|
||||
Auth AuthConfig `mapstructure:"auth"` // 认证配置
|
||||
Sign SignConfig `mapstructure:"sign"` // 签署配置
|
||||
Notify NotifyConfig `mapstructure:"notify"` // 通知配置
|
||||
}
|
||||
|
||||
// ContractConfig 合同配置
|
||||
type ContractConfig struct {
|
||||
Name string `mapstructure:"name"` // 合同名称
|
||||
ExpireDays int `mapstructure:"expire_days"` // 签署链接过期天数
|
||||
RetryCount int `mapstructure:"retry_count"` // 重试次数
|
||||
}
|
||||
|
||||
// AuthConfig 认证配置
|
||||
type AuthConfig struct {
|
||||
OrgAuthModes []string `mapstructure:"org_auth_modes"` // 机构可用认证模式
|
||||
DefaultAuthMode string `mapstructure:"default_auth_mode"` // 默认认证模式
|
||||
PsnAuthModes []string `mapstructure:"psn_auth_modes"` // 个人可用认证模式
|
||||
WillingnessAuthModes []string `mapstructure:"willingness_auth_modes"` // 意愿认证模式
|
||||
}
|
||||
|
||||
// SignConfig 签署配置
|
||||
type SignConfig struct {
|
||||
AutoFinish bool `mapstructure:"auto_finish"` // 是否自动完结
|
||||
SignFieldStyle int `mapstructure:"sign_field_style"` // 签署区样式
|
||||
ClientType string `mapstructure:"client_type"` // 客户端类型
|
||||
}
|
||||
|
||||
// NotifyConfig 通知配置
|
||||
type NotifyConfig struct {
|
||||
Types string `mapstructure:"types"` // 通知类型
|
||||
RedirectURL string `mapstructure:"redirect_url"` // 重定向URL
|
||||
}
|
||||
|
||||
153
internal/container/cache_setup.go
Normal file
153
internal/container/cache_setup.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/config"
|
||||
"tyapi-server/internal/shared/cache"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// SetupGormCache 设置GORM缓存插件
|
||||
func SetupGormCache(db *gorm.DB, cacheService interfaces.CacheService, cfg *config.Config, logger *zap.Logger) error {
|
||||
// 创建缓存配置
|
||||
cacheConfig := cache.CacheConfig{
|
||||
DefaultTTL: 30 * time.Minute,
|
||||
TablePrefix: "gorm_cache",
|
||||
MaxCacheSize: 1000,
|
||||
CacheComplexSQL: false,
|
||||
EnableStats: true,
|
||||
EnableWarmup: true,
|
||||
PenetrationGuard: true,
|
||||
BloomFilter: false,
|
||||
AutoInvalidate: true,
|
||||
InvalidateDelay: 100 * time.Millisecond,
|
||||
|
||||
// 配置启用缓存的表
|
||||
EnabledTables: []string{
|
||||
"users",
|
||||
"products",
|
||||
"product_categories",
|
||||
"enterprise_info_submit_records",
|
||||
// 添加更多需要缓存的表
|
||||
},
|
||||
|
||||
// 配置禁用缓存的表(日志表等)
|
||||
DisabledTables: []string{
|
||||
"sms_codes", // 短信验证码变化频繁
|
||||
"audit_logs", // 审计日志
|
||||
"system_logs", // 系统日志
|
||||
"operation_logs", // 操作日志
|
||||
},
|
||||
}
|
||||
|
||||
// 创建缓存插件
|
||||
cachePlugin := cache.NewGormCachePlugin(cacheService, logger, cacheConfig)
|
||||
|
||||
// 注册插件到GORM
|
||||
if err := db.Use(cachePlugin); err != nil {
|
||||
logger.Error("注册GORM缓存插件失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("GORM缓存插件已成功注册",
|
||||
zap.Duration("default_ttl", cacheConfig.DefaultTTL),
|
||||
zap.Strings("enabled_tables", cacheConfig.EnabledTables),
|
||||
zap.Strings("disabled_tables", cacheConfig.DisabledTables),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCacheConfig 根据环境获取缓存配置
|
||||
func GetCacheConfig(cfg *config.Config) cache.CacheConfig {
|
||||
// 生产环境配置
|
||||
if cfg.Server.Mode == "release" {
|
||||
return cache.CacheConfig{
|
||||
DefaultTTL: 60 * time.Minute, // 生产环境延长缓存时间
|
||||
TablePrefix: "prod_cache",
|
||||
MaxCacheSize: 5000, // 生产环境增加缓存大小
|
||||
CacheComplexSQL: false, // 生产环境不缓存复杂SQL
|
||||
EnableStats: true,
|
||||
EnableWarmup: true,
|
||||
PenetrationGuard: true,
|
||||
BloomFilter: true, // 生产环境启用布隆过滤器
|
||||
AutoInvalidate: true,
|
||||
InvalidateDelay: 50 * time.Millisecond,
|
||||
|
||||
EnabledTables: []string{
|
||||
"users", "products", "product_categories",
|
||||
"enterprise_info_submit_records", "certifications",
|
||||
"product_documentations",
|
||||
},
|
||||
|
||||
DisabledTables: []string{
|
||||
"sms_codes", "audit_logs", "system_logs",
|
||||
"operation_logs", "sessions", "api_keys",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 开发环境配置
|
||||
return cache.CacheConfig{
|
||||
DefaultTTL: 10 * time.Minute, // 开发环境缩短缓存时间,便于测试
|
||||
TablePrefix: "dev_cache",
|
||||
MaxCacheSize: 500,
|
||||
CacheComplexSQL: true, // 开发环境允许缓存复杂SQL,便于调试
|
||||
EnableStats: true,
|
||||
EnableWarmup: false, // 开发环境关闭预热
|
||||
PenetrationGuard: false, // 开发环境关闭穿透保护
|
||||
BloomFilter: false,
|
||||
AutoInvalidate: true,
|
||||
InvalidateDelay: 200 * time.Millisecond,
|
||||
|
||||
EnabledTables: []string{
|
||||
"users", "products", "product_categories",
|
||||
},
|
||||
|
||||
DisabledTables: []string{
|
||||
"sms_codes", "audit_logs",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CacheMetrics 缓存性能指标
|
||||
type CacheMetrics struct {
|
||||
HitRate float64 `json:"hit_rate"`
|
||||
MissRate float64 `json:"miss_rate"`
|
||||
TotalHits int64 `json:"total_hits"`
|
||||
TotalMisses int64 `json:"total_misses"`
|
||||
CachedTables int `json:"cached_tables"`
|
||||
CacheSize int64 `json:"cache_size"`
|
||||
AvgResponseMs float64 `json:"avg_response_ms"`
|
||||
}
|
||||
|
||||
// GetCacheMetrics 获取缓存性能指标
|
||||
func GetCacheMetrics(cacheService interfaces.CacheService) (*CacheMetrics, error) {
|
||||
stats, err := cacheService.Stats(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
total := stats.Hits + stats.Misses
|
||||
hitRate := float64(0)
|
||||
missRate := float64(0)
|
||||
|
||||
if total > 0 {
|
||||
hitRate = float64(stats.Hits) / float64(total) * 100
|
||||
missRate = float64(stats.Misses) / float64(total) * 100
|
||||
}
|
||||
|
||||
return &CacheMetrics{
|
||||
HitRate: hitRate,
|
||||
MissRate: missRate,
|
||||
TotalHits: stats.Hits,
|
||||
TotalMisses: stats.Misses,
|
||||
CacheSize: stats.Memory,
|
||||
CachedTables: int(stats.Keys),
|
||||
}, nil
|
||||
}
|
||||
@@ -2,40 +2,37 @@ package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/fx"
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/application/admin"
|
||||
"tyapi-server/internal/application/certification"
|
||||
"tyapi-server/internal/application/finance"
|
||||
"tyapi-server/internal/application/product"
|
||||
"tyapi-server/internal/application/user"
|
||||
"tyapi-server/internal/config"
|
||||
domain_admin_repo "tyapi-server/internal/domains/admin/repositories"
|
||||
admin_service "tyapi-server/internal/domains/admin/services"
|
||||
domain_certification_repo "tyapi-server/internal/domains/certification/repositories"
|
||||
certification_service "tyapi-server/internal/domains/certification/services"
|
||||
domain_finance_repo "tyapi-server/internal/domains/finance/repositories"
|
||||
finance_service "tyapi-server/internal/domains/finance/services"
|
||||
domain_product_repo "tyapi-server/internal/domains/product/repositories"
|
||||
product_service "tyapi-server/internal/domains/product/services"
|
||||
domain_user_repo "tyapi-server/internal/domains/user/repositories"
|
||||
user_service "tyapi-server/internal/domains/user/services"
|
||||
"tyapi-server/internal/infrastructure/cache"
|
||||
"tyapi-server/internal/infrastructure/database"
|
||||
admin_repo "tyapi-server/internal/infrastructure/database/repositories/admin"
|
||||
certification_repo "tyapi-server/internal/infrastructure/database/repositories/certification"
|
||||
finance_repo "tyapi-server/internal/infrastructure/database/repositories/finance"
|
||||
product_repo "tyapi-server/internal/infrastructure/database/repositories/product"
|
||||
user_repo "tyapi-server/internal/infrastructure/database/repositories/user"
|
||||
"tyapi-server/internal/infrastructure/external/ocr"
|
||||
"tyapi-server/internal/infrastructure/external/sms"
|
||||
"tyapi-server/internal/infrastructure/external/storage"
|
||||
"tyapi-server/internal/infrastructure/http/handlers"
|
||||
"tyapi-server/internal/infrastructure/http/routes"
|
||||
shared_database "tyapi-server/internal/shared/database"
|
||||
"tyapi-server/internal/shared/esign"
|
||||
"tyapi-server/internal/shared/events"
|
||||
"tyapi-server/internal/shared/health"
|
||||
"tyapi-server/internal/shared/hooks"
|
||||
@@ -49,6 +46,10 @@ import (
|
||||
"tyapi-server/internal/shared/saga"
|
||||
sharedStorage "tyapi-server/internal/shared/storage"
|
||||
"tyapi-server/internal/shared/tracing"
|
||||
"tyapi-server/internal/shared/validator"
|
||||
|
||||
domain_user_repo "tyapi-server/internal/domains/user/repositories"
|
||||
user_repo "tyapi-server/internal/infrastructure/database/repositories/user"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
@@ -92,7 +93,7 @@ func NewContainer() *Container {
|
||||
return defaultLogger
|
||||
},
|
||||
// 数据库连接
|
||||
func(cfg *config.Config) (*gorm.DB, error) {
|
||||
func(cfg *config.Config, cacheService interfaces.CacheService, logger *zap.Logger) (*gorm.DB, error) {
|
||||
dbCfg := database.Config{
|
||||
Host: cfg.Database.Host,
|
||||
Port: cfg.Database.Port,
|
||||
@@ -109,6 +110,13 @@ func NewContainer() *Container {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置GORM缓存插件
|
||||
if err := SetupGormCache(db.DB, cacheService, cfg, logger); err != nil {
|
||||
logger.Warn("GORM缓存插件设置失败", zap.Error(err))
|
||||
// 不返回错误,允许系统在没有缓存的情况下运行
|
||||
}
|
||||
|
||||
return db.DB, nil
|
||||
},
|
||||
// Redis客户端
|
||||
@@ -132,6 +140,10 @@ func NewContainer() *Container {
|
||||
func(cfg *config.Config) config.AppConfig {
|
||||
return cfg.App
|
||||
},
|
||||
// 事务管理器
|
||||
func(db *gorm.DB, logger *zap.Logger) *shared_database.TransactionManager {
|
||||
return shared_database.NewTransactionManager(db, logger)
|
||||
},
|
||||
// 短信服务
|
||||
sms.NewAliSMSService,
|
||||
// 存储服务
|
||||
@@ -158,6 +170,19 @@ func NewContainer() *Container {
|
||||
},
|
||||
fx.As(new(sharedOCR.OCRService)),
|
||||
),
|
||||
// e签宝服务
|
||||
func(cfg *config.Config) *esign.Client {
|
||||
esignConfig, err := esign.NewConfig(
|
||||
cfg.Esign.AppID,
|
||||
cfg.Esign.AppSecret,
|
||||
cfg.Esign.ServerURL,
|
||||
cfg.Esign.TemplateID,
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("e签宝配置创建失败: %v", err))
|
||||
}
|
||||
return esign.NewClient(esignConfig)
|
||||
},
|
||||
),
|
||||
|
||||
// 高级特性模块
|
||||
@@ -185,7 +210,7 @@ func NewContainer() *Container {
|
||||
// HTTP基础组件
|
||||
fx.Provide(
|
||||
sharedhttp.NewResponseBuilder,
|
||||
sharedhttp.NewRequestValidatorZh,
|
||||
validator.NewRequestValidator,
|
||||
sharedhttp.NewGinRouter,
|
||||
),
|
||||
|
||||
@@ -199,6 +224,7 @@ func NewContainer() *Container {
|
||||
NewRequestLoggerMiddlewareWrapper,
|
||||
middleware.NewJWTAuthMiddleware,
|
||||
middleware.NewOptionalAuthMiddleware,
|
||||
middleware.NewAdminAuthMiddleware,
|
||||
middleware.NewTraceIDMiddleware,
|
||||
middleware.NewErrorTrackingMiddleware,
|
||||
NewRequestBodyLoggerMiddlewareWrapper,
|
||||
@@ -223,30 +249,6 @@ func NewContainer() *Container {
|
||||
),
|
||||
),
|
||||
|
||||
// 仓储层 - 管理员域
|
||||
fx.Provide(
|
||||
// 管理员仓储 - 同时注册具体类型和接口类型
|
||||
fx.Annotate(
|
||||
admin_repo.NewGormAdminRepository,
|
||||
fx.As(new(domain_admin_repo.AdminRepository)),
|
||||
),
|
||||
// 管理员登录日志仓储
|
||||
fx.Annotate(
|
||||
admin_repo.NewGormAdminLoginLogRepository,
|
||||
fx.As(new(domain_admin_repo.AdminLoginLogRepository)),
|
||||
),
|
||||
// 管理员操作日志仓储
|
||||
fx.Annotate(
|
||||
admin_repo.NewGormAdminOperationLogRepository,
|
||||
fx.As(new(domain_admin_repo.AdminOperationLogRepository)),
|
||||
),
|
||||
// 管理员权限仓储
|
||||
fx.Annotate(
|
||||
admin_repo.NewGormAdminPermissionRepository,
|
||||
fx.As(new(domain_admin_repo.AdminPermissionRepository)),
|
||||
),
|
||||
),
|
||||
|
||||
// 仓储层 - 认证域
|
||||
fx.Provide(
|
||||
// 认证申请仓储
|
||||
@@ -254,25 +256,20 @@ func NewContainer() *Container {
|
||||
certification_repo.NewGormCertificationRepository,
|
||||
fx.As(new(domain_certification_repo.CertificationRepository)),
|
||||
),
|
||||
// 人脸识别记录仓储
|
||||
// 企业信息提交记录仓储
|
||||
fx.Annotate(
|
||||
certification_repo.NewGormFaceVerifyRecordRepository,
|
||||
fx.As(new(domain_certification_repo.FaceVerifyRecordRepository)),
|
||||
certification_repo.NewGormEnterpriseInfoSubmitRecordRepository,
|
||||
fx.As(new(domain_certification_repo.EnterpriseInfoSubmitRecordRepository)),
|
||||
),
|
||||
// 合同记录仓储
|
||||
// e签宝生成合同记录仓储
|
||||
fx.Annotate(
|
||||
certification_repo.NewGormContractRecordRepository,
|
||||
fx.As(new(domain_certification_repo.ContractRecordRepository)),
|
||||
certification_repo.NewGormEsignContractGenerateRecordRepository,
|
||||
fx.As(new(domain_certification_repo.EsignContractGenerateRecordRepository)),
|
||||
),
|
||||
// 营业执照上传记录仓储
|
||||
// e签宝签署合同记录仓储
|
||||
fx.Annotate(
|
||||
certification_repo.NewGormLicenseUploadRecordRepository,
|
||||
fx.As(new(domain_certification_repo.LicenseUploadRecordRepository)),
|
||||
),
|
||||
// 通知记录仓储
|
||||
fx.Annotate(
|
||||
certification_repo.NewGormNotificationRecordRepository,
|
||||
fx.As(new(domain_certification_repo.NotificationRecordRepository)),
|
||||
certification_repo.NewGormEsignContractSignRecordRepository,
|
||||
fx.As(new(domain_certification_repo.EsignContractSignRecordRepository)),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -311,14 +308,18 @@ func NewContainer() *Container {
|
||||
|
||||
// 领域服务
|
||||
fx.Provide(
|
||||
user_service.NewUserService,
|
||||
user_service.NewUserManagementService,
|
||||
user_service.NewUserAuthService,
|
||||
user_service.NewSMSCodeService,
|
||||
user_service.NewEnterpriseService,
|
||||
admin_service.NewAdminService,
|
||||
certification_service.NewCertificationService,
|
||||
product_service.NewProductManagementService,
|
||||
product_service.NewProductSubscriptionService,
|
||||
certification_service.NewCertificationManagementService,
|
||||
certification_service.NewCertificationWorkflowService,
|
||||
certification_service.NewCertificationStateMachine,
|
||||
certification_service.NewEnterpriseInfoSubmitRecordService,
|
||||
certification_service.NewCertificationEsignService,
|
||||
finance_service.NewFinanceService,
|
||||
product_service.NewProductService,
|
||||
),
|
||||
|
||||
// 应用服务
|
||||
@@ -328,16 +329,16 @@ func NewContainer() *Container {
|
||||
user.NewUserApplicationService,
|
||||
fx.As(new(user.UserApplicationService)),
|
||||
),
|
||||
// 管理员应用服务 - 绑定到接口
|
||||
fx.Annotate(
|
||||
admin.NewAdminApplicationService,
|
||||
fx.As(new(admin.AdminApplicationService)),
|
||||
),
|
||||
// 认证应用服务 - 绑定到接口
|
||||
fx.Annotate(
|
||||
certification.NewCertificationApplicationService,
|
||||
fx.As(new(certification.CertificationApplicationService)),
|
||||
),
|
||||
// e签宝回调应用服务 - 绑定到接口
|
||||
fx.Annotate(
|
||||
certification.NewEsignCallbackApplicationService,
|
||||
fx.As(new(certification.EsignCallbackApplicationService)),
|
||||
),
|
||||
// 财务应用服务 - 绑定到接口
|
||||
fx.Annotate(
|
||||
finance.NewFinanceApplicationService,
|
||||
@@ -364,28 +365,28 @@ func NewContainer() *Container {
|
||||
fx.Provide(
|
||||
// 用户HTTP处理器
|
||||
handlers.NewUserHandler,
|
||||
// 管理员HTTP处理器
|
||||
handlers.NewAdminHandler,
|
||||
// 认证HTTP处理器
|
||||
handlers.NewCertificationHandler,
|
||||
// 财务HTTP处理器
|
||||
handlers.NewFinanceHandler,
|
||||
// 产品HTTP处理器
|
||||
handlers.NewProductHandler,
|
||||
// 产品管理员HTTP处理器
|
||||
handlers.NewProductAdminHandler,
|
||||
),
|
||||
|
||||
// 路由注册
|
||||
fx.Provide(
|
||||
// 用户路由
|
||||
routes.NewUserRoutes,
|
||||
// 管理员路由
|
||||
routes.NewAdminRoutes,
|
||||
// 认证路由
|
||||
routes.NewCertificationRoutes,
|
||||
// 财务路由
|
||||
routes.NewFinanceRoutes,
|
||||
// 产品路由
|
||||
routes.NewProductRoutes,
|
||||
// 产品管理员路由
|
||||
routes.NewProductAdminRoutes,
|
||||
),
|
||||
|
||||
// 应用生命周期
|
||||
@@ -460,10 +461,10 @@ func RegisterMiddlewares(
|
||||
func RegisterRoutes(
|
||||
router *sharedhttp.GinRouter,
|
||||
userRoutes *routes.UserRoutes,
|
||||
adminRoutes *routes.AdminRoutes,
|
||||
certificationRoutes *routes.CertificationRoutes,
|
||||
financeRoutes *routes.FinanceRoutes,
|
||||
productRoutes *routes.ProductRoutes,
|
||||
productAdminRoutes *routes.ProductAdminRoutes,
|
||||
cfg *config.Config,
|
||||
logger *zap.Logger,
|
||||
) {
|
||||
@@ -471,10 +472,10 @@ func RegisterRoutes(
|
||||
|
||||
// 注册所有路由
|
||||
userRoutes.Register(router)
|
||||
adminRoutes.Register(router)
|
||||
certificationRoutes.Register(router)
|
||||
financeRoutes.Register(router)
|
||||
productRoutes.Register(router)
|
||||
productAdminRoutes.Register(router)
|
||||
|
||||
// 打印注册的路由信息
|
||||
router.PrintRoutes()
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// AdminRole 管理员角色枚举
|
||||
// 定义系统中不同级别的管理员角色,用于权限控制和功能分配
|
||||
type AdminRole string
|
||||
|
||||
const (
|
||||
RoleSuperAdmin AdminRole = "super_admin" // 超级管理员 - 拥有所有权限
|
||||
RoleAdmin AdminRole = "admin" // 普通管理员 - 拥有大部分管理权限
|
||||
RoleReviewer AdminRole = "reviewer" // 审核员 - 仅拥有审核相关权限
|
||||
)
|
||||
|
||||
// Admin 管理员实体
|
||||
// 系统管理员的核心信息,包括账户信息、权限配置、操作统计等
|
||||
// 支持多角色管理,提供完整的权限控制和操作审计功能
|
||||
type Admin struct {
|
||||
// 基础标识
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"管理员唯一标识"`
|
||||
Username string `gorm:"type:varchar(100);not null;uniqueIndex" comment:"登录用户名"`
|
||||
Password string `gorm:"type:varchar(255);not null" comment:"登录密码(加密存储)"`
|
||||
Email string `gorm:"type:varchar(255);not null;uniqueIndex" comment:"邮箱地址"`
|
||||
Phone string `gorm:"type:varchar(20)" comment:"手机号码"`
|
||||
RealName string `gorm:"type:varchar(100);not null" comment:"真实姓名"`
|
||||
Role AdminRole `gorm:"type:varchar(50);not null;default:'reviewer'" comment:"管理员角色"`
|
||||
|
||||
// 状态信息 - 账户状态和登录统计
|
||||
IsActive bool `gorm:"default:true" comment:"账户是否激活"`
|
||||
LastLoginAt *time.Time `comment:"最后登录时间"`
|
||||
LoginCount int `gorm:"default:0" comment:"登录次数统计"`
|
||||
|
||||
// 权限信息 - 细粒度权限控制
|
||||
Permissions string `gorm:"type:text" comment:"权限列表(JSON格式存储)"`
|
||||
|
||||
// 审核统计 - 管理员的工作绩效统计
|
||||
ReviewCount int `gorm:"default:0" comment:"审核总数"`
|
||||
ApprovedCount int `gorm:"default:0" comment:"通过数量"`
|
||||
RejectedCount int `gorm:"default:0" comment:"拒绝数量"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// AdminLoginLog 管理员登录日志实体
|
||||
// 记录管理员的所有登录尝试,包括成功和失败的登录记录
|
||||
// 用于安全审计和异常登录检测
|
||||
type AdminLoginLog struct {
|
||||
// 基础标识
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"日志记录唯一标识"`
|
||||
AdminID string `gorm:"type:varchar(36);not null;index" comment:"管理员ID"`
|
||||
Username string `gorm:"type:varchar(100);not null" comment:"登录用户名"`
|
||||
IP string `gorm:"type:varchar(45);not null" comment:"登录IP地址"`
|
||||
UserAgent string `gorm:"type:varchar(500)" comment:"客户端信息"`
|
||||
Status string `gorm:"type:varchar(20);not null" comment:"登录状态(success/failed)"`
|
||||
Message string `gorm:"type:varchar(500)" comment:"登录结果消息"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
}
|
||||
|
||||
// AdminOperationLog 管理员操作日志实体
|
||||
// 记录管理员在系统中的所有重要操作,用于操作审计和问题追踪
|
||||
// 支持操作类型、资源、详情等完整信息的记录
|
||||
type AdminOperationLog struct {
|
||||
// 基础标识
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"操作日志唯一标识"`
|
||||
AdminID string `gorm:"type:varchar(36);not null;index" comment:"操作管理员ID"`
|
||||
Username string `gorm:"type:varchar(100);not null" comment:"操作管理员用户名"`
|
||||
Action string `gorm:"type:varchar(100);not null" comment:"操作类型"`
|
||||
Resource string `gorm:"type:varchar(100);not null" comment:"操作资源"`
|
||||
ResourceID string `gorm:"type:varchar(36)" comment:"资源ID"`
|
||||
Details string `gorm:"type:text" comment:"操作详情(JSON格式)"`
|
||||
IP string `gorm:"type:varchar(45);not null" comment:"操作IP地址"`
|
||||
UserAgent string `gorm:"type:varchar(500)" comment:"客户端信息"`
|
||||
Status string `gorm:"type:varchar(20);not null" comment:"操作状态(success/failed)"`
|
||||
Message string `gorm:"type:varchar(500)" comment:"操作结果消息"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
}
|
||||
|
||||
// AdminPermission 管理员权限实体
|
||||
// 定义系统中的所有权限项,支持模块化权限管理
|
||||
// 每个权限都有唯一的代码标识,便于程序中的权限检查
|
||||
type AdminPermission struct {
|
||||
// 基础标识
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"权限唯一标识"`
|
||||
Name string `gorm:"type:varchar(100);not null;uniqueIndex" comment:"权限名称"`
|
||||
Code string `gorm:"type:varchar(100);not null;uniqueIndex" comment:"权限代码"`
|
||||
Description string `gorm:"type:varchar(500)" comment:"权限描述"`
|
||||
Module string `gorm:"type:varchar(50);not null" comment:"所属模块"`
|
||||
IsActive bool `gorm:"default:true" comment:"权限是否启用"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// AdminRolePermission 角色权限关联实体
|
||||
// 建立角色和权限之间的多对多关系,实现基于角色的权限控制(RBAC)
|
||||
type AdminRolePermission struct {
|
||||
// 基础标识
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"关联记录唯一标识"`
|
||||
Role AdminRole `gorm:"type:varchar(50);not null;index" comment:"角色"`
|
||||
PermissionID string `gorm:"type:varchar(36);not null;index" comment:"权限ID"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
}
|
||||
|
||||
// TableName 指定数据库表名
|
||||
func (Admin) TableName() string {
|
||||
return "admins"
|
||||
}
|
||||
|
||||
// IsValid 检查管理员账户是否有效
|
||||
// 判断管理员账户是否处于可用状态,包括激活状态和软删除状态检查
|
||||
func (a *Admin) IsValid() bool {
|
||||
return a.IsActive && a.DeletedAt.Time.IsZero()
|
||||
}
|
||||
|
||||
// UpdateLastLoginAt 更新最后登录时间
|
||||
// 在管理员成功登录后调用,记录最新的登录时间
|
||||
func (a *Admin) UpdateLastLoginAt() {
|
||||
now := time.Now()
|
||||
a.LastLoginAt = &now
|
||||
}
|
||||
|
||||
// Deactivate 停用管理员账户
|
||||
// 将管理员账户设置为非激活状态,禁止登录和操作
|
||||
func (a *Admin) Deactivate() {
|
||||
a.IsActive = false
|
||||
}
|
||||
|
||||
// Activate 激活管理员账户
|
||||
// 重新启用管理员账户,允许正常登录和操作
|
||||
func (a *Admin) Activate() {
|
||||
a.IsActive = true
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (a *Admin) BeforeCreate(tx *gorm.DB) error {
|
||||
if a.ID == "" {
|
||||
a.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/domains/admin/entities"
|
||||
"tyapi-server/internal/domains/admin/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// AdminStats 管理员统计
|
||||
type AdminStats struct {
|
||||
TotalAdmins int64
|
||||
ActiveAdmins int64
|
||||
TodayLogins int64
|
||||
TotalOperations int64
|
||||
}
|
||||
|
||||
// AdminRepository 管理员仓储接口
|
||||
type AdminRepository interface {
|
||||
interfaces.Repository[entities.Admin]
|
||||
|
||||
// 管理员认证
|
||||
FindByUsername(ctx context.Context, username string) (*entities.Admin, error)
|
||||
FindByEmail(ctx context.Context, email string) (*entities.Admin, error)
|
||||
|
||||
// 管理员管理
|
||||
ListAdmins(ctx context.Context, query *queries.ListAdminsQuery) ([]*entities.Admin, int64, error)
|
||||
GetStats(ctx context.Context, query *queries.GetAdminInfoQuery) (*AdminStats, error)
|
||||
|
||||
// 权限管理
|
||||
GetPermissionsByRole(ctx context.Context, role entities.AdminRole) ([]entities.AdminPermission, error)
|
||||
UpdatePermissions(ctx context.Context, adminID string, permissions []string) error
|
||||
|
||||
// 统计信息
|
||||
UpdateLoginStats(ctx context.Context, adminID string) error
|
||||
UpdateReviewStats(ctx context.Context, adminID string, approved bool) error
|
||||
}
|
||||
|
||||
// AdminLoginLogRepository 管理员登录日志仓储接口
|
||||
type AdminLoginLogRepository interface {
|
||||
interfaces.Repository[entities.AdminLoginLog]
|
||||
|
||||
// 日志查询
|
||||
ListLogs(ctx context.Context, query *queries.ListAdminLoginLogQuery) ([]*entities.AdminLoginLog, int64, error)
|
||||
|
||||
// 统计查询
|
||||
GetTodayLoginCount(ctx context.Context) (int64, error)
|
||||
GetLoginCountByAdmin(ctx context.Context, adminID string, days int) (int64, error)
|
||||
}
|
||||
|
||||
// AdminOperationLogRepository 管理员操作日志仓储接口
|
||||
type AdminOperationLogRepository interface {
|
||||
interfaces.Repository[entities.AdminOperationLog]
|
||||
|
||||
// 日志查询
|
||||
ListLogs(ctx context.Context, query *queries.ListAdminOperationLogQuery) ([]*entities.AdminOperationLog, int64, error)
|
||||
|
||||
// 统计查询
|
||||
GetTotalOperations(ctx context.Context) (int64, error)
|
||||
GetOperationsByAdmin(ctx context.Context, adminID string, days int) (int64, error)
|
||||
|
||||
// 批量操作
|
||||
BatchCreate(ctx context.Context, logs []entities.AdminOperationLog) error
|
||||
}
|
||||
|
||||
// AdminPermissionRepository 管理员权限仓储接口
|
||||
type AdminPermissionRepository interface {
|
||||
interfaces.Repository[entities.AdminPermission]
|
||||
|
||||
// 权限查询
|
||||
FindByCode(ctx context.Context, code string) (*entities.AdminPermission, error)
|
||||
FindByModule(ctx context.Context, module string) ([]entities.AdminPermission, error)
|
||||
ListActive(ctx context.Context) ([]entities.AdminPermission, error)
|
||||
|
||||
// 角色权限管理
|
||||
GetPermissionsByRole(ctx context.Context, role entities.AdminRole) ([]entities.AdminPermission, error)
|
||||
AssignPermissionsToRole(ctx context.Context, role entities.AdminRole, permissionIDs []string) error
|
||||
RemovePermissionsFromRole(ctx context.Context, role entities.AdminRole, permissionIDs []string) error
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package queries
|
||||
|
||||
type ListAdminLoginLogQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
AdminID string `json:"admin_id"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package queries
|
||||
|
||||
type ListAdminOperationLogQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
AdminID string `json:"admin_id"`
|
||||
Module string `json:"module"`
|
||||
Action string `json:"action"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package queries
|
||||
|
||||
import "tyapi-server/internal/domains/admin/entities"
|
||||
|
||||
type ListAdminsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Username string `json:"username"`
|
||||
Email string `json:"email"`
|
||||
Role entities.AdminRole `json:"role"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
}
|
||||
|
||||
type GetAdminInfoQuery struct {
|
||||
AdminID string `json:"admin_id"`
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/admin/entities"
|
||||
"tyapi-server/internal/domains/admin/repositories"
|
||||
)
|
||||
|
||||
// AdminService 管理员领域服务
|
||||
type AdminService struct {
|
||||
adminRepo repositories.AdminRepository
|
||||
permissionRepo repositories.AdminPermissionRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewAdminService 创建管理员领域服务
|
||||
func NewAdminService(
|
||||
adminRepo repositories.AdminRepository,
|
||||
permissionRepo repositories.AdminPermissionRepository,
|
||||
logger *zap.Logger,
|
||||
) *AdminService {
|
||||
return &AdminService{
|
||||
adminRepo: adminRepo,
|
||||
permissionRepo: permissionRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAdminPermissions 获取管理员权限
|
||||
func (s *AdminService) GetAdminPermissions(ctx context.Context, admin *entities.Admin) ([]string, error) {
|
||||
// 首先从角色获取权限
|
||||
rolePermissions, err := s.adminRepo.GetPermissionsByRole(ctx, admin.Role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 从角色权限中提取权限代码
|
||||
permissions := make([]string, 0, len(rolePermissions))
|
||||
for _, perm := range rolePermissions {
|
||||
permissions = append(permissions, perm.Code)
|
||||
}
|
||||
|
||||
// 如果有自定义权限,也添加进去
|
||||
if admin.Permissions != "" {
|
||||
var customPermissions []string
|
||||
if err := json.Unmarshal([]byte(admin.Permissions), &customPermissions); err == nil {
|
||||
permissions = append(permissions, customPermissions...)
|
||||
}
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/certification/enums"
|
||||
)
|
||||
|
||||
// CertificationCreateRequest 创建认证申请请求
|
||||
type CertificationCreateRequest struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
}
|
||||
|
||||
// CertificationCreateResponse 创建认证申请响应
|
||||
type CertificationCreateResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
}
|
||||
|
||||
// CertificationStatusResponse 认证状态响应
|
||||
type CertificationStatusResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
StatusName string `json:"status_name"`
|
||||
Progress int `json:"progress"`
|
||||
IsUserActionRequired bool `json:"is_user_action_required"`
|
||||
IsAdminActionRequired bool `json:"is_admin_action_required"`
|
||||
|
||||
// 时间节点
|
||||
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty"`
|
||||
FaceVerifiedAt *time.Time `json:"face_verified_at,omitempty"`
|
||||
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty"`
|
||||
ContractApprovedAt *time.Time `json:"contract_approved_at,omitempty"`
|
||||
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||
|
||||
// 关联信息
|
||||
Enterprise *EnterpriseInfoResponse `json:"enterprise,omitempty"`
|
||||
ContractURL string `json:"contract_url,omitempty"`
|
||||
SigningURL string `json:"signing_url,omitempty"`
|
||||
RejectReason string `json:"reject_reason,omitempty"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// SubmitEnterpriseInfoRequest 提交企业信息请求
|
||||
type SubmitEnterpriseInfoRequest struct {
|
||||
CompanyName string `json:"company_name" binding:"required"`
|
||||
UnifiedSocialCode string `json:"unified_social_code" binding:"required"`
|
||||
LegalPersonName string `json:"legal_person_name" binding:"required"`
|
||||
LegalPersonID string `json:"legal_person_id" binding:"required"`
|
||||
LicenseUploadRecordID string `json:"license_upload_record_id" binding:"required"`
|
||||
}
|
||||
|
||||
// SubmitEnterpriseInfoResponse 提交企业信息响应
|
||||
type SubmitEnterpriseInfoResponse struct {
|
||||
ID string `json:"id"`
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
Enterprise *EnterpriseInfoResponse `json:"enterprise"`
|
||||
}
|
||||
|
||||
// FaceVerifyRequest 人脸识别请求
|
||||
type FaceVerifyRequest struct {
|
||||
RealName string `json:"real_name" binding:"required"`
|
||||
IDCardNumber string `json:"id_card_number" binding:"required"`
|
||||
ReturnURL string `json:"return_url" binding:"required"`
|
||||
}
|
||||
|
||||
// FaceVerifyResponse 人脸识别响应
|
||||
type FaceVerifyResponse struct {
|
||||
CertifyID string `json:"certify_id"`
|
||||
VerifyURL string `json:"verify_url"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
// ApplyContractRequest 申请合同请求(无需额外参数)
|
||||
type ApplyContractRequest struct{}
|
||||
|
||||
// ApplyContractResponse 申请合同响应
|
||||
type ApplyContractResponse struct {
|
||||
ID string `json:"id"`
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
ContractAppliedAt time.Time `json:"contract_applied_at"`
|
||||
}
|
||||
|
||||
// SignContractRequest 签署合同请求
|
||||
type SignContractRequest struct {
|
||||
SignatureData string `json:"signature_data,omitempty"`
|
||||
}
|
||||
|
||||
// SignContractResponse 签署合同响应
|
||||
type SignContractResponse struct {
|
||||
ID string `json:"id"`
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
ContractSignedAt time.Time `json:"contract_signed_at"`
|
||||
}
|
||||
|
||||
// CertificationDetailResponse 认证详情响应
|
||||
type CertificationDetailResponse struct {
|
||||
*CertificationStatusResponse
|
||||
|
||||
// 详细记录
|
||||
LicenseUploadRecord *LicenseUploadRecordResponse `json:"license_upload_record,omitempty"`
|
||||
FaceVerifyRecords []FaceVerifyRecordResponse `json:"face_verify_records,omitempty"`
|
||||
ContractRecords []ContractRecordResponse `json:"contract_records,omitempty"`
|
||||
NotificationRecords []NotificationRecordResponse `json:"notification_records,omitempty"`
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package dto
|
||||
|
||||
import "time"
|
||||
|
||||
// EnterpriseInfoResponse 企业信息响应
|
||||
type EnterpriseInfoResponse struct {
|
||||
ID string `json:"id"`
|
||||
CertificationID string `json:"certification_id"`
|
||||
CompanyName string `json:"company_name"`
|
||||
UnifiedSocialCode string `json:"unified_social_code"`
|
||||
LegalPersonName string `json:"legal_person_name"`
|
||||
LegalPersonID string `json:"legal_person_id"`
|
||||
LicenseUploadRecordID string `json:"license_upload_record_id"`
|
||||
OCRRawData string `json:"ocr_raw_data,omitempty"`
|
||||
OCRConfidence float64 `json:"ocr_confidence,omitempty"`
|
||||
IsOCRVerified bool `json:"is_ocr_verified"`
|
||||
IsFaceVerified bool `json:"is_face_verified"`
|
||||
VerificationData string `json:"verification_data,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// LicenseUploadRecordResponse 营业执照上传记录响应
|
||||
type LicenseUploadRecordResponse struct {
|
||||
ID string `json:"id"`
|
||||
CertificationID *string `json:"certification_id,omitempty"`
|
||||
UserID string `json:"user_id"`
|
||||
OriginalFileName string `json:"original_file_name"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
FileType string `json:"file_type"`
|
||||
FileURL string `json:"file_url"`
|
||||
QiNiuKey string `json:"qiniu_key"`
|
||||
OCRProcessed bool `json:"ocr_processed"`
|
||||
OCRSuccess bool `json:"ocr_success"`
|
||||
OCRConfidence float64 `json:"ocr_confidence,omitempty"`
|
||||
OCRRawData string `json:"ocr_raw_data,omitempty"`
|
||||
OCRErrorMessage string `json:"ocr_error_message,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// FaceVerifyRecordResponse 人脸识别记录响应
|
||||
type FaceVerifyRecordResponse struct {
|
||||
ID string `json:"id"`
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
CertifyID string `json:"certify_id"`
|
||||
VerifyURL string `json:"verify_url,omitempty"`
|
||||
ReturnURL string `json:"return_url,omitempty"`
|
||||
RealName string `json:"real_name"`
|
||||
IDCardNumber string `json:"id_card_number"`
|
||||
Status string `json:"status"`
|
||||
StatusName string `json:"status_name"`
|
||||
ResultCode string `json:"result_code,omitempty"`
|
||||
ResultMessage string `json:"result_message,omitempty"`
|
||||
VerifyScore float64 `json:"verify_score,omitempty"`
|
||||
InitiatedAt time.Time `json:"initiated_at"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// ContractRecordResponse 合同记录响应
|
||||
type ContractRecordResponse struct {
|
||||
ID string `json:"id"`
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
AdminID *string `json:"admin_id,omitempty"`
|
||||
ContractType string `json:"contract_type"`
|
||||
ContractURL string `json:"contract_url,omitempty"`
|
||||
SigningURL string `json:"signing_url,omitempty"`
|
||||
SignatureData string `json:"signature_data,omitempty"`
|
||||
SignedAt *time.Time `json:"signed_at,omitempty"`
|
||||
ClientIP string `json:"client_ip,omitempty"`
|
||||
UserAgent string `json:"user_agent,omitempty"`
|
||||
Status string `json:"status"`
|
||||
StatusName string `json:"status_name"`
|
||||
ApprovalNotes string `json:"approval_notes,omitempty"`
|
||||
RejectReason string `json:"reject_reason,omitempty"`
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// NotificationRecordResponse 通知记录响应
|
||||
type NotificationRecordResponse struct {
|
||||
ID string `json:"id"`
|
||||
CertificationID *string `json:"certification_id,omitempty"`
|
||||
UserID *string `json:"user_id,omitempty"`
|
||||
NotificationType string `json:"notification_type"`
|
||||
NotificationTypeName string `json:"notification_type_name"`
|
||||
NotificationScene string `json:"notification_scene"`
|
||||
NotificationSceneName string `json:"notification_scene_name"`
|
||||
Recipient string `json:"recipient"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Content string `json:"content"`
|
||||
TemplateID string `json:"template_id,omitempty"`
|
||||
TemplateParams string `json:"template_params,omitempty"`
|
||||
Status string `json:"status"`
|
||||
StatusName string `json:"status_name"`
|
||||
ErrorMessage string `json:"error_message,omitempty"`
|
||||
SentAt *time.Time `json:"sent_at,omitempty"`
|
||||
RetryCount int `json:"retry_count"`
|
||||
MaxRetryCount int `json:"max_retry_count"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package dto
|
||||
|
||||
// BusinessLicenseResult 营业执照识别结果
|
||||
type BusinessLicenseResult struct {
|
||||
CompanyName string `json:"company_name"` // 公司名称
|
||||
LegalRepresentative string `json:"legal_representative"` // 法定代表人
|
||||
RegisteredCapital string `json:"registered_capital"` // 注册资本
|
||||
RegisteredAddress string `json:"registered_address"` // 注册地址
|
||||
RegistrationNumber string `json:"registration_number"` // 统一社会信用代码
|
||||
BusinessScope string `json:"business_scope"` // 经营范围
|
||||
RegistrationDate string `json:"registration_date"` // 成立日期
|
||||
ValidDate string `json:"valid_date"` // 营业期限
|
||||
Confidence float64 `json:"confidence"` // 识别置信度
|
||||
Words []string `json:"words"` // 识别的所有文字
|
||||
}
|
||||
|
||||
// IDCardResult 身份证识别结果
|
||||
type IDCardResult struct {
|
||||
Side string `json:"side"` // 身份证面(front/back)
|
||||
Name string `json:"name"` // 姓名(正面)
|
||||
Sex string `json:"sex"` // 性别(正面)
|
||||
Nation string `json:"nation"` // 民族(正面)
|
||||
BirthDate string `json:"birth_date"` // 出生日期(正面)
|
||||
Address string `json:"address"` // 住址(正面)
|
||||
IDNumber string `json:"id_number"` // 身份证号码(正面)
|
||||
IssuingAuthority string `json:"issuing_authority"` // 签发机关(背面)
|
||||
ValidDate string `json:"valid_date"` // 有效期限(背面)
|
||||
Confidence float64 `json:"confidence"` // 识别置信度
|
||||
Words []string `json:"words"` // 识别的所有文字
|
||||
}
|
||||
|
||||
// GeneralTextResult 通用文字识别结果
|
||||
type GeneralTextResult struct {
|
||||
Words []string `json:"words"` // 识别的文字列表
|
||||
Confidence float64 `json:"confidence"` // 识别置信度
|
||||
}
|
||||
|
||||
// OCREnterpriseInfo OCR识别的企业信息
|
||||
type OCREnterpriseInfo struct {
|
||||
CompanyName string `json:"company_name"` // 企业名称
|
||||
UnifiedSocialCode string `json:"unified_social_code"` // 统一社会信用代码
|
||||
LegalPersonName string `json:"legal_person_name"` // 法人姓名
|
||||
LegalPersonID string `json:"legal_person_id"` // 法人身份证号
|
||||
Confidence float64 `json:"confidence"` // 识别置信度
|
||||
}
|
||||
|
||||
// LicenseProcessResult 营业执照处理结果
|
||||
type LicenseProcessResult struct {
|
||||
LicenseURL string `json:"license_url"` // 营业执照文件URL
|
||||
EnterpriseInfo *OCREnterpriseInfo `json:"enterprise_info"` // OCR识别的企业信息
|
||||
OCRSuccess bool `json:"ocr_success"` // OCR是否成功
|
||||
OCRError string `json:"ocr_error,omitempty"` // OCR错误信息
|
||||
}
|
||||
|
||||
// UploadLicenseRequest 上传营业执照请求
|
||||
type UploadLicenseRequest struct {
|
||||
// 文件通过multipart/form-data上传,这里定义验证规则
|
||||
}
|
||||
|
||||
// UploadLicenseResponse 上传营业执照响应
|
||||
type UploadLicenseResponse struct {
|
||||
UploadRecordID string `json:"upload_record_id"` // 上传记录ID
|
||||
FileURL string `json:"file_url"` // 文件URL
|
||||
OCRProcessed bool `json:"ocr_processed"` // OCR是否已处理
|
||||
OCRSuccess bool `json:"ocr_success"` // OCR是否成功
|
||||
EnterpriseInfo *OCREnterpriseInfo `json:"enterprise_info"` // OCR识别的企业信息(如果成功)
|
||||
OCRErrorMessage string `json:"ocr_error_message,omitempty"` // OCR错误信息(如果失败)
|
||||
}
|
||||
|
||||
// UploadResult 上传结果
|
||||
type UploadResult struct {
|
||||
Key string `json:"key"` // 文件key
|
||||
URL string `json:"url"` // 文件访问URL
|
||||
MimeType string `json:"mime_type"` // MIME类型
|
||||
Size int64 `json:"size"` // 文件大小
|
||||
Hash string `json:"hash"` // 文件哈希值
|
||||
}
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
|
||||
// Certification 认证申请实体
|
||||
// 这是企业认证流程的核心实体,负责管理整个认证申请的生命周期
|
||||
// 包含认证状态、时间节点、审核信息、合同信息等核心数据
|
||||
type Certification struct {
|
||||
// 基础信息
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"认证申请唯一标识"`
|
||||
@@ -20,36 +19,25 @@ type Certification struct {
|
||||
Status enums.CertificationStatus `gorm:"type:varchar(50);not null;index" json:"status" comment:"当前认证状态"`
|
||||
|
||||
// 流程节点时间戳 - 记录每个关键步骤的完成时间
|
||||
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
|
||||
FaceVerifiedAt *time.Time `json:"face_verified_at,omitempty" comment:"人脸识别完成时间"`
|
||||
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty" comment:"合同申请时间"`
|
||||
ContractApprovedAt *time.Time `json:"contract_approved_at,omitempty" comment:"合同审核通过时间"`
|
||||
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty" comment:"合同签署完成时间"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty" comment:"认证完成时间"`
|
||||
|
||||
// 审核信息 - 管理员审核相关数据
|
||||
AdminID *string `gorm:"type:varchar(36)" json:"admin_id,omitempty" comment:"审核管理员ID"`
|
||||
ApprovalNotes string `gorm:"type:text" json:"approval_notes,omitempty" comment:"审核备注信息"`
|
||||
RejectReason string `gorm:"type:text" json:"reject_reason,omitempty" comment:"拒绝原因说明"`
|
||||
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
|
||||
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty" comment:"企业认证完成时间"`
|
||||
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty" comment:"合同申请时间"`
|
||||
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty" comment:"合同签署完成时间"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty" comment:"认证完成时间"`
|
||||
|
||||
// 合同信息 - 电子合同相关链接
|
||||
ContractURL string `gorm:"type:varchar(500)" json:"contract_url,omitempty" comment:"合同文件访问链接"`
|
||||
SigningURL string `gorm:"type:varchar(500)" json:"signing_url,omitempty" comment:"电子签署链接"`
|
||||
ContractFileID string `gorm:"type:varchar(500)" json:"contract_file_id,omitempty" comment:"合同文件ID"`
|
||||
EsignFlowID string `gorm:"type:varchar(500)" json:"esign_flow_id,omitempty" comment:"签署流程ID"`
|
||||
ContractURL string `gorm:"type:varchar(500)" json:"contract_url,omitempty" comment:"合同文件访问链接"`
|
||||
ContractSignURL string `gorm:"type:varchar(500)" json:"contract_sign_url,omitempty" comment:"合同签署链接"`
|
||||
|
||||
// OCR识别信息 - 营业执照OCR识别结果
|
||||
OCRRequestID string `gorm:"type:varchar(100)" json:"ocr_request_id,omitempty" comment:"OCR识别请求ID"`
|
||||
OCRConfidence float64 `gorm:"type:decimal(5,2)" json:"ocr_confidence,omitempty" comment:"OCR识别置信度(0-1)"`
|
||||
// 认证信息
|
||||
AuthFlowID string `gorm:"type:varchar(500)" json:"auth_flow_id,omitempty" comment:"认证流程ID"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
|
||||
|
||||
// 关联关系 - 与其他实体的关联
|
||||
LicenseUploadRecord *LicenseUploadRecord `gorm:"foreignKey:CertificationID" json:"license_upload_record,omitempty" comment:"关联的营业执照上传记录"`
|
||||
FaceVerifyRecords []FaceVerifyRecord `gorm:"foreignKey:CertificationID" json:"face_verify_records,omitempty" comment:"关联的人脸识别记录列表"`
|
||||
ContractRecords []ContractRecord `gorm:"foreignKey:CertificationID" json:"contract_records,omitempty" comment:"关联的合同记录列表"`
|
||||
NotificationRecords []NotificationRecord `gorm:"foreignKey:CertificationID" json:"notification_records,omitempty" comment:"关联的通知记录列表"`
|
||||
}
|
||||
|
||||
// TableName 指定数据库表名
|
||||
@@ -66,122 +54,94 @@ func (c *Certification) BeforeCreate(tx *gorm.DB) error {
|
||||
}
|
||||
|
||||
// IsStatusChangeable 检查状态是否可以变更
|
||||
// 只有非最终状态(完成/拒绝)的认证申请才能进行状态变更
|
||||
func (c *Certification) IsStatusChangeable() bool {
|
||||
return !enums.IsFinalStatus(c.Status)
|
||||
}
|
||||
|
||||
// CanRetryFaceVerify 检查是否可以重试人脸识别
|
||||
// 只有人脸识别失败状态的申请才能重试
|
||||
func (c *Certification) CanRetryFaceVerify() bool {
|
||||
return c.Status == enums.StatusFaceFailed
|
||||
// GetStatusName 获取状态名称
|
||||
func (c *Certification) GetStatusName() string {
|
||||
return enums.GetStatusName(c.Status)
|
||||
}
|
||||
|
||||
// CanRetrySign 检查是否可以重试签署
|
||||
// 只有签署失败状态的申请才能重试
|
||||
func (c *Certification) CanRetrySign() bool {
|
||||
return c.Status == enums.StatusSignFailed
|
||||
// IsFinalStatus 判断是否为最终状态
|
||||
func (c *Certification) IsFinalStatus() bool {
|
||||
return enums.IsFinalStatus(c.Status)
|
||||
}
|
||||
|
||||
// CanRestart 检查是否可以重新开始流程
|
||||
// 只有被拒绝的申请才能重新开始认证流程
|
||||
func (c *Certification) CanRestart() bool {
|
||||
return c.Status == enums.StatusRejected
|
||||
// GetStatusCategory 获取状态分类
|
||||
func (c *Certification) GetStatusCategory() string {
|
||||
return enums.GetStatusCategory(c.Status)
|
||||
}
|
||||
|
||||
// GetNextValidStatuses 获取当前状态可以转换到的下一个状态列表
|
||||
// 根据状态机规则,返回所有合法的下一个状态
|
||||
func (c *Certification) GetNextValidStatuses() []enums.CertificationStatus {
|
||||
switch c.Status {
|
||||
case enums.StatusPending:
|
||||
return []enums.CertificationStatus{enums.StatusInfoSubmitted}
|
||||
case enums.StatusInfoSubmitted:
|
||||
return []enums.CertificationStatus{enums.StatusFaceVerified, enums.StatusFaceFailed}
|
||||
case enums.StatusFaceVerified:
|
||||
return []enums.CertificationStatus{enums.StatusContractApplied}
|
||||
case enums.StatusContractApplied:
|
||||
return []enums.CertificationStatus{enums.StatusContractPending}
|
||||
case enums.StatusContractPending:
|
||||
return []enums.CertificationStatus{enums.StatusContractApproved, enums.StatusRejected}
|
||||
case enums.StatusContractApproved:
|
||||
return []enums.CertificationStatus{enums.StatusContractSigned, enums.StatusSignFailed}
|
||||
case enums.StatusContractSigned:
|
||||
return []enums.CertificationStatus{enums.StatusCompleted}
|
||||
case enums.StatusFaceFailed:
|
||||
return []enums.CertificationStatus{enums.StatusFaceVerified}
|
||||
case enums.StatusSignFailed:
|
||||
return []enums.CertificationStatus{enums.StatusContractSigned}
|
||||
case enums.StatusRejected:
|
||||
return []enums.CertificationStatus{enums.StatusInfoSubmitted}
|
||||
default:
|
||||
return []enums.CertificationStatus{}
|
||||
}
|
||||
// GetStatusPriority 获取状态优先级
|
||||
func (c *Certification) GetStatusPriority() int {
|
||||
return enums.GetStatusPriority(c.Status)
|
||||
}
|
||||
|
||||
// CanTransitionTo 检查是否可以转换到指定状态
|
||||
// 验证状态转换的合法性,确保状态机规则得到遵守
|
||||
func (c *Certification) CanTransitionTo(targetStatus enums.CertificationStatus) bool {
|
||||
validStatuses := c.GetNextValidStatuses()
|
||||
for _, status := range validStatuses {
|
||||
if status == targetStatus {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetProgressPercentage 获取认证进度百分比
|
||||
// 根据当前状态计算认证流程的完成进度,用于前端进度条显示
|
||||
// GetProgressPercentage 获取进度百分比
|
||||
func (c *Certification) GetProgressPercentage() int {
|
||||
switch c.Status {
|
||||
case enums.StatusPending:
|
||||
return 0
|
||||
case enums.StatusInfoSubmitted:
|
||||
return 12
|
||||
case enums.StatusFaceVerified:
|
||||
return 25
|
||||
case enums.StatusContractApplied:
|
||||
return 37
|
||||
case enums.StatusContractPending:
|
||||
return 50
|
||||
case enums.StatusContractApproved:
|
||||
return 75
|
||||
case enums.StatusContractSigned:
|
||||
return 87
|
||||
case enums.StatusCompleted:
|
||||
return 100
|
||||
case enums.StatusFaceFailed, enums.StatusSignFailed:
|
||||
return c.GetProgressPercentage() // 失败状态保持原进度
|
||||
case enums.StatusRejected:
|
||||
return 0
|
||||
default:
|
||||
return 0
|
||||
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 {
|
||||
userActionStatuses := []enums.CertificationStatus{
|
||||
enums.StatusPending,
|
||||
enums.StatusInfoSubmitted,
|
||||
enums.StatusFaceVerified,
|
||||
enums.StatusContractApproved,
|
||||
enums.StatusFaceFailed,
|
||||
enums.StatusSignFailed,
|
||||
enums.StatusRejected,
|
||||
userActionRequired := map[enums.CertificationStatus]bool{
|
||||
enums.StatusPending: true,
|
||||
enums.StatusInfoSubmitted: true,
|
||||
enums.StatusEnterpriseVerified: true,
|
||||
enums.StatusContractApplied: true,
|
||||
enums.StatusContractSigned: false,
|
||||
enums.StatusCompleted: false,
|
||||
}
|
||||
|
||||
for _, status := range userActionStatuses {
|
||||
if c.Status == status {
|
||||
return true
|
||||
}
|
||||
if required, exists := userActionRequired[c.Status]; exists {
|
||||
return required
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAdminActionRequired 检查是否需要管理员操作
|
||||
// 判断当前状态是否需要管理员审核,用于后台管理界面
|
||||
func (c *Certification) IsAdminActionRequired() bool {
|
||||
return c.Status == enums.StatusContractPending
|
||||
// GetNextValidStatuses 获取下一个有效状态
|
||||
func (c *Certification) GetNextValidStatuses() []enums.CertificationStatus {
|
||||
nextStatusMap := map[enums.CertificationStatus][]enums.CertificationStatus{
|
||||
enums.StatusPending: {enums.StatusInfoSubmitted},
|
||||
enums.StatusInfoSubmitted: {enums.StatusEnterpriseVerified, enums.StatusInfoSubmitted}, // 可以重新提交
|
||||
enums.StatusEnterpriseVerified: {enums.StatusContractApplied},
|
||||
enums.StatusContractApplied: {enums.StatusContractSigned},
|
||||
enums.StatusContractSigned: {enums.StatusCompleted},
|
||||
enums.StatusCompleted: {},
|
||||
}
|
||||
|
||||
if nextStatuses, exists := nextStatusMap[c.Status]; exists {
|
||||
return nextStatuses
|
||||
}
|
||||
return []enums.CertificationStatus{}
|
||||
}
|
||||
|
||||
// CanTransitionTo 检查是否可以转换到指定状态
|
||||
func (c *Certification) CanTransitionTo(targetStatus enums.CertificationStatus, isUser bool) (bool, string) {
|
||||
nextStatuses := c.GetNextValidStatuses()
|
||||
|
||||
for _, nextStatus := range nextStatuses {
|
||||
if nextStatus == targetStatus {
|
||||
// 检查权限
|
||||
if isUser && !c.IsUserActionRequired() {
|
||||
return false, "当前状态不需要用户操作"
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
}
|
||||
|
||||
return false, "不支持的状态转换"
|
||||
}
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// ContractRecord 合同记录实体
|
||||
// 记录电子合同的详细信息,包括合同生成、审核、签署的完整流程
|
||||
// 支持合同状态跟踪、签署信息记录、审核流程管理等功能
|
||||
type ContractRecord struct {
|
||||
// 基础标识
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"合同记录唯一标识"`
|
||||
CertificationID string `gorm:"type:varchar(36);not null;index" json:"certification_id" comment:"关联的认证申请ID"`
|
||||
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"合同申请人ID"`
|
||||
AdminID *string `gorm:"type:varchar(36);index" json:"admin_id,omitempty" comment:"审核管理员ID"`
|
||||
|
||||
// 合同信息 - 电子合同的基本信息
|
||||
ContractType string `gorm:"type:varchar(50);not null" json:"contract_type" comment:"合同类型(ENTERPRISE_CERTIFICATION)"`
|
||||
ContractURL string `gorm:"type:varchar(500)" json:"contract_url,omitempty" comment:"合同文件访问链接"`
|
||||
SigningURL string `gorm:"type:varchar(500)" json:"signing_url,omitempty" comment:"电子签署链接"`
|
||||
|
||||
// 签署信息 - 记录用户签署的详细信息
|
||||
SignatureData string `gorm:"type:text" json:"signature_data,omitempty" comment:"签署数据(JSON格式)"`
|
||||
SignedAt *time.Time `json:"signed_at,omitempty" comment:"签署完成时间"`
|
||||
ClientIP string `gorm:"type:varchar(50)" json:"client_ip,omitempty" comment:"签署客户端IP"`
|
||||
UserAgent string `gorm:"type:varchar(500)" json:"user_agent,omitempty" comment:"签署客户端信息"`
|
||||
|
||||
// 状态信息 - 合同的生命周期状态
|
||||
Status string `gorm:"type:varchar(50);not null;index" json:"status" comment:"合同状态(PENDING/APPROVED/SIGNED/EXPIRED)"`
|
||||
ApprovalNotes string `gorm:"type:text" json:"approval_notes,omitempty" comment:"审核备注信息"`
|
||||
RejectReason string `gorm:"type:text" json:"reject_reason,omitempty" comment:"拒绝原因说明"`
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty" comment:"合同过期时间"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
|
||||
|
||||
// 关联关系
|
||||
Certification *Certification `gorm:"foreignKey:CertificationID" json:"certification,omitempty" comment:"关联的认证申请"`
|
||||
}
|
||||
|
||||
// TableName 指定数据库表名
|
||||
func (ContractRecord) TableName() string {
|
||||
return "contract_records"
|
||||
}
|
||||
|
||||
// IsPending 检查合同是否待审核
|
||||
// 判断合同是否处于等待管理员审核的状态
|
||||
func (c *ContractRecord) IsPending() bool {
|
||||
return c.Status == "PENDING"
|
||||
}
|
||||
|
||||
// IsApproved 检查合同是否已审核通过
|
||||
// 判断合同是否已通过管理员审核,可以进入签署阶段
|
||||
func (c *ContractRecord) IsApproved() bool {
|
||||
return c.Status == "APPROVED"
|
||||
}
|
||||
|
||||
// IsSigned 检查合同是否已签署
|
||||
// 判断合同是否已完成电子签署,认证流程即将完成
|
||||
func (c *ContractRecord) IsSigned() bool {
|
||||
return c.Status == "SIGNED"
|
||||
}
|
||||
|
||||
// IsExpired 检查合同是否已过期
|
||||
// 判断合同是否已超过有效期,过期后需要重新申请
|
||||
func (c *ContractRecord) IsExpired() bool {
|
||||
if c.ExpiresAt == nil {
|
||||
return false
|
||||
}
|
||||
return time.Now().After(*c.ExpiresAt)
|
||||
}
|
||||
|
||||
// HasSigningURL 检查是否有签署链接
|
||||
// 判断是否已生成电子签署链接,用于前端判断是否显示签署按钮
|
||||
func (c *ContractRecord) HasSigningURL() bool {
|
||||
return c.SigningURL != ""
|
||||
}
|
||||
|
||||
// GetStatusName 获取状态的中文名称
|
||||
// 将英文状态码转换为中文显示名称,用于前端展示和用户理解
|
||||
func (c *ContractRecord) GetStatusName() string {
|
||||
statusNames := map[string]string{
|
||||
"PENDING": "待审核",
|
||||
"APPROVED": "已审核",
|
||||
"SIGNED": "已签署",
|
||||
"EXPIRED": "已过期",
|
||||
"REJECTED": "已拒绝",
|
||||
}
|
||||
|
||||
if name, exists := statusNames[c.Status]; exists {
|
||||
return name
|
||||
}
|
||||
return c.Status
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (c *ContractRecord) BeforeCreate(tx *gorm.DB) error {
|
||||
if c.ID == "" {
|
||||
c.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// EnterpriseInfoSubmitRecord 企业信息提交记录
|
||||
type EnterpriseInfoSubmitRecord struct {
|
||||
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
|
||||
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
|
||||
|
||||
// 企业信息
|
||||
CompanyName string `json:"company_name" gorm:"type:varchar(200);not null"`
|
||||
UnifiedSocialCode string `json:"unified_social_code" gorm:"type:varchar(50);not null;index"`
|
||||
LegalPersonName string `json:"legal_person_name" 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"`
|
||||
// 提交状态
|
||||
Status string `json:"status" gorm:"type:varchar(20);not null;default:'submitted'"` // submitted, verified, failed
|
||||
SubmitAt time.Time `json:"submit_at" gorm:"not null"`
|
||||
VerifiedAt *time.Time `json:"verified_at"`
|
||||
FailedAt *time.Time `json:"failed_at"`
|
||||
FailureReason string `json:"failure_reason" gorm:"type:text"`
|
||||
|
||||
// 系统字段
|
||||
CreatedAt time.Time `json:"created_at" gorm:"not null"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (EnterpriseInfoSubmitRecord) TableName() string {
|
||||
return "enterprise_info_submit_records"
|
||||
}
|
||||
|
||||
// NewEnterpriseInfoSubmitRecord 创建新的企业信息提交记录
|
||||
func NewEnterpriseInfoSubmitRecord(
|
||||
userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone string,
|
||||
) *EnterpriseInfoSubmitRecord {
|
||||
return &EnterpriseInfoSubmitRecord{
|
||||
ID: uuid.New().String(),
|
||||
UserID: userID,
|
||||
CompanyName: companyName,
|
||||
UnifiedSocialCode: unifiedSocialCode,
|
||||
LegalPersonName: legalPersonName,
|
||||
LegalPersonID: legalPersonID,
|
||||
LegalPersonPhone: legalPersonPhone,
|
||||
Status: "submitted",
|
||||
SubmitAt: time.Now(),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// MarkAsVerified 标记为已验证
|
||||
func (r *EnterpriseInfoSubmitRecord) MarkAsVerified() {
|
||||
now := time.Now()
|
||||
r.Status = "verified"
|
||||
r.VerifiedAt = &now
|
||||
r.UpdatedAt = now
|
||||
}
|
||||
|
||||
// MarkAsFailed 标记为验证失败
|
||||
func (r *EnterpriseInfoSubmitRecord) MarkAsFailed(reason string) {
|
||||
now := time.Now()
|
||||
r.Status = "failed"
|
||||
r.FailedAt = &now
|
||||
r.FailureReason = reason
|
||||
r.UpdatedAt = now
|
||||
}
|
||||
|
||||
// IsVerified 检查是否已验证
|
||||
func (r *EnterpriseInfoSubmitRecord) IsVerified() bool {
|
||||
return r.Status == "verified"
|
||||
}
|
||||
|
||||
// IsFailed 检查是否验证失败
|
||||
func (r *EnterpriseInfoSubmitRecord) IsFailed() bool {
|
||||
return r.Status == "failed"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// EsignContractGenerateRecord e签宝生成合同文件记录
|
||||
type EsignContractGenerateRecord struct {
|
||||
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
|
||||
CertificationID string `json:"certification_id" gorm:"type:varchar(36);not null;index"`
|
||||
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
|
||||
|
||||
// e签宝相关
|
||||
TemplateID string `json:"template_id" gorm:"type:varchar(100);index"` // 模板ID
|
||||
ContractFileID string `json:"contract_file_id" gorm:"type:varchar(100);index"` // 合同文件ID
|
||||
ContractURL string `json:"contract_url" gorm:"type:varchar(500)"` // 合同文件URL
|
||||
ContractName string `json:"contract_name" gorm:"type:varchar(200)"` // 合同名称
|
||||
|
||||
// 生成状态
|
||||
Status string `json:"status" gorm:"type:varchar(20);not null"` // success, failed
|
||||
|
||||
FillTime *time.Time `json:"fill_time"` // 填写时间
|
||||
|
||||
// 系统字段
|
||||
CreatedAt time.Time `json:"created_at" gorm:"not null"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (EsignContractGenerateRecord) TableName() string {
|
||||
return "esign_contract_generate_records"
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// EsignContractSignRecord e签宝签署合同记录
|
||||
type EsignContractSignRecord struct {
|
||||
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
|
||||
CertificationID string `json:"certification_id" gorm:"type:varchar(36);not null;index"`
|
||||
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
|
||||
EnterpriseInfoID string `json:"enterprise_info_id" gorm:"type:varchar(36);not null;index"` // 企业信息ID
|
||||
|
||||
// e签宝相关
|
||||
EsignFlowID string `json:"esign_flow_id" gorm:"type:varchar(100);index"` // e签宝流程ID
|
||||
ContractFileID string `json:"contract_file_id" gorm:"type:varchar(100);index"` // 合同文件ID
|
||||
SignURL string `json:"sign_url" gorm:"type:varchar(500)"` // 签署链接
|
||||
SignShortURL string `json:"sign_short_url" gorm:"type:varchar(500)"` // 签署短链接
|
||||
SignedFileURL string `json:"signed_file_url" gorm:"type:varchar(500)"` // 已签署文件URL
|
||||
|
||||
// 签署状态
|
||||
Status string `json:"status" gorm:"type:varchar(20);not null;default:'pending'"` // pending, signing, success, failed, expired
|
||||
RequestAt time.Time `json:"request_at" gorm:"not null"` // 申请签署时间
|
||||
SignedAt *time.Time `json:"signed_at"` // 签署完成时间
|
||||
ExpiredAt *time.Time `json:"expired_at"` // 签署链接过期时间
|
||||
FailedAt *time.Time `json:"failed_at"` // 失败时间
|
||||
FailureReason string `json:"failure_reason" gorm:"type:text"` // 失败原因
|
||||
|
||||
// 签署人信息
|
||||
SignerName string `json:"signer_name" gorm:"type:varchar(50)"` // 签署人姓名
|
||||
SignerPhone string `json:"signer_phone" gorm:"type:varchar(20)"` // 签署人手机号
|
||||
SignerIDCard string `json:"signer_id_card" gorm:"type:varchar(20)"` // 签署人身份证号
|
||||
|
||||
// 系统字段
|
||||
CreatedAt time.Time `json:"created_at" gorm:"not null"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (EsignContractSignRecord) TableName() string {
|
||||
return "esign_contract_sign_records"
|
||||
}
|
||||
|
||||
// NewEsignContractSignRecord 创建新的e签宝签署合同记录
|
||||
func NewEsignContractSignRecord(
|
||||
certificationID, userID, esignFlowID, contractFileID, signerName, signerPhone, signerIDCard, signURL, signShortURL string,
|
||||
) *EsignContractSignRecord {
|
||||
// 设置签署链接过期时间为7天后
|
||||
expiredAt := time.Now().AddDate(0, 0, 7)
|
||||
|
||||
return &EsignContractSignRecord{
|
||||
ID: uuid.New().String(),
|
||||
CertificationID: certificationID,
|
||||
UserID: userID,
|
||||
EsignFlowID: esignFlowID,
|
||||
ContractFileID: contractFileID,
|
||||
SignURL: signURL,
|
||||
SignShortURL: signShortURL,
|
||||
SignerName: signerName,
|
||||
SignerPhone: signerPhone,
|
||||
SignerIDCard: signerIDCard,
|
||||
Status: "pending",
|
||||
RequestAt: time.Now(),
|
||||
ExpiredAt: &expiredAt,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// MarkAsSigning 标记为签署中
|
||||
func (r *EsignContractSignRecord) MarkAsSigning() {
|
||||
r.Status = "signing"
|
||||
r.UpdatedAt = time.Now()
|
||||
}
|
||||
|
||||
// MarkAsSuccess 标记为签署成功
|
||||
func (r *EsignContractSignRecord) MarkAsSuccess(signedFileURL string) {
|
||||
now := time.Now()
|
||||
r.Status = "success"
|
||||
r.SignedFileURL = signedFileURL
|
||||
r.SignedAt = &now
|
||||
r.UpdatedAt = now
|
||||
}
|
||||
|
||||
// MarkAsFailed 标记为签署失败
|
||||
func (r *EsignContractSignRecord) MarkAsFailed(reason string) {
|
||||
now := time.Now()
|
||||
r.Status = "failed"
|
||||
r.FailedAt = &now
|
||||
r.FailureReason = reason
|
||||
r.UpdatedAt = now
|
||||
}
|
||||
|
||||
// MarkAsExpired 标记为已过期
|
||||
func (r *EsignContractSignRecord) MarkAsExpired() {
|
||||
now := time.Now()
|
||||
r.Status = "expired"
|
||||
r.ExpiredAt = &now
|
||||
r.UpdatedAt = now
|
||||
}
|
||||
|
||||
// SetSignURL 设置签署链接
|
||||
func (r *EsignContractSignRecord) SetSignURL(signURL string) {
|
||||
r.SignURL = signURL
|
||||
r.UpdatedAt = time.Now()
|
||||
}
|
||||
|
||||
// IsSuccess 检查是否签署成功
|
||||
func (r *EsignContractSignRecord) IsSuccess() bool {
|
||||
return r.Status == "success"
|
||||
}
|
||||
|
||||
// IsFailed 检查是否签署失败
|
||||
func (r *EsignContractSignRecord) IsFailed() bool {
|
||||
return r.Status == "failed"
|
||||
}
|
||||
|
||||
// IsExpired 检查是否已过期
|
||||
func (r *EsignContractSignRecord) IsExpired() bool {
|
||||
return r.Status == "expired" || (r.ExpiredAt != nil && time.Now().After(*r.ExpiredAt))
|
||||
}
|
||||
|
||||
// IsPending 检查是否待处理
|
||||
func (r *EsignContractSignRecord) IsPending() bool {
|
||||
return r.Status == "pending"
|
||||
}
|
||||
|
||||
// IsSigning 检查是否签署中
|
||||
func (r *EsignContractSignRecord) IsSigning() bool {
|
||||
return r.Status == "signing"
|
||||
}
|
||||
|
||||
// GetRemainingTime 获取剩余签署时间
|
||||
func (r *EsignContractSignRecord) GetRemainingTime() time.Duration {
|
||||
if r.ExpiredAt == nil {
|
||||
return 0
|
||||
}
|
||||
remaining := time.Until(*r.ExpiredAt)
|
||||
if remaining < 0 {
|
||||
return 0
|
||||
}
|
||||
return remaining
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// FaceVerifyRecord 人脸识别记录实体
|
||||
// 记录用户进行人脸识别验证的详细信息,包括验证状态、结果和身份信息
|
||||
// 支持多次验证尝试,每次验证都会生成独立的记录,便于追踪和重试
|
||||
type FaceVerifyRecord struct {
|
||||
// 基础标识
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"人脸识别记录唯一标识"`
|
||||
CertificationID string `gorm:"type:varchar(36);not null;index" json:"certification_id" comment:"关联的认证申请ID"`
|
||||
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"进行验证的用户ID"`
|
||||
|
||||
// 阿里云人脸识别信息 - 第三方服务的相关数据
|
||||
CertifyID string `gorm:"type:varchar(100);not null;index" json:"certify_id" comment:"阿里云人脸识别任务ID"`
|
||||
VerifyURL string `gorm:"type:varchar(500)" json:"verify_url,omitempty" comment:"人脸识别验证页面URL"`
|
||||
ReturnURL string `gorm:"type:varchar(500)" json:"return_url,omitempty" comment:"验证完成后的回调URL"`
|
||||
|
||||
// 身份信息 - 用于人脸识别的身份验证数据
|
||||
RealName string `gorm:"type:varchar(100);not null" json:"real_name" comment:"真实姓名"`
|
||||
IDCardNumber string `gorm:"type:varchar(50);not null" json:"id_card_number" comment:"身份证号码"`
|
||||
|
||||
// 验证结果 - 记录验证的详细结果信息
|
||||
Status string `gorm:"type:varchar(50);not null;index" json:"status" comment:"验证状态(PROCESSING/SUCCESS/FAIL)"`
|
||||
ResultCode string `gorm:"type:varchar(50)" json:"result_code,omitempty" comment:"结果代码"`
|
||||
ResultMessage string `gorm:"type:varchar(500)" json:"result_message,omitempty" comment:"结果描述信息"`
|
||||
VerifyScore float64 `gorm:"type:decimal(5,2)" json:"verify_score,omitempty" comment:"验证分数(0-1)"`
|
||||
|
||||
// 时间信息 - 验证流程的时间节点
|
||||
InitiatedAt time.Time `gorm:"autoCreateTime" json:"initiated_at" comment:"验证发起时间"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty" comment:"验证完成时间"`
|
||||
ExpiresAt time.Time `gorm:"not null" json:"expires_at" comment:"验证链接过期时间"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
|
||||
|
||||
// 关联关系
|
||||
Certification *Certification `gorm:"foreignKey:CertificationID" json:"certification,omitempty" comment:"关联的认证申请"`
|
||||
}
|
||||
|
||||
// TableName 指定数据库表名
|
||||
func (FaceVerifyRecord) TableName() string {
|
||||
return "face_verify_records"
|
||||
}
|
||||
|
||||
// IsSuccess 检查人脸识别是否成功
|
||||
// 判断验证状态是否为成功状态
|
||||
func (f *FaceVerifyRecord) IsSuccess() bool {
|
||||
return f.Status == "SUCCESS"
|
||||
}
|
||||
|
||||
// IsProcessing 检查是否正在处理中
|
||||
// 判断验证是否正在进行中,等待用户完成验证
|
||||
func (f *FaceVerifyRecord) IsProcessing() bool {
|
||||
return f.Status == "PROCESSING"
|
||||
}
|
||||
|
||||
// IsFailed 检查是否失败
|
||||
// 判断验证是否失败,包括超时、验证不通过等情况
|
||||
func (f *FaceVerifyRecord) IsFailed() bool {
|
||||
return f.Status == "FAIL"
|
||||
}
|
||||
|
||||
// IsExpired 检查是否已过期
|
||||
// 判断验证链接是否已超过有效期,过期后需要重新发起验证
|
||||
func (f *FaceVerifyRecord) IsExpired() bool {
|
||||
return time.Now().After(f.ExpiresAt)
|
||||
}
|
||||
|
||||
// GetStatusName 获取状态的中文名称
|
||||
// 将英文状态码转换为中文显示名称,用于前端展示
|
||||
func (f *FaceVerifyRecord) GetStatusName() string {
|
||||
statusNames := map[string]string{
|
||||
"PROCESSING": "处理中",
|
||||
"SUCCESS": "成功",
|
||||
"FAIL": "失败",
|
||||
}
|
||||
|
||||
if name, exists := statusNames[f.Status]; exists {
|
||||
return name
|
||||
}
|
||||
return f.Status
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (f *FaceVerifyRecord) BeforeCreate(tx *gorm.DB) error {
|
||||
if f.ID == "" {
|
||||
f.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// LicenseUploadRecord 营业执照上传记录实体
|
||||
// 记录用户上传营业执照文件的详细信息,包括文件元数据和OCR处理结果
|
||||
// 支持多种文件格式,自动进行OCR识别,为后续企业信息验证提供数据支持
|
||||
type LicenseUploadRecord struct {
|
||||
// 基础标识
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"上传记录唯一标识"`
|
||||
CertificationID *string `gorm:"type:varchar(36);index" json:"certification_id,omitempty" comment:"关联的认证申请ID(可为空,表示独立上传)"`
|
||||
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"上传用户ID"`
|
||||
|
||||
// 文件信息 - 存储文件的元数据信息
|
||||
OriginalFileName string `gorm:"type:varchar(255);not null" json:"original_file_name" comment:"原始文件名"`
|
||||
FileSize int64 `gorm:"not null" json:"file_size" comment:"文件大小(字节)"`
|
||||
FileType string `gorm:"type:varchar(50);not null" json:"file_type" comment:"文件MIME类型"`
|
||||
FileURL string `gorm:"type:varchar(500);not null" json:"file_url" comment:"文件访问URL"`
|
||||
QiNiuKey string `gorm:"type:varchar(255);not null;index" json:"qiniu_key" comment:"七牛云存储的Key"`
|
||||
|
||||
// OCR处理结果 - 记录OCR识别的详细结果
|
||||
OCRProcessed bool `gorm:"default:false" json:"ocr_processed" comment:"是否已进行OCR处理"`
|
||||
OCRSuccess bool `gorm:"default:false" json:"ocr_success" comment:"OCR识别是否成功"`
|
||||
OCRConfidence float64 `gorm:"type:decimal(5,2)" json:"ocr_confidence,omitempty" comment:"OCR识别置信度(0-1)"`
|
||||
OCRRawData string `gorm:"type:text" json:"ocr_raw_data,omitempty" comment:"OCR原始返回数据(JSON格式)"`
|
||||
OCRErrorMessage string `gorm:"type:varchar(500)" json:"ocr_error_message,omitempty" comment:"OCR处理错误信息"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
|
||||
|
||||
// 关联关系
|
||||
Certification *Certification `gorm:"foreignKey:CertificationID" json:"certification,omitempty" comment:"关联的认证申请"`
|
||||
}
|
||||
|
||||
// TableName 指定数据库表名
|
||||
func (LicenseUploadRecord) TableName() string {
|
||||
return "license_upload_records"
|
||||
}
|
||||
|
||||
// IsOCRSuccess 检查OCR是否成功
|
||||
// 判断OCR处理已完成且识别成功
|
||||
func (l *LicenseUploadRecord) IsOCRSuccess() bool {
|
||||
return l.OCRProcessed && l.OCRSuccess
|
||||
}
|
||||
|
||||
// GetFileExtension 获取文件扩展名
|
||||
// 从原始文件名中提取文件扩展名,用于文件类型判断
|
||||
func (l *LicenseUploadRecord) GetFileExtension() string {
|
||||
// 从OriginalFileName提取扩展名的逻辑
|
||||
// 这里简化处理,实际使用时可以用path.Ext()
|
||||
return l.FileType
|
||||
}
|
||||
|
||||
// IsValidForOCR 检查文件是否适合OCR处理
|
||||
// 验证文件类型是否支持OCR识别,目前支持JPEG、PNG格式
|
||||
func (l *LicenseUploadRecord) IsValidForOCR() bool {
|
||||
validTypes := []string{"image/jpeg", "image/png", "image/jpg"}
|
||||
for _, validType := range validTypes {
|
||||
if l.FileType == validType {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (l *LicenseUploadRecord) BeforeCreate(tx *gorm.DB) error {
|
||||
if l.ID == "" {
|
||||
l.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// NotificationRecord 通知记录实体
|
||||
// 记录系统发送的所有通知信息,包括短信、企业微信、邮件等多种通知渠道
|
||||
// 支持通知状态跟踪、重试机制、模板化消息等功能,确保通知的可靠送达
|
||||
type NotificationRecord struct {
|
||||
// 基础标识
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"通知记录唯一标识"`
|
||||
CertificationID *string `gorm:"type:varchar(36);index" json:"certification_id,omitempty" comment:"关联的认证申请ID(可为空)"`
|
||||
UserID *string `gorm:"type:varchar(36);index" json:"user_id,omitempty" comment:"接收用户ID(可为空)"`
|
||||
|
||||
// 通知类型和渠道 - 定义通知的发送方式和业务场景
|
||||
NotificationType string `gorm:"type:varchar(50);not null;index" json:"notification_type" comment:"通知类型(SMS/WECHAT_WORK/EMAIL)"`
|
||||
NotificationScene string `gorm:"type:varchar(50);not null;index" json:"notification_scene" comment:"通知场景(ADMIN_NEW_APPLICATION/USER_CONTRACT_READY等)"`
|
||||
|
||||
// 接收方信息 - 通知的目标接收者
|
||||
Recipient string `gorm:"type:varchar(255);not null" json:"recipient" comment:"接收方标识(手机号/邮箱/用户ID)"`
|
||||
|
||||
// 消息内容 - 通知的具体内容信息
|
||||
Title string `gorm:"type:varchar(255)" json:"title,omitempty" comment:"通知标题"`
|
||||
Content string `gorm:"type:text;not null" json:"content" comment:"通知内容"`
|
||||
TemplateID string `gorm:"type:varchar(100)" json:"template_id,omitempty" comment:"消息模板ID"`
|
||||
TemplateParams string `gorm:"type:text" json:"template_params,omitempty" comment:"模板参数(JSON格式)"`
|
||||
|
||||
// 发送状态 - 记录通知的发送过程和结果
|
||||
Status string `gorm:"type:varchar(50);not null;index" json:"status" comment:"发送状态(PENDING/SENT/FAILED)"`
|
||||
ErrorMessage string `gorm:"type:varchar(500)" json:"error_message,omitempty" comment:"发送失败的错误信息"`
|
||||
SentAt *time.Time `json:"sent_at,omitempty" comment:"发送成功时间"`
|
||||
RetryCount int `gorm:"default:0" json:"retry_count" comment:"当前重试次数"`
|
||||
MaxRetryCount int `gorm:"default:3" json:"max_retry_count" comment:"最大重试次数"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
|
||||
|
||||
// 关联关系
|
||||
Certification *Certification `gorm:"foreignKey:CertificationID" json:"certification,omitempty" comment:"关联的认证申请"`
|
||||
}
|
||||
|
||||
// TableName 指定数据库表名
|
||||
func (NotificationRecord) TableName() string {
|
||||
return "notification_records"
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (n *NotificationRecord) BeforeCreate(tx *gorm.DB) error {
|
||||
if n.ID == "" {
|
||||
n.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsPending 检查通知是否待发送
|
||||
// 判断通知是否处于等待发送的状态
|
||||
func (n *NotificationRecord) IsPending() bool {
|
||||
return n.Status == "PENDING"
|
||||
}
|
||||
|
||||
// IsSent 检查通知是否已发送
|
||||
// 判断通知是否已成功发送到接收方
|
||||
func (n *NotificationRecord) IsSent() bool {
|
||||
return n.Status == "SENT"
|
||||
}
|
||||
|
||||
// IsFailed 检查通知是否发送失败
|
||||
// 判断通知是否发送失败,包括网络错误、接收方无效等情况
|
||||
func (n *NotificationRecord) IsFailed() bool {
|
||||
return n.Status == "FAILED"
|
||||
}
|
||||
|
||||
// CanRetry 检查是否可以重试
|
||||
// 判断失败的通知是否还可以进行重试发送
|
||||
func (n *NotificationRecord) CanRetry() bool {
|
||||
return n.IsFailed() && n.RetryCount < n.MaxRetryCount
|
||||
}
|
||||
|
||||
// IncrementRetryCount 增加重试次数
|
||||
// 在重试发送时增加重试计数器
|
||||
func (n *NotificationRecord) IncrementRetryCount() {
|
||||
n.RetryCount++
|
||||
}
|
||||
|
||||
// GetStatusName 获取状态的中文名称
|
||||
// 将英文状态码转换为中文显示名称,用于前端展示
|
||||
func (n *NotificationRecord) GetStatusName() string {
|
||||
statusNames := map[string]string{
|
||||
"PENDING": "待发送",
|
||||
"SENT": "已发送",
|
||||
"FAILED": "发送失败",
|
||||
}
|
||||
|
||||
if name, exists := statusNames[n.Status]; exists {
|
||||
return name
|
||||
}
|
||||
return n.Status
|
||||
}
|
||||
|
||||
// GetNotificationTypeName 获取通知类型的中文名称
|
||||
// 将通知类型转换为中文显示名称,便于用户理解
|
||||
func (n *NotificationRecord) GetNotificationTypeName() string {
|
||||
typeNames := map[string]string{
|
||||
"SMS": "短信",
|
||||
"WECHAT_WORK": "企业微信",
|
||||
"EMAIL": "邮件",
|
||||
}
|
||||
|
||||
if name, exists := typeNames[n.NotificationType]; exists {
|
||||
return name
|
||||
}
|
||||
return n.NotificationType
|
||||
}
|
||||
|
||||
// GetNotificationSceneName 获取通知场景的中文名称
|
||||
// 将通知场景转换为中文显示名称,便于业务人员理解通知的触发原因
|
||||
func (n *NotificationRecord) GetNotificationSceneName() string {
|
||||
sceneNames := map[string]string{
|
||||
"ADMIN_NEW_APPLICATION": "管理员新申请通知",
|
||||
"USER_CONTRACT_READY": "用户合同就绪通知",
|
||||
"USER_CERTIFICATION_COMPLETED": "用户认证完成通知",
|
||||
"USER_FACE_VERIFY_FAILED": "用户人脸识别失败通知",
|
||||
"USER_CONTRACT_REJECTED": "用户合同被拒绝通知",
|
||||
}
|
||||
|
||||
if name, exists := sceneNames[n.NotificationScene]; exists {
|
||||
return name
|
||||
}
|
||||
return n.NotificationScene
|
||||
}
|
||||
@@ -5,29 +5,19 @@ type CertificationStatus string
|
||||
|
||||
const (
|
||||
// 主流程状态
|
||||
StatusNotStarted CertificationStatus = "not_started" // 未开始认证
|
||||
StatusPending CertificationStatus = "pending" // 待开始
|
||||
StatusInfoSubmitted CertificationStatus = "info_submitted" // 企业信息已提交
|
||||
StatusFaceVerified CertificationStatus = "face_verified" // 人脸识别完成
|
||||
StatusContractApplied CertificationStatus = "contract_applied" // 已申请合同
|
||||
StatusContractPending CertificationStatus = "contract_pending" // 合同待审核
|
||||
StatusContractApproved CertificationStatus = "contract_approved" // 合同已审核(有链接)
|
||||
StatusContractSigned CertificationStatus = "contract_signed" // 合同已签署
|
||||
StatusPending CertificationStatus = "pending" // 待认证
|
||||
StatusInfoSubmitted CertificationStatus = "info_submitted" // 已提交企业信息
|
||||
StatusEnterpriseVerified CertificationStatus = "enterprise_verified" // 已企业认证
|
||||
StatusContractApplied CertificationStatus = "contract_applied" // 已申请签署合同
|
||||
StatusContractSigned CertificationStatus = "contract_signed" // 已签署合同
|
||||
StatusCompleted CertificationStatus = "completed" // 认证完成
|
||||
|
||||
// 失败和重试状态
|
||||
StatusFaceFailed CertificationStatus = "face_failed" // 人脸识别失败
|
||||
StatusSignFailed CertificationStatus = "sign_failed" // 签署失败
|
||||
StatusRejected CertificationStatus = "rejected" // 已拒绝
|
||||
)
|
||||
|
||||
// IsValidStatus 检查状态是否有效
|
||||
func IsValidStatus(status CertificationStatus) bool {
|
||||
validStatuses := []CertificationStatus{
|
||||
StatusNotStarted, StatusPending, StatusInfoSubmitted, StatusFaceVerified,
|
||||
StatusContractApplied, StatusContractPending, StatusContractApproved,
|
||||
StatusContractSigned, StatusCompleted, StatusFaceFailed,
|
||||
StatusSignFailed, StatusRejected,
|
||||
StatusPending, StatusInfoSubmitted, StatusEnterpriseVerified,
|
||||
StatusContractApplied, StatusContractSigned, StatusCompleted,
|
||||
}
|
||||
|
||||
for _, validStatus := range validStatuses {
|
||||
@@ -41,18 +31,12 @@ func IsValidStatus(status CertificationStatus) bool {
|
||||
// GetStatusName 获取状态的中文名称
|
||||
func GetStatusName(status CertificationStatus) string {
|
||||
statusNames := map[CertificationStatus]string{
|
||||
StatusNotStarted: "未开始认证",
|
||||
StatusPending: "待开始",
|
||||
StatusInfoSubmitted: "企业信息已提交",
|
||||
StatusFaceVerified: "人脸识别完成",
|
||||
StatusContractApplied: "已申请合同",
|
||||
StatusContractPending: "合同待审核",
|
||||
StatusContractApproved: "合同已审核",
|
||||
StatusContractSigned: "合同已签署",
|
||||
StatusCompleted: "认证完成",
|
||||
StatusFaceFailed: "人脸识别失败",
|
||||
StatusSignFailed: "签署失败",
|
||||
StatusRejected: "已拒绝",
|
||||
StatusPending: "待认证",
|
||||
StatusInfoSubmitted: "已提交企业信息",
|
||||
StatusEnterpriseVerified: "已企业认证",
|
||||
StatusContractApplied: "已申请合同",
|
||||
StatusContractSigned: "已签署合同",
|
||||
StatusCompleted: "认证完成",
|
||||
}
|
||||
|
||||
if name, exists := statusNames[status]; exists {
|
||||
@@ -63,28 +47,36 @@ func GetStatusName(status CertificationStatus) string {
|
||||
|
||||
// IsFinalStatus 判断是否为最终状态
|
||||
func IsFinalStatus(status CertificationStatus) bool {
|
||||
finalStatuses := []CertificationStatus{
|
||||
StatusCompleted, StatusRejected,
|
||||
}
|
||||
|
||||
for _, finalStatus := range finalStatuses {
|
||||
if status == finalStatus {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return status == StatusCompleted
|
||||
}
|
||||
|
||||
// IsFailedStatus 判断是否为失败状态
|
||||
func IsFailedStatus(status CertificationStatus) bool {
|
||||
failedStatuses := []CertificationStatus{
|
||||
StatusFaceFailed, StatusSignFailed, StatusRejected,
|
||||
// GetStatusCategory 获取状态分类
|
||||
func GetStatusCategory(status CertificationStatus) string {
|
||||
switch status {
|
||||
case StatusPending:
|
||||
return "initial"
|
||||
case StatusInfoSubmitted, StatusEnterpriseVerified, StatusContractApplied, StatusContractSigned:
|
||||
return "processing"
|
||||
case StatusCompleted:
|
||||
return "completed"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// GetStatusPriority 获取状态优先级(用于排序)
|
||||
func GetStatusPriority(status CertificationStatus) int {
|
||||
priorities := map[CertificationStatus]int{
|
||||
StatusPending: 0,
|
||||
StatusInfoSubmitted: 1,
|
||||
StatusEnterpriseVerified: 2,
|
||||
StatusContractApplied: 3,
|
||||
StatusContractSigned: 4,
|
||||
StatusCompleted: 5,
|
||||
}
|
||||
|
||||
for _, failedStatus := range failedStatuses {
|
||||
if status == failedStatus {
|
||||
return true
|
||||
}
|
||||
if priority, exists := priorities[status]; exists {
|
||||
return priority
|
||||
}
|
||||
return false
|
||||
return 999
|
||||
}
|
||||
|
||||
@@ -5,25 +5,18 @@ import (
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/certification/entities"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// 认证事件类型常量
|
||||
// 事件类型常量
|
||||
const (
|
||||
EventTypeCertificationCreated = "certification.created"
|
||||
EventTypeCertificationSubmitted = "certification.submitted"
|
||||
EventTypeLicenseUploaded = "certification.license.uploaded"
|
||||
EventTypeOCRCompleted = "certification.ocr.completed"
|
||||
EventTypeEnterpriseInfoConfirmed = "certification.enterprise.confirmed"
|
||||
EventTypeFaceVerifyInitiated = "certification.face_verify.initiated"
|
||||
EventTypeFaceVerifyCompleted = "certification.face_verify.completed"
|
||||
EventTypeContractRequested = "certification.contract.requested"
|
||||
EventTypeContractGenerated = "certification.contract.generated"
|
||||
EventTypeContractSigned = "certification.contract.signed"
|
||||
EventTypeCertificationApproved = "certification.approved"
|
||||
EventTypeCertificationRejected = "certification.rejected"
|
||||
EventTypeWalletCreated = "certification.wallet.created"
|
||||
EventTypeCertificationCompleted = "certification.completed"
|
||||
EventTypeCertificationFailed = "certification.failed"
|
||||
EventTypeCertificationCreated = "certification.created"
|
||||
EventTypeEnterpriseInfoSubmitted = "enterprise.info.submitted"
|
||||
EventTypeEnterpriseVerified = "enterprise.verified"
|
||||
EventTypeContractApplied = "contract.applied"
|
||||
EventTypeContractSigned = "contract.signed"
|
||||
EventTypeCertificationCompleted = "certification.completed"
|
||||
)
|
||||
|
||||
// BaseCertificationEvent 认证事件基础结构
|
||||
@@ -39,7 +32,7 @@ type BaseCertificationEvent struct {
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
|
||||
// 实现 Event 接口
|
||||
// 实现 DomainEvent 接口
|
||||
func (e *BaseCertificationEvent) GetID() string { return e.ID }
|
||||
func (e *BaseCertificationEvent) GetType() string { return e.Type }
|
||||
func (e *BaseCertificationEvent) GetVersion() string { return e.Version }
|
||||
@@ -62,15 +55,15 @@ func NewBaseCertificationEvent(eventType, aggregateID string, payload interface{
|
||||
Type: eventType,
|
||||
Version: "1.0",
|
||||
Timestamp: time.Now(),
|
||||
Source: "certification-domain",
|
||||
Source: "certification-service",
|
||||
AggregateID: aggregateID,
|
||||
AggregateType: "certification",
|
||||
AggregateType: "Certification",
|
||||
Metadata: make(map[string]interface{}),
|
||||
Payload: payload,
|
||||
}
|
||||
}
|
||||
|
||||
// CertificationCreatedEvent 认证创建事件
|
||||
// CertificationCreatedEvent 认证申请创建事件
|
||||
type CertificationCreatedEvent struct {
|
||||
*BaseCertificationEvent
|
||||
Data struct {
|
||||
@@ -80,7 +73,7 @@ type CertificationCreatedEvent struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewCertificationCreatedEvent 创建认证创建事件
|
||||
// NewCertificationCreatedEvent 创建认证申请创建事件
|
||||
func NewCertificationCreatedEvent(certification *entities.Certification) *CertificationCreatedEvent {
|
||||
event := &CertificationCreatedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
@@ -96,8 +89,8 @@ func NewCertificationCreatedEvent(certification *entities.Certification) *Certif
|
||||
return event
|
||||
}
|
||||
|
||||
// CertificationSubmittedEvent 认证提交事件
|
||||
type CertificationSubmittedEvent struct {
|
||||
// EnterpriseInfoSubmittedEvent 企业信息提交事件
|
||||
type EnterpriseInfoSubmittedEvent struct {
|
||||
*BaseCertificationEvent
|
||||
Data struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
@@ -106,11 +99,11 @@ type CertificationSubmittedEvent struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewCertificationSubmittedEvent 创建认证提交事件
|
||||
func NewCertificationSubmittedEvent(certification *entities.Certification) *CertificationSubmittedEvent {
|
||||
event := &CertificationSubmittedEvent{
|
||||
// NewEnterpriseInfoSubmittedEvent 创建企业信息提交事件
|
||||
func NewEnterpriseInfoSubmittedEvent(certification *entities.Certification) *EnterpriseInfoSubmittedEvent {
|
||||
event := &EnterpriseInfoSubmittedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
EventTypeCertificationSubmitted,
|
||||
EventTypeEnterpriseInfoSubmitted,
|
||||
certification.ID,
|
||||
nil,
|
||||
),
|
||||
@@ -122,158 +115,8 @@ func NewCertificationSubmittedEvent(certification *entities.Certification) *Cert
|
||||
return event
|
||||
}
|
||||
|
||||
// LicenseUploadedEvent 营业执照上传事件
|
||||
type LicenseUploadedEvent struct {
|
||||
*BaseCertificationEvent
|
||||
Data struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
FileURL string `json:"file_url"`
|
||||
FileName string `json:"file_name"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewLicenseUploadedEvent 创建营业执照上传事件
|
||||
func NewLicenseUploadedEvent(certification *entities.Certification, record *entities.LicenseUploadRecord) *LicenseUploadedEvent {
|
||||
event := &LicenseUploadedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
EventTypeLicenseUploaded,
|
||||
certification.ID,
|
||||
nil,
|
||||
),
|
||||
}
|
||||
event.Data.CertificationID = certification.ID
|
||||
event.Data.UserID = certification.UserID
|
||||
event.Data.FileURL = record.FileURL
|
||||
event.Data.FileName = record.OriginalFileName
|
||||
event.Data.FileSize = record.FileSize
|
||||
event.Data.Status = string(certification.Status)
|
||||
event.Payload = event.Data
|
||||
return event
|
||||
}
|
||||
|
||||
// OCRCompletedEvent OCR识别完成事件
|
||||
type OCRCompletedEvent struct {
|
||||
*BaseCertificationEvent
|
||||
Data struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
OCRResult map[string]interface{} `json:"ocr_result"`
|
||||
Confidence float64 `json:"confidence"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewOCRCompletedEvent 创建OCR识别完成事件
|
||||
func NewOCRCompletedEvent(certification *entities.Certification, ocrResult map[string]interface{}, confidence float64) *OCRCompletedEvent {
|
||||
event := &OCRCompletedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
EventTypeOCRCompleted,
|
||||
certification.ID,
|
||||
nil,
|
||||
),
|
||||
}
|
||||
event.Data.CertificationID = certification.ID
|
||||
event.Data.UserID = certification.UserID
|
||||
event.Data.OCRResult = ocrResult
|
||||
event.Data.Confidence = confidence
|
||||
event.Data.Status = string(certification.Status)
|
||||
event.Payload = event.Data
|
||||
return event
|
||||
}
|
||||
|
||||
// EnterpriseInfoConfirmedEvent 企业信息确认事件
|
||||
type EnterpriseInfoConfirmedEvent struct {
|
||||
*BaseCertificationEvent
|
||||
Data struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
EnterpriseInfo map[string]interface{} `json:"enterprise_info"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewEnterpriseInfoConfirmedEvent 创建企业信息确认事件
|
||||
func NewEnterpriseInfoConfirmedEvent(certification *entities.Certification, enterpriseInfo map[string]interface{}) *EnterpriseInfoConfirmedEvent {
|
||||
event := &EnterpriseInfoConfirmedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
EventTypeEnterpriseInfoConfirmed,
|
||||
certification.ID,
|
||||
nil,
|
||||
),
|
||||
}
|
||||
event.Data.CertificationID = certification.ID
|
||||
event.Data.UserID = certification.UserID
|
||||
event.Data.EnterpriseInfo = enterpriseInfo
|
||||
event.Data.Status = string(certification.Status)
|
||||
event.Payload = event.Data
|
||||
return event
|
||||
}
|
||||
|
||||
// FaceVerifyInitiatedEvent 人脸识别初始化事件
|
||||
type FaceVerifyInitiatedEvent struct {
|
||||
*BaseCertificationEvent
|
||||
Data struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
VerifyToken string `json:"verify_token"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewFaceVerifyInitiatedEvent 创建人脸识别初始化事件
|
||||
func NewFaceVerifyInitiatedEvent(certification *entities.Certification, verifyToken string) *FaceVerifyInitiatedEvent {
|
||||
event := &FaceVerifyInitiatedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
EventTypeFaceVerifyInitiated,
|
||||
certification.ID,
|
||||
nil,
|
||||
),
|
||||
}
|
||||
event.Data.CertificationID = certification.ID
|
||||
event.Data.UserID = certification.UserID
|
||||
event.Data.VerifyToken = verifyToken
|
||||
event.Data.Status = string(certification.Status)
|
||||
event.Payload = event.Data
|
||||
return event
|
||||
}
|
||||
|
||||
// FaceVerifyCompletedEvent 人脸识别完成事件
|
||||
type FaceVerifyCompletedEvent struct {
|
||||
*BaseCertificationEvent
|
||||
Data struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
VerifyToken string `json:"verify_token"`
|
||||
Success bool `json:"success"`
|
||||
Score float64 `json:"score"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewFaceVerifyCompletedEvent 创建人脸识别完成事件
|
||||
func NewFaceVerifyCompletedEvent(certification *entities.Certification, record *entities.FaceVerifyRecord) *FaceVerifyCompletedEvent {
|
||||
event := &FaceVerifyCompletedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
EventTypeFaceVerifyCompleted,
|
||||
certification.ID,
|
||||
nil,
|
||||
),
|
||||
}
|
||||
event.Data.CertificationID = certification.ID
|
||||
event.Data.UserID = certification.UserID
|
||||
event.Data.VerifyToken = record.CertifyID
|
||||
event.Data.Success = record.IsSuccess()
|
||||
event.Data.Score = record.VerifyScore
|
||||
event.Data.Status = string(certification.Status)
|
||||
event.Payload = event.Data
|
||||
return event
|
||||
}
|
||||
|
||||
// ContractRequestedEvent 合同申请事件
|
||||
type ContractRequestedEvent struct {
|
||||
// EnterpriseVerifiedEvent 企业认证完成事件
|
||||
type EnterpriseVerifiedEvent struct {
|
||||
*BaseCertificationEvent
|
||||
Data struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
@@ -282,11 +125,11 @@ type ContractRequestedEvent struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewContractRequestedEvent 创建合同申请事件
|
||||
func NewContractRequestedEvent(certification *entities.Certification) *ContractRequestedEvent {
|
||||
event := &ContractRequestedEvent{
|
||||
// NewEnterpriseVerifiedEvent 创建企业认证完成事件
|
||||
func NewEnterpriseVerifiedEvent(certification *entities.Certification) *EnterpriseVerifiedEvent {
|
||||
event := &EnterpriseVerifiedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
EventTypeContractRequested,
|
||||
EventTypeEnterpriseVerified,
|
||||
certification.ID,
|
||||
nil,
|
||||
),
|
||||
@@ -298,31 +141,27 @@ func NewContractRequestedEvent(certification *entities.Certification) *ContractR
|
||||
return event
|
||||
}
|
||||
|
||||
// ContractGeneratedEvent 合同生成事件
|
||||
type ContractGeneratedEvent struct {
|
||||
// ContractAppliedEvent 合同申请事件
|
||||
type ContractAppliedEvent struct {
|
||||
*BaseCertificationEvent
|
||||
Data struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
ContractURL string `json:"contract_url"`
|
||||
ContractID string `json:"contract_id"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewContractGeneratedEvent 创建合同生成事件
|
||||
func NewContractGeneratedEvent(certification *entities.Certification, record *entities.ContractRecord) *ContractGeneratedEvent {
|
||||
event := &ContractGeneratedEvent{
|
||||
// NewContractAppliedEvent 创建合同申请事件
|
||||
func NewContractAppliedEvent(certification *entities.Certification) *ContractAppliedEvent {
|
||||
event := &ContractAppliedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
EventTypeContractGenerated,
|
||||
EventTypeContractApplied,
|
||||
certification.ID,
|
||||
nil,
|
||||
),
|
||||
}
|
||||
event.Data.CertificationID = certification.ID
|
||||
event.Data.UserID = certification.UserID
|
||||
event.Data.ContractURL = record.ContractURL
|
||||
event.Data.ContractID = record.ID
|
||||
event.Data.Status = string(certification.Status)
|
||||
event.Payload = event.Data
|
||||
return event
|
||||
@@ -334,14 +173,13 @@ type ContractSignedEvent struct {
|
||||
Data struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
ContractID string `json:"contract_id"`
|
||||
SignedAt string `json:"signed_at"`
|
||||
ContractURL string `json:"contract_url"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewContractSignedEvent 创建合同签署事件
|
||||
func NewContractSignedEvent(certification *entities.Certification, record *entities.ContractRecord) *ContractSignedEvent {
|
||||
func NewContractSignedEvent(certification *entities.Certification, contractURL string) *ContractSignedEvent {
|
||||
event := &ContractSignedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
EventTypeContractSigned,
|
||||
@@ -351,100 +189,7 @@ func NewContractSignedEvent(certification *entities.Certification, record *entit
|
||||
}
|
||||
event.Data.CertificationID = certification.ID
|
||||
event.Data.UserID = certification.UserID
|
||||
event.Data.ContractID = record.ID
|
||||
event.Data.SignedAt = record.SignedAt.Format(time.RFC3339)
|
||||
event.Data.Status = string(certification.Status)
|
||||
event.Payload = event.Data
|
||||
return event
|
||||
}
|
||||
|
||||
// CertificationApprovedEvent 认证审核通过事件
|
||||
type CertificationApprovedEvent struct {
|
||||
*BaseCertificationEvent
|
||||
Data struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
AdminID string `json:"admin_id"`
|
||||
ApprovedAt string `json:"approved_at"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewCertificationApprovedEvent 创建认证审核通过事件
|
||||
func NewCertificationApprovedEvent(certification *entities.Certification, adminID string) *CertificationApprovedEvent {
|
||||
event := &CertificationApprovedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
EventTypeCertificationApproved,
|
||||
certification.ID,
|
||||
nil,
|
||||
),
|
||||
}
|
||||
event.Data.CertificationID = certification.ID
|
||||
event.Data.UserID = certification.UserID
|
||||
event.Data.AdminID = adminID
|
||||
event.Data.ApprovedAt = time.Now().Format(time.RFC3339)
|
||||
event.Data.Status = string(certification.Status)
|
||||
event.Payload = event.Data
|
||||
return event
|
||||
}
|
||||
|
||||
// CertificationRejectedEvent 认证审核拒绝事件
|
||||
type CertificationRejectedEvent struct {
|
||||
*BaseCertificationEvent
|
||||
Data struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
AdminID string `json:"admin_id"`
|
||||
RejectReason string `json:"reject_reason"`
|
||||
RejectedAt string `json:"rejected_at"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewCertificationRejectedEvent 创建认证审核拒绝事件
|
||||
func NewCertificationRejectedEvent(certification *entities.Certification, adminID, rejectReason string) *CertificationRejectedEvent {
|
||||
event := &CertificationRejectedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
EventTypeCertificationRejected,
|
||||
certification.ID,
|
||||
nil,
|
||||
),
|
||||
}
|
||||
event.Data.CertificationID = certification.ID
|
||||
event.Data.UserID = certification.UserID
|
||||
event.Data.AdminID = adminID
|
||||
event.Data.RejectReason = rejectReason
|
||||
event.Data.RejectedAt = time.Now().Format(time.RFC3339)
|
||||
event.Data.Status = string(certification.Status)
|
||||
event.Payload = event.Data
|
||||
return event
|
||||
}
|
||||
|
||||
// WalletCreatedEvent 钱包创建事件
|
||||
type WalletCreatedEvent struct {
|
||||
*BaseCertificationEvent
|
||||
Data struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
WalletID string `json:"wallet_id"`
|
||||
AccessID string `json:"access_id"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewWalletCreatedEvent 创建钱包创建事件
|
||||
func NewWalletCreatedEvent(certification *entities.Certification, walletID, accessID string) *WalletCreatedEvent {
|
||||
event := &WalletCreatedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
EventTypeWalletCreated,
|
||||
certification.ID,
|
||||
nil,
|
||||
),
|
||||
}
|
||||
event.Data.CertificationID = certification.ID
|
||||
event.Data.UserID = certification.UserID
|
||||
event.Data.WalletID = walletID
|
||||
event.Data.AccessID = accessID
|
||||
event.Data.ContractURL = contractURL
|
||||
event.Data.Status = string(certification.Status)
|
||||
event.Payload = event.Data
|
||||
return event
|
||||
@@ -456,14 +201,13 @@ type CertificationCompletedEvent struct {
|
||||
Data struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
WalletID string `json:"wallet_id"`
|
||||
CompletedAt string `json:"completed_at"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewCertificationCompletedEvent 创建认证完成事件
|
||||
func NewCertificationCompletedEvent(certification *entities.Certification, walletID string) *CertificationCompletedEvent {
|
||||
func NewCertificationCompletedEvent(certification *entities.Certification) *CertificationCompletedEvent {
|
||||
event := &CertificationCompletedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
EventTypeCertificationCompleted,
|
||||
@@ -473,54 +217,13 @@ func NewCertificationCompletedEvent(certification *entities.Certification, walle
|
||||
}
|
||||
event.Data.CertificationID = certification.ID
|
||||
event.Data.UserID = certification.UserID
|
||||
event.Data.WalletID = walletID
|
||||
event.Data.CompletedAt = time.Now().Format(time.RFC3339)
|
||||
event.Data.Status = string(certification.Status)
|
||||
event.Payload = event.Data
|
||||
return event
|
||||
}
|
||||
|
||||
// CertificationFailedEvent 认证失败事件
|
||||
type CertificationFailedEvent struct {
|
||||
*BaseCertificationEvent
|
||||
Data struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
FailedAt string `json:"failed_at"`
|
||||
FailureReason string `json:"failure_reason"`
|
||||
Status string `json:"status"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// NewCertificationFailedEvent 创建认证失败事件
|
||||
func NewCertificationFailedEvent(certification *entities.Certification, failureReason string) *CertificationFailedEvent {
|
||||
event := &CertificationFailedEvent{
|
||||
BaseCertificationEvent: NewBaseCertificationEvent(
|
||||
EventTypeCertificationFailed,
|
||||
certification.ID,
|
||||
nil,
|
||||
),
|
||||
}
|
||||
event.Data.CertificationID = certification.ID
|
||||
event.Data.UserID = certification.UserID
|
||||
event.Data.FailedAt = time.Now().Format(time.RFC3339)
|
||||
event.Data.FailureReason = failureReason
|
||||
event.Data.Status = string(certification.Status)
|
||||
event.Payload = event.Data
|
||||
return event
|
||||
}
|
||||
|
||||
// generateEventID 生成事件ID
|
||||
// 工具函数
|
||||
func generateEventID() string {
|
||||
return time.Now().Format("20060102150405") + "-" + generateRandomString(8)
|
||||
}
|
||||
|
||||
// generateRandomString 生成随机字符串
|
||||
func generateRandomString(length int) string {
|
||||
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
b := make([]byte, length)
|
||||
for i := range b {
|
||||
b[i] = charset[time.Now().UnixNano()%int64(len(charset))]
|
||||
}
|
||||
return string(b)
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
@@ -29,20 +29,11 @@ func NewCertificationEventHandler(logger *zap.Logger, notification notification.
|
||||
name: "certification-event-handler",
|
||||
eventTypes: []string{
|
||||
EventTypeCertificationCreated,
|
||||
EventTypeCertificationSubmitted,
|
||||
EventTypeLicenseUploaded,
|
||||
EventTypeOCRCompleted,
|
||||
EventTypeEnterpriseInfoConfirmed,
|
||||
EventTypeFaceVerifyInitiated,
|
||||
EventTypeFaceVerifyCompleted,
|
||||
EventTypeContractRequested,
|
||||
EventTypeContractGenerated,
|
||||
EventTypeEnterpriseInfoSubmitted,
|
||||
EventTypeEnterpriseVerified,
|
||||
EventTypeContractApplied,
|
||||
EventTypeContractSigned,
|
||||
EventTypeCertificationApproved,
|
||||
EventTypeCertificationRejected,
|
||||
EventTypeWalletCreated,
|
||||
EventTypeCertificationCompleted,
|
||||
EventTypeCertificationFailed,
|
||||
},
|
||||
isAsync: true,
|
||||
}
|
||||
@@ -84,34 +75,16 @@ func (h *CertificationEventHandler) Handle(ctx context.Context, event interfaces
|
||||
switch event.GetType() {
|
||||
case EventTypeCertificationCreated:
|
||||
return h.handleCertificationCreated(ctx, event)
|
||||
case EventTypeCertificationSubmitted:
|
||||
return h.handleCertificationSubmitted(ctx, event)
|
||||
case EventTypeLicenseUploaded:
|
||||
return h.handleLicenseUploaded(ctx, event)
|
||||
case EventTypeOCRCompleted:
|
||||
return h.handleOCRCompleted(ctx, event)
|
||||
case EventTypeEnterpriseInfoConfirmed:
|
||||
return h.handleEnterpriseInfoConfirmed(ctx, event)
|
||||
case EventTypeFaceVerifyInitiated:
|
||||
return h.handleFaceVerifyInitiated(ctx, event)
|
||||
case EventTypeFaceVerifyCompleted:
|
||||
return h.handleFaceVerifyCompleted(ctx, event)
|
||||
case EventTypeContractRequested:
|
||||
return h.handleContractRequested(ctx, event)
|
||||
case EventTypeContractGenerated:
|
||||
return h.handleContractGenerated(ctx, event)
|
||||
case EventTypeEnterpriseInfoSubmitted:
|
||||
return h.handleEnterpriseInfoSubmitted(ctx, event)
|
||||
case EventTypeEnterpriseVerified:
|
||||
return h.handleEnterpriseVerified(ctx, event)
|
||||
case EventTypeContractApplied:
|
||||
return h.handleContractApplied(ctx, event)
|
||||
case EventTypeContractSigned:
|
||||
return h.handleContractSigned(ctx, event)
|
||||
case EventTypeCertificationApproved:
|
||||
return h.handleCertificationApproved(ctx, event)
|
||||
case EventTypeCertificationRejected:
|
||||
return h.handleCertificationRejected(ctx, event)
|
||||
case EventTypeWalletCreated:
|
||||
return h.handleWalletCreated(ctx, event)
|
||||
case EventTypeCertificationCompleted:
|
||||
return h.handleCertificationCompleted(ctx, event)
|
||||
case EventTypeCertificationFailed:
|
||||
return h.handleCertificationFailed(ctx, event)
|
||||
default:
|
||||
h.logger.Warn("未知的事件类型", zap.String("event_type", event.GetType()))
|
||||
return nil
|
||||
@@ -133,126 +106,49 @@ func (h *CertificationEventHandler) handleCertificationCreated(ctx context.Conte
|
||||
return h.sendUserNotification(ctx, event, "认证申请创建成功", message)
|
||||
}
|
||||
|
||||
// handleCertificationSubmitted 处理认证提交事件
|
||||
func (h *CertificationEventHandler) handleCertificationSubmitted(ctx context.Context, event interfaces.Event) error {
|
||||
h.logger.Info("认证申请已提交",
|
||||
zap.String("certification_id", event.GetAggregateID()),
|
||||
zap.String("user_id", h.extractUserID(event)),
|
||||
)
|
||||
|
||||
// 发送通知给管理员
|
||||
adminMessage := fmt.Sprintf("📋 新的企业认证申请待审核\n\n认证ID: %s\n用户ID: %s\n提交时间: %s\n\n请及时处理审核。",
|
||||
event.GetAggregateID(),
|
||||
h.extractUserID(event),
|
||||
event.GetTimestamp().Format("2006-01-02 15:04:05"))
|
||||
|
||||
return h.sendAdminNotification(ctx, event, "新认证申请待审核", adminMessage)
|
||||
}
|
||||
|
||||
// handleLicenseUploaded 处理营业执照上传事件
|
||||
func (h *CertificationEventHandler) handleLicenseUploaded(ctx context.Context, event interfaces.Event) error {
|
||||
h.logger.Info("营业执照已上传",
|
||||
// handleEnterpriseInfoSubmitted 处理企业信息提交事件
|
||||
func (h *CertificationEventHandler) handleEnterpriseInfoSubmitted(ctx context.Context, event interfaces.Event) error {
|
||||
h.logger.Info("企业信息已提交",
|
||||
zap.String("certification_id", event.GetAggregateID()),
|
||||
zap.String("user_id", h.extractUserID(event)),
|
||||
)
|
||||
|
||||
// 发送通知给用户
|
||||
message := fmt.Sprintf("📄 营业执照上传成功!\n\n认证ID: %s\n上传时间: %s\n\n系统正在识别营业执照信息,请稍候...",
|
||||
message := fmt.Sprintf("✅ 企业信息提交成功!\n\n认证ID: %s\n提交时间: %s\n\n系统正在验证企业信息,请稍候...",
|
||||
event.GetAggregateID(),
|
||||
event.GetTimestamp().Format("2006-01-02 15:04:05"))
|
||||
|
||||
return h.sendUserNotification(ctx, event, "营业执照上传成功", message)
|
||||
return h.sendUserNotification(ctx, event, "企业信息提交成功", message)
|
||||
}
|
||||
|
||||
// handleOCRCompleted 处理OCR识别完成事件
|
||||
func (h *CertificationEventHandler) handleOCRCompleted(ctx context.Context, event interfaces.Event) error {
|
||||
h.logger.Info("OCR识别已完成",
|
||||
// handleEnterpriseVerified 处理企业认证完成事件
|
||||
func (h *CertificationEventHandler) handleEnterpriseVerified(ctx context.Context, event interfaces.Event) error {
|
||||
h.logger.Info("企业认证已完成",
|
||||
zap.String("certification_id", event.GetAggregateID()),
|
||||
zap.String("user_id", h.extractUserID(event)),
|
||||
)
|
||||
|
||||
// 发送通知给用户
|
||||
message := fmt.Sprintf("✅ OCR识别完成!\n\n认证ID: %s\n识别时间: %s\n\n请确认企业信息是否正确,如有问题请及时联系客服。",
|
||||
message := fmt.Sprintf("✅ 企业认证完成!\n\n认证ID: %s\n完成时间: %s\n\n下一步:请申请电子合同。",
|
||||
event.GetAggregateID(),
|
||||
event.GetTimestamp().Format("2006-01-02 15:04:05"))
|
||||
|
||||
return h.sendUserNotification(ctx, event, "OCR识别完成", message)
|
||||
return h.sendUserNotification(ctx, event, "企业认证完成", message)
|
||||
}
|
||||
|
||||
// handleEnterpriseInfoConfirmed 处理企业信息确认事件
|
||||
func (h *CertificationEventHandler) handleEnterpriseInfoConfirmed(ctx context.Context, event interfaces.Event) error {
|
||||
h.logger.Info("企业信息已确认",
|
||||
zap.String("certification_id", event.GetAggregateID()),
|
||||
zap.String("user_id", h.extractUserID(event)),
|
||||
)
|
||||
|
||||
// 发送通知给用户
|
||||
message := fmt.Sprintf("✅ 企业信息确认成功!\n\n认证ID: %s\n确认时间: %s\n\n下一步:请完成人脸识别验证。",
|
||||
event.GetAggregateID(),
|
||||
event.GetTimestamp().Format("2006-01-02 15:04:05"))
|
||||
|
||||
return h.sendUserNotification(ctx, event, "企业信息确认成功", message)
|
||||
}
|
||||
|
||||
// handleFaceVerifyInitiated 处理人脸识别初始化事件
|
||||
func (h *CertificationEventHandler) handleFaceVerifyInitiated(ctx context.Context, event interfaces.Event) error {
|
||||
h.logger.Info("人脸识别已初始化",
|
||||
zap.String("certification_id", event.GetAggregateID()),
|
||||
zap.String("user_id", h.extractUserID(event)),
|
||||
)
|
||||
|
||||
// 发送通知给用户
|
||||
message := fmt.Sprintf("👤 人脸识别验证已开始!\n\n认证ID: %s\n开始时间: %s\n\n请按照指引完成人脸识别验证。",
|
||||
event.GetAggregateID(),
|
||||
event.GetTimestamp().Format("2006-01-02 15:04:05"))
|
||||
|
||||
return h.sendUserNotification(ctx, event, "人脸识别验证开始", message)
|
||||
}
|
||||
|
||||
// handleFaceVerifyCompleted 处理人脸识别完成事件
|
||||
func (h *CertificationEventHandler) handleFaceVerifyCompleted(ctx context.Context, event interfaces.Event) error {
|
||||
h.logger.Info("人脸识别已完成",
|
||||
zap.String("certification_id", event.GetAggregateID()),
|
||||
zap.String("user_id", h.extractUserID(event)),
|
||||
)
|
||||
|
||||
// 发送通知给用户
|
||||
message := fmt.Sprintf("✅ 人脸识别验证完成!\n\n认证ID: %s\n完成时间: %s\n\n下一步:系统将为您申请电子合同。",
|
||||
event.GetAggregateID(),
|
||||
event.GetTimestamp().Format("2006-01-02 15:04:05"))
|
||||
|
||||
return h.sendUserNotification(ctx, event, "人脸识别验证完成", message)
|
||||
}
|
||||
|
||||
// handleContractRequested 处理合同申请事件
|
||||
func (h *CertificationEventHandler) handleContractRequested(ctx context.Context, event interfaces.Event) error {
|
||||
// handleContractApplied 处理合同申请事件
|
||||
func (h *CertificationEventHandler) handleContractApplied(ctx context.Context, event interfaces.Event) error {
|
||||
h.logger.Info("电子合同申请已提交",
|
||||
zap.String("certification_id", event.GetAggregateID()),
|
||||
zap.String("user_id", h.extractUserID(event)),
|
||||
)
|
||||
|
||||
// 发送通知给管理员
|
||||
adminMessage := fmt.Sprintf("📋 新的电子合同申请待审核\n\n认证ID: %s\n用户ID: %s\n申请时间: %s\n\n请及时处理合同审核。",
|
||||
event.GetAggregateID(),
|
||||
h.extractUserID(event),
|
||||
event.GetTimestamp().Format("2006-01-02 15:04:05"))
|
||||
|
||||
return h.sendAdminNotification(ctx, event, "新合同申请待审核", adminMessage)
|
||||
}
|
||||
|
||||
// handleContractGenerated 处理合同生成事件
|
||||
func (h *CertificationEventHandler) handleContractGenerated(ctx context.Context, event interfaces.Event) error {
|
||||
h.logger.Info("电子合同已生成",
|
||||
zap.String("certification_id", event.GetAggregateID()),
|
||||
zap.String("user_id", h.extractUserID(event)),
|
||||
)
|
||||
|
||||
// 发送通知给用户
|
||||
message := fmt.Sprintf("📄 电子合同已生成!\n\n认证ID: %s\n生成时间: %s\n\n请及时签署电子合同以完成认证流程。",
|
||||
message := fmt.Sprintf("📋 电子合同申请已提交!\n\n认证ID: %s\n申请时间: %s\n\n系统正在生成电子合同,请稍候...",
|
||||
event.GetAggregateID(),
|
||||
event.GetTimestamp().Format("2006-01-02 15:04:05"))
|
||||
|
||||
return h.sendUserNotification(ctx, event, "电子合同已生成", message)
|
||||
return h.sendUserNotification(ctx, event, "合同申请已提交", message)
|
||||
}
|
||||
|
||||
// handleContractSigned 处理合同签署事件
|
||||
@@ -263,56 +159,11 @@ func (h *CertificationEventHandler) handleContractSigned(ctx context.Context, ev
|
||||
)
|
||||
|
||||
// 发送通知给用户
|
||||
message := fmt.Sprintf("✅ 电子合同签署成功!\n\n认证ID: %s\n签署时间: %s\n\n您的企业认证申请已进入最终审核阶段。",
|
||||
message := fmt.Sprintf("✅ 电子合同签署完成!\n\n认证ID: %s\n签署时间: %s\n\n恭喜!您的企业认证已完成。",
|
||||
event.GetAggregateID(),
|
||||
event.GetTimestamp().Format("2006-01-02 15:04:05"))
|
||||
|
||||
return h.sendUserNotification(ctx, event, "电子合同签署成功", message)
|
||||
}
|
||||
|
||||
// handleCertificationApproved 处理认证审核通过事件
|
||||
func (h *CertificationEventHandler) handleCertificationApproved(ctx context.Context, event interfaces.Event) error {
|
||||
h.logger.Info("认证申请已审核通过",
|
||||
zap.String("certification_id", event.GetAggregateID()),
|
||||
zap.String("user_id", h.extractUserID(event)),
|
||||
)
|
||||
|
||||
// 发送通知给用户
|
||||
message := fmt.Sprintf("🎉 恭喜!您的企业认证申请已审核通过!\n\n认证ID: %s\n审核时间: %s\n\n系统正在为您创建钱包和访问密钥...",
|
||||
event.GetAggregateID(),
|
||||
event.GetTimestamp().Format("2006-01-02 15:04:05"))
|
||||
|
||||
return h.sendUserNotification(ctx, event, "认证申请审核通过", message)
|
||||
}
|
||||
|
||||
// handleCertificationRejected 处理认证审核拒绝事件
|
||||
func (h *CertificationEventHandler) handleCertificationRejected(ctx context.Context, event interfaces.Event) error {
|
||||
h.logger.Info("认证申请已被拒绝",
|
||||
zap.String("certification_id", event.GetAggregateID()),
|
||||
zap.String("user_id", h.extractUserID(event)),
|
||||
)
|
||||
|
||||
// 发送通知给用户
|
||||
message := fmt.Sprintf("❌ 很抱歉,您的企业认证申请未通过审核\n\n认证ID: %s\n拒绝时间: %s\n\n请根据拒绝原因修改后重新提交申请。",
|
||||
event.GetAggregateID(),
|
||||
event.GetTimestamp().Format("2006-01-02 15:04:05"))
|
||||
|
||||
return h.sendUserNotification(ctx, event, "认证申请审核未通过", message)
|
||||
}
|
||||
|
||||
// handleWalletCreated 处理钱包创建事件
|
||||
func (h *CertificationEventHandler) handleWalletCreated(ctx context.Context, event interfaces.Event) error {
|
||||
h.logger.Info("钱包已创建",
|
||||
zap.String("certification_id", event.GetAggregateID()),
|
||||
zap.String("user_id", h.extractUserID(event)),
|
||||
)
|
||||
|
||||
// 发送通知给用户
|
||||
message := fmt.Sprintf("💰 钱包创建成功!\n\n认证ID: %s\n创建时间: %s\n\n您的企业钱包已激活,可以开始使用相关服务。",
|
||||
event.GetAggregateID(),
|
||||
event.GetTimestamp().Format("2006-01-02 15:04:05"))
|
||||
|
||||
return h.sendUserNotification(ctx, event, "钱包创建成功", message)
|
||||
return h.sendUserNotification(ctx, event, "合同签署完成", message)
|
||||
}
|
||||
|
||||
// handleCertificationCompleted 处理认证完成事件
|
||||
@@ -323,44 +174,26 @@ func (h *CertificationEventHandler) handleCertificationCompleted(ctx context.Con
|
||||
)
|
||||
|
||||
// 发送通知给用户
|
||||
message := fmt.Sprintf("🎉 恭喜!您的企业认证已全部完成!\n\n认证ID: %s\n完成时间: %s\n\n您现在可以享受完整的企业级服务功能。",
|
||||
message := fmt.Sprintf("🎉 恭喜!您的企业认证已完成!\n\n认证ID: %s\n完成时间: %s\n\n您现在可以享受企业用户的所有权益。",
|
||||
event.GetAggregateID(),
|
||||
event.GetTimestamp().Format("2006-01-02 15:04:05"))
|
||||
|
||||
return h.sendUserNotification(ctx, event, "企业认证完成", message)
|
||||
}
|
||||
|
||||
// handleCertificationFailed 处理认证失败事件
|
||||
func (h *CertificationEventHandler) handleCertificationFailed(ctx context.Context, event interfaces.Event) error {
|
||||
h.logger.Error("企业认证失败",
|
||||
zap.String("certification_id", event.GetAggregateID()),
|
||||
zap.String("user_id", h.extractUserID(event)),
|
||||
)
|
||||
|
||||
// 发送通知给用户
|
||||
message := fmt.Sprintf("❌ 企业认证流程遇到问题\n\n认证ID: %s\n失败时间: %s\n\n请联系客服获取帮助。",
|
||||
event.GetAggregateID(),
|
||||
event.GetTimestamp().Format("2006-01-02 15:04:05"))
|
||||
|
||||
return h.sendUserNotification(ctx, event, "企业认证失败", message)
|
||||
}
|
||||
|
||||
// sendUserNotification 发送用户通知
|
||||
func (h *CertificationEventHandler) sendUserNotification(ctx context.Context, event interfaces.Event, title, message string) error {
|
||||
url := fmt.Sprintf("https://example.com/certification/%s", event.GetAggregateID())
|
||||
btnText := "查看详情"
|
||||
if err := h.notification.SendCardMessage(ctx, title, message, url, btnText); err != nil {
|
||||
h.logger.Error("发送用户通知失败",
|
||||
zap.String("event_type", event.GetType()),
|
||||
zap.String("event_id", event.GetID()),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
userID := h.extractUserID(event)
|
||||
if userID == "" {
|
||||
h.logger.Warn("无法提取用户ID,跳过通知发送")
|
||||
return nil
|
||||
}
|
||||
|
||||
h.logger.Info("用户通知发送成功",
|
||||
zap.String("event_type", event.GetType()),
|
||||
zap.String("event_id", event.GetID()),
|
||||
// 这里可以调用通知服务发送消息
|
||||
h.logger.Info("发送用户通知",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("title", title),
|
||||
zap.String("message", message),
|
||||
)
|
||||
|
||||
return nil
|
||||
@@ -368,20 +201,10 @@ func (h *CertificationEventHandler) sendUserNotification(ctx context.Context, ev
|
||||
|
||||
// sendAdminNotification 发送管理员通知
|
||||
func (h *CertificationEventHandler) sendAdminNotification(ctx context.Context, event interfaces.Event, title, message string) error {
|
||||
url := fmt.Sprintf("https://admin.example.com/certification/%s", event.GetAggregateID())
|
||||
btnText := "立即处理"
|
||||
if err := h.notification.SendCardMessage(ctx, title, message, url, btnText); err != nil {
|
||||
h.logger.Error("发送管理员通知失败",
|
||||
zap.String("event_type", event.GetType()),
|
||||
zap.String("event_id", event.GetID()),
|
||||
zap.Error(err),
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
h.logger.Info("管理员通知发送成功",
|
||||
zap.String("event_type", event.GetType()),
|
||||
zap.String("event_id", event.GetID()),
|
||||
// 这里可以调用通知服务发送管理员消息
|
||||
h.logger.Info("发送管理员通知",
|
||||
zap.String("title", title),
|
||||
zap.String("message", message),
|
||||
)
|
||||
|
||||
return nil
|
||||
@@ -389,29 +212,58 @@ func (h *CertificationEventHandler) sendAdminNotification(ctx context.Context, e
|
||||
|
||||
// extractUserID 从事件中提取用户ID
|
||||
func (h *CertificationEventHandler) extractUserID(event interfaces.Event) string {
|
||||
if payload, ok := event.GetPayload().(map[string]interface{}); ok {
|
||||
if userID, exists := payload["user_id"]; exists {
|
||||
if id, ok := userID.(string); ok {
|
||||
return id
|
||||
payload := event.GetPayload()
|
||||
if payload == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 尝试从payload中提取user_id
|
||||
if data, ok := payload.(map[string]interface{}); ok {
|
||||
if userID, exists := data["user_id"]; exists {
|
||||
if str, ok := userID.(string); ok {
|
||||
return str
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试从事件数据中提取
|
||||
if eventData, ok := event.(*BaseCertificationEvent); ok {
|
||||
if data, ok := eventData.Payload.(map[string]interface{}); ok {
|
||||
if userID, exists := data["user_id"]; exists {
|
||||
if id, ok := userID.(string); ok {
|
||||
return id
|
||||
// 尝试从JSON中解析
|
||||
if data, ok := payload.(map[string]interface{}); ok {
|
||||
if dataField, exists := data["data"]; exists {
|
||||
if dataMap, ok := dataField.(map[string]interface{}); ok {
|
||||
if userID, exists := dataMap["user_id"]; exists {
|
||||
if str, ok := userID.(string); ok {
|
||||
return str
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
// 尝试从JSON字符串解析
|
||||
if jsonData, err := json.Marshal(payload); err == nil {
|
||||
var data map[string]interface{}
|
||||
if err := json.Unmarshal(jsonData, &data); err == nil {
|
||||
if userID, exists := data["user_id"]; exists {
|
||||
if str, ok := userID.(string); ok {
|
||||
return str
|
||||
}
|
||||
}
|
||||
if dataField, exists := data["data"]; exists {
|
||||
if dataMap, ok := dataField.(map[string]interface{}); ok {
|
||||
if userID, exists := dataMap["user_id"]; exists {
|
||||
if str, ok := userID.(string); ok {
|
||||
return str
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// LoggingEventHandler 日志记录事件处理器
|
||||
// LoggingEventHandler 日志事件处理器
|
||||
type LoggingEventHandler struct {
|
||||
logger *zap.Logger
|
||||
name string
|
||||
@@ -419,29 +271,20 @@ type LoggingEventHandler struct {
|
||||
isAsync bool
|
||||
}
|
||||
|
||||
// NewLoggingEventHandler 创建日志记录事件处理器
|
||||
// NewLoggingEventHandler 创建日志事件处理器
|
||||
func NewLoggingEventHandler(logger *zap.Logger) *LoggingEventHandler {
|
||||
return &LoggingEventHandler{
|
||||
logger: logger,
|
||||
name: "logging-event-handler",
|
||||
eventTypes: []string{
|
||||
EventTypeCertificationCreated,
|
||||
EventTypeCertificationSubmitted,
|
||||
EventTypeLicenseUploaded,
|
||||
EventTypeOCRCompleted,
|
||||
EventTypeEnterpriseInfoConfirmed,
|
||||
EventTypeFaceVerifyInitiated,
|
||||
EventTypeFaceVerifyCompleted,
|
||||
EventTypeContractRequested,
|
||||
EventTypeContractGenerated,
|
||||
EventTypeEnterpriseInfoSubmitted,
|
||||
EventTypeEnterpriseVerified,
|
||||
EventTypeContractApplied,
|
||||
EventTypeContractSigned,
|
||||
EventTypeCertificationApproved,
|
||||
EventTypeCertificationRejected,
|
||||
EventTypeWalletCreated,
|
||||
EventTypeCertificationCompleted,
|
||||
EventTypeCertificationFailed,
|
||||
},
|
||||
isAsync: false, // 同步处理,确保日志及时记录
|
||||
isAsync: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,27 +306,21 @@ func (l *LoggingEventHandler) IsAsync() bool {
|
||||
// GetRetryConfig 获取重试配置
|
||||
func (l *LoggingEventHandler) GetRetryConfig() interfaces.RetryConfig {
|
||||
return interfaces.RetryConfig{
|
||||
MaxRetries: 1,
|
||||
RetryDelay: 1 * time.Second,
|
||||
MaxRetries: 0,
|
||||
RetryDelay: 0,
|
||||
BackoffFactor: 1.0,
|
||||
MaxDelay: 1 * time.Second,
|
||||
MaxDelay: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Handle 处理事件
|
||||
func (l *LoggingEventHandler) Handle(ctx context.Context, event interfaces.Event) error {
|
||||
// 记录结构化日志
|
||||
eventData, _ := json.Marshal(event.GetPayload())
|
||||
|
||||
l.logger.Info("认证事件记录",
|
||||
zap.String("event_id", event.GetID()),
|
||||
l.logger.Info("认证事件日志",
|
||||
zap.String("event_type", event.GetType()),
|
||||
zap.String("event_id", event.GetID()),
|
||||
zap.String("aggregate_id", event.GetAggregateID()),
|
||||
zap.String("aggregate_type", event.GetAggregateType()),
|
||||
zap.Time("timestamp", event.GetTimestamp()),
|
||||
zap.String("source", event.GetSource()),
|
||||
zap.String("payload", string(eventData)),
|
||||
zap.Any("payload", event.GetPayload()),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ type CertificationStats struct {
|
||||
TotalCertifications int64
|
||||
PendingCertifications int64
|
||||
CompletedCertifications int64
|
||||
RejectedCertifications int64
|
||||
TodaySubmissions int64
|
||||
}
|
||||
|
||||
@@ -24,75 +23,69 @@ type CertificationRepository interface {
|
||||
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, adminID *string, notes string) 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)
|
||||
}
|
||||
|
||||
// FaceVerifyRecordRepository 人脸识别记录仓储接口
|
||||
type FaceVerifyRecordRepository interface {
|
||||
interfaces.Repository[entities.FaceVerifyRecord]
|
||||
// EnterpriseInfoSubmitRecordRepository 企业信息提交记录仓储接口
|
||||
type EnterpriseInfoSubmitRecordRepository interface {
|
||||
interfaces.Repository[entities.EnterpriseInfoSubmitRecord]
|
||||
|
||||
// 基础查询 - 直接使用实体
|
||||
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.FaceVerifyRecord, error)
|
||||
GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.FaceVerifyRecord, error)
|
||||
// 基础查询
|
||||
GetByUserID(ctx context.Context, userID string) ([]*entities.EnterpriseInfoSubmitRecord, error)
|
||||
GetLatestByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error)
|
||||
|
||||
// 复杂查询 - 使用查询参数
|
||||
ListRecords(ctx context.Context, query *queries.ListFaceVerifyRecordsQuery) ([]*entities.FaceVerifyRecord, int64, error)
|
||||
|
||||
// 统计信息
|
||||
GetSuccessRate(ctx context.Context, days int) (float64, error)
|
||||
}
|
||||
|
||||
// ContractRecordRepository 合同记录仓储接口
|
||||
type ContractRecordRepository interface {
|
||||
interfaces.Repository[entities.ContractRecord]
|
||||
|
||||
// 基础查询 - 直接使用实体
|
||||
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.ContractRecord, error)
|
||||
GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.ContractRecord, error)
|
||||
|
||||
// 复杂查询 - 使用查询参数
|
||||
ListRecords(ctx context.Context, query *queries.ListContractRecordsQuery) ([]*entities.ContractRecord, int64, error)
|
||||
// 复杂查询
|
||||
ListRecords(ctx context.Context, query *queries.ListEnterpriseInfoSubmitRecordsQuery) ([]*entities.EnterpriseInfoSubmitRecord, int64, error)
|
||||
|
||||
// 业务操作
|
||||
UpdateContractStatus(ctx context.Context, recordID string, status string, adminID *string, notes string) error
|
||||
UpdateStatus(ctx context.Context, recordID string, status string, reason string) error
|
||||
}
|
||||
|
||||
// LicenseUploadRecordRepository 营业执照上传记录仓储接口
|
||||
type LicenseUploadRecordRepository interface {
|
||||
interfaces.Repository[entities.LicenseUploadRecord]
|
||||
// EsignContractGenerateRecordRepository e签宝生成合同记录仓储接口
|
||||
type EsignContractGenerateRecordRepository interface {
|
||||
interfaces.Repository[entities.EsignContractGenerateRecord]
|
||||
|
||||
// 基础查询 - 直接使用实体
|
||||
GetByCertificationID(ctx context.Context, certificationID string) (*entities.LicenseUploadRecord, error)
|
||||
// 基础查询
|
||||
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.ListLicenseUploadRecordsQuery) ([]*entities.LicenseUploadRecord, int64, error)
|
||||
// 复杂查询
|
||||
ListRecords(ctx context.Context, query *queries.ListEsignContractGenerateRecordsQuery) ([]*entities.EsignContractGenerateRecord, int64, error)
|
||||
|
||||
// 业务操作
|
||||
UpdateOCRResult(ctx context.Context, recordID string, ocrResult string, confidence float64) 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
|
||||
}
|
||||
|
||||
// NotificationRecordRepository 通知记录仓储接口
|
||||
type NotificationRecordRepository interface {
|
||||
interfaces.Repository[entities.NotificationRecord]
|
||||
// EsignContractSignRecordRepository e签宝签署合同记录仓储接口
|
||||
type EsignContractSignRecordRepository interface {
|
||||
interfaces.Repository[entities.EsignContractSignRecord]
|
||||
|
||||
// 基础查询 - 直接使用实体
|
||||
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.NotificationRecord, error)
|
||||
GetUnreadByUserID(ctx context.Context, userID string) ([]*entities.NotificationRecord, error)
|
||||
// 基础查询
|
||||
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.ListNotificationRecordsQuery) ([]*entities.NotificationRecord, int64, error)
|
||||
// 复杂查询
|
||||
ListRecords(ctx context.Context, query *queries.ListEsignContractSignRecordsQuery) ([]*entities.EsignContractSignRecord, int64, error)
|
||||
|
||||
// 批量操作
|
||||
BatchCreate(ctx context.Context, records []entities.NotificationRecord) error
|
||||
MarkAsRead(ctx context.Context, recordIDs []string) error
|
||||
MarkAllAsReadByUser(ctx context.Context, userID string) 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
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ type ListCertificationsQuery struct {
|
||||
PageSize int `json:"page_size"`
|
||||
UserID string `json:"user_id"`
|
||||
Status enums.CertificationStatus `json:"status"`
|
||||
AdminID string `json:"admin_id"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
EnterpriseName string `json:"enterprise_name"`
|
||||
@@ -26,47 +25,38 @@ type ListEnterprisesQuery struct {
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
|
||||
// ListFaceVerifyRecordsQuery 人脸识别记录列表查询参数
|
||||
type ListFaceVerifyRecordsQuery struct {
|
||||
// ListEnterpriseInfoSubmitRecordsQuery 企业信息提交记录列表查询参数
|
||||
type ListEnterpriseInfoSubmitRecordsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
Status string `json:"status"`
|
||||
CompanyName string `json:"company_name"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
|
||||
// ListContractRecordsQuery 合同记录列表查询参数
|
||||
type ListContractRecordsQuery struct {
|
||||
// ListEsignContractGenerateRecordsQuery e签宝生成合同记录列表查询参数
|
||||
type ListEsignContractGenerateRecordsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
Status string `json:"status"`
|
||||
ContractType string `json:"contract_type"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
|
||||
// ListLicenseUploadRecordsQuery 营业执照上传记录列表查询参数
|
||||
type ListLicenseUploadRecordsQuery struct {
|
||||
// ListEsignContractSignRecordsQuery e签宝签署合同记录列表查询参数
|
||||
type ListEsignContractSignRecordsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
Status string `json:"status"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
|
||||
// ListNotificationRecordsQuery 通知记录列表查询参数
|
||||
type ListNotificationRecordsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
Type string `json:"type"`
|
||||
IsRead *bool `json:"is_read"`
|
||||
SignerName string `json:"signer_name"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
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
|
||||
}
|
||||
@@ -1,774 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/application/certification/dto/responses"
|
||||
"tyapi-server/internal/domains/certification/entities"
|
||||
"tyapi-server/internal/domains/certification/enums"
|
||||
"tyapi-server/internal/domains/certification/repositories"
|
||||
sharedOCR "tyapi-server/internal/shared/ocr"
|
||||
sharedStorage "tyapi-server/internal/shared/storage"
|
||||
)
|
||||
|
||||
// CertificationService 认证领域服务
|
||||
type CertificationService struct {
|
||||
certRepo repositories.CertificationRepository
|
||||
licenseRepo repositories.LicenseUploadRecordRepository
|
||||
faceVerifyRepo repositories.FaceVerifyRecordRepository
|
||||
contractRepo repositories.ContractRecordRepository
|
||||
stateMachine *CertificationStateMachine
|
||||
logger *zap.Logger
|
||||
ocrService sharedOCR.OCRService
|
||||
storageService sharedStorage.StorageService
|
||||
}
|
||||
|
||||
// NewCertificationService 创建认证领域服务
|
||||
func NewCertificationService(
|
||||
certRepo repositories.CertificationRepository,
|
||||
licenseRepo repositories.LicenseUploadRecordRepository,
|
||||
faceVerifyRepo repositories.FaceVerifyRecordRepository,
|
||||
contractRepo repositories.ContractRecordRepository,
|
||||
stateMachine *CertificationStateMachine,
|
||||
logger *zap.Logger,
|
||||
ocrService sharedOCR.OCRService,
|
||||
storageService sharedStorage.StorageService,
|
||||
) *CertificationService {
|
||||
return &CertificationService{
|
||||
certRepo: certRepo,
|
||||
licenseRepo: licenseRepo,
|
||||
faceVerifyRepo: faceVerifyRepo,
|
||||
contractRepo: contractRepo,
|
||||
stateMachine: stateMachine,
|
||||
logger: logger,
|
||||
ocrService: ocrService,
|
||||
storageService: storageService,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCertification 创建认证申请
|
||||
func (s *CertificationService) 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
|
||||
}
|
||||
|
||||
// SubmitEnterpriseInfo 提交企业信息
|
||||
func (s *CertificationService) 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 {
|
||||
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
|
||||
}
|
||||
|
||||
// InitiateFaceVerify 发起人脸识别验证
|
||||
func (s *CertificationService) InitiateFaceVerify(ctx context.Context, certificationID, realName, idCardNumber string) (*entities.FaceVerifyRecord, error) {
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查当前状态是否可以发起人脸识别
|
||||
if cert.Status != enums.StatusInfoSubmitted {
|
||||
return nil, fmt.Errorf("当前状态不允许发起人脸识别")
|
||||
}
|
||||
|
||||
// 创建人脸识别记录
|
||||
faceVerifyRecord := &entities.FaceVerifyRecord{
|
||||
CertificationID: certificationID,
|
||||
UserID: cert.UserID,
|
||||
RealName: realName,
|
||||
IDCardNumber: idCardNumber,
|
||||
Status: "PROCESSING",
|
||||
ExpiresAt: time.Now().Add(30 * time.Minute), // 30分钟有效期
|
||||
}
|
||||
|
||||
createdFaceRecord, err := s.faceVerifyRepo.Create(ctx, *faceVerifyRecord)
|
||||
if err != nil {
|
||||
s.logger.Error("创建人脸识别记录失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建人脸识别记录失败: %w", err)
|
||||
}
|
||||
faceVerifyRecord = &createdFaceRecord
|
||||
|
||||
s.logger.Info("人脸识别验证发起成功",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("face_verify_id", faceVerifyRecord.ID),
|
||||
)
|
||||
|
||||
return faceVerifyRecord, nil
|
||||
}
|
||||
|
||||
// CompleteFaceVerify 完成人脸识别验证
|
||||
func (s *CertificationService) CompleteFaceVerify(ctx context.Context, faceVerifyID string, isSuccess bool) error {
|
||||
faceVerifyRecord, err := s.faceVerifyRepo.GetByID(ctx, faceVerifyID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("人脸识别记录不存在: %w", err)
|
||||
}
|
||||
|
||||
// 更新人脸识别记录状态
|
||||
now := time.Now()
|
||||
faceVerifyRecord.CompletedAt = &now
|
||||
|
||||
if isSuccess {
|
||||
faceVerifyRecord.Status = "SUCCESS"
|
||||
faceVerifyRecord.VerifyScore = 0.95 // 示例分数
|
||||
} else {
|
||||
faceVerifyRecord.Status = "FAIL"
|
||||
faceVerifyRecord.ResultMessage = "人脸识别验证失败"
|
||||
}
|
||||
|
||||
if err := s.faceVerifyRepo.Update(ctx, faceVerifyRecord); err != nil {
|
||||
s.logger.Error("更新人脸识别记录失败", zap.Error(err))
|
||||
return fmt.Errorf("更新人脸识别记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 根据验证结果转换认证状态
|
||||
var targetStatus enums.CertificationStatus
|
||||
if isSuccess {
|
||||
targetStatus = enums.StatusFaceVerified
|
||||
} else {
|
||||
targetStatus = enums.StatusFaceFailed
|
||||
}
|
||||
|
||||
if err := s.stateMachine.TransitionTo(ctx, faceVerifyRecord.CertificationID, targetStatus, false, false, nil); err != nil {
|
||||
return fmt.Errorf("状态转换失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("人脸识别验证完成",
|
||||
zap.String("face_verify_id", faceVerifyID),
|
||||
zap.Bool("is_success", isSuccess),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyContract 申请合同
|
||||
func (s *CertificationService) 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.StatusFaceVerified {
|
||||
return fmt.Errorf("当前状态不允许申请合同")
|
||||
}
|
||||
|
||||
// 使用状态机转换状态
|
||||
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractApplied, true, false, nil); err != nil {
|
||||
return fmt.Errorf("状态转换失败: %w", err)
|
||||
}
|
||||
|
||||
// 创建合同记录
|
||||
contractRecord := &entities.ContractRecord{
|
||||
CertificationID: certificationID,
|
||||
UserID: cert.UserID,
|
||||
Status: "PENDING",
|
||||
ContractType: "ENTERPRISE_CERTIFICATION",
|
||||
}
|
||||
|
||||
createdContract, err := s.contractRepo.Create(ctx, *contractRecord)
|
||||
if err != nil {
|
||||
s.logger.Error("创建合同记录失败", zap.Error(err))
|
||||
return fmt.Errorf("创建合同记录失败: %w", err)
|
||||
}
|
||||
contractRecord = &createdContract
|
||||
|
||||
// 自动转换到待审核状态
|
||||
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractPending, false, false, nil); err != nil {
|
||||
return fmt.Errorf("状态转换失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("合同申请成功",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("contract_id", contractRecord.ID),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApproveContract 管理员审核合同
|
||||
func (s *CertificationService) ApproveContract(ctx context.Context, certificationID, adminID, signingURL, approvalNotes string) error {
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查当前状态是否可以审核
|
||||
if cert.Status != enums.StatusContractPending {
|
||||
return fmt.Errorf("当前状态不允许审核")
|
||||
}
|
||||
|
||||
// 准备审核元数据
|
||||
metadata := map[string]interface{}{
|
||||
"admin_id": adminID,
|
||||
"signing_url": signingURL,
|
||||
"approval_notes": approvalNotes,
|
||||
}
|
||||
|
||||
// 使用状态机转换状态
|
||||
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusContractApproved, false, true, metadata); err != nil {
|
||||
return fmt.Errorf("状态转换失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("合同审核通过",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("admin_id", adminID),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RejectContract 管理员拒绝合同
|
||||
func (s *CertificationService) RejectContract(ctx context.Context, certificationID, adminID, rejectReason string) error {
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查当前状态是否可以拒绝
|
||||
if cert.Status != enums.StatusContractPending {
|
||||
return fmt.Errorf("当前状态不允许拒绝")
|
||||
}
|
||||
|
||||
// 准备拒绝元数据
|
||||
metadata := map[string]interface{}{
|
||||
"admin_id": adminID,
|
||||
"reject_reason": rejectReason,
|
||||
}
|
||||
|
||||
// 使用状态机转换状态
|
||||
if err := s.stateMachine.TransitionTo(ctx, certificationID, enums.StatusRejected, false, true, metadata); err != nil {
|
||||
return fmt.Errorf("状态转换失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("合同审核拒绝",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("admin_id", adminID),
|
||||
zap.String("reject_reason", rejectReason),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompleteContractSign 完成合同签署
|
||||
func (s *CertificationService) 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.StatusContractApproved {
|
||||
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),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompleteCertification 完成认证
|
||||
func (s *CertificationService) 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
|
||||
}
|
||||
|
||||
// RetryFaceVerify 重试人脸识别
|
||||
func (s *CertificationService) RetryFaceVerify(ctx context.Context, certificationID string) error {
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查是否可以重试
|
||||
if !cert.CanRetryFaceVerify() {
|
||||
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),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestartCertification 重新开始认证流程
|
||||
func (s *CertificationService) RestartCertification(ctx context.Context, certificationID string) error {
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查是否可以重新开始
|
||||
if !cert.CanRestart() {
|
||||
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),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCertificationByUserID 根据用户ID获取认证申请
|
||||
func (s *CertificationService) GetCertificationByUserID(ctx context.Context, userID string) (*entities.Certification, error) {
|
||||
return s.certRepo.GetByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
// GetCertificationByID 根据ID获取认证申请
|
||||
func (s *CertificationService) 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
|
||||
}
|
||||
|
||||
// GetFaceVerifyRecords 获取人脸识别记录
|
||||
func (s *CertificationService) GetFaceVerifyRecords(ctx context.Context, certificationID string) ([]*entities.FaceVerifyRecord, error) {
|
||||
return s.faceVerifyRepo.GetByCertificationID(ctx, certificationID)
|
||||
}
|
||||
|
||||
// GetContractRecords 获取合同记录
|
||||
func (s *CertificationService) GetContractRecords(ctx context.Context, certificationID string) ([]*entities.ContractRecord, error) {
|
||||
return s.contractRepo.GetByCertificationID(ctx, certificationID)
|
||||
}
|
||||
|
||||
// GetCertificationProgress 获取认证进度信息
|
||||
func (s *CertificationService) 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(),
|
||||
"is_admin_action_required": cert.IsAdminActionRequired(),
|
||||
"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.FaceVerifiedAt != nil {
|
||||
progress["face_verified_at"] = cert.FaceVerifiedAt
|
||||
}
|
||||
if cert.ContractAppliedAt != nil {
|
||||
progress["contract_applied_at"] = cert.ContractAppliedAt
|
||||
}
|
||||
if cert.ContractApprovedAt != nil {
|
||||
progress["contract_approved_at"] = cert.ContractApprovedAt
|
||||
}
|
||||
if cert.ContractSignedAt != nil {
|
||||
progress["contract_signed_at"] = cert.ContractSignedAt
|
||||
}
|
||||
if cert.CompletedAt != nil {
|
||||
progress["completed_at"] = cert.CompletedAt
|
||||
}
|
||||
|
||||
return progress, nil
|
||||
}
|
||||
|
||||
// GetCertificationWithDetails 获取认证申请详细信息(包含关联记录)
|
||||
func (s *CertificationService) GetCertificationWithDetails(ctx context.Context, certificationID string) (*entities.Certification, error) {
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 获取人脸识别记录
|
||||
faceRecords, err := s.faceVerifyRepo.GetByCertificationID(ctx, certificationID)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取人脸识别记录失败", zap.Error(err))
|
||||
} else {
|
||||
// 将指针切片转换为值切片
|
||||
for _, record := range faceRecords {
|
||||
cert.FaceVerifyRecords = append(cert.FaceVerifyRecords, *record)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取合同记录
|
||||
contractRecords, err := s.contractRepo.GetByCertificationID(ctx, certificationID)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取合同记录失败", zap.Error(err))
|
||||
} else {
|
||||
// 将指针切片转换为值切片
|
||||
for _, record := range contractRecords {
|
||||
cert.ContractRecords = append(cert.ContractRecords, *record)
|
||||
}
|
||||
}
|
||||
|
||||
// 获取营业执照上传记录
|
||||
licenseRecord, err := s.licenseRepo.GetByCertificationID(ctx, certificationID)
|
||||
if err != nil {
|
||||
s.logger.Warn("获取营业执照记录失败", zap.Error(err))
|
||||
} else {
|
||||
cert.LicenseUploadRecord = licenseRecord
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// UpdateOCRResult 更新OCR识别结果
|
||||
func (s *CertificationService) UpdateOCRResult(ctx context.Context, certificationID, ocrRequestID string, confidence float64) error {
|
||||
cert, err := s.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("认证申请不存在: %w", err)
|
||||
}
|
||||
|
||||
// 更新OCR信息
|
||||
cert.OCRRequestID = ocrRequestID
|
||||
cert.OCRConfidence = confidence
|
||||
|
||||
if err := s.certRepo.Update(ctx, cert); err != nil {
|
||||
s.logger.Error("更新OCR结果失败", zap.Error(err))
|
||||
return fmt.Errorf("更新OCR结果失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("OCR结果更新成功",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("ocr_request_id", ocrRequestID),
|
||||
zap.Float64("confidence", confidence),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateLicenseUpload 验证营业执照上传
|
||||
func (s *CertificationService) ValidateLicenseUpload(ctx context.Context, userID, fileName string, fileSize int64) error {
|
||||
// 检查文件类型
|
||||
allowedExts := []string{".jpg", ".jpeg", ".png", ".pdf"}
|
||||
ext := strings.ToLower(filepath.Ext(fileName))
|
||||
|
||||
isAllowed := false
|
||||
for _, allowedExt := range allowedExts {
|
||||
if ext == allowedExt {
|
||||
isAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isAllowed {
|
||||
return fmt.Errorf("文件格式不支持,仅支持 JPG、PNG、PDF 格式")
|
||||
}
|
||||
|
||||
// 检查文件大小(限制为10MB)
|
||||
const maxFileSize = 10 * 1024 * 1024 // 10MB
|
||||
if fileSize > maxFileSize {
|
||||
return fmt.Errorf("文件大小不能超过10MB")
|
||||
}
|
||||
|
||||
// 检查用户是否已有上传记录(可选,根据业务需求决定)
|
||||
// existingRecords, err := s.licenseRepo.GetByUserID(ctx, userID, 1, 1)
|
||||
// if err == nil && len(existingRecords) > 0 {
|
||||
// return fmt.Errorf("用户已有营业执照上传记录")
|
||||
// }
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateLicenseUploadRecord 创建营业执照上传记录
|
||||
func (s *CertificationService) CreateLicenseUploadRecord(ctx context.Context, userID, fileName string, fileSize int64, uploadResult *sharedStorage.UploadResult) (*entities.LicenseUploadRecord, error) {
|
||||
// 获取文件MIME类型
|
||||
fileType := mime.TypeByExtension(filepath.Ext(fileName))
|
||||
if fileType == "" {
|
||||
fileType = "application/octet-stream"
|
||||
}
|
||||
|
||||
// 创建营业执照上传记录
|
||||
licenseRecord := &entities.LicenseUploadRecord{
|
||||
UserID: userID,
|
||||
OriginalFileName: fileName,
|
||||
FileURL: uploadResult.URL,
|
||||
FileSize: fileSize,
|
||||
FileType: fileType,
|
||||
QiNiuKey: uploadResult.Key,
|
||||
OCRProcessed: false,
|
||||
OCRSuccess: false,
|
||||
}
|
||||
|
||||
createdLicense, err := s.licenseRepo.Create(ctx, *licenseRecord)
|
||||
if err != nil {
|
||||
s.logger.Error("创建营业执照记录失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建营业执照记录失败: %w", err)
|
||||
}
|
||||
licenseRecord = &createdLicense
|
||||
|
||||
s.logger.Info("营业执照上传记录创建成功",
|
||||
zap.String("license_id", licenseRecord.ID),
|
||||
zap.String("user_id", userID),
|
||||
zap.String("file_name", fileName),
|
||||
)
|
||||
|
||||
return licenseRecord, nil
|
||||
}
|
||||
|
||||
// ProcessOCRAsync 异步处理OCR识别
|
||||
func (s *CertificationService) ProcessOCRAsync(ctx context.Context, licenseID string, fileBytes []byte) error {
|
||||
// 创建临时文件
|
||||
tempFile, err := os.CreateTemp("", "license-upload-*.jpg")
|
||||
if err != nil {
|
||||
s.logger.Error("创建临时文件失败", zap.Error(err))
|
||||
return fmt.Errorf("创建临时文件失败: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
tempFile.Close()
|
||||
os.Remove(tempFile.Name()) // 清理临时文件
|
||||
}()
|
||||
|
||||
// 将文件内容写入临时文件
|
||||
if _, err := tempFile.Write(fileBytes); err != nil {
|
||||
s.logger.Error("写入临时文件失败", zap.Error(err))
|
||||
return fmt.Errorf("写入临时文件失败: %w", err)
|
||||
}
|
||||
|
||||
// 调用OCR服务识别营业执照
|
||||
ocrResult, err := s.ocrService.RecognizeBusinessLicense(ctx, fileBytes)
|
||||
if err != nil {
|
||||
s.logger.Error("OCR识别失败", zap.Error(err), zap.String("license_id", licenseID))
|
||||
// 更新OCR处理状态为失败
|
||||
if updateErr := s.updateOCRStatus(ctx, licenseID, false, "", 0, err.Error()); updateErr != nil {
|
||||
s.logger.Error("更新OCR失败状态失败", zap.Error(updateErr))
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// 将OCR结果转换为JSON字符串
|
||||
ocrRawData := fmt.Sprintf(`{"company_name":"%s","unified_social_code":"%s","legal_person_name":"%s","confidence":%.2f}`,
|
||||
ocrResult.CompanyName, ocrResult.UnifiedSocialCode, ocrResult.LegalPersonName, ocrResult.Confidence)
|
||||
|
||||
// 更新OCR处理状态
|
||||
success := ocrResult.Confidence >= 0.8 // 置信度大于0.8认为成功
|
||||
if err := s.updateOCRStatus(ctx, licenseID, true, ocrRawData, ocrResult.Confidence, ""); err != nil {
|
||||
s.logger.Error("更新OCR结果失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Info("OCR识别完成",
|
||||
zap.String("license_id", licenseID),
|
||||
zap.Bool("success", success),
|
||||
zap.Float64("confidence", ocrResult.Confidence),
|
||||
zap.String("company_name", ocrResult.CompanyName),
|
||||
zap.String("legal_person", ocrResult.LegalPersonName),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateOCRStatus 更新OCR处理状态
|
||||
func (s *CertificationService) updateOCRStatus(ctx context.Context, licenseID string, processed bool, ocrRawData string, confidence float64, errorMessage string) error {
|
||||
licenseRecord, err := s.licenseRepo.GetByID(ctx, licenseID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取营业执照记录失败: %w", err)
|
||||
}
|
||||
|
||||
licenseRecord.OCRProcessed = processed
|
||||
if processed {
|
||||
licenseRecord.OCRSuccess = confidence >= 0.8
|
||||
licenseRecord.OCRRawData = ocrRawData
|
||||
licenseRecord.OCRConfidence = confidence
|
||||
} else {
|
||||
licenseRecord.OCRSuccess = false
|
||||
licenseRecord.OCRErrorMessage = errorMessage
|
||||
}
|
||||
|
||||
if err := s.licenseRepo.Update(ctx, licenseRecord); err != nil {
|
||||
return fmt.Errorf("更新OCR结果失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateLicenseFile 验证营业执照文件
|
||||
func (s *CertificationService) validateLicenseFile(fileBytes []byte, fileName string) error {
|
||||
// 检查文件大小(最大10MB)
|
||||
if len(fileBytes) > 10*1024*1024 {
|
||||
return fmt.Errorf("文件大小超过限制,最大支持10MB")
|
||||
}
|
||||
|
||||
// 检查文件类型
|
||||
ext := filepath.Ext(fileName)
|
||||
allowedExts := []string{".jpg", ".jpeg", ".png", ".bmp"}
|
||||
isAllowed := false
|
||||
for _, allowedExt := range allowedExts {
|
||||
if ext == allowedExt {
|
||||
isAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isAllowed {
|
||||
return fmt.Errorf("不支持的文件格式,仅支持jpg、jpeg、png、bmp格式")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UploadBusinessLicense 上传营业执照,先OCR识别,成功后再上传到七牛
|
||||
func (s *CertificationService) UploadBusinessLicense(ctx context.Context, userID string, fileBytes []byte, fileName string) (*entities.LicenseUploadRecord, *responses.BusinessLicenseResult, error) {
|
||||
s.logger.Info("开始上传营业执照",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("file_name", fileName),
|
||||
zap.Int("file_size", len(fileBytes)),
|
||||
)
|
||||
|
||||
// 1. 验证文件
|
||||
if err := s.validateLicenseFile(fileBytes, fileName); err != nil {
|
||||
return nil, nil, fmt.Errorf("文件验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 先OCR识别
|
||||
s.logger.Info("开始OCR识别营业执照")
|
||||
ocrResult, err := s.ocrService.RecognizeBusinessLicense(ctx, fileBytes)
|
||||
if err != nil {
|
||||
s.logger.Error("OCR识别失败", zap.Error(err))
|
||||
return nil, nil, fmt.Errorf("营业执照识别失败,请上传清晰的营业执照图片")
|
||||
}
|
||||
if ocrResult.CompanyName == "" || ocrResult.LegalPersonName == "" {
|
||||
s.logger.Warn("OCR识别未提取到关键信息")
|
||||
return nil, nil, fmt.Errorf("营业执照识别失败,请上传清晰的营业执照图片")
|
||||
}
|
||||
|
||||
// 3. 识别成功后上传到七牛
|
||||
uploadResult, err := s.storageService.UploadFile(ctx, fileBytes, fileName)
|
||||
if err != nil {
|
||||
s.logger.Error("文件上传失败", zap.Error(err))
|
||||
return nil, nil, fmt.Errorf("文件上传失败: %w", err)
|
||||
}
|
||||
|
||||
// 4. 创建上传记录
|
||||
uploadRecord := &entities.LicenseUploadRecord{
|
||||
UserID: userID,
|
||||
OriginalFileName: fileName,
|
||||
FileURL: uploadResult.URL,
|
||||
QiNiuKey: uploadResult.Key,
|
||||
FileSize: uploadResult.Size,
|
||||
FileType: uploadResult.MimeType,
|
||||
OCRProcessed: true,
|
||||
OCRSuccess: true,
|
||||
OCRConfidence: ocrResult.Confidence,
|
||||
OCRRawData: "", // 可存储OCR原始数据
|
||||
OCRErrorMessage: "",
|
||||
}
|
||||
|
||||
// 5. 保存上传记录并获取新创建的记录
|
||||
createdRecord, err := s.licenseRepo.Create(ctx, *uploadRecord)
|
||||
if err != nil {
|
||||
s.logger.Error("保存上传记录失败", zap.Error(err))
|
||||
return nil, nil, fmt.Errorf("保存上传记录失败: %w", err)
|
||||
}
|
||||
uploadRecord.ID = createdRecord.ID
|
||||
s.logger.Info("营业执照上传和OCR识别完成",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("file_url", uploadResult.URL),
|
||||
zap.Bool("ocr_success", uploadRecord.OCRSuccess),
|
||||
)
|
||||
|
||||
return uploadRecord, ocrResult, nil
|
||||
}
|
||||
|
||||
// getOCRStatus 根据OCR结果确定状态
|
||||
func (s *CertificationService) getOCRStatus(result *responses.BusinessLicenseResult) string {
|
||||
if result.CompanyName == "" && result.LegalPersonName == "" {
|
||||
return "failed"
|
||||
}
|
||||
if result.CompanyName != "" && result.LegalPersonName != "" {
|
||||
return "success"
|
||||
}
|
||||
return "partial"
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
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
|
||||
}
|
||||
256
internal/domains/certification/services/state_config.go
Normal file
256
internal/domains/certification/services/state_config.go
Normal file
@@ -0,0 +1,256 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"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"`
|
||||
IsAdminActionRequired bool `json:"is_admin_action_required"`
|
||||
TimestampField string `json:"timestamp_field,omitempty"`
|
||||
Description string `json:"description"`
|
||||
NextValidStatuses []enums.CertificationStatus `json:"next_valid_statuses"`
|
||||
}
|
||||
|
||||
// TransitionConfig 状态转换配置
|
||||
type TransitionConfig struct {
|
||||
From enums.CertificationStatus `json:"from"`
|
||||
To enums.CertificationStatus `json:"to"`
|
||||
Action string `json:"action"`
|
||||
ActionName string `json:"action_name"`
|
||||
AllowUser bool `json:"allow_user"`
|
||||
AllowAdmin bool `json:"allow_admin"`
|
||||
RequiresValidation bool `json:"requires_validation"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// CertificationStateManager 认证状态管理器
|
||||
type CertificationStateManager struct {
|
||||
stateMap map[enums.CertificationStatus]*StateConfig
|
||||
transitionMap map[enums.CertificationStatus][]*TransitionConfig
|
||||
}
|
||||
|
||||
// NewCertificationStateManager 创建认证状态管理器
|
||||
func NewCertificationStateManager() *CertificationStateManager {
|
||||
manager := &CertificationStateManager{
|
||||
stateMap: make(map[enums.CertificationStatus]*StateConfig),
|
||||
transitionMap: make(map[enums.CertificationStatus][]*TransitionConfig),
|
||||
}
|
||||
|
||||
// 初始化状态配置
|
||||
manager.initStateConfigs()
|
||||
return manager
|
||||
}
|
||||
|
||||
// initStateConfigs 初始化状态配置
|
||||
func (manager *CertificationStateManager) initStateConfigs() {
|
||||
// 状态配置
|
||||
states := []*StateConfig{
|
||||
{
|
||||
Status: enums.StatusPending,
|
||||
Name: "待认证",
|
||||
ProgressPercentage: 0,
|
||||
IsUserActionRequired: true,
|
||||
IsAdminActionRequired: false,
|
||||
Description: "等待用户提交企业信息",
|
||||
NextValidStatuses: []enums.CertificationStatus{enums.StatusInfoSubmitted},
|
||||
},
|
||||
{
|
||||
Status: enums.StatusInfoSubmitted,
|
||||
Name: "已提交企业信息",
|
||||
ProgressPercentage: 20,
|
||||
IsUserActionRequired: true,
|
||||
IsAdminActionRequired: false,
|
||||
TimestampField: "InfoSubmittedAt",
|
||||
Description: "用户已提交企业信息",
|
||||
NextValidStatuses: []enums.CertificationStatus{enums.StatusEnterpriseVerified, enums.StatusInfoSubmitted}, // 可以重新提交
|
||||
},
|
||||
{
|
||||
Status: enums.StatusEnterpriseVerified,
|
||||
Name: "已企业认证",
|
||||
ProgressPercentage: 40,
|
||||
IsUserActionRequired: true,
|
||||
IsAdminActionRequired: false,
|
||||
TimestampField: "EnterpriseVerifiedAt",
|
||||
Description: "企业认证已完成",
|
||||
NextValidStatuses: []enums.CertificationStatus{enums.StatusContractApplied},
|
||||
},
|
||||
{
|
||||
Status: enums.StatusContractApplied,
|
||||
Name: "已申请合同",
|
||||
ProgressPercentage: 60,
|
||||
IsUserActionRequired: true,
|
||||
IsAdminActionRequired: false,
|
||||
TimestampField: "ContractAppliedAt",
|
||||
Description: "合同已申请",
|
||||
NextValidStatuses: []enums.CertificationStatus{enums.StatusContractSigned},
|
||||
},
|
||||
{
|
||||
Status: enums.StatusContractSigned,
|
||||
Name: "已签署合同",
|
||||
ProgressPercentage: 80,
|
||||
IsUserActionRequired: false,
|
||||
IsAdminActionRequired: false,
|
||||
TimestampField: "ContractSignedAt",
|
||||
Description: "合同已签署",
|
||||
NextValidStatuses: []enums.CertificationStatus{enums.StatusCompleted},
|
||||
},
|
||||
{
|
||||
Status: enums.StatusCompleted,
|
||||
Name: "认证完成",
|
||||
ProgressPercentage: 100,
|
||||
IsUserActionRequired: false,
|
||||
IsAdminActionRequired: false,
|
||||
TimestampField: "CompletedAt",
|
||||
Description: "认证流程已完成",
|
||||
NextValidStatuses: []enums.CertificationStatus{},
|
||||
},
|
||||
}
|
||||
|
||||
// 转换配置
|
||||
transitions := []*TransitionConfig{
|
||||
// 提交企业信息
|
||||
{
|
||||
From: enums.StatusPending,
|
||||
To: enums.StatusInfoSubmitted,
|
||||
Action: "submit_info",
|
||||
ActionName: "提交企业信息",
|
||||
AllowUser: true,
|
||||
AllowAdmin: false,
|
||||
RequiresValidation: true,
|
||||
Description: "用户提交企业信息",
|
||||
},
|
||||
// 重新提交企业信息
|
||||
{
|
||||
From: enums.StatusInfoSubmitted,
|
||||
To: enums.StatusInfoSubmitted,
|
||||
Action: "resubmit_info",
|
||||
ActionName: "重新提交企业信息",
|
||||
AllowUser: true,
|
||||
AllowAdmin: false,
|
||||
RequiresValidation: true,
|
||||
Description: "用户重新提交企业信息",
|
||||
},
|
||||
// 企业认证
|
||||
{
|
||||
From: enums.StatusInfoSubmitted,
|
||||
To: enums.StatusEnterpriseVerified,
|
||||
Action: "enterprise_verify",
|
||||
ActionName: "企业认证",
|
||||
AllowUser: true,
|
||||
AllowAdmin: false,
|
||||
RequiresValidation: true,
|
||||
Description: "用户完成企业认证",
|
||||
},
|
||||
// 申请合同
|
||||
{
|
||||
From: enums.StatusEnterpriseVerified,
|
||||
To: enums.StatusContractApplied,
|
||||
Action: "apply_contract",
|
||||
ActionName: "申请合同",
|
||||
AllowUser: true,
|
||||
AllowAdmin: false,
|
||||
RequiresValidation: false,
|
||||
Description: "用户申请合同",
|
||||
},
|
||||
// 签署合同
|
||||
{
|
||||
From: enums.StatusContractApplied,
|
||||
To: enums.StatusContractSigned,
|
||||
Action: "sign_contract",
|
||||
ActionName: "签署合同",
|
||||
AllowUser: true,
|
||||
AllowAdmin: false,
|
||||
RequiresValidation: true,
|
||||
Description: "用户签署合同",
|
||||
},
|
||||
// 完成认证
|
||||
{
|
||||
From: enums.StatusContractSigned,
|
||||
To: enums.StatusCompleted,
|
||||
Action: "complete",
|
||||
ActionName: "完成认证",
|
||||
AllowUser: false,
|
||||
AllowAdmin: false,
|
||||
RequiresValidation: false,
|
||||
Description: "系统自动完成认证",
|
||||
},
|
||||
}
|
||||
|
||||
// 构建映射
|
||||
for _, state := range states {
|
||||
manager.stateMap[state.Status] = state
|
||||
}
|
||||
|
||||
for _, transition := range transitions {
|
||||
manager.transitionMap[transition.From] = append(manager.transitionMap[transition.From], transition)
|
||||
}
|
||||
}
|
||||
|
||||
// GetStateConfig 获取状态配置
|
||||
func (manager *CertificationStateManager) GetStateConfig(status enums.CertificationStatus) *StateConfig {
|
||||
return manager.stateMap[status]
|
||||
}
|
||||
|
||||
// GetTransitionConfigs 获取状态转换配置
|
||||
func (manager *CertificationStateManager) GetTransitionConfigs(from enums.CertificationStatus) []*TransitionConfig {
|
||||
return manager.transitionMap[from]
|
||||
}
|
||||
|
||||
// CanTransition 检查是否可以转换
|
||||
func (manager *CertificationStateManager) CanTransition(from enums.CertificationStatus, to enums.CertificationStatus, isUser bool, isAdmin bool) (bool, string) {
|
||||
transitions := manager.GetTransitionConfigs(from)
|
||||
|
||||
for _, transition := range transitions {
|
||||
if transition.To == to {
|
||||
if isUser && !transition.AllowUser {
|
||||
return false, "用户不允许执行此操作"
|
||||
}
|
||||
if isAdmin && !transition.AllowAdmin {
|
||||
return false, "管理员不允许执行此操作"
|
||||
}
|
||||
if !isUser && !isAdmin && (transition.AllowUser || transition.AllowAdmin) {
|
||||
return false, "此操作需要用户或管理员权限"
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
}
|
||||
|
||||
return false, "不支持的状态转换"
|
||||
}
|
||||
|
||||
// GetProgressPercentage 获取进度百分比
|
||||
func (manager *CertificationStateManager) GetProgressPercentage(status enums.CertificationStatus) int {
|
||||
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
|
||||
return stateConfig.ProgressPercentage
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// IsUserActionRequired 检查是否需要用户操作
|
||||
func (manager *CertificationStateManager) IsUserActionRequired(status enums.CertificationStatus) bool {
|
||||
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
|
||||
return stateConfig.IsUserActionRequired
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsAdminActionRequired 检查是否需要管理员操作
|
||||
func (manager *CertificationStateManager) IsAdminActionRequired(status enums.CertificationStatus) bool {
|
||||
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
|
||||
return stateConfig.IsAdminActionRequired
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetNextValidStatuses 获取下一个有效状态
|
||||
func (manager *CertificationStateManager) GetNextValidStatuses(status enums.CertificationStatus) []enums.CertificationStatus {
|
||||
if stateConfig := manager.GetStateConfig(status); stateConfig != nil {
|
||||
return stateConfig.NextValidStatuses
|
||||
}
|
||||
return []enums.CertificationStatus{}
|
||||
}
|
||||
@@ -12,21 +12,11 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// StateTransition 状态转换规则
|
||||
type StateTransition struct {
|
||||
From enums.CertificationStatus
|
||||
To enums.CertificationStatus
|
||||
Action string
|
||||
AllowUser bool // 是否允许用户操作
|
||||
AllowAdmin bool // 是否允许管理员操作
|
||||
RequiresValidation bool // 是否需要额外验证
|
||||
}
|
||||
|
||||
// CertificationStateMachine 认证状态机
|
||||
type CertificationStateMachine struct {
|
||||
transitions map[enums.CertificationStatus][]StateTransition
|
||||
certRepo repositories.CertificationRepository
|
||||
logger *zap.Logger
|
||||
stateManager *CertificationStateManager
|
||||
certRepo repositories.CertificationRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCertificationStateMachine 创建认证状态机
|
||||
@@ -34,41 +24,10 @@ func NewCertificationStateMachine(
|
||||
certRepo repositories.CertificationRepository,
|
||||
logger *zap.Logger,
|
||||
) *CertificationStateMachine {
|
||||
sm := &CertificationStateMachine{
|
||||
transitions: make(map[enums.CertificationStatus][]StateTransition),
|
||||
certRepo: certRepo,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
// 初始化状态转换规则
|
||||
sm.initializeTransitions()
|
||||
return sm
|
||||
}
|
||||
|
||||
// initializeTransitions 初始化状态转换规则
|
||||
func (sm *CertificationStateMachine) initializeTransitions() {
|
||||
transitions := []StateTransition{
|
||||
// 正常流程转换
|
||||
{enums.StatusPending, enums.StatusInfoSubmitted, "submit_info", true, false, true},
|
||||
{enums.StatusInfoSubmitted, enums.StatusFaceVerified, "face_verify", true, false, true},
|
||||
{enums.StatusFaceVerified, enums.StatusContractApplied, "apply_contract", true, false, false},
|
||||
{enums.StatusContractApplied, enums.StatusContractPending, "system_process", false, false, false},
|
||||
{enums.StatusContractPending, enums.StatusContractApproved, "admin_approve", false, true, true},
|
||||
{enums.StatusContractApproved, enums.StatusContractSigned, "user_sign", true, false, true},
|
||||
{enums.StatusContractSigned, enums.StatusCompleted, "system_complete", false, false, false},
|
||||
|
||||
// 失败和重试转换
|
||||
{enums.StatusInfoSubmitted, enums.StatusFaceFailed, "face_fail", false, false, false},
|
||||
{enums.StatusFaceFailed, enums.StatusFaceVerified, "retry_face", true, false, true},
|
||||
{enums.StatusContractPending, enums.StatusRejected, "admin_reject", false, true, true},
|
||||
{enums.StatusRejected, enums.StatusInfoSubmitted, "restart_process", true, false, false},
|
||||
{enums.StatusContractApproved, enums.StatusSignFailed, "sign_fail", false, false, false},
|
||||
{enums.StatusSignFailed, enums.StatusContractSigned, "retry_sign", true, false, true},
|
||||
}
|
||||
|
||||
// 构建状态转换映射
|
||||
for _, transition := range transitions {
|
||||
sm.transitions[transition.From] = append(sm.transitions[transition.From], transition)
|
||||
return &CertificationStateMachine{
|
||||
stateManager: NewCertificationStateManager(),
|
||||
certRepo: certRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,27 +38,7 @@ func (sm *CertificationStateMachine) CanTransition(
|
||||
isUser bool,
|
||||
isAdmin bool,
|
||||
) (bool, string) {
|
||||
validTransitions, exists := sm.transitions[from]
|
||||
if !exists {
|
||||
return false, "当前状态不支持任何转换"
|
||||
}
|
||||
|
||||
for _, transition := range validTransitions {
|
||||
if transition.To == to {
|
||||
if isUser && !transition.AllowUser {
|
||||
return false, "用户不允许执行此操作"
|
||||
}
|
||||
if isAdmin && !transition.AllowAdmin {
|
||||
return false, "管理员不允许执行此操作"
|
||||
}
|
||||
if !isUser && !isAdmin && (transition.AllowUser || transition.AllowAdmin) {
|
||||
return false, "此操作需要用户或管理员权限"
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
}
|
||||
|
||||
return false, "不支持的状态转换"
|
||||
return sm.stateManager.CanTransition(from, to, isUser, isAdmin)
|
||||
}
|
||||
|
||||
// TransitionTo 执行状态转换
|
||||
@@ -123,11 +62,6 @@ func (sm *CertificationStateMachine) TransitionTo(
|
||||
return fmt.Errorf("状态转换失败: %s", reason)
|
||||
}
|
||||
|
||||
// 执行状态转换前的验证
|
||||
if err := sm.validateTransition(ctx, &cert, targetStatus, metadata); err != nil {
|
||||
return fmt.Errorf("状态转换验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 更新状态和时间戳
|
||||
oldStatus := cert.Status
|
||||
cert.Status = targetStatus
|
||||
@@ -154,20 +88,23 @@ func (sm *CertificationStateMachine) TransitionTo(
|
||||
|
||||
// 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 status {
|
||||
case enums.StatusInfoSubmitted:
|
||||
switch stateConfig.TimestampField {
|
||||
case "InfoSubmittedAt":
|
||||
cert.InfoSubmittedAt = &now
|
||||
case enums.StatusFaceVerified:
|
||||
cert.FaceVerifiedAt = &now
|
||||
case enums.StatusContractApplied:
|
||||
case "EnterpriseVerifiedAt":
|
||||
cert.EnterpriseVerifiedAt = &now
|
||||
case "ContractAppliedAt":
|
||||
cert.ContractAppliedAt = &now
|
||||
case enums.StatusContractApproved:
|
||||
cert.ContractApprovedAt = &now
|
||||
case enums.StatusContractSigned:
|
||||
case "ContractSignedAt":
|
||||
cert.ContractSignedAt = &now
|
||||
case enums.StatusCompleted:
|
||||
case "CompletedAt":
|
||||
cert.CompletedAt = &now
|
||||
}
|
||||
}
|
||||
@@ -179,25 +116,6 @@ func (sm *CertificationStateMachine) updateCertificationFields(
|
||||
metadata map[string]interface{},
|
||||
) {
|
||||
switch status {
|
||||
case enums.StatusContractApproved:
|
||||
if adminID, ok := metadata["admin_id"].(string); ok {
|
||||
cert.AdminID = &adminID
|
||||
}
|
||||
if approvalNotes, ok := metadata["approval_notes"].(string); ok {
|
||||
cert.ApprovalNotes = approvalNotes
|
||||
}
|
||||
if signingURL, ok := metadata["signing_url"].(string); ok {
|
||||
cert.SigningURL = signingURL
|
||||
}
|
||||
|
||||
case enums.StatusRejected:
|
||||
if adminID, ok := metadata["admin_id"].(string); ok {
|
||||
cert.AdminID = &adminID
|
||||
}
|
||||
if rejectReason, ok := metadata["reject_reason"].(string); ok {
|
||||
cert.RejectReason = rejectReason
|
||||
}
|
||||
|
||||
case enums.StatusContractSigned:
|
||||
if contractURL, ok := metadata["contract_url"].(string); ok {
|
||||
cert.ContractURL = contractURL
|
||||
@@ -205,66 +123,13 @@ func (sm *CertificationStateMachine) updateCertificationFields(
|
||||
}
|
||||
}
|
||||
|
||||
// validateTransition 验证状态转换的有效性
|
||||
func (sm *CertificationStateMachine) validateTransition(
|
||||
ctx context.Context,
|
||||
cert *entities.Certification,
|
||||
targetStatus enums.CertificationStatus,
|
||||
metadata map[string]interface{},
|
||||
) error {
|
||||
switch targetStatus {
|
||||
case enums.StatusInfoSubmitted:
|
||||
// 验证企业信息是否完整
|
||||
// 这里应该检查用户是否有企业信息,通过用户域的企业服务验证
|
||||
// 暂时跳过验证,由应用服务层协调
|
||||
break
|
||||
|
||||
case enums.StatusFaceVerified:
|
||||
// 验证人脸识别是否成功
|
||||
// 这里可以添加人脸识别结果的验证逻辑
|
||||
|
||||
case enums.StatusContractApproved:
|
||||
// 验证管理员审核信息
|
||||
if metadata["signing_url"] == nil || metadata["signing_url"].(string) == "" {
|
||||
return fmt.Errorf("缺少合同签署链接")
|
||||
}
|
||||
|
||||
case enums.StatusRejected:
|
||||
// 验证拒绝原因
|
||||
if metadata["reject_reason"] == nil || metadata["reject_reason"].(string) == "" {
|
||||
return fmt.Errorf("缺少拒绝原因")
|
||||
}
|
||||
|
||||
case enums.StatusContractSigned:
|
||||
// 验证合同签署信息
|
||||
if cert.SigningURL == "" {
|
||||
return fmt.Errorf("缺少合同签署链接")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValidNextStatuses 获取当前状态可以转换到的下一个状态列表
|
||||
func (sm *CertificationStateMachine) GetValidNextStatuses(
|
||||
currentStatus enums.CertificationStatus,
|
||||
isUser bool,
|
||||
isAdmin bool,
|
||||
) []enums.CertificationStatus {
|
||||
var validStatuses []enums.CertificationStatus
|
||||
|
||||
transitions, exists := sm.transitions[currentStatus]
|
||||
if !exists {
|
||||
return validStatuses
|
||||
}
|
||||
|
||||
for _, transition := range transitions {
|
||||
if (isUser && transition.AllowUser) || (isAdmin && transition.AllowAdmin) {
|
||||
validStatuses = append(validStatuses, transition.To)
|
||||
}
|
||||
}
|
||||
|
||||
return validStatuses
|
||||
return sm.stateManager.GetNextValidStatuses(currentStatus)
|
||||
}
|
||||
|
||||
// GetTransitionAction 获取状态转换对应的操作名称
|
||||
@@ -272,20 +137,49 @@ func (sm *CertificationStateMachine) GetTransitionAction(
|
||||
from enums.CertificationStatus,
|
||||
to enums.CertificationStatus,
|
||||
) string {
|
||||
transitions, exists := sm.transitions[from]
|
||||
if !exists {
|
||||
return ""
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -315,12 +209,12 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
|
||||
})
|
||||
}
|
||||
|
||||
if cert.FaceVerifiedAt != nil {
|
||||
if cert.EnterpriseVerifiedAt != nil {
|
||||
history = append(history, map[string]interface{}{
|
||||
"status": string(enums.StatusFaceVerified),
|
||||
"timestamp": *cert.FaceVerifiedAt,
|
||||
"action": "face_verify",
|
||||
"performer": "system",
|
||||
"status": string(enums.StatusEnterpriseVerified),
|
||||
"timestamp": *cert.EnterpriseVerifiedAt,
|
||||
"action": "enterprise_verify",
|
||||
"performer": "user",
|
||||
"metadata": map[string]interface{}{},
|
||||
})
|
||||
}
|
||||
@@ -335,27 +229,6 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
|
||||
})
|
||||
}
|
||||
|
||||
if cert.ContractApprovedAt != nil {
|
||||
metadata := map[string]interface{}{}
|
||||
if cert.AdminID != nil {
|
||||
metadata["admin_id"] = *cert.AdminID
|
||||
}
|
||||
if cert.ApprovalNotes != "" {
|
||||
metadata["approval_notes"] = cert.ApprovalNotes
|
||||
}
|
||||
if cert.SigningURL != "" {
|
||||
metadata["signing_url"] = cert.SigningURL
|
||||
}
|
||||
|
||||
history = append(history, map[string]interface{}{
|
||||
"status": string(enums.StatusContractApproved),
|
||||
"timestamp": *cert.ContractApprovedAt,
|
||||
"action": "admin_approve",
|
||||
"performer": "admin",
|
||||
"metadata": metadata,
|
||||
})
|
||||
}
|
||||
|
||||
if cert.ContractSignedAt != nil {
|
||||
metadata := map[string]interface{}{}
|
||||
if cert.ContractURL != "" {
|
||||
@@ -365,7 +238,7 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
|
||||
history = append(history, map[string]interface{}{
|
||||
"status": string(enums.StatusContractSigned),
|
||||
"timestamp": *cert.ContractSignedAt,
|
||||
"action": "user_sign",
|
||||
"action": "sign_contract",
|
||||
"performer": "user",
|
||||
"metadata": metadata,
|
||||
})
|
||||
@@ -375,7 +248,7 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
|
||||
history = append(history, map[string]interface{}{
|
||||
"status": string(enums.StatusCompleted),
|
||||
"timestamp": *cert.CompletedAt,
|
||||
"action": "system_complete",
|
||||
"action": "complete",
|
||||
"performer": "system",
|
||||
"metadata": map[string]interface{}{},
|
||||
})
|
||||
@@ -383,66 +256,3 @@ func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, c
|
||||
|
||||
return history, nil
|
||||
}
|
||||
|
||||
// ValidateCertificationFlow 验证认证流程的完整性
|
||||
func (sm *CertificationStateMachine) ValidateCertificationFlow(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)
|
||||
}
|
||||
|
||||
validation := map[string]interface{}{
|
||||
"certification_id": certificationID,
|
||||
"current_status": cert.Status,
|
||||
"is_valid": true,
|
||||
"issues": []string{},
|
||||
"warnings": []string{},
|
||||
}
|
||||
|
||||
// 检查必要的时间节点
|
||||
if cert.Status != enums.StatusPending {
|
||||
if cert.InfoSubmittedAt == nil {
|
||||
validation["is_valid"] = false
|
||||
validation["issues"] = append(validation["issues"].([]string), "缺少企业信息提交时间")
|
||||
}
|
||||
}
|
||||
|
||||
if cert.Status == enums.StatusFaceVerified || cert.Status == enums.StatusContractApplied ||
|
||||
cert.Status == enums.StatusContractPending || cert.Status == enums.StatusContractApproved ||
|
||||
cert.Status == enums.StatusContractSigned || cert.Status == enums.StatusCompleted {
|
||||
if cert.FaceVerifiedAt == nil {
|
||||
validation["is_valid"] = false
|
||||
validation["issues"] = append(validation["issues"].([]string), "缺少人脸识别完成时间")
|
||||
}
|
||||
}
|
||||
|
||||
if cert.Status == enums.StatusContractApproved || cert.Status == enums.StatusContractSigned ||
|
||||
cert.Status == enums.StatusCompleted {
|
||||
if cert.ContractApprovedAt == nil {
|
||||
validation["is_valid"] = false
|
||||
validation["issues"] = append(validation["issues"].([]string), "缺少合同审核时间")
|
||||
}
|
||||
if cert.SigningURL == "" {
|
||||
validation["warnings"] = append(validation["warnings"].([]string), "缺少合同签署链接")
|
||||
}
|
||||
}
|
||||
|
||||
if cert.Status == enums.StatusContractSigned || cert.Status == enums.StatusCompleted {
|
||||
if cert.ContractSignedAt == nil {
|
||||
validation["is_valid"] = false
|
||||
validation["issues"] = append(validation["issues"].([]string), "缺少合同签署时间")
|
||||
}
|
||||
if cert.ContractURL == "" {
|
||||
validation["warnings"] = append(validation["warnings"].([]string), "缺少合同文件链接")
|
||||
}
|
||||
}
|
||||
|
||||
if cert.Status == enums.StatusCompleted {
|
||||
if cert.CompletedAt == nil {
|
||||
validation["is_valid"] = false
|
||||
validation["issues"] = append(validation["issues"].([]string), "缺少认证完成时间")
|
||||
}
|
||||
}
|
||||
|
||||
return validation, nil
|
||||
}
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// WalletInfo 钱包信息
|
||||
type WalletInfo struct {
|
||||
ID string `json:"id"` // 钱包ID
|
||||
UserID string `json:"user_id"` // 用户ID
|
||||
IsActive bool `json:"is_active"` // 是否激活
|
||||
Balance decimal.Decimal `json:"balance"` // 余额
|
||||
CreatedAt time.Time `json:"created_at"` // 创建时间
|
||||
UpdatedAt time.Time `json:"updated_at"` // 更新时间
|
||||
}
|
||||
|
||||
// UserSecretsInfo 用户密钥信息
|
||||
type UserSecretsInfo struct {
|
||||
ID string `json:"id"` // 密钥ID
|
||||
UserID string `json:"user_id"` // 用户ID
|
||||
AccessID string `json:"access_id"` // 访问ID
|
||||
AccessKey string `json:"access_key"` // 访问密钥
|
||||
IsActive bool `json:"is_active"` // 是否激活
|
||||
LastUsedAt *time.Time `json:"last_used_at"` // 最后使用时间
|
||||
ExpiresAt *time.Time `json:"expires_at"` // 过期时间
|
||||
CreatedAt time.Time `json:"created_at"` // 创建时间
|
||||
UpdatedAt time.Time `json:"updated_at"` // 更新时间
|
||||
}
|
||||
|
||||
// CreateWalletRequest 创建钱包请求
|
||||
type CreateWalletRequest struct {
|
||||
UserID string `json:"user_id" binding:"required"` // 用户ID
|
||||
}
|
||||
|
||||
// CreateWalletResponse 创建钱包响应
|
||||
type CreateWalletResponse struct {
|
||||
Wallet WalletInfo `json:"wallet"` // 钱包信息
|
||||
}
|
||||
|
||||
// GetWalletRequest 获取钱包请求
|
||||
type GetWalletRequest struct {
|
||||
UserID string `form:"user_id" binding:"required"` // 用户ID
|
||||
}
|
||||
|
||||
// UpdateWalletRequest 更新钱包请求
|
||||
type UpdateWalletRequest struct {
|
||||
UserID string `json:"user_id" binding:"required"` // 用户ID
|
||||
Balance decimal.Decimal `json:"balance"` // 余额
|
||||
IsActive *bool `json:"is_active"` // 是否激活
|
||||
}
|
||||
|
||||
// RechargeRequest 充值请求
|
||||
type RechargeRequest struct {
|
||||
UserID string `json:"user_id" binding:"required"` // 用户ID
|
||||
Amount decimal.Decimal `json:"amount" binding:"required"` // 充值金额
|
||||
}
|
||||
|
||||
// RechargeResponse 充值响应
|
||||
type RechargeResponse struct {
|
||||
WalletID string `json:"wallet_id"` // 钱包ID
|
||||
Amount decimal.Decimal `json:"amount"` // 充值金额
|
||||
Balance decimal.Decimal `json:"balance"` // 充值后余额
|
||||
}
|
||||
|
||||
// WithdrawRequest 提现请求
|
||||
type WithdrawRequest struct {
|
||||
UserID string `json:"user_id" binding:"required"` // 用户ID
|
||||
Amount decimal.Decimal `json:"amount" binding:"required"` // 提现金额
|
||||
}
|
||||
|
||||
// WithdrawResponse 提现响应
|
||||
type WithdrawResponse struct {
|
||||
WalletID string `json:"wallet_id"` // 钱包ID
|
||||
Amount decimal.Decimal `json:"amount"` // 提现金额
|
||||
Balance decimal.Decimal `json:"balance"` // 提现后余额
|
||||
}
|
||||
|
||||
// CreateUserSecretsRequest 创建用户密钥请求
|
||||
type CreateUserSecretsRequest struct {
|
||||
UserID string `json:"user_id" binding:"required"` // 用户ID
|
||||
ExpiresAt *time.Time `json:"expires_at"` // 过期时间
|
||||
}
|
||||
|
||||
// CreateUserSecretsResponse 创建用户密钥响应
|
||||
type CreateUserSecretsResponse struct {
|
||||
Secrets UserSecretsInfo `json:"secrets"` // 密钥信息
|
||||
}
|
||||
|
||||
// GetUserSecretsRequest 获取用户密钥请求
|
||||
type GetUserSecretsRequest struct {
|
||||
UserID string `form:"user_id" binding:"required"` // 用户ID
|
||||
}
|
||||
|
||||
// RegenerateAccessKeyRequest 重新生成访问密钥请求
|
||||
type RegenerateAccessKeyRequest struct {
|
||||
UserID string `json:"user_id" binding:"required"` // 用户ID
|
||||
ExpiresAt *time.Time `json:"expires_at"` // 过期时间
|
||||
}
|
||||
|
||||
// RegenerateAccessKeyResponse 重新生成访问密钥响应
|
||||
type RegenerateAccessKeyResponse struct {
|
||||
AccessID string `json:"access_id"` // 新的访问ID
|
||||
AccessKey string `json:"access_key"` // 新的访问密钥
|
||||
}
|
||||
|
||||
// DeactivateUserSecretsRequest 停用用户密钥请求
|
||||
type DeactivateUserSecretsRequest struct {
|
||||
UserID string `json:"user_id" binding:"required"` // 用户ID
|
||||
}
|
||||
|
||||
// WalletTransactionRequest 钱包交易请求
|
||||
type WalletTransactionRequest struct {
|
||||
FromUserID string `json:"from_user_id" binding:"required"` // 转出用户ID
|
||||
ToUserID string `json:"to_user_id" binding:"required"` // 转入用户ID
|
||||
Amount decimal.Decimal `json:"amount" binding:"required"` // 交易金额
|
||||
Notes string `json:"notes"` // 交易备注
|
||||
}
|
||||
|
||||
// WalletTransactionResponse 钱包交易响应
|
||||
type WalletTransactionResponse struct {
|
||||
TransactionID string `json:"transaction_id"` // 交易ID
|
||||
FromUserID string `json:"from_user_id"` // 转出用户ID
|
||||
ToUserID string `json:"to_user_id"` // 转入用户ID
|
||||
Amount decimal.Decimal `json:"amount"` // 交易金额
|
||||
FromBalance decimal.Decimal `json:"from_balance"` // 转出后余额
|
||||
ToBalance decimal.Decimal `json:"to_balance"` // 转入后余额
|
||||
Notes string `json:"notes"` // 交易备注
|
||||
CreatedAt time.Time `json:"created_at"` // 交易时间
|
||||
}
|
||||
|
||||
// WalletStatsResponse 钱包统计响应
|
||||
type WalletStatsResponse struct {
|
||||
TotalWallets int64 `json:"total_wallets"` // 总钱包数
|
||||
ActiveWallets int64 `json:"active_wallets"` // 激活钱包数
|
||||
TotalBalance decimal.Decimal `json:"total_balance"` // 总余额
|
||||
TodayTransactions int64 `json:"today_transactions"` // 今日交易数
|
||||
TodayVolume decimal.Decimal `json:"today_volume"` // 今日交易量
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/finance/entities"
|
||||
"tyapi-server/internal/domains/finance/repositories"
|
||||
)
|
||||
|
||||
// FinanceService 财务领域服务
|
||||
// 负责财务相关的业务逻辑,包括钱包管理、余额操作等
|
||||
type FinanceService struct {
|
||||
walletRepo repositories.WalletRepository
|
||||
logger *zap.Logger
|
||||
@@ -22,3 +28,133 @@ func NewFinanceService(
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWallet 创建钱包
|
||||
func (s *FinanceService) CreateWallet(ctx context.Context, userID string) (*entities.Wallet, error) {
|
||||
// 检查用户是否已有钱包
|
||||
existingWallet, err := s.walletRepo.GetByUserID(ctx, userID)
|
||||
if err == nil && existingWallet != nil {
|
||||
return nil, fmt.Errorf("用户已有钱包")
|
||||
}
|
||||
|
||||
// 创建钱包
|
||||
wallet := &entities.Wallet{
|
||||
UserID: userID,
|
||||
Balance: decimal.Zero,
|
||||
IsActive: true,
|
||||
WalletType: "MAIN",
|
||||
}
|
||||
|
||||
createdWallet, err := s.walletRepo.Create(ctx, *wallet)
|
||||
if err != nil {
|
||||
s.logger.Error("创建钱包失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建钱包失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("钱包创建成功",
|
||||
zap.String("wallet_id", createdWallet.ID),
|
||||
zap.String("user_id", userID),
|
||||
)
|
||||
|
||||
return &createdWallet, nil
|
||||
}
|
||||
|
||||
// GetWallet 获取钱包信息
|
||||
func (s *FinanceService) GetWallet(ctx context.Context, userID string) (*entities.Wallet, error) {
|
||||
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("钱包不存在: %w", err)
|
||||
}
|
||||
return wallet, nil
|
||||
}
|
||||
|
||||
// GetWalletByID 根据ID获取钱包
|
||||
func (s *FinanceService) GetWalletByID(ctx context.Context, walletID string) (*entities.Wallet, error) {
|
||||
wallet, err := s.walletRepo.GetByID(ctx, walletID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("钱包不存在: %w", err)
|
||||
}
|
||||
return &wallet, nil
|
||||
}
|
||||
|
||||
// RechargeWallet 充值钱包
|
||||
func (s *FinanceService) RechargeWallet(ctx context.Context, userID string, amount float64) error {
|
||||
if amount <= 0 {
|
||||
return fmt.Errorf("充值金额必须大于0")
|
||||
}
|
||||
|
||||
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("钱包不存在: %w", err)
|
||||
}
|
||||
|
||||
// 更新余额
|
||||
amountDecimal := decimal.NewFromFloat(amount)
|
||||
wallet.AddBalance(amountDecimal)
|
||||
|
||||
if err := s.walletRepo.Update(ctx, *wallet); err != nil {
|
||||
s.logger.Error("充值失败", zap.Error(err))
|
||||
return fmt.Errorf("充值失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("钱包充值成功",
|
||||
zap.String("wallet_id", wallet.ID),
|
||||
zap.String("user_id", userID),
|
||||
zap.Float64("amount", amount),
|
||||
zap.String("new_balance", wallet.GetFormattedBalance()),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeductWallet 扣减钱包余额
|
||||
func (s *FinanceService) DeductWallet(ctx context.Context, userID string, amount float64) error {
|
||||
if amount <= 0 {
|
||||
return fmt.Errorf("扣减金额必须大于0")
|
||||
}
|
||||
|
||||
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("钱包不存在: %w", err)
|
||||
}
|
||||
|
||||
amountDecimal := decimal.NewFromFloat(amount)
|
||||
if err := wallet.SubtractBalance(amountDecimal); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.walletRepo.Update(ctx, *wallet); err != nil {
|
||||
s.logger.Error("扣减失败", zap.Error(err))
|
||||
return fmt.Errorf("扣减失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("钱包扣减成功",
|
||||
zap.String("wallet_id", wallet.ID),
|
||||
zap.String("user_id", userID),
|
||||
zap.Float64("amount", amount),
|
||||
zap.String("new_balance", wallet.GetFormattedBalance()),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetWalletBalance 获取钱包余额
|
||||
func (s *FinanceService) GetWalletBalance(ctx context.Context, userID string) (float64, error) {
|
||||
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("钱包不存在: %w", err)
|
||||
}
|
||||
balance, _ := wallet.Balance.Float64()
|
||||
return balance, nil
|
||||
}
|
||||
|
||||
// CheckWalletBalance 检查钱包余额是否足够
|
||||
func (s *FinanceService) CheckWalletBalance(ctx context.Context, userID string, amount float64) (bool, error) {
|
||||
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("钱包不存在: %w", err)
|
||||
}
|
||||
|
||||
amountDecimal := decimal.NewFromFloat(amount)
|
||||
return wallet.HasSufficientBalance(amountDecimal), nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package entities
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -12,7 +13,7 @@ type Product struct {
|
||||
Name string `gorm:"type:varchar(100);not null" comment:"产品名称"`
|
||||
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"产品编号"`
|
||||
Description string `gorm:"type:text" comment:"产品简介"`
|
||||
Content string `gorm:"type:longtext" comment:"产品内容"`
|
||||
Content string `gorm:"type:text" comment:"产品内容"`
|
||||
CategoryID string `gorm:"type:varchar(36);not null" comment:"产品分类ID"`
|
||||
Price float64 `gorm:"type:decimal(10,2);not null;default:0" comment:"产品价格"`
|
||||
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
|
||||
@@ -32,6 +33,14 @@ type Product struct {
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (p *Product) BeforeCreate(tx *gorm.DB) error {
|
||||
if p.ID == "" {
|
||||
p.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValid 检查产品是否有效
|
||||
func (p *Product) IsValid() bool {
|
||||
return p.DeletedAt.Time.IsZero() && p.IsEnabled
|
||||
|
||||
@@ -3,6 +3,7 @@ package entities
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -12,22 +13,26 @@ type ProductCategory struct {
|
||||
Name string `gorm:"type:varchar(100);not null" comment:"分类名称"`
|
||||
Code string `gorm:"type:varchar(50);uniqueIndex;not null" comment:"分类编号"`
|
||||
Description string `gorm:"type:text" comment:"分类描述"`
|
||||
ParentID *string `gorm:"type:varchar(36)" comment:"父分类ID"`
|
||||
Level int `gorm:"default:1" comment:"分类层级"`
|
||||
Sort int `gorm:"default:0" comment:"排序"`
|
||||
IsEnabled bool `gorm:"default:true" comment:"是否启用"`
|
||||
IsVisible bool `gorm:"default:true" comment:"是否展示"`
|
||||
|
||||
// 关联关系
|
||||
Parent *ProductCategory `gorm:"foreignKey:ParentID" comment:"父分类"`
|
||||
Children []ProductCategory `gorm:"foreignKey:ParentID" comment:"子分类"`
|
||||
Products []Product `gorm:"foreignKey:CategoryID" comment:"产品列表"`
|
||||
Products []Product `gorm:"foreignKey:CategoryID" comment:"产品列表"`
|
||||
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (pc *ProductCategory) BeforeCreate(tx *gorm.DB) error {
|
||||
if pc.ID == "" {
|
||||
pc.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValid 检查分类是否有效
|
||||
func (pc *ProductCategory) IsValid() bool {
|
||||
return pc.DeletedAt.Time.IsZero() && pc.IsEnabled
|
||||
@@ -38,27 +43,6 @@ func (pc *ProductCategory) IsVisibleToUser() bool {
|
||||
return pc.IsValid() && pc.IsVisible
|
||||
}
|
||||
|
||||
// IsRoot 检查是否为根分类
|
||||
func (pc *ProductCategory) IsRoot() bool {
|
||||
return pc.ParentID == nil || *pc.ParentID == ""
|
||||
}
|
||||
|
||||
// IsLeaf 检查是否为叶子分类
|
||||
func (pc *ProductCategory) IsLeaf() bool {
|
||||
return len(pc.Children) == 0
|
||||
}
|
||||
|
||||
// GetFullPath 获取完整分类路径
|
||||
func (pc *ProductCategory) GetFullPath() string {
|
||||
if pc.IsRoot() {
|
||||
return pc.Name
|
||||
}
|
||||
if pc.Parent != nil {
|
||||
return pc.Parent.GetFullPath() + " > " + pc.Name
|
||||
}
|
||||
return pc.Name
|
||||
}
|
||||
|
||||
// Enable 启用分类
|
||||
func (pc *ProductCategory) Enable() {
|
||||
pc.IsEnabled = true
|
||||
|
||||
@@ -3,6 +3,7 @@ package entities
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -11,11 +12,11 @@ type ProductDocumentation struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"文档ID"`
|
||||
ProductID string `gorm:"type:varchar(36);not null;uniqueIndex" comment:"产品ID"`
|
||||
Title string `gorm:"type:varchar(200);not null" comment:"文档标题"`
|
||||
Content string `gorm:"type:longtext;not null" comment:"文档内容"`
|
||||
UsageGuide string `gorm:"type:longtext" comment:"使用指南"`
|
||||
APIDocs string `gorm:"type:longtext" comment:"API文档"`
|
||||
Examples string `gorm:"type:longtext" comment:"使用示例"`
|
||||
FAQ string `gorm:"type:longtext" comment:"常见问题"`
|
||||
Content string `gorm:"type:text;not null" comment:"文档内容"`
|
||||
UsageGuide string `gorm:"type:text" comment:"使用指南"`
|
||||
APIDocs string `gorm:"type:text" comment:"API文档"`
|
||||
Examples string `gorm:"type:text" comment:"使用示例"`
|
||||
FAQ string `gorm:"type:text" comment:"常见问题"`
|
||||
Version string `gorm:"type:varchar(20);default:'1.0'" comment:"文档版本"`
|
||||
Published bool `gorm:"default:false" comment:"是否已发布"`
|
||||
|
||||
@@ -27,6 +28,14 @@ type ProductDocumentation struct {
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (pd *ProductDocumentation) BeforeCreate(tx *gorm.DB) error {
|
||||
if pd.ID == "" {
|
||||
pd.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValid 检查文档是否有效
|
||||
func (pd *ProductDocumentation) IsValid() bool {
|
||||
return pd.DeletedAt.Time.IsZero()
|
||||
|
||||
@@ -3,25 +3,15 @@ package entities
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SubscriptionStatus 订阅状态枚举
|
||||
type SubscriptionStatus string
|
||||
|
||||
const (
|
||||
SubscriptionStatusActive SubscriptionStatus = "active" // 活跃
|
||||
SubscriptionStatusInactive SubscriptionStatus = "inactive" // 非活跃
|
||||
SubscriptionStatusExpired SubscriptionStatus = "expired" // 已过期
|
||||
SubscriptionStatusCanceled SubscriptionStatus = "canceled" // 已取消
|
||||
)
|
||||
|
||||
// Subscription 订阅实体
|
||||
type Subscription struct {
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" comment:"订阅ID"`
|
||||
UserID string `gorm:"type:varchar(36);not null;index" comment:"用户ID"`
|
||||
ProductID string `gorm:"type:varchar(36);not null;index" comment:"产品ID"`
|
||||
Status SubscriptionStatus `gorm:"type:varchar(20);not null;default:'active'" comment:"订阅状态"`
|
||||
Price float64 `gorm:"type:decimal(10,2);not null" comment:"订阅价格"`
|
||||
APIUsed int64 `gorm:"default:0" comment:"已使用API调用次数"`
|
||||
|
||||
@@ -33,6 +23,14 @@ type Subscription struct {
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (s *Subscription) BeforeCreate(tx *gorm.DB) error {
|
||||
if s.ID == "" {
|
||||
s.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsValid 检查订阅是否有效
|
||||
func (s *Subscription) IsValid() bool {
|
||||
return s.DeletedAt.Time.IsZero()
|
||||
@@ -43,16 +41,6 @@ func (s *Subscription) IncrementAPIUsage(count int64) {
|
||||
s.APIUsed += count
|
||||
}
|
||||
|
||||
// Activate 激活订阅
|
||||
func (s *Subscription) Activate() {
|
||||
s.Status = SubscriptionStatusActive
|
||||
}
|
||||
|
||||
// Deactivate 停用订阅
|
||||
func (s *Subscription) Deactivate() {
|
||||
s.Status = SubscriptionStatusInactive
|
||||
}
|
||||
|
||||
// ResetAPIUsage 重置API使用次数
|
||||
func (s *Subscription) ResetAPIUsage() {
|
||||
s.APIUsed = 0
|
||||
|
||||
@@ -13,21 +13,13 @@ type ProductCategoryRepository interface {
|
||||
|
||||
// 基础查询方法
|
||||
FindByCode(ctx context.Context, code string) (*entities.ProductCategory, error)
|
||||
FindByParentID(ctx context.Context, parentID *string) ([]*entities.ProductCategory, error)
|
||||
FindRootCategories(ctx context.Context) ([]*entities.ProductCategory, error)
|
||||
FindVisible(ctx context.Context) ([]*entities.ProductCategory, error)
|
||||
FindEnabled(ctx context.Context) ([]*entities.ProductCategory, error)
|
||||
|
||||
// 复杂查询方法
|
||||
ListCategories(ctx context.Context, query *queries.ListCategoriesQuery) ([]*entities.ProductCategory, int64, error)
|
||||
GetCategoryTree(ctx context.Context) ([]*entities.ProductCategory, error)
|
||||
|
||||
// 业务查询方法
|
||||
FindCategoriesByLevel(ctx context.Context, level int) ([]*entities.ProductCategory, error)
|
||||
FindCategoryPath(ctx context.Context, categoryID string) ([]*entities.ProductCategory, error)
|
||||
|
||||
// 统计方法
|
||||
CountByParent(ctx context.Context, parentID *string) (int64, error)
|
||||
CountEnabled(ctx context.Context) (int64, error)
|
||||
CountVisible(ctx context.Context) (int64, error)
|
||||
}
|
||||
@@ -4,8 +4,6 @@ package queries
|
||||
type ListCategoriesQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
ParentID *string `json:"parent_id"`
|
||||
Level *int `json:"level"`
|
||||
IsEnabled *bool `json:"is_enabled"`
|
||||
IsVisible *bool `json:"is_visible"`
|
||||
SortBy string `json:"sort_by"`
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
package queries
|
||||
|
||||
import "tyapi-server/internal/domains/product/entities"
|
||||
|
||||
// ListSubscriptionsQuery 订阅列表查询
|
||||
type ListSubscriptionsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
UserID string `json:"user_id"`
|
||||
ProductID string `json:"product_id"`
|
||||
Status entities.SubscriptionStatus `json:"status"`
|
||||
Keyword string `json:"keyword"`
|
||||
SortBy string `json:"sort_by"`
|
||||
SortOrder string `json:"sort_order"`
|
||||
}
|
||||
@@ -21,11 +18,9 @@ type GetSubscriptionQuery struct {
|
||||
// GetUserSubscriptionsQuery 获取用户订阅查询
|
||||
type GetUserSubscriptionsQuery struct {
|
||||
UserID string `json:"user_id"`
|
||||
Status *entities.SubscriptionStatus `json:"status"`
|
||||
}
|
||||
|
||||
// GetProductSubscriptionsQuery 获取产品订阅查询
|
||||
type GetProductSubscriptionsQuery struct {
|
||||
ProductID string `json:"product_id"`
|
||||
Status *entities.SubscriptionStatus `json:"status"`
|
||||
}
|
||||
@@ -10,25 +10,16 @@ import (
|
||||
// SubscriptionRepository 订阅仓储接口
|
||||
type SubscriptionRepository interface {
|
||||
interfaces.Repository[entities.Subscription]
|
||||
|
||||
|
||||
// 基础查询方法
|
||||
FindByUserID(ctx context.Context, userID string) ([]*entities.Subscription, error)
|
||||
FindByProductID(ctx context.Context, productID string) ([]*entities.Subscription, error)
|
||||
FindByUserAndProduct(ctx context.Context, userID, productID string) (*entities.Subscription, error)
|
||||
FindActive(ctx context.Context) ([]*entities.Subscription, error)
|
||||
|
||||
|
||||
// 复杂查询方法
|
||||
ListSubscriptions(ctx context.Context, query *queries.ListSubscriptionsQuery) ([]*entities.Subscription, int64, error)
|
||||
FindUserActiveSubscriptions(ctx context.Context, userID string) ([]*entities.Subscription, error)
|
||||
FindExpiredSubscriptions(ctx context.Context) ([]*entities.Subscription, error)
|
||||
|
||||
// 业务查询方法
|
||||
FindSubscriptionsByStatus(ctx context.Context, status entities.SubscriptionStatus) ([]*entities.Subscription, error)
|
||||
FindSubscriptionsByDateRange(ctx context.Context, startDate, endDate string) ([]*entities.Subscription, error)
|
||||
|
||||
|
||||
// 统计方法
|
||||
CountByUser(ctx context.Context, userID string) (int64, error)
|
||||
CountByProduct(ctx context.Context, productID string) (int64, error)
|
||||
CountByStatus(ctx context.Context, status entities.SubscriptionStatus) (int64, error)
|
||||
CountActive(ctx context.Context) (int64, error)
|
||||
}
|
||||
}
|
||||
|
||||
185
internal/domains/product/services/product_management_service.go
Normal file
185
internal/domains/product/services/product_management_service.go
Normal file
@@ -0,0 +1,185 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
)
|
||||
|
||||
// ProductManagementService 产品管理领域服务
|
||||
// 负责产品的基本管理操作,包括创建、查询、更新等
|
||||
type ProductManagementService struct {
|
||||
productRepo repositories.ProductRepository
|
||||
categoryRepo repositories.ProductCategoryRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewProductManagementService 创建产品管理领域服务
|
||||
func NewProductManagementService(
|
||||
productRepo repositories.ProductRepository,
|
||||
categoryRepo repositories.ProductCategoryRepository,
|
||||
logger *zap.Logger,
|
||||
) *ProductManagementService {
|
||||
return &ProductManagementService{
|
||||
productRepo: productRepo,
|
||||
categoryRepo: categoryRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateProduct 创建产品
|
||||
func (s *ProductManagementService) CreateProduct(ctx context.Context, product *entities.Product) (*entities.Product, error) {
|
||||
// 验证产品信息
|
||||
if err := s.ValidateProduct(product); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 验证产品编号唯一性
|
||||
if err := s.ValidateProductCode(product.Code, ""); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建产品
|
||||
createdProduct, err := s.productRepo.Create(ctx, *product)
|
||||
if err != nil {
|
||||
s.logger.Error("创建产品失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("产品创建成功",
|
||||
zap.String("product_id", createdProduct.ID),
|
||||
zap.String("product_name", createdProduct.Name),
|
||||
)
|
||||
|
||||
return &createdProduct, nil
|
||||
}
|
||||
|
||||
// GetProductByID 根据ID获取产品
|
||||
func (s *ProductManagementService) GetProductByID(ctx context.Context, productID string) (*entities.Product, error) {
|
||||
product, err := s.productRepo.GetByID(ctx, productID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
return &product, nil
|
||||
}
|
||||
|
||||
// GetProductWithCategory 获取产品及其分类信息
|
||||
func (s *ProductManagementService) GetProductWithCategory(ctx context.Context, productID string) (*entities.Product, error) {
|
||||
product, err := s.productRepo.GetByID(ctx, productID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
// 加载分类信息
|
||||
if product.CategoryID != "" {
|
||||
category, err := s.categoryRepo.GetByID(ctx, product.CategoryID)
|
||||
if err == nil {
|
||||
product.Category = &category
|
||||
}
|
||||
}
|
||||
|
||||
return &product, nil
|
||||
}
|
||||
|
||||
// UpdateProduct 更新产品
|
||||
func (s *ProductManagementService) UpdateProduct(ctx context.Context, product *entities.Product) error {
|
||||
// 验证产品信息
|
||||
if err := s.ValidateProduct(product); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 验证产品编号唯一性(排除自己)
|
||||
if err := s.ValidateProductCode(product.Code, product.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.productRepo.Update(ctx, *product); err != nil {
|
||||
s.logger.Error("更新产品失败", zap.Error(err))
|
||||
return fmt.Errorf("更新产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("产品更新成功",
|
||||
zap.String("product_id", product.ID),
|
||||
zap.String("product_name", product.Name),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteProduct 删除产品
|
||||
func (s *ProductManagementService) DeleteProduct(ctx context.Context, productID string) error {
|
||||
if err := s.productRepo.Delete(ctx, productID); err != nil {
|
||||
s.logger.Error("删除产品失败", zap.Error(err))
|
||||
return fmt.Errorf("删除产品失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("产品删除成功", zap.String("product_id", productID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVisibleProducts 获取可见产品列表
|
||||
func (s *ProductManagementService) GetVisibleProducts(ctx context.Context) ([]*entities.Product, error) {
|
||||
return s.productRepo.FindVisible(ctx)
|
||||
}
|
||||
|
||||
// GetEnabledProducts 获取启用产品列表
|
||||
func (s *ProductManagementService) GetEnabledProducts(ctx context.Context) ([]*entities.Product, error) {
|
||||
return s.productRepo.FindEnabled(ctx)
|
||||
}
|
||||
|
||||
// GetProductsByCategory 根据分类获取产品
|
||||
func (s *ProductManagementService) GetProductsByCategory(ctx context.Context, categoryID string) ([]*entities.Product, error) {
|
||||
return s.productRepo.FindByCategoryID(ctx, categoryID)
|
||||
}
|
||||
|
||||
// ValidateProduct 验证产品
|
||||
func (s *ProductManagementService) ValidateProduct(product *entities.Product) error {
|
||||
if product == nil {
|
||||
return errors.New("产品不能为空")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(product.Name) == "" {
|
||||
return errors.New("产品名称不能为空")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(product.Code) == "" {
|
||||
return errors.New("产品编号不能为空")
|
||||
}
|
||||
|
||||
if product.Price < 0 {
|
||||
return errors.New("产品价格不能为负数")
|
||||
}
|
||||
|
||||
// 验证分类是否存在
|
||||
if product.CategoryID != "" {
|
||||
category, err := s.categoryRepo.GetByID(context.Background(), product.CategoryID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品分类不存在: %w", err)
|
||||
}
|
||||
if !category.IsValid() {
|
||||
return errors.New("产品分类已禁用或删除")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateProductCode 验证产品编号唯一性
|
||||
func (s *ProductManagementService) ValidateProductCode(code string, excludeID string) error {
|
||||
if strings.TrimSpace(code) == "" {
|
||||
return errors.New("产品编号不能为空")
|
||||
}
|
||||
|
||||
existingProduct, err := s.productRepo.FindByCode(context.Background(), code)
|
||||
if err == nil && existingProduct != nil && existingProduct.ID != excludeID {
|
||||
return errors.New("产品编号已存在")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
)
|
||||
|
||||
// ProductService 产品领域服务
|
||||
type ProductService struct {
|
||||
productRepo repositories.ProductRepository
|
||||
categoryRepo repositories.ProductCategoryRepository
|
||||
subscriptionRepo repositories.SubscriptionRepository
|
||||
}
|
||||
|
||||
// NewProductService 创建产品领域服务
|
||||
func NewProductService(
|
||||
productRepo repositories.ProductRepository,
|
||||
categoryRepo repositories.ProductCategoryRepository,
|
||||
subscriptionRepo repositories.SubscriptionRepository,
|
||||
) *ProductService {
|
||||
return &ProductService{
|
||||
productRepo: productRepo,
|
||||
categoryRepo: categoryRepo,
|
||||
subscriptionRepo: subscriptionRepo,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateProduct 验证产品
|
||||
func (s *ProductService) ValidateProduct(product *entities.Product) error {
|
||||
if product == nil {
|
||||
return errors.New("产品不能为空")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(product.Name) == "" {
|
||||
return errors.New("产品名称不能为空")
|
||||
}
|
||||
|
||||
if strings.TrimSpace(product.Code) == "" {
|
||||
return errors.New("产品编号不能为空")
|
||||
}
|
||||
|
||||
if product.Price < 0 {
|
||||
return errors.New("产品价格不能为负数")
|
||||
}
|
||||
|
||||
// 验证分类是否存在
|
||||
if product.CategoryID != "" {
|
||||
category, err := s.categoryRepo.GetByID(nil, product.CategoryID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("产品分类不存在: %w", err)
|
||||
}
|
||||
if !category.IsValid() {
|
||||
return errors.New("产品分类已禁用或删除")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateProductCode 验证产品编号唯一性
|
||||
func (s *ProductService) ValidateProductCode(code string, excludeID string) error {
|
||||
if strings.TrimSpace(code) == "" {
|
||||
return errors.New("产品编号不能为空")
|
||||
}
|
||||
|
||||
existingProduct, err := s.productRepo.FindByCode(nil, code)
|
||||
if err == nil && existingProduct != nil && existingProduct.ID != excludeID {
|
||||
return errors.New("产品编号已存在")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CanUserSubscribeProduct 检查用户是否可以订阅产品
|
||||
func (s *ProductService) CanUserSubscribeProduct(userID string, productID string) (bool, error) {
|
||||
// 检查产品是否存在且可订阅
|
||||
product, err := s.productRepo.GetByID(nil, productID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
if !product.CanBeSubscribed() {
|
||||
return false, errors.New("产品不可订阅")
|
||||
}
|
||||
|
||||
// 检查用户是否已有该产品的订阅
|
||||
existingSubscription, err := s.subscriptionRepo.FindByUserAndProduct(nil, userID, productID)
|
||||
if err == nil && existingSubscription != nil {
|
||||
return false, errors.New("用户已有该产品的订阅")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// GetProductWithCategory 获取产品及其分类信息
|
||||
func (s *ProductService) GetProductWithCategory(productID string) (*entities.Product, error) {
|
||||
product, err := s.productRepo.GetByID(nil, productID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
// 加载分类信息
|
||||
if product.CategoryID != "" {
|
||||
category, err := s.categoryRepo.GetByID(nil, product.CategoryID)
|
||||
if err == nil {
|
||||
product.Category = &category
|
||||
}
|
||||
}
|
||||
|
||||
return &product, nil
|
||||
}
|
||||
|
||||
// GetVisibleProducts 获取可见产品列表
|
||||
func (s *ProductService) GetVisibleProducts() ([]*entities.Product, error) {
|
||||
return s.productRepo.FindVisible(nil)
|
||||
}
|
||||
|
||||
// GetEnabledProducts 获取启用产品列表
|
||||
func (s *ProductService) GetEnabledProducts() ([]*entities.Product, error) {
|
||||
return s.productRepo.FindEnabled(nil)
|
||||
}
|
||||
|
||||
// GetProductsByCategory 根据分类获取产品
|
||||
func (s *ProductService) GetProductsByCategory(categoryID string) ([]*entities.Product, error) {
|
||||
return s.productRepo.FindByCategoryID(nil, categoryID)
|
||||
}
|
||||
|
||||
// GetProductStats 获取产品统计信息
|
||||
func (s *ProductService) GetProductStats() (map[string]int64, error) {
|
||||
stats := make(map[string]int64)
|
||||
|
||||
total, err := s.productRepo.CountByCategory(nil, "")
|
||||
if err == nil {
|
||||
stats["total"] = total
|
||||
}
|
||||
|
||||
enabled, err := s.productRepo.CountEnabled(nil)
|
||||
if err == nil {
|
||||
stats["enabled"] = enabled
|
||||
}
|
||||
|
||||
visible, err := s.productRepo.CountVisible(nil)
|
||||
if err == nil {
|
||||
stats["visible"] = visible
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
)
|
||||
|
||||
// ProductSubscriptionService 产品订阅领域服务
|
||||
// 负责产品订阅相关的业务逻辑,包括订阅验证、订阅管理等
|
||||
type ProductSubscriptionService struct {
|
||||
productRepo repositories.ProductRepository
|
||||
subscriptionRepo repositories.SubscriptionRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewProductSubscriptionService 创建产品订阅领域服务
|
||||
func NewProductSubscriptionService(
|
||||
productRepo repositories.ProductRepository,
|
||||
subscriptionRepo repositories.SubscriptionRepository,
|
||||
logger *zap.Logger,
|
||||
) *ProductSubscriptionService {
|
||||
return &ProductSubscriptionService{
|
||||
productRepo: productRepo,
|
||||
subscriptionRepo: subscriptionRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CanUserSubscribeProduct 检查用户是否可以订阅产品
|
||||
func (s *ProductSubscriptionService) CanUserSubscribeProduct(ctx context.Context, userID string, productID string) (bool, error) {
|
||||
// 检查产品是否存在且可订阅
|
||||
product, err := s.productRepo.GetByID(ctx, productID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
if !product.CanBeSubscribed() {
|
||||
return false, errors.New("产品不可订阅")
|
||||
}
|
||||
|
||||
// 检查用户是否已有该产品的订阅
|
||||
existingSubscription, err := s.subscriptionRepo.FindByUserAndProduct(ctx, userID, productID)
|
||||
if err == nil && existingSubscription != nil {
|
||||
return false, errors.New("用户已有该产品的订阅")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CreateSubscription 创建订阅
|
||||
func (s *ProductSubscriptionService) CreateSubscription(ctx context.Context, userID, productID string) (*entities.Subscription, error) {
|
||||
// 检查是否可以订阅
|
||||
canSubscribe, err := s.CanUserSubscribeProduct(ctx, userID, productID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !canSubscribe {
|
||||
return nil, errors.New("无法订阅该产品")
|
||||
}
|
||||
|
||||
// 获取产品信息以获取价格
|
||||
product, err := s.productRepo.GetByID(ctx, productID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("产品不存在: %w", err)
|
||||
}
|
||||
|
||||
// 创建订阅
|
||||
subscription := &entities.Subscription{
|
||||
UserID: userID,
|
||||
ProductID: productID,
|
||||
Price: product.Price,
|
||||
}
|
||||
|
||||
createdSubscription, err := s.subscriptionRepo.Create(ctx, *subscription)
|
||||
if err != nil {
|
||||
s.logger.Error("创建订阅失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建订阅失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("订阅创建成功",
|
||||
zap.String("subscription_id", createdSubscription.ID),
|
||||
zap.String("user_id", userID),
|
||||
zap.String("product_id", productID),
|
||||
)
|
||||
|
||||
return &createdSubscription, nil
|
||||
}
|
||||
|
||||
// GetUserSubscriptions 获取用户订阅列表
|
||||
func (s *ProductSubscriptionService) GetUserSubscriptions(ctx context.Context, userID string) ([]*entities.Subscription, error) {
|
||||
return s.subscriptionRepo.FindByUserID(ctx, userID)
|
||||
}
|
||||
|
||||
// GetSubscriptionByID 根据ID获取订阅
|
||||
func (s *ProductSubscriptionService) GetSubscriptionByID(ctx context.Context, subscriptionID string) (*entities.Subscription, error) {
|
||||
subscription, err := s.subscriptionRepo.GetByID(ctx, subscriptionID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("订阅不存在: %w", err)
|
||||
}
|
||||
return &subscription, nil
|
||||
}
|
||||
|
||||
// CancelSubscription 取消订阅
|
||||
func (s *ProductSubscriptionService) CancelSubscription(ctx context.Context, subscriptionID string) error {
|
||||
// 由于订阅实体没有状态字段,这里直接删除订阅
|
||||
if err := s.subscriptionRepo.Delete(ctx, subscriptionID); err != nil {
|
||||
s.logger.Error("取消订阅失败", zap.Error(err))
|
||||
return fmt.Errorf("取消订阅失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("订阅取消成功",
|
||||
zap.String("subscription_id", subscriptionID),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetProductStats 获取产品统计信息
|
||||
func (s *ProductSubscriptionService) GetProductStats(ctx context.Context) (map[string]int64, error) {
|
||||
stats := make(map[string]int64)
|
||||
|
||||
total, err := s.productRepo.CountByCategory(ctx, "")
|
||||
if err == nil {
|
||||
stats["total"] = total
|
||||
}
|
||||
|
||||
enabled, err := s.productRepo.CountEnabled(ctx)
|
||||
if err == nil {
|
||||
stats["enabled"] = enabled
|
||||
}
|
||||
|
||||
visible, err := s.productRepo.CountVisible(ctx)
|
||||
if err == nil {
|
||||
stats["visible"] = visible
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
)
|
||||
|
||||
// SendCodeRequest 发送验证码请求
|
||||
type SendCodeRequest struct {
|
||||
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
|
||||
Scene entities.SMSScene `json:"scene" binding:"required,oneof=register login change_password reset_password bind unbind" example:"register"`
|
||||
}
|
||||
|
||||
// SendCodeResponse 发送验证码响应
|
||||
type SendCodeResponse struct {
|
||||
Message string `json:"message" example:"验证码发送成功"`
|
||||
ExpiresAt time.Time `json:"expires_at" example:"2024-01-01T00:05:00Z"`
|
||||
}
|
||||
|
||||
// VerifyCodeRequest 验证验证码请求
|
||||
type VerifyCodeRequest struct {
|
||||
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
|
||||
Code string `json:"code" binding:"required,len=6" example:"123456"`
|
||||
Scene entities.SMSScene `json:"scene" binding:"required,oneof=register login change_password reset_password bind unbind" example:"register"`
|
||||
}
|
||||
|
||||
// SMSCodeResponse SMS验证码记录响应
|
||||
type SMSCodeResponse struct {
|
||||
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
|
||||
Phone string `json:"phone" example:"13800138000"`
|
||||
Scene entities.SMSScene `json:"scene" example:"register"`
|
||||
Used bool `json:"used" example:"false"`
|
||||
ExpiresAt time.Time `json:"expires_at" example:"2024-01-01T00:05:00Z"`
|
||||
CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"`
|
||||
}
|
||||
|
||||
// SMSCodeListRequest SMS验证码列表请求
|
||||
type SMSCodeListRequest struct {
|
||||
Phone string `form:"phone" binding:"omitempty,len=11" example:"13800138000"`
|
||||
Scene entities.SMSScene `form:"scene" binding:"omitempty,oneof=register login change_password reset_password bind unbind" example:"register"`
|
||||
Page int `form:"page" binding:"omitempty,min=1" example:"1"`
|
||||
PageSize int `form:"page_size" binding:"omitempty,min=1,max=100" example:"20"`
|
||||
}
|
||||
|
||||
// 转换方法
|
||||
func FromSMSCodeEntity(smsCode *entities.SMSCode) *SMSCodeResponse {
|
||||
if smsCode == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &SMSCodeResponse{
|
||||
ID: smsCode.ID,
|
||||
Phone: smsCode.Phone,
|
||||
Scene: smsCode.Scene,
|
||||
Used: smsCode.Used,
|
||||
ExpiresAt: smsCode.ExpiresAt,
|
||||
CreatedAt: smsCode.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func FromSMSCodeEntities(smsCodes []*entities.SMSCode) []*SMSCodeResponse {
|
||||
if smsCodes == nil {
|
||||
return []*SMSCodeResponse{}
|
||||
}
|
||||
|
||||
responses := make([]*SMSCodeResponse, len(smsCodes))
|
||||
for i, smsCode := range smsCodes {
|
||||
responses[i] = FromSMSCodeEntity(smsCode)
|
||||
}
|
||||
return responses
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
)
|
||||
|
||||
// RegisterRequest 用户注册请求
|
||||
type RegisterRequest struct {
|
||||
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
|
||||
Password string `json:"password" binding:"required,min=6,max=128" example:"password123"`
|
||||
ConfirmPassword string `json:"confirm_password" binding:"required,eqfield=Password" example:"password123"`
|
||||
Code string `json:"code" binding:"required,len=6" example:"123456"`
|
||||
}
|
||||
|
||||
// LoginWithPasswordRequest 密码登录请求
|
||||
type LoginWithPasswordRequest struct {
|
||||
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
|
||||
Password string `json:"password" binding:"required" example:"password123"`
|
||||
}
|
||||
|
||||
// LoginWithSMSRequest 短信验证码登录请求
|
||||
type LoginWithSMSRequest struct {
|
||||
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
|
||||
Code string `json:"code" binding:"required,len=6" example:"123456"`
|
||||
}
|
||||
|
||||
// ChangePasswordRequest 修改密码请求
|
||||
type ChangePasswordRequest struct {
|
||||
OldPassword string `json:"old_password" binding:"required" example:"oldpassword123"`
|
||||
NewPassword string `json:"new_password" binding:"required,min=6,max=128" example:"newpassword123"`
|
||||
ConfirmNewPassword string `json:"confirm_new_password" binding:"required,eqfield=NewPassword" example:"newpassword123"`
|
||||
Code string `json:"code" binding:"required,len=6" example:"123456"`
|
||||
}
|
||||
|
||||
// UpdateProfileRequest 更新用户信息请求
|
||||
type UpdateProfileRequest struct {
|
||||
Phone string `json:"phone" binding:"omitempty,len=11" example:"13800138000"`
|
||||
// 可以在这里添加更多用户信息字段,如昵称、头像等
|
||||
}
|
||||
|
||||
// UserResponse 用户响应
|
||||
type UserResponse struct {
|
||||
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
|
||||
Phone string `json:"phone" example:"13800138000"`
|
||||
CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"`
|
||||
UpdatedAt time.Time `json:"updated_at" example:"2024-01-01T00:00:00Z"`
|
||||
}
|
||||
|
||||
// LoginResponse 登录响应
|
||||
type LoginResponse struct {
|
||||
User *UserResponse `json:"user"`
|
||||
AccessToken string `json:"access_token" example:"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."`
|
||||
TokenType string `json:"token_type" example:"Bearer"`
|
||||
ExpiresIn int64 `json:"expires_in" example:"86400"`
|
||||
LoginMethod string `json:"login_method" example:"password"` // password 或 sms
|
||||
}
|
||||
|
||||
// 转换方法
|
||||
func (r *RegisterRequest) ToEntity() *entities.User {
|
||||
return &entities.User{
|
||||
Phone: r.Phone,
|
||||
Password: r.Password,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LoginWithPasswordRequest) ToEntity() *entities.User {
|
||||
return &entities.User{
|
||||
Phone: r.Phone,
|
||||
Password: r.Password,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *LoginWithSMSRequest) ToEntity() *entities.User {
|
||||
return &entities.User{
|
||||
Phone: r.Phone,
|
||||
}
|
||||
}
|
||||
|
||||
func FromEntity(user *entities.User) *UserResponse {
|
||||
if user == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &UserResponse{
|
||||
ID: user.ID,
|
||||
Phone: user.Phone,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
}
|
||||
@@ -21,19 +21,7 @@ type EnterpriseInfo struct {
|
||||
UnifiedSocialCode string `gorm:"type:varchar(50);not null;index" json:"unified_social_code" comment:"统一社会信用代码"`
|
||||
LegalPersonName string `gorm:"type:varchar(100);not null" json:"legal_person_name" comment:"法定代表人姓名"`
|
||||
LegalPersonID string `gorm:"type:varchar(50);not null" json:"legal_person_id" comment:"法定代表人身份证号"`
|
||||
|
||||
// 认证状态 - 各环节的验证结果
|
||||
IsOCRVerified bool `gorm:"default:false" json:"is_ocr_verified" comment:"OCR验证是否通过"`
|
||||
IsFaceVerified bool `gorm:"default:false" json:"is_face_verified" comment:"人脸识别是否通过"`
|
||||
IsCertified bool `gorm:"default:false" json:"is_certified" comment:"是否已完成认证"`
|
||||
VerificationData string `gorm:"type:text" json:"verification_data,omitempty" comment:"验证数据(JSON格式)"`
|
||||
|
||||
// OCR识别结果 - 从营业执照中自动识别的信息
|
||||
OCRRawData string `gorm:"type:text" json:"ocr_raw_data,omitempty" comment:"OCR原始返回数据(JSON格式)"`
|
||||
OCRConfidence float64 `gorm:"type:decimal(5,2)" json:"ocr_confidence,omitempty" comment:"OCR识别置信度(0-1)"`
|
||||
|
||||
// 认证完成时间
|
||||
CertifiedAt *time.Time `json:"certified_at,omitempty" comment:"认证完成时间"`
|
||||
LegalPersonPhone string `gorm:"type:varchar(50);not null" json:"legal_person_phone" comment:"法定代表人手机号"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||
@@ -70,34 +58,6 @@ func (e *EnterpriseInfo) Validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsFullyVerified 检查是否已完成所有验证
|
||||
func (e *EnterpriseInfo) IsFullyVerified() bool {
|
||||
return e.IsOCRVerified && e.IsFaceVerified && e.IsCertified
|
||||
}
|
||||
|
||||
// UpdateOCRVerification 更新OCR验证状态
|
||||
func (e *EnterpriseInfo) UpdateOCRVerification(isVerified bool, rawData string, confidence float64) {
|
||||
e.IsOCRVerified = isVerified
|
||||
e.OCRRawData = rawData
|
||||
e.OCRConfidence = confidence
|
||||
}
|
||||
|
||||
// UpdateFaceVerification 更新人脸识别验证状态
|
||||
func (e *EnterpriseInfo) UpdateFaceVerification(isVerified bool) {
|
||||
e.IsFaceVerified = isVerified
|
||||
}
|
||||
|
||||
// CompleteCertification 完成认证
|
||||
func (e *EnterpriseInfo) CompleteCertification() {
|
||||
e.IsCertified = true
|
||||
now := time.Now()
|
||||
e.CertifiedAt = &now
|
||||
}
|
||||
|
||||
// IsReadOnly 检查企业信息是否只读(认证完成后不可修改)
|
||||
func (e *EnterpriseInfo) IsReadOnly() bool {
|
||||
return e.IsCertified
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (e *EnterpriseInfo) BeforeCreate(tx *gorm.DB) error {
|
||||
|
||||
@@ -41,6 +41,7 @@ const (
|
||||
SMSSceneResetPassword SMSScene = "reset_password" // 重置密码 - 忘记密码重置
|
||||
SMSSceneBind SMSScene = "bind" // 绑定手机号 - 绑定新手机号
|
||||
SMSSceneUnbind SMSScene = "unbind" // 解绑手机号 - 解绑当前手机号
|
||||
SMSSceneCertification SMSScene = "certification" // 企业认证 - 企业入驻认证
|
||||
)
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
@@ -195,6 +196,7 @@ func (s *SMSCode) IsSceneValid() bool {
|
||||
SMSSceneResetPassword,
|
||||
SMSSceneBind,
|
||||
SMSSceneUnbind,
|
||||
SMSSceneCertification,
|
||||
}
|
||||
|
||||
for _, scene := range validScenes {
|
||||
@@ -214,6 +216,7 @@ func (s *SMSCode) GetSceneName() string {
|
||||
SMSSceneResetPassword: "重置密码",
|
||||
SMSSceneBind: "绑定手机号",
|
||||
SMSSceneUnbind: "解绑手机号",
|
||||
SMSSceneCertification: "企业认证",
|
||||
}
|
||||
|
||||
if name, exists := sceneNames[s.Scene]; exists {
|
||||
@@ -283,6 +286,7 @@ func IsValidScene(scene SMSScene) bool {
|
||||
SMSSceneResetPassword,
|
||||
SMSSceneBind,
|
||||
SMSSceneUnbind,
|
||||
SMSSceneCertification,
|
||||
}
|
||||
|
||||
for _, validScene := range validScenes {
|
||||
@@ -302,6 +306,7 @@ func GetSceneName(scene SMSScene) string {
|
||||
SMSSceneResetPassword: "重置密码",
|
||||
SMSSceneBind: "绑定手机号",
|
||||
SMSSceneUnbind: "解绑手机号",
|
||||
SMSSceneCertification: "企业认证",
|
||||
}
|
||||
|
||||
if name, exists := sceneNames[scene]; exists {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
@@ -11,6 +10,14 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserType 用户类型枚举
|
||||
type UserType string
|
||||
|
||||
const (
|
||||
UserTypeNormal UserType = "user" // 普通用户
|
||||
UserTypeAdmin UserType = "admin" // 管理员
|
||||
)
|
||||
|
||||
// User 用户实体
|
||||
// 系统用户的核心信息,提供基础的账户管理功能
|
||||
// 支持手机号登录,密码加密存储,实现Entity接口便于统一管理
|
||||
@@ -20,6 +27,16 @@ type User struct {
|
||||
Phone string `gorm:"uniqueIndex;type:varchar(20);not null" json:"phone" comment:"手机号码(登录账号)"`
|
||||
Password string `gorm:"type:varchar(255);not null" json:"-" comment:"登录密码(加密存储,不返回前端)"`
|
||||
|
||||
// 用户类型和基本信息
|
||||
UserType string `gorm:"type:varchar(20);not null;default:'user'" json:"user_type" comment:"用户类型(user/admin)"`
|
||||
Username string `gorm:"type:varchar(100)" json:"username" comment:"用户名(管理员专用)"`
|
||||
|
||||
// 管理员特有字段
|
||||
Active bool `gorm:"default:true" json:"is_active" comment:"账户是否激活"`
|
||||
LastLoginAt *time.Time `json:"last_login_at" comment:"最后登录时间"`
|
||||
LoginCount int `gorm:"default:0" json:"login_count" comment:"登录次数统计"`
|
||||
Permissions string `gorm:"type:text" json:"permissions" comment:"权限列表(JSON格式存储)"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
||||
@@ -78,6 +95,42 @@ func (u *User) Validate() error {
|
||||
|
||||
// ================ 业务方法 ================
|
||||
|
||||
// IsAdmin 检查是否为管理员
|
||||
func (u *User) IsAdmin() bool {
|
||||
return u.UserType == string(UserTypeAdmin)
|
||||
}
|
||||
|
||||
// IsNormalUser 检查是否为普通用户
|
||||
func (u *User) IsNormalUser() bool {
|
||||
return u.UserType == string(UserTypeNormal)
|
||||
}
|
||||
|
||||
// SetUserType 设置用户类型
|
||||
func (u *User) SetUserType(userType UserType) {
|
||||
u.UserType = string(userType)
|
||||
}
|
||||
|
||||
// UpdateLastLoginAt 更新最后登录时间
|
||||
func (u *User) UpdateLastLoginAt() {
|
||||
now := time.Now()
|
||||
u.LastLoginAt = &now
|
||||
}
|
||||
|
||||
// IncrementLoginCount 增加登录次数
|
||||
func (u *User) IncrementLoginCount() {
|
||||
u.LoginCount++
|
||||
}
|
||||
|
||||
// Activate 激活用户账户
|
||||
func (u *User) Activate() {
|
||||
u.Active = true
|
||||
}
|
||||
|
||||
// Deactivate 停用用户账户
|
||||
func (u *User) Deactivate() {
|
||||
u.Active = false
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
// 验证旧密码,检查新密码强度,更新密码
|
||||
func (u *User) ChangePassword(oldPassword, newPassword, confirmPassword string) error {
|
||||
@@ -168,6 +221,11 @@ func (u *User) CanLogin() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// 如果是管理员,检查是否激活
|
||||
if u.IsAdmin() && !u.Active {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -205,7 +263,7 @@ func (u *User) GetMaskedPhone() string {
|
||||
return u.Phone[:3] + "****" + u.Phone[len(u.Phone)-4:]
|
||||
}
|
||||
|
||||
// ================ 私有辅助方法 ================
|
||||
// ================ 私有方法 ================
|
||||
|
||||
// hashPassword 加密密码
|
||||
func (u *User) hashPassword(password string) (string, error) {
|
||||
@@ -218,61 +276,30 @@ func (u *User) hashPassword(password string) (string, error) {
|
||||
|
||||
// validatePasswordStrength 验证密码强度
|
||||
func (u *User) validatePasswordStrength(password string) error {
|
||||
if len(password) < 8 {
|
||||
return NewValidationError("密码长度至少8位")
|
||||
if len(password) < 6 {
|
||||
return NewValidationError("密码长度不能少于6位")
|
||||
}
|
||||
|
||||
if len(password) > 128 {
|
||||
return NewValidationError("密码长度不能超过128位")
|
||||
if len(password) > 20 {
|
||||
return NewValidationError("密码长度不能超过20位")
|
||||
}
|
||||
|
||||
// 检查是否包含数字
|
||||
hasDigit := regexp.MustCompile(`[0-9]`).MatchString(password)
|
||||
if !hasDigit {
|
||||
return NewValidationError("密码必须包含数字")
|
||||
}
|
||||
|
||||
// 检查是否包含字母
|
||||
hasLetter := regexp.MustCompile(`[a-zA-Z]`).MatchString(password)
|
||||
if !hasLetter {
|
||||
return NewValidationError("密码必须包含字母")
|
||||
}
|
||||
|
||||
// 检查是否包含特殊字符(可选,可以根据需求调整)
|
||||
hasSpecial := regexp.MustCompile(`[!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]`).MatchString(password)
|
||||
if !hasSpecial {
|
||||
return NewValidationError("密码必须包含特殊字符")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ================ 静态工具方法 ================
|
||||
|
||||
// IsValidPhoneFormat 验证手机号格式(静态方法)
|
||||
// IsValidPhoneFormat 验证手机号格式
|
||||
func IsValidPhoneFormat(phone string) bool {
|
||||
if phone == "" {
|
||||
return false
|
||||
}
|
||||
// 中国手机号验证(11位数字,以1开头)
|
||||
pattern := `^1[3-9]\d{9}$`
|
||||
matched, _ := regexp.MatchString(pattern, phone)
|
||||
return matched
|
||||
}
|
||||
|
||||
// NewUser 创建新用户(工厂方法)
|
||||
// NewUser 创建新用户
|
||||
func NewUser(phone, password string) (*User, error) {
|
||||
user := &User{
|
||||
ID: "", // 由数据库或调用方设置
|
||||
Phone: phone,
|
||||
Phone: phone,
|
||||
UserType: string(UserTypeNormal), // 默认为普通用户
|
||||
Active: true,
|
||||
}
|
||||
|
||||
// 验证手机号
|
||||
if err := user.SetPhone(phone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 设置密码
|
||||
if err := user.SetPassword(password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -280,41 +307,60 @@ func NewUser(phone, password string) (*User, error) {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
// NewAdminUser 创建新管理员用户
|
||||
func NewAdminUser(phone, password, username string) (*User, error) {
|
||||
user := &User{
|
||||
Phone: phone,
|
||||
Username: username,
|
||||
UserType: string(UserTypeAdmin),
|
||||
Active: true,
|
||||
}
|
||||
|
||||
if err := user.SetPassword(password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// TableName 指定数据库表名
|
||||
func (User) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
// ValidationError 验证错误
|
||||
// 自定义验证错误类型,提供结构化的错误信息
|
||||
// ================ 错误处理 ================
|
||||
|
||||
type ValidationError struct {
|
||||
Message string
|
||||
}
|
||||
|
||||
// Error 实现error接口
|
||||
func (e *ValidationError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// NewValidationError 创建新的验证错误
|
||||
// 工厂方法,用于创建验证错误实例
|
||||
func NewValidationError(message string) *ValidationError {
|
||||
return &ValidationError{Message: message}
|
||||
}
|
||||
|
||||
// IsValidationError 检查是否为验证错误
|
||||
func IsValidationError(err error) bool {
|
||||
var validationErr *ValidationError
|
||||
return errors.As(err, &validationErr)
|
||||
_, ok := err.(*ValidationError)
|
||||
return ok
|
||||
}
|
||||
|
||||
// UserCache 用户缓存结构体
|
||||
// 专门用于缓存序列化,包含Password字段
|
||||
// ================ 缓存相关 ================
|
||||
|
||||
type UserCache struct {
|
||||
// 基础标识
|
||||
ID string `json:"id" comment:"用户唯一标识"`
|
||||
Phone string `json:"phone" comment:"手机号码(登录账号)"`
|
||||
Password string `json:"password" comment:"登录密码(加密存储)"`
|
||||
UserType string `json:"user_type" comment:"用户类型"`
|
||||
Username string `json:"username" comment:"用户名"`
|
||||
|
||||
Active bool `gorm:"default:true" json:"is_active" comment:"账户是否激活"`
|
||||
LastLoginAt *time.Time `json:"last_login_at" comment:"最后登录时间"`
|
||||
LoginCount int `gorm:"default:0" json:"login_count" comment:"登录次数统计"`
|
||||
Permissions string `gorm:"type:text" json:"permissions" comment:"权限列表(JSON格式存储)"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
@@ -322,24 +368,38 @@ type UserCache struct {
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// ToCache 转换为缓存结构体
|
||||
// ToCache 转换为缓存结构
|
||||
func (u *User) ToCache() *UserCache {
|
||||
return &UserCache{
|
||||
ID: u.ID,
|
||||
Phone: u.Phone,
|
||||
Password: u.Password,
|
||||
UserType: u.UserType,
|
||||
Username: u.Username,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
DeletedAt: u.DeletedAt,
|
||||
// 补充所有字段
|
||||
// 管理员特有字段
|
||||
Active: u.Active,
|
||||
LastLoginAt: u.LastLoginAt,
|
||||
LoginCount: u.LoginCount,
|
||||
Permissions: u.Permissions,
|
||||
}
|
||||
}
|
||||
|
||||
// FromCache 从缓存结构体转换
|
||||
// FromCache 从缓存结构恢复
|
||||
func (u *User) FromCache(cache *UserCache) {
|
||||
u.ID = cache.ID
|
||||
u.Phone = cache.Phone
|
||||
u.Password = cache.Password
|
||||
u.UserType = cache.UserType
|
||||
u.Username = cache.Username
|
||||
u.CreatedAt = cache.CreatedAt
|
||||
u.UpdatedAt = cache.UpdatedAt
|
||||
u.DeletedAt = cache.DeletedAt
|
||||
u.Active = cache.Active
|
||||
u.LastLoginAt = cache.LastLoginAt
|
||||
u.LoginCount = cache.LoginCount
|
||||
u.Permissions = cache.Permissions
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ type UserRepository interface {
|
||||
|
||||
// 基础查询 - 直接使用实体
|
||||
GetByPhone(ctx context.Context, phone string) (*entities.User, error)
|
||||
GetByUsername(ctx context.Context, username string) (*entities.User, error)
|
||||
GetByUserType(ctx context.Context, userType string) ([]*entities.User, error)
|
||||
|
||||
// 复杂查询 - 使用查询参数
|
||||
ListUsers(ctx context.Context, query *queries.ListUsersQuery) ([]*entities.User, int64, error)
|
||||
@@ -32,6 +34,7 @@ type UserRepository interface {
|
||||
CheckPassword(ctx context.Context, userID string, password string) (bool, error)
|
||||
ActivateUser(ctx context.Context, userID string) error
|
||||
DeactivateUser(ctx context.Context, userID string) error
|
||||
UpdateLoginStats(ctx context.Context, userID string) error
|
||||
|
||||
// 统计信息
|
||||
GetStats(ctx context.Context) (*UserStats, error)
|
||||
|
||||
@@ -107,11 +107,6 @@ func (s *EnterpriseService) UpdateEnterpriseInfo(ctx context.Context, userID, co
|
||||
return nil, fmt.Errorf("企业信息不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查企业信息是否已认证完成(认证完成后不可修改)
|
||||
if enterpriseInfo.IsReadOnly() {
|
||||
return nil, fmt.Errorf("企业信息已认证完成,不可修改")
|
||||
}
|
||||
|
||||
// 检查统一社会信用代码是否已被其他用户使用
|
||||
if unifiedSocialCode != enterpriseInfo.UnifiedSocialCode {
|
||||
exists, err := s.enterpriseInfoRepo.CheckUnifiedSocialCodeExists(ctx, unifiedSocialCode, userID)
|
||||
@@ -142,55 +137,6 @@ func (s *EnterpriseService) UpdateEnterpriseInfo(ctx context.Context, userID, co
|
||||
return enterpriseInfo, nil
|
||||
}
|
||||
|
||||
// UpdateOCRVerification 更新OCR验证状态
|
||||
func (s *EnterpriseService) UpdateOCRVerification(ctx context.Context, userID string, isVerified bool, rawData string, confidence float64) error {
|
||||
// 获取企业信息
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("企业信息不存在: %w", err)
|
||||
}
|
||||
|
||||
// 更新OCR验证状态
|
||||
enterpriseInfo.UpdateOCRVerification(isVerified, rawData, confidence)
|
||||
|
||||
if err := s.enterpriseInfoRepo.Update(ctx, *enterpriseInfo); err != nil {
|
||||
s.logger.Error("更新OCR验证状态失败", zap.Error(err))
|
||||
return fmt.Errorf("更新OCR验证状态失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("OCR验证状态更新成功",
|
||||
zap.String("user_id", userID),
|
||||
zap.Bool("is_verified", isVerified),
|
||||
zap.Float64("confidence", confidence),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateFaceVerification 更新人脸识别验证状态
|
||||
func (s *EnterpriseService) UpdateFaceVerification(ctx context.Context, userID string, isVerified bool) error {
|
||||
// 获取企业信息
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("企业信息不存在: %w", err)
|
||||
}
|
||||
|
||||
// 更新人脸识别验证状态
|
||||
enterpriseInfo.UpdateFaceVerification(isVerified)
|
||||
|
||||
if err := s.enterpriseInfoRepo.Update(ctx, *enterpriseInfo); err != nil {
|
||||
s.logger.Error("更新人脸识别验证状态失败", zap.Error(err))
|
||||
return fmt.Errorf("更新人脸识别验证状态失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("人脸识别验证状态更新成功",
|
||||
zap.String("user_id", userID),
|
||||
zap.Bool("is_verified", isVerified),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompleteEnterpriseCertification 完成企业认证
|
||||
func (s *EnterpriseService) CompleteEnterpriseCertification(ctx context.Context, userID string) error {
|
||||
// 获取企业信息
|
||||
@@ -199,14 +145,6 @@ func (s *EnterpriseService) CompleteEnterpriseCertification(ctx context.Context,
|
||||
return fmt.Errorf("企业信息不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查是否已完成所有验证
|
||||
if !enterpriseInfo.IsOCRVerified || !enterpriseInfo.IsFaceVerified {
|
||||
return fmt.Errorf("企业信息验证未完成,无法完成认证")
|
||||
}
|
||||
|
||||
// 完成认证
|
||||
enterpriseInfo.CompleteCertification()
|
||||
|
||||
if err := s.enterpriseInfoRepo.Update(ctx, *enterpriseInfo); err != nil {
|
||||
s.logger.Error("完成企业认证失败", zap.Error(err))
|
||||
return fmt.Errorf("完成企业认证失败: %w", err)
|
||||
@@ -264,17 +202,6 @@ func (s *EnterpriseService) GetEnterpriseInfoByUnifiedSocialCode(ctx context.Con
|
||||
return s.enterpriseInfoRepo.GetByUnifiedSocialCode(ctx, unifiedSocialCode)
|
||||
}
|
||||
|
||||
// IsEnterpriseCertified 检查用户是否已完成企业认证
|
||||
func (s *EnterpriseService) IsEnterpriseCertified(ctx context.Context, userID string) (bool, error) {
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
// 没有企业信息,认为未认证
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return enterpriseInfo.IsFullyVerified(), nil
|
||||
}
|
||||
|
||||
// GetEnterpriseCertificationStatus 获取企业认证状态
|
||||
func (s *EnterpriseService) GetEnterpriseCertificationStatus(ctx context.Context, userID string) (map[string]interface{}, error) {
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
@@ -288,11 +215,6 @@ func (s *EnterpriseService) GetEnterpriseCertificationStatus(ctx context.Context
|
||||
|
||||
status := map[string]interface{}{
|
||||
"has_enterprise_info": true,
|
||||
"is_certified": enterpriseInfo.IsFullyVerified(),
|
||||
"is_readonly": enterpriseInfo.IsReadOnly(),
|
||||
"ocr_verified": enterpriseInfo.IsOCRVerified,
|
||||
"face_verified": enterpriseInfo.IsFaceVerified,
|
||||
"certified_at": enterpriseInfo.CertifiedAt,
|
||||
"company_name": enterpriseInfo.CompanyName,
|
||||
"unified_social_code": enterpriseInfo.UnifiedSocialCode,
|
||||
"legal_person_name": enterpriseInfo.LegalPersonName,
|
||||
|
||||
130
internal/domains/user/services/user_auth_service.go
Normal file
130
internal/domains/user/services/user_auth_service.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/repositories"
|
||||
)
|
||||
|
||||
// UserAuthService 用户认证领域服务
|
||||
// 负责用户认证相关的业务逻辑,包括密码验证、登录状态管理等
|
||||
type UserAuthService struct {
|
||||
userRepo repositories.UserRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewUserAuthService 创建用户认证领域服务
|
||||
func NewUserAuthService(
|
||||
userRepo repositories.UserRepository,
|
||||
logger *zap.Logger,
|
||||
) *UserAuthService {
|
||||
return &UserAuthService{
|
||||
userRepo: userRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// ValidatePassword 验证用户密码
|
||||
func (s *UserAuthService) ValidatePassword(ctx context.Context, phone, password string) (*entities.User, error) {
|
||||
user, err := s.userRepo.GetByPhone(ctx, phone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户名或密码错误")
|
||||
}
|
||||
|
||||
if !user.CanLogin() {
|
||||
return nil, fmt.Errorf("用户状态异常,无法登录")
|
||||
}
|
||||
|
||||
if !user.CheckPassword(password) {
|
||||
return nil, fmt.Errorf("用户名或密码错误")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// ValidateUserLogin 验证用户登录状态
|
||||
func (s *UserAuthService) ValidateUserLogin(ctx context.Context, phone string) (*entities.User, error) {
|
||||
user, err := s.userRepo.GetByPhone(ctx, phone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在")
|
||||
}
|
||||
|
||||
if !user.CanLogin() {
|
||||
return nil, fmt.Errorf("用户状态异常,无法登录")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// ChangePassword 修改用户密码
|
||||
func (s *UserAuthService) ChangePassword(ctx context.Context, userID, oldPassword, newPassword string) error {
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
if err := user.ChangePassword(oldPassword, newPassword, newPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.userRepo.Update(ctx, user); err != nil {
|
||||
s.logger.Error("密码修改失败", zap.Error(err))
|
||||
return fmt.Errorf("密码修改失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("密码修改成功",
|
||||
zap.String("user_id", userID),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetPassword 重置用户密码
|
||||
func (s *UserAuthService) ResetPassword(ctx context.Context, phone, newPassword string) error {
|
||||
user, err := s.userRepo.GetByPhone(ctx, phone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
if err := user.ResetPassword(newPassword, newPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.userRepo.Update(ctx, *user); err != nil {
|
||||
s.logger.Error("密码重置失败", zap.Error(err))
|
||||
return fmt.Errorf("密码重置失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("密码重置成功",
|
||||
zap.String("user_id", user.ID),
|
||||
zap.String("phone", user.Phone),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserPermissions 获取用户权限
|
||||
func (s *UserAuthService) GetUserPermissions(ctx context.Context, user *entities.User) ([]string, error) {
|
||||
if !user.IsAdmin() {
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// 这里可以根据用户角色返回不同的权限
|
||||
// 目前返回默认的管理员权限
|
||||
permissions := []string{
|
||||
"user:read",
|
||||
"user:write",
|
||||
"product:read",
|
||||
"product:write",
|
||||
"certification:read",
|
||||
"certification:write",
|
||||
"finance:read",
|
||||
"finance:write",
|
||||
}
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
128
internal/domains/user/services/user_management_service.go
Normal file
128
internal/domains/user/services/user_management_service.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/repositories"
|
||||
)
|
||||
|
||||
// UserManagementService 用户管理领域服务
|
||||
// 负责用户的基本管理操作,包括创建、查询、更新等
|
||||
type UserManagementService struct {
|
||||
userRepo repositories.UserRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewUserManagementService 创建用户管理领域服务
|
||||
func NewUserManagementService(
|
||||
userRepo repositories.UserRepository,
|
||||
logger *zap.Logger,
|
||||
) *UserManagementService {
|
||||
return &UserManagementService{
|
||||
userRepo: userRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateUser 创建用户
|
||||
func (s *UserManagementService) CreateUser(ctx context.Context, phone, password string) (*entities.User, error) {
|
||||
// 检查手机号是否已注册
|
||||
exists, err := s.IsPhoneRegistered(ctx, phone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("检查手机号失败: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("手机号已注册")
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
user, err := entities.NewUser(phone, password)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建用户失败: %w", err)
|
||||
}
|
||||
|
||||
createdUser, err := s.userRepo.Create(ctx, *user)
|
||||
if err != nil {
|
||||
s.logger.Error("创建用户失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建用户失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("用户创建成功",
|
||||
zap.String("user_id", user.ID),
|
||||
zap.String("phone", user.Phone),
|
||||
)
|
||||
|
||||
return &createdUser, nil
|
||||
}
|
||||
|
||||
// GetUserByID 根据ID获取用户信息
|
||||
func (s *UserManagementService) GetUserByID(ctx context.Context, userID string) (*entities.User, error) {
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetUserByPhone 根据手机号获取用户信息
|
||||
func (s *UserManagementService) GetUserByPhone(ctx context.Context, phone string) (*entities.User, error) {
|
||||
user, err := s.userRepo.GetByPhone(ctx, phone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// UpdateUser 更新用户信息
|
||||
func (s *UserManagementService) UpdateUser(ctx context.Context, user *entities.User) error {
|
||||
if err := s.userRepo.Update(ctx, *user); err != nil {
|
||||
s.logger.Error("更新用户信息失败", zap.Error(err))
|
||||
return fmt.Errorf("更新用户信息失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("用户信息更新成功",
|
||||
zap.String("user_id", user.ID),
|
||||
zap.String("phone", user.Phone),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsPhoneRegistered 检查手机号是否已注册
|
||||
func (s *UserManagementService) IsPhoneRegistered(ctx context.Context, phone string) (bool, error) {
|
||||
_, err := s.userRepo.GetByPhone(ctx, phone)
|
||||
if err != nil {
|
||||
return false, nil // 用户不存在,可以注册
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ValidateUser 验证用户信息
|
||||
func (s *UserManagementService) ValidateUser(ctx context.Context, userID string) error {
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
// 这里可以添加更多的用户验证逻辑
|
||||
if user.Phone == "" {
|
||||
return fmt.Errorf("用户手机号不能为空")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateLoginStats 更新登录统计
|
||||
func (s *UserManagementService) UpdateLoginStats(ctx context.Context, userID string) error {
|
||||
if err := s.userRepo.UpdateLoginStats(ctx, userID); err != nil {
|
||||
s.logger.Error("更新登录统计失败", zap.Error(err))
|
||||
return fmt.Errorf("更新登录统计失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("登录统计更新成功", zap.String("user_id", userID))
|
||||
return nil
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/repositories"
|
||||
)
|
||||
|
||||
// UserService 用户领域服务
|
||||
type UserService struct {
|
||||
userRepo repositories.UserRepository
|
||||
enterpriseService *EnterpriseService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewUserService 创建用户领域服务
|
||||
func NewUserService(
|
||||
userRepo repositories.UserRepository,
|
||||
enterpriseService *EnterpriseService,
|
||||
logger *zap.Logger,
|
||||
) *UserService {
|
||||
return &UserService{
|
||||
userRepo: userRepo,
|
||||
enterpriseService: enterpriseService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// IsPhoneRegistered 检查手机号是否已注册
|
||||
func (s *UserService) IsPhoneRegistered(ctx context.Context, phone string) (bool, error) {
|
||||
_, err := s.userRepo.GetByPhone(ctx, phone)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// GetUserWithEnterpriseInfo 获取用户信息(包含企业信息)
|
||||
func (s *UserService) GetUserWithEnterpriseInfo(ctx context.Context, userID string) (*entities.User, error) {
|
||||
// 通过企业服务获取用户信息(包含企业信息)
|
||||
return s.enterpriseService.GetUserWithEnterpriseInfo(ctx, userID)
|
||||
}
|
||||
|
||||
// GetUserByID 根据ID获取用户信息
|
||||
func (s *UserService) GetUserByID(ctx context.Context, userID string) (*entities.User, error) {
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// GetUserByPhone 根据手机号获取用户信息
|
||||
func (s *UserService) GetUserByPhone(ctx context.Context, phone string) (*entities.User, error) {
|
||||
user, err := s.userRepo.GetByPhone(ctx, phone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// UpdateUser 更新用户信息
|
||||
func (s *UserService) UpdateUser(ctx context.Context, user *entities.User) error {
|
||||
if err := s.userRepo.Update(ctx, *user); err != nil {
|
||||
s.logger.Error("更新用户信息失败", zap.Error(err))
|
||||
return fmt.Errorf("更新用户信息失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("用户信息更新成功",
|
||||
zap.String("user_id", user.ID),
|
||||
zap.String("phone", user.Phone),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangePassword 修改用户密码
|
||||
func (s *UserService) ChangePassword(ctx context.Context, userID, oldPassword, newPassword string) error {
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
if err := user.ChangePassword(oldPassword, newPassword, newPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.userRepo.Update(ctx, user); err != nil {
|
||||
s.logger.Error("密码修改失败", zap.Error(err))
|
||||
return fmt.Errorf("密码修改失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("密码修改成功",
|
||||
zap.String("user_id", userID),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateUser 验证用户信息
|
||||
func (s *UserService) ValidateUser(ctx context.Context, userID string) error {
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
// 这里可以添加更多的用户验证逻辑
|
||||
if user.Phone == "" {
|
||||
return fmt.Errorf("用户手机号不能为空")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetUserStats 获取用户统计信息
|
||||
func (s *UserService) GetUserStats(ctx context.Context) (map[string]interface{}, error) {
|
||||
// 这里可以添加用户统计逻辑
|
||||
stats := map[string]interface{}{
|
||||
"total_users": 0, // 需要实现具体的统计逻辑
|
||||
"active_users": 0,
|
||||
"new_users_today": 0,
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
@@ -130,7 +130,8 @@ func (db *DB) GetStats() (map[string]interface{}, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// BeginTx 开始事务
|
||||
// BeginTx 开始事务(已废弃,请使用shared/database.TransactionManager)
|
||||
// @deprecated 请使用 shared/database.TransactionManager
|
||||
func (db *DB) BeginTx() *gorm.DB {
|
||||
return db.DB.Begin()
|
||||
}
|
||||
@@ -153,47 +154,5 @@ func (db *DB) WithContext(ctx interface{}) *gorm.DB {
|
||||
return db.DB
|
||||
}
|
||||
|
||||
// 事务包装器
|
||||
type TxWrapper struct {
|
||||
tx *gorm.DB
|
||||
}
|
||||
|
||||
// NewTxWrapper 创建事务包装器
|
||||
func (db *DB) NewTxWrapper() *TxWrapper {
|
||||
return &TxWrapper{
|
||||
tx: db.BeginTx(),
|
||||
}
|
||||
}
|
||||
|
||||
// Commit 提交事务
|
||||
func (tx *TxWrapper) Commit() error {
|
||||
return tx.tx.Commit().Error
|
||||
}
|
||||
|
||||
// Rollback 回滚事务
|
||||
func (tx *TxWrapper) Rollback() error {
|
||||
return tx.tx.Rollback().Error
|
||||
}
|
||||
|
||||
// GetDB 获取事务数据库实例
|
||||
func (tx *TxWrapper) GetDB() *gorm.DB {
|
||||
return tx.tx
|
||||
}
|
||||
|
||||
// WithTx 在事务中执行函数
|
||||
func (db *DB) WithTx(fn func(*gorm.DB) error) error {
|
||||
tx := db.BeginTx()
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
tx.Rollback()
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := fn(tx); err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
|
||||
return tx.Commit().Error
|
||||
}
|
||||
// 注意:事务相关功能已迁移到 shared/database.TransactionManager
|
||||
// 请使用 TransactionManager 进行事务管理
|
||||
|
||||
@@ -1,225 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/admin/entities"
|
||||
"tyapi-server/internal/domains/admin/repositories"
|
||||
"tyapi-server/internal/domains/admin/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// GormAdminLoginLogRepository 管理员登录日志GORM仓储实现
|
||||
type GormAdminLoginLogRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.AdminLoginLogRepository = (*GormAdminLoginLogRepository)(nil)
|
||||
|
||||
// NewGormAdminLoginLogRepository 创建管理员登录日志GORM仓储
|
||||
func NewGormAdminLoginLogRepository(db *gorm.DB, logger *zap.Logger) repositories.AdminLoginLogRepository {
|
||||
return &GormAdminLoginLogRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// ================ 基础CRUD操作 ================
|
||||
|
||||
// Create 创建登录日志
|
||||
func (r *GormAdminLoginLogRepository) Create(ctx context.Context, log entities.AdminLoginLog) (entities.AdminLoginLog, error) {
|
||||
r.logger.Info("创建管理员登录日志", zap.String("admin_id", log.AdminID))
|
||||
err := r.db.WithContext(ctx).Create(&log).Error
|
||||
return log, err
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取登录日志
|
||||
func (r *GormAdminLoginLogRepository) GetByID(ctx context.Context, id string) (entities.AdminLoginLog, error) {
|
||||
var log entities.AdminLoginLog
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&log).Error
|
||||
return log, err
|
||||
}
|
||||
|
||||
// Update 更新登录日志
|
||||
func (r *GormAdminLoginLogRepository) Update(ctx context.Context, log entities.AdminLoginLog) error {
|
||||
r.logger.Info("更新管理员登录日志", zap.String("id", log.ID))
|
||||
return r.db.WithContext(ctx).Save(&log).Error
|
||||
}
|
||||
|
||||
// Delete 删除登录日志
|
||||
func (r *GormAdminLoginLogRepository) Delete(ctx context.Context, id string) error {
|
||||
r.logger.Info("删除管理员登录日志", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Delete(&entities.AdminLoginLog{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// SoftDelete 软删除登录日志
|
||||
func (r *GormAdminLoginLogRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
r.logger.Info("软删除管理员登录日志", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Delete(&entities.AdminLoginLog{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// Restore 恢复登录日志
|
||||
func (r *GormAdminLoginLogRepository) Restore(ctx context.Context, id string) error {
|
||||
r.logger.Info("恢复管理员登录日志", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Unscoped().Model(&entities.AdminLoginLog{}).Where("id = ?", id).Update("deleted_at", nil).Error
|
||||
}
|
||||
|
||||
// Count 统计登录日志数量
|
||||
func (r *GormAdminLoginLogRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.AdminLoginLog{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("admin_id LIKE ? OR ip_address LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
return count, query.Count(&count).Error
|
||||
}
|
||||
|
||||
// Exists 检查登录日志是否存在
|
||||
func (r *GormAdminLoginLogRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.AdminLoginLog{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建登录日志
|
||||
func (r *GormAdminLoginLogRepository) CreateBatch(ctx context.Context, logs []entities.AdminLoginLog) error {
|
||||
r.logger.Info("批量创建管理员登录日志", zap.Int("count", len(logs)))
|
||||
return r.db.WithContext(ctx).Create(&logs).Error
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取登录日志
|
||||
func (r *GormAdminLoginLogRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.AdminLoginLog, error) {
|
||||
var logs []entities.AdminLoginLog
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&logs).Error
|
||||
return logs, err
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新登录日志
|
||||
func (r *GormAdminLoginLogRepository) UpdateBatch(ctx context.Context, logs []entities.AdminLoginLog) error {
|
||||
r.logger.Info("批量更新管理员登录日志", zap.Int("count", len(logs)))
|
||||
return r.db.WithContext(ctx).Save(&logs).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除登录日志
|
||||
func (r *GormAdminLoginLogRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
r.logger.Info("批量删除管理员登录日志", zap.Strings("ids", ids))
|
||||
return r.db.WithContext(ctx).Delete(&entities.AdminLoginLog{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取登录日志列表
|
||||
func (r *GormAdminLoginLogRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.AdminLoginLog, error) {
|
||||
var logs []entities.AdminLoginLog
|
||||
query := r.db.WithContext(ctx).Model(&entities.AdminLoginLog{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("admin_id LIKE ? OR ip_address LIKE ?", "%"+options.Search+"%", "%"+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 logs, query.Find(&logs).Error
|
||||
}
|
||||
|
||||
// WithTx 使用事务
|
||||
func (r *GormAdminLoginLogRepository) WithTx(tx interface{}) interfaces.Repository[entities.AdminLoginLog] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormAdminLoginLogRepository{
|
||||
db: gormTx,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ================ 业务方法 ================
|
||||
|
||||
// ListLogs 获取登录日志列表(带分页和筛选)
|
||||
func (r *GormAdminLoginLogRepository) ListLogs(ctx context.Context, query *queries.ListAdminLoginLogQuery) ([]*entities.AdminLoginLog, int64, error) {
|
||||
var logs []entities.AdminLoginLog
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.AdminLoginLog{})
|
||||
|
||||
// 应用筛选条件
|
||||
if query.AdminID != "" {
|
||||
dbQuery = dbQuery.Where("admin_id = ?", query.AdminID)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
dbQuery = dbQuery.Offset(offset).Limit(query.PageSize)
|
||||
|
||||
// 默认排序
|
||||
dbQuery = dbQuery.Order("created_at DESC")
|
||||
|
||||
// 查询数据
|
||||
if err := dbQuery.Find(&logs).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
logPtrs := make([]*entities.AdminLoginLog, len(logs))
|
||||
for i := range logs {
|
||||
logPtrs[i] = &logs[i]
|
||||
}
|
||||
|
||||
return logPtrs, total, nil
|
||||
}
|
||||
|
||||
// GetTodayLoginCount 获取今日登录次数
|
||||
func (r *GormAdminLoginLogRepository) GetTodayLoginCount(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
err := r.db.WithContext(ctx).Model(&entities.AdminLoginLog{}).Where("created_at >= ?", today).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetLoginCountByAdmin 获取指定管理员在指定天数内的登录次数
|
||||
func (r *GormAdminLoginLogRepository) GetLoginCountByAdmin(ctx context.Context, adminID string, days int) (int64, error) {
|
||||
var count int64
|
||||
startDate := time.Now().AddDate(0, 0, -days)
|
||||
err := r.db.WithContext(ctx).Model(&entities.AdminLoginLog{}).Where("admin_id = ? AND created_at >= ?", adminID, startDate).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
@@ -1,236 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/admin/entities"
|
||||
"tyapi-server/internal/domains/admin/repositories"
|
||||
"tyapi-server/internal/domains/admin/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// GormAdminOperationLogRepository 管理员操作日志GORM仓储实现
|
||||
type GormAdminOperationLogRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.AdminOperationLogRepository = (*GormAdminOperationLogRepository)(nil)
|
||||
|
||||
// NewGormAdminOperationLogRepository 创建管理员操作日志GORM仓储
|
||||
func NewGormAdminOperationLogRepository(db *gorm.DB, logger *zap.Logger) repositories.AdminOperationLogRepository {
|
||||
return &GormAdminOperationLogRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// ================ 基础CRUD操作 ================
|
||||
|
||||
// Create 创建操作日志
|
||||
func (r *GormAdminOperationLogRepository) Create(ctx context.Context, log entities.AdminOperationLog) (entities.AdminOperationLog, error) {
|
||||
r.logger.Info("创建管理员操作日志", zap.String("admin_id", log.AdminID), zap.String("action", log.Action))
|
||||
err := r.db.WithContext(ctx).Create(&log).Error
|
||||
return log, err
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取操作日志
|
||||
func (r *GormAdminOperationLogRepository) GetByID(ctx context.Context, id string) (entities.AdminOperationLog, error) {
|
||||
var log entities.AdminOperationLog
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&log).Error
|
||||
return log, err
|
||||
}
|
||||
|
||||
// Update 更新操作日志
|
||||
func (r *GormAdminOperationLogRepository) Update(ctx context.Context, log entities.AdminOperationLog) error {
|
||||
r.logger.Info("更新管理员操作日志", zap.String("id", log.ID))
|
||||
return r.db.WithContext(ctx).Save(&log).Error
|
||||
}
|
||||
|
||||
// Delete 删除操作日志
|
||||
func (r *GormAdminOperationLogRepository) Delete(ctx context.Context, id string) error {
|
||||
r.logger.Info("删除管理员操作日志", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Delete(&entities.AdminOperationLog{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// SoftDelete 软删除操作日志
|
||||
func (r *GormAdminOperationLogRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
r.logger.Info("软删除管理员操作日志", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Delete(&entities.AdminOperationLog{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// Restore 恢复操作日志
|
||||
func (r *GormAdminOperationLogRepository) Restore(ctx context.Context, id string) error {
|
||||
r.logger.Info("恢复管理员操作日志", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Unscoped().Model(&entities.AdminOperationLog{}).Where("id = ?", id).Update("deleted_at", nil).Error
|
||||
}
|
||||
|
||||
// Count 统计操作日志数量
|
||||
func (r *GormAdminOperationLogRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.AdminOperationLog{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("admin_id LIKE ? OR action LIKE ? OR module LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
return count, query.Count(&count).Error
|
||||
}
|
||||
|
||||
// Exists 检查操作日志是否存在
|
||||
func (r *GormAdminOperationLogRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.AdminOperationLog{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建操作日志
|
||||
func (r *GormAdminOperationLogRepository) CreateBatch(ctx context.Context, logs []entities.AdminOperationLog) error {
|
||||
r.logger.Info("批量创建管理员操作日志", zap.Int("count", len(logs)))
|
||||
return r.db.WithContext(ctx).Create(&logs).Error
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取操作日志
|
||||
func (r *GormAdminOperationLogRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.AdminOperationLog, error) {
|
||||
var logs []entities.AdminOperationLog
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&logs).Error
|
||||
return logs, err
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新操作日志
|
||||
func (r *GormAdminOperationLogRepository) UpdateBatch(ctx context.Context, logs []entities.AdminOperationLog) error {
|
||||
r.logger.Info("批量更新管理员操作日志", zap.Int("count", len(logs)))
|
||||
return r.db.WithContext(ctx).Save(&logs).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除操作日志
|
||||
func (r *GormAdminOperationLogRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
r.logger.Info("批量删除管理员操作日志", zap.Strings("ids", ids))
|
||||
return r.db.WithContext(ctx).Delete(&entities.AdminOperationLog{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取操作日志列表
|
||||
func (r *GormAdminOperationLogRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.AdminOperationLog, error) {
|
||||
var logs []entities.AdminOperationLog
|
||||
query := r.db.WithContext(ctx).Model(&entities.AdminOperationLog{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("admin_id LIKE ? OR action LIKE ? OR module LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%", "%"+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 logs, query.Find(&logs).Error
|
||||
}
|
||||
|
||||
// WithTx 使用事务
|
||||
func (r *GormAdminOperationLogRepository) WithTx(tx interface{}) interfaces.Repository[entities.AdminOperationLog] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormAdminOperationLogRepository{
|
||||
db: gormTx,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ================ 业务方法 ================
|
||||
|
||||
// ListLogs 获取操作日志列表(带分页和筛选)
|
||||
func (r *GormAdminOperationLogRepository) ListLogs(ctx context.Context, query *queries.ListAdminOperationLogQuery) ([]*entities.AdminOperationLog, int64, error) {
|
||||
var logs []entities.AdminOperationLog
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.AdminOperationLog{})
|
||||
|
||||
// 应用筛选条件
|
||||
if query.AdminID != "" {
|
||||
dbQuery = dbQuery.Where("admin_id = ?", query.AdminID)
|
||||
}
|
||||
if query.Module != "" {
|
||||
dbQuery = dbQuery.Where("module = ?", query.Module)
|
||||
}
|
||||
if query.Action != "" {
|
||||
dbQuery = dbQuery.Where("action = ?", query.Action)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
dbQuery = dbQuery.Offset(offset).Limit(query.PageSize)
|
||||
|
||||
// 默认排序
|
||||
dbQuery = dbQuery.Order("created_at DESC")
|
||||
|
||||
// 查询数据
|
||||
if err := dbQuery.Find(&logs).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
logPtrs := make([]*entities.AdminOperationLog, len(logs))
|
||||
for i := range logs {
|
||||
logPtrs[i] = &logs[i]
|
||||
}
|
||||
|
||||
return logPtrs, total, nil
|
||||
}
|
||||
|
||||
// GetTotalOperations 获取总操作数
|
||||
func (r *GormAdminOperationLogRepository) GetTotalOperations(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.AdminOperationLog{}).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetOperationsByAdmin 获取指定管理员在指定天数内的操作数
|
||||
func (r *GormAdminOperationLogRepository) GetOperationsByAdmin(ctx context.Context, adminID string, days int) (int64, error) {
|
||||
var count int64
|
||||
startDate := time.Now().AddDate(0, 0, -days)
|
||||
err := r.db.WithContext(ctx).Model(&entities.AdminOperationLog{}).Where("admin_id = ? AND created_at >= ?", adminID, startDate).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// BatchCreate 批量创建操作日志
|
||||
func (r *GormAdminOperationLogRepository) BatchCreate(ctx context.Context, logs []entities.AdminOperationLog) error {
|
||||
r.logger.Info("批量创建管理员操作日志", zap.Int("count", len(logs)))
|
||||
return r.db.WithContext(ctx).Create(&logs).Error
|
||||
}
|
||||
@@ -1,222 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/admin/entities"
|
||||
"tyapi-server/internal/domains/admin/repositories"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// GormAdminPermissionRepository 管理员权限GORM仓储实现
|
||||
type GormAdminPermissionRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.AdminPermissionRepository = (*GormAdminPermissionRepository)(nil)
|
||||
|
||||
// NewGormAdminPermissionRepository 创建管理员权限GORM仓储
|
||||
func NewGormAdminPermissionRepository(db *gorm.DB, logger *zap.Logger) repositories.AdminPermissionRepository {
|
||||
return &GormAdminPermissionRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// ================ 基础CRUD操作 ================
|
||||
|
||||
// Create 创建权限
|
||||
func (r *GormAdminPermissionRepository) Create(ctx context.Context, permission entities.AdminPermission) (entities.AdminPermission, error) {
|
||||
r.logger.Info("创建管理员权限", zap.String("code", permission.Code))
|
||||
err := r.db.WithContext(ctx).Create(&permission).Error
|
||||
return permission, err
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取权限
|
||||
func (r *GormAdminPermissionRepository) GetByID(ctx context.Context, id string) (entities.AdminPermission, error) {
|
||||
var permission entities.AdminPermission
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&permission).Error
|
||||
return permission, err
|
||||
}
|
||||
|
||||
// Update 更新权限
|
||||
func (r *GormAdminPermissionRepository) Update(ctx context.Context, permission entities.AdminPermission) error {
|
||||
r.logger.Info("更新管理员权限", zap.String("id", permission.ID))
|
||||
return r.db.WithContext(ctx).Save(&permission).Error
|
||||
}
|
||||
|
||||
// Delete 删除权限
|
||||
func (r *GormAdminPermissionRepository) Delete(ctx context.Context, id string) error {
|
||||
r.logger.Info("删除管理员权限", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Delete(&entities.AdminPermission{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// SoftDelete 软删除权限
|
||||
func (r *GormAdminPermissionRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
r.logger.Info("软删除管理员权限", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Delete(&entities.AdminPermission{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// Restore 恢复权限
|
||||
func (r *GormAdminPermissionRepository) Restore(ctx context.Context, id string) error {
|
||||
r.logger.Info("恢复管理员权限", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Unscoped().Model(&entities.AdminPermission{}).Where("id = ?", id).Update("deleted_at", nil).Error
|
||||
}
|
||||
|
||||
// Count 统计权限数量
|
||||
func (r *GormAdminPermissionRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.AdminPermission{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("code LIKE ? OR name LIKE ? OR module LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
return count, query.Count(&count).Error
|
||||
}
|
||||
|
||||
// Exists 检查权限是否存在
|
||||
func (r *GormAdminPermissionRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.AdminPermission{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建权限
|
||||
func (r *GormAdminPermissionRepository) CreateBatch(ctx context.Context, permissions []entities.AdminPermission) error {
|
||||
r.logger.Info("批量创建管理员权限", zap.Int("count", len(permissions)))
|
||||
return r.db.WithContext(ctx).Create(&permissions).Error
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取权限
|
||||
func (r *GormAdminPermissionRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.AdminPermission, error) {
|
||||
var permissions []entities.AdminPermission
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&permissions).Error
|
||||
return permissions, err
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新权限
|
||||
func (r *GormAdminPermissionRepository) UpdateBatch(ctx context.Context, permissions []entities.AdminPermission) error {
|
||||
r.logger.Info("批量更新管理员权限", zap.Int("count", len(permissions)))
|
||||
return r.db.WithContext(ctx).Save(&permissions).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除权限
|
||||
func (r *GormAdminPermissionRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
r.logger.Info("批量删除管理员权限", zap.Strings("ids", ids))
|
||||
return r.db.WithContext(ctx).Delete(&entities.AdminPermission{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取权限列表
|
||||
func (r *GormAdminPermissionRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.AdminPermission, error) {
|
||||
var permissions []entities.AdminPermission
|
||||
query := r.db.WithContext(ctx).Model(&entities.AdminPermission{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("code LIKE ? OR name LIKE ? OR module LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%", "%"+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 permissions, query.Find(&permissions).Error
|
||||
}
|
||||
|
||||
// WithTx 使用事务
|
||||
func (r *GormAdminPermissionRepository) WithTx(tx interface{}) interfaces.Repository[entities.AdminPermission] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormAdminPermissionRepository{
|
||||
db: gormTx,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ================ 业务方法 ================
|
||||
|
||||
// FindByCode 根据权限代码查找权限
|
||||
func (r *GormAdminPermissionRepository) FindByCode(ctx context.Context, code string) (*entities.AdminPermission, error) {
|
||||
var permission entities.AdminPermission
|
||||
err := r.db.WithContext(ctx).Where("code = ?", code).First(&permission).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &permission, nil
|
||||
}
|
||||
|
||||
// FindByModule 根据模块查找权限
|
||||
func (r *GormAdminPermissionRepository) FindByModule(ctx context.Context, module string) ([]entities.AdminPermission, error) {
|
||||
var permissions []entities.AdminPermission
|
||||
err := r.db.WithContext(ctx).Where("module = ?", module).Find(&permissions).Error
|
||||
return permissions, err
|
||||
}
|
||||
|
||||
// ListActive 获取所有激活的权限
|
||||
func (r *GormAdminPermissionRepository) ListActive(ctx context.Context) ([]entities.AdminPermission, error) {
|
||||
var permissions []entities.AdminPermission
|
||||
err := r.db.WithContext(ctx).Where("is_active = ?", true).Find(&permissions).Error
|
||||
return permissions, err
|
||||
}
|
||||
|
||||
// GetPermissionsByRole 根据角色获取权限
|
||||
func (r *GormAdminPermissionRepository) GetPermissionsByRole(ctx context.Context, role entities.AdminRole) ([]entities.AdminPermission, error) {
|
||||
var permissions []entities.AdminPermission
|
||||
|
||||
query := r.db.WithContext(ctx).
|
||||
Joins("JOIN admin_role_permissions ON admin_permissions.id = admin_role_permissions.permission_id").
|
||||
Where("admin_role_permissions.role = ? AND admin_permissions.is_active = ?", role, true)
|
||||
|
||||
return permissions, query.Find(&permissions).Error
|
||||
}
|
||||
|
||||
// AssignPermissionsToRole 为角色分配权限
|
||||
func (r *GormAdminPermissionRepository) AssignPermissionsToRole(ctx context.Context, role entities.AdminRole, permissionIDs []string) error {
|
||||
// 先删除现有权限
|
||||
if err := r.db.WithContext(ctx).Where("role = ?", role).Delete(&entities.AdminRolePermission{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 批量插入新权限
|
||||
var rolePermissions []entities.AdminRolePermission
|
||||
for _, permissionID := range permissionIDs {
|
||||
rolePermissions = append(rolePermissions, entities.AdminRolePermission{
|
||||
Role: role,
|
||||
PermissionID: permissionID,
|
||||
})
|
||||
}
|
||||
|
||||
return r.db.WithContext(ctx).Create(&rolePermissions).Error
|
||||
}
|
||||
|
||||
// RemovePermissionsFromRole 从角色移除权限
|
||||
func (r *GormAdminPermissionRepository) RemovePermissionsFromRole(ctx context.Context, role entities.AdminRole, permissionIDs []string) error {
|
||||
return r.db.WithContext(ctx).Where("role = ? AND permission_id IN ?", role, permissionIDs).Delete(&entities.AdminRolePermission{}).Error
|
||||
}
|
||||
@@ -1,319 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/admin/entities"
|
||||
"tyapi-server/internal/domains/admin/repositories"
|
||||
"tyapi-server/internal/domains/admin/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// GormAdminRepository 管理员GORM仓储实现
|
||||
type GormAdminRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.AdminRepository = (*GormAdminRepository)(nil)
|
||||
|
||||
// NewGormAdminRepository 创建管理员GORM仓储
|
||||
func NewGormAdminRepository(db *gorm.DB, logger *zap.Logger) *GormAdminRepository {
|
||||
return &GormAdminRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建管理员
|
||||
func (r *GormAdminRepository) Create(ctx context.Context, admin entities.Admin) (entities.Admin, error) {
|
||||
r.logger.Info("创建管理员", zap.String("username", admin.Username))
|
||||
err := r.db.WithContext(ctx).Create(&admin).Error
|
||||
return admin, err
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取管理员
|
||||
func (r *GormAdminRepository) GetByID(ctx context.Context, id string) (entities.Admin, error) {
|
||||
var admin entities.Admin
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&admin).Error
|
||||
return admin, err
|
||||
}
|
||||
|
||||
// Update 更新管理员
|
||||
func (r *GormAdminRepository) Update(ctx context.Context, admin entities.Admin) error {
|
||||
r.logger.Info("更新管理员", zap.String("id", admin.ID))
|
||||
return r.db.WithContext(ctx).Save(&admin).Error
|
||||
}
|
||||
|
||||
// Delete 删除管理员
|
||||
func (r *GormAdminRepository) Delete(ctx context.Context, id string) error {
|
||||
r.logger.Info("删除管理员", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Delete(&entities.Admin{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// SoftDelete 软删除管理员
|
||||
func (r *GormAdminRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
r.logger.Info("软删除管理员", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Delete(&entities.Admin{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// Restore 恢复管理员
|
||||
func (r *GormAdminRepository) Restore(ctx context.Context, id string) error {
|
||||
r.logger.Info("恢复管理员", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Unscoped().Model(&entities.Admin{}).Where("id = ?", id).Update("deleted_at", nil).Error
|
||||
}
|
||||
|
||||
// Count 统计管理员数量
|
||||
func (r *GormAdminRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.Admin{})
|
||||
|
||||
// 应用过滤条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用搜索条件
|
||||
if options.Search != "" {
|
||||
query = query.Where("username LIKE ? OR email LIKE ? OR real_name LIKE ?",
|
||||
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
return count, query.Count(&count).Error
|
||||
}
|
||||
|
||||
// Exists 检查管理员是否存在
|
||||
func (r *GormAdminRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.Admin{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建管理员
|
||||
func (r *GormAdminRepository) CreateBatch(ctx context.Context, admins []entities.Admin) error {
|
||||
r.logger.Info("批量创建管理员", zap.Int("count", len(admins)))
|
||||
return r.db.WithContext(ctx).Create(&admins).Error
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取管理员
|
||||
func (r *GormAdminRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.Admin, error) {
|
||||
var admins []entities.Admin
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&admins).Error
|
||||
return admins, err
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新管理员
|
||||
func (r *GormAdminRepository) UpdateBatch(ctx context.Context, admins []entities.Admin) error {
|
||||
r.logger.Info("批量更新管理员", zap.Int("count", len(admins)))
|
||||
return r.db.WithContext(ctx).Save(&admins).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除管理员
|
||||
func (r *GormAdminRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
r.logger.Info("批量删除管理员", zap.Strings("ids", ids))
|
||||
return r.db.WithContext(ctx).Delete(&entities.Admin{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取管理员列表
|
||||
func (r *GormAdminRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.Admin, error) {
|
||||
var admins []entities.Admin
|
||||
query := r.db.WithContext(ctx).Model(&entities.Admin{})
|
||||
|
||||
// 应用过滤条件
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
// 应用搜索条件
|
||||
if options.Search != "" {
|
||||
query = query.Where("username LIKE ? OR email LIKE ? OR real_name LIKE ?",
|
||||
"%"+options.Search+"%", "%"+options.Search+"%", "%"+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 admins, query.Find(&admins).Error
|
||||
}
|
||||
|
||||
// WithTx 使用事务
|
||||
func (r *GormAdminRepository) WithTx(tx interface{}) interfaces.Repository[entities.Admin] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormAdminRepository{
|
||||
db: gormTx,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// FindByUsername 根据用户名查找管理员
|
||||
func (r *GormAdminRepository) FindByUsername(ctx context.Context, username string) (*entities.Admin, error) {
|
||||
var admin entities.Admin
|
||||
err := r.db.WithContext(ctx).Where("username = ?", username).First(&admin).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin, nil
|
||||
}
|
||||
|
||||
// FindByEmail 根据邮箱查找管理员
|
||||
func (r *GormAdminRepository) FindByEmail(ctx context.Context, email string) (*entities.Admin, error) {
|
||||
var admin entities.Admin
|
||||
err := r.db.WithContext(ctx).Where("email = ?", email).First(&admin).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin, nil
|
||||
}
|
||||
|
||||
// ListAdmins 获取管理员列表(带分页和筛选)
|
||||
func (r *GormAdminRepository) ListAdmins(ctx context.Context, query *queries.ListAdminsQuery) ([]*entities.Admin, int64, error) {
|
||||
var admins []entities.Admin
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Admin{})
|
||||
|
||||
// 应用筛选条件
|
||||
if query.Username != "" {
|
||||
dbQuery = dbQuery.Where("username LIKE ?", "%"+query.Username+"%")
|
||||
}
|
||||
if query.Email != "" {
|
||||
dbQuery = dbQuery.Where("email LIKE ?", "%"+query.Email+"%")
|
||||
}
|
||||
if query.Role != "" {
|
||||
dbQuery = dbQuery.Where("role = ?", query.Role)
|
||||
}
|
||||
if query.IsActive != nil {
|
||||
dbQuery = dbQuery.Where("is_active = ?", *query.IsActive)
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
if err := dbQuery.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
dbQuery = dbQuery.Offset(offset).Limit(query.PageSize)
|
||||
|
||||
// 默认排序
|
||||
dbQuery = dbQuery.Order("created_at DESC")
|
||||
|
||||
// 查询数据
|
||||
if err := dbQuery.Find(&admins).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
adminPtrs := make([]*entities.Admin, len(admins))
|
||||
for i := range admins {
|
||||
adminPtrs[i] = &admins[i]
|
||||
}
|
||||
|
||||
return adminPtrs, total, nil
|
||||
}
|
||||
|
||||
// GetStats 获取管理员统计信息
|
||||
func (r *GormAdminRepository) GetStats(ctx context.Context, query *queries.GetAdminInfoQuery) (*repositories.AdminStats, error) {
|
||||
var stats repositories.AdminStats
|
||||
|
||||
// 总管理员数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Admin{}).Count(&stats.TotalAdmins).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 激活管理员数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Admin{}).Where("is_active = ?", true).Count(&stats.ActiveAdmins).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 今日登录数
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
if err := r.db.WithContext(ctx).Model(&entities.AdminLoginLog{}).Where("created_at >= ?", today).Count(&stats.TodayLogins).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 总操作数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.AdminOperationLog{}).Count(&stats.TotalOperations).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// GetPermissionsByRole 根据角色获取权限
|
||||
func (r *GormAdminRepository) GetPermissionsByRole(ctx context.Context, role entities.AdminRole) ([]entities.AdminPermission, error) {
|
||||
var permissions []entities.AdminPermission
|
||||
|
||||
query := r.db.WithContext(ctx).
|
||||
Joins("JOIN admin_role_permissions ON admin_permissions.id = admin_role_permissions.permission_id").
|
||||
Where("admin_role_permissions.role = ? AND admin_permissions.is_active = ?", role, true)
|
||||
|
||||
return permissions, query.Find(&permissions).Error
|
||||
}
|
||||
|
||||
// UpdatePermissions 更新管理员权限
|
||||
func (r *GormAdminRepository) UpdatePermissions(ctx context.Context, adminID string, permissions []string) error {
|
||||
permissionsJSON, err := json.Marshal(permissions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化权限失败: %w", err)
|
||||
}
|
||||
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&entities.Admin{}).
|
||||
Where("id = ?", adminID).
|
||||
Update("permissions", string(permissionsJSON)).Error
|
||||
}
|
||||
|
||||
// UpdateLoginStats 更新登录统计
|
||||
func (r *GormAdminRepository) UpdateLoginStats(ctx context.Context, adminID string) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&entities.Admin{}).
|
||||
Where("id = ?", adminID).
|
||||
Updates(map[string]interface{}{
|
||||
"last_login_at": time.Now(),
|
||||
"login_count": gorm.Expr("login_count + 1"),
|
||||
}).Error
|
||||
}
|
||||
|
||||
// UpdateReviewStats 更新审核统计
|
||||
func (r *GormAdminRepository) UpdateReviewStats(ctx context.Context, adminID string, approved bool) error {
|
||||
updates := map[string]interface{}{
|
||||
"review_count": gorm.Expr("review_count + 1"),
|
||||
}
|
||||
|
||||
if approved {
|
||||
updates["approved_count"] = gorm.Expr("approved_count + 1")
|
||||
} else {
|
||||
updates["rejected_count"] = gorm.Expr("rejected_count + 1")
|
||||
}
|
||||
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&entities.Admin{}).
|
||||
Where("id = ?", adminID).
|
||||
Updates(updates).Error
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
@@ -43,6 +44,26 @@ func (r *GormCertificationRepository) Create(ctx context.Context, cert entities.
|
||||
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
|
||||
}
|
||||
|
||||
@@ -179,9 +200,6 @@ func (r *GormCertificationRepository) ListCertifications(ctx context.Context, qu
|
||||
if query.Status != "" {
|
||||
dbQuery = dbQuery.Where("status = ?", query.Status)
|
||||
}
|
||||
if query.AdminID != "" {
|
||||
dbQuery = dbQuery.Where("admin_id = ?", query.AdminID)
|
||||
}
|
||||
if query.StartDate != "" {
|
||||
dbQuery = dbQuery.Where("created_at >= ?", query.StartDate)
|
||||
}
|
||||
@@ -189,8 +207,8 @@ func (r *GormCertificationRepository) ListCertifications(ctx context.Context, qu
|
||||
dbQuery = dbQuery.Where("created_at <= ?", query.EndDate)
|
||||
}
|
||||
if query.EnterpriseName != "" {
|
||||
dbQuery = dbQuery.Joins("JOIN enterprises ON certifications.enterprise_id = enterprises.id").
|
||||
Where("enterprises.enterprise_name LIKE ?", "%"+query.EnterpriseName+"%")
|
||||
// 简化企业名称查询,暂时不关联企业表
|
||||
dbQuery = dbQuery.Where("user_id IN (SELECT user_id FROM enterprise_infos WHERE company_name LIKE ?)", "%"+query.EnterpriseName+"%")
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
@@ -199,24 +217,26 @@ func (r *GormCertificationRepository) ListCertifications(ctx context.Context, qu
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
dbQuery = dbQuery.Offset(offset).Limit(query.PageSize)
|
||||
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
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
certPtrs := make([]*entities.Certification, len(certs))
|
||||
var result []*entities.Certification
|
||||
for i := range certs {
|
||||
certPtrs[i] = &certs[i]
|
||||
result = append(result, &certs[i])
|
||||
}
|
||||
|
||||
return certPtrs, total, nil
|
||||
return result, total, nil
|
||||
}
|
||||
|
||||
// GetByUserID 根据用户ID获取认证申请
|
||||
@@ -224,6 +244,9 @@ func (r *GormCertificationRepository) GetByUserID(ctx context.Context, userID st
|
||||
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
|
||||
@@ -237,82 +260,73 @@ func (r *GormCertificationRepository) GetByStatus(ctx context.Context, status st
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certPtrs := make([]*entities.Certification, len(certs))
|
||||
var result []*entities.Certification
|
||||
for i := range certs {
|
||||
certPtrs[i] = &certs[i]
|
||||
result = append(result, &certs[i])
|
||||
}
|
||||
|
||||
return certPtrs, nil
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// UpdateStatus 更新认证状态
|
||||
func (r *GormCertificationRepository) UpdateStatus(ctx context.Context, certificationID string, status string, adminID *string, notes string) error {
|
||||
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,
|
||||
"status": status,
|
||||
"updated_at": time.Now(),
|
||||
}
|
||||
|
||||
if adminID != nil {
|
||||
updates["admin_id"] = *adminID
|
||||
}
|
||||
|
||||
if notes != "" {
|
||||
updates["approval_notes"] = notes
|
||||
}
|
||||
|
||||
// 根据状态设置相应的时间戳
|
||||
// 根据状态更新时间戳
|
||||
switch status {
|
||||
case "INFO_SUBMITTED":
|
||||
case "info_submitted":
|
||||
updates["info_submitted_at"] = time.Now()
|
||||
case "FACE_VERIFIED":
|
||||
updates["face_verified_at"] = time.Now()
|
||||
case "CONTRACT_APPLIED":
|
||||
case "enterprise_verified":
|
||||
updates["enterprise_verified_at"] = time.Now()
|
||||
case "contract_applied":
|
||||
updates["contract_applied_at"] = time.Now()
|
||||
case "CONTRACT_APPROVED":
|
||||
updates["contract_approved_at"] = time.Now()
|
||||
case "CONTRACT_SIGNED":
|
||||
case "contract_signed":
|
||||
updates["contract_signed_at"] = time.Now()
|
||||
case "COMPLETED":
|
||||
case "completed":
|
||||
updates["completed_at"] = time.Now()
|
||||
}
|
||||
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&entities.Certification{}).
|
||||
return r.db.WithContext(ctx).Model(&entities.Certification{}).
|
||||
Where("id = ?", certificationID).
|
||||
Updates(updates).Error
|
||||
}
|
||||
|
||||
// GetPendingCertifications 获取待审核的认证申请
|
||||
// GetPendingCertifications 获取待处理的认证申请
|
||||
func (r *GormCertificationRepository) GetPendingCertifications(ctx context.Context) ([]*entities.Certification, error) {
|
||||
return r.GetByStatus(ctx, "CONTRACT_PENDING")
|
||||
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 = ?", "CONTRACT_PENDING").Count(&stats.PendingCertifications).Error; err != nil {
|
||||
// 待处理认证数
|
||||
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
|
||||
}
|
||||
|
||||
// 被拒绝认证申请数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("status = ?", "REJECTED").Count(&stats.RejectedCertifications).Error; err != nil {
|
||||
// 已完成认证数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("status = ?", "completed").Count(&stats.CompletedCertifications).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 今日提交数
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("created_at >= ?", today).Count(&stats.TodaySubmissions).Error; err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -323,29 +337,32 @@ func (r *GormCertificationRepository) GetStats(ctx context.Context) (*repositori
|
||||
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 {
|
||||
// 总认证数
|
||||
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 ?", "CONTRACT_PENDING", startDate, endDate).Count(&stats.PendingCertifications).Error; err != nil {
|
||||
// 待处理认证数
|
||||
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
|
||||
}
|
||||
|
||||
// 被拒绝认证申请数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("status = ? AND created_at BETWEEN ? AND ?", "REJECTED", startDate, endDate).Count(&stats.RejectedCertifications).Error; err != nil {
|
||||
// 已完成认证数
|
||||
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().Truncate(24 * time.Hour)
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("created_at >= ?", today).Count(&stats.TodaySubmissions).Error; err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -1,422 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// GormContractRecordRepository GORM合同记录仓储实现
|
||||
type GormContractRecordRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.ContractRecordRepository = (*GormContractRecordRepository)(nil)
|
||||
|
||||
// NewGormContractRecordRepository 创建GORM合同记录仓储
|
||||
func NewGormContractRecordRepository(db *gorm.DB, logger *zap.Logger) repositories.ContractRecordRepository {
|
||||
return &GormContractRecordRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// ================ 基础CRUD操作 ================
|
||||
|
||||
// Create 创建合同记录
|
||||
func (r *GormContractRecordRepository) Create(ctx context.Context, record entities.ContractRecord) (entities.ContractRecord, error) {
|
||||
if err := r.db.WithContext(ctx).Create(&record).Error; err != nil {
|
||||
r.logger.Error("创建合同记录失败",
|
||||
zap.String("certification_id", record.CertificationID),
|
||||
zap.String("contract_type", record.ContractType),
|
||||
zap.Error(err),
|
||||
)
|
||||
return entities.ContractRecord{}, fmt.Errorf("创建合同记录失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("合同记录创建成功",
|
||||
zap.String("id", record.ID),
|
||||
zap.String("contract_type", record.ContractType),
|
||||
)
|
||||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取合同记录
|
||||
func (r *GormContractRecordRepository) GetByID(ctx context.Context, id string) (entities.ContractRecord, error) {
|
||||
var record entities.ContractRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).First(&record, "id = ?", id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return entities.ContractRecord{}, fmt.Errorf("合同记录不存在")
|
||||
}
|
||||
r.logger.Error("获取合同记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return entities.ContractRecord{}, fmt.Errorf("获取合同记录失败: %w", err)
|
||||
}
|
||||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// Update 更新合同记录
|
||||
func (r *GormContractRecordRepository) Update(ctx context.Context, record entities.ContractRecord) error {
|
||||
if err := r.db.WithContext(ctx).Save(&record).Error; err != nil {
|
||||
r.logger.Error("更新合同记录失败",
|
||||
zap.String("id", record.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("更新合同记录失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除合同记录
|
||||
func (r *GormContractRecordRepository) Delete(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Delete(&entities.ContractRecord{}, "id = ?", id).Error; err != nil {
|
||||
r.logger.Error("删除合同记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("删除合同记录失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SoftDelete 软删除合同记录
|
||||
func (r *GormContractRecordRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
return r.Delete(ctx, id)
|
||||
}
|
||||
|
||||
// Restore 恢复合同记录
|
||||
func (r *GormContractRecordRepository) Restore(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Unscoped().Model(&entities.ContractRecord{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
|
||||
r.logger.Error("恢复合同记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("恢复合同记录失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("合同记录恢复成功", zap.String("id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count 统计合同记录数量
|
||||
func (r *GormContractRecordRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.ContractRecord{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("contract_type LIKE ? OR contract_name LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
return count, query.Count(&count).Error
|
||||
}
|
||||
|
||||
// Exists 检查合同记录是否存在
|
||||
func (r *GormContractRecordRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.ContractRecord{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建合同记录
|
||||
func (r *GormContractRecordRepository) CreateBatch(ctx context.Context, records []entities.ContractRecord) error {
|
||||
r.logger.Info("批量创建合同记录", zap.Int("count", len(records)))
|
||||
return r.db.WithContext(ctx).Create(&records).Error
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取合同记录
|
||||
func (r *GormContractRecordRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.ContractRecord, error) {
|
||||
var records []entities.ContractRecord
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&records).Error
|
||||
return records, err
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新合同记录
|
||||
func (r *GormContractRecordRepository) UpdateBatch(ctx context.Context, records []entities.ContractRecord) error {
|
||||
r.logger.Info("批量更新合同记录", zap.Int("count", len(records)))
|
||||
return r.db.WithContext(ctx).Save(&records).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除合同记录
|
||||
func (r *GormContractRecordRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
r.logger.Info("批量删除合同记录", zap.Strings("ids", ids))
|
||||
return r.db.WithContext(ctx).Delete(&entities.ContractRecord{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取合同记录列表
|
||||
func (r *GormContractRecordRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.ContractRecord, error) {
|
||||
var records []entities.ContractRecord
|
||||
query := r.db.WithContext(ctx).Model(&entities.ContractRecord{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("contract_type LIKE ? OR contract_name LIKE ?", "%"+options.Search+"%", "%"+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 *GormContractRecordRepository) WithTx(tx interface{}) interfaces.Repository[entities.ContractRecord] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormContractRecordRepository{
|
||||
db: gormTx,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ================ 业务方法 ================
|
||||
|
||||
// GetByCertificationID 根据认证申请ID获取合同记录列表
|
||||
func (r *GormContractRecordRepository) GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.ContractRecord, error) {
|
||||
var records []entities.ContractRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).Where("certification_id = ?", certificationID).Order("created_at DESC").Find(&records).Error; err != nil {
|
||||
r.logger.Error("根据认证申请ID获取合同记录失败",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取合同记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.ContractRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, nil
|
||||
}
|
||||
|
||||
// GetLatestByCertificationID 根据认证申请ID获取最新的合同记录
|
||||
func (r *GormContractRecordRepository) GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.ContractRecord, error) {
|
||||
var record entities.ContractRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).Where("certification_id = ?", certificationID).Order("created_at DESC").First(&record).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("合同记录不存在")
|
||||
}
|
||||
r.logger.Error("根据认证申请ID获取最新合同记录失败",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取合同记录失败: %w", err)
|
||||
}
|
||||
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// ListRecords 获取合同记录列表(带分页和筛选)
|
||||
func (r *GormContractRecordRepository) ListRecords(ctx context.Context, query *queries.ListContractRecordsQuery) ([]*entities.ContractRecord, int64, error) {
|
||||
var records []entities.ContractRecord
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.ContractRecord{})
|
||||
|
||||
// 应用筛选条件
|
||||
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.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
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
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
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.ContractRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, total, nil
|
||||
}
|
||||
|
||||
// UpdateContractStatus 更新合同状态
|
||||
func (r *GormContractRecordRepository) UpdateContractStatus(ctx context.Context, recordID string, status string, adminID *string, notes string) error {
|
||||
updates := map[string]interface{}{
|
||||
"status": status,
|
||||
}
|
||||
|
||||
if adminID != nil {
|
||||
updates["admin_id"] = *adminID
|
||||
}
|
||||
|
||||
if notes != "" {
|
||||
updates["admin_notes"] = notes
|
||||
}
|
||||
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.ContractRecord{}).
|
||||
Where("id = ?", recordID).
|
||||
Updates(updates).Error; err != nil {
|
||||
r.logger.Error("更新合同状态失败",
|
||||
zap.String("record_id", recordID),
|
||||
zap.String("status", status),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("更新合同状态失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("合同状态更新成功",
|
||||
zap.String("record_id", recordID),
|
||||
zap.String("status", status),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByUserID 根据用户ID获取合同记录列表
|
||||
func (r *GormContractRecordRepository) GetByUserID(ctx context.Context, userID string, page, pageSize int) ([]*entities.ContractRecord, int, error) {
|
||||
var records []entities.ContractRecord
|
||||
var total int64
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&entities.ContractRecord{}).Where("user_id = ?", userID)
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
r.logger.Error("获取用户合同记录总数失败", zap.Error(err))
|
||||
return nil, 0, fmt.Errorf("获取合同记录总数失败: %w", err)
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&records).Error; err != nil {
|
||||
r.logger.Error("获取用户合同记录列表失败", zap.Error(err))
|
||||
return nil, 0, fmt.Errorf("获取合同记录列表失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.ContractRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, int(total), nil
|
||||
}
|
||||
|
||||
// GetByStatus 根据状态获取合同记录列表
|
||||
func (r *GormContractRecordRepository) GetByStatus(ctx context.Context, status string, page, pageSize int) ([]*entities.ContractRecord, int, error) {
|
||||
var records []entities.ContractRecord
|
||||
var total int64
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&entities.ContractRecord{}).Where("status = ?", status)
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
r.logger.Error("根据状态获取合同记录总数失败", zap.Error(err))
|
||||
return nil, 0, fmt.Errorf("获取合同记录总数失败: %w", err)
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&records).Error; err != nil {
|
||||
r.logger.Error("根据状态获取合同记录列表失败", zap.Error(err))
|
||||
return nil, 0, fmt.Errorf("获取合同记录列表失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.ContractRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, int(total), nil
|
||||
}
|
||||
|
||||
// GetPendingContracts 获取待审核的合同记录
|
||||
func (r *GormContractRecordRepository) GetPendingContracts(ctx context.Context, page, pageSize int) ([]*entities.ContractRecord, int, error) {
|
||||
return r.GetByStatus(ctx, "PENDING", page, pageSize)
|
||||
}
|
||||
|
||||
// GetExpiredSigningContracts 获取签署链接已过期的合同记录
|
||||
func (r *GormContractRecordRepository) GetExpiredSigningContracts(ctx context.Context, limit int) ([]*entities.ContractRecord, error) {
|
||||
var records []entities.ContractRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("expires_at < NOW() AND status = ?", "APPROVED").
|
||||
Limit(limit).
|
||||
Order("expires_at ASC").
|
||||
Find(&records).Error; err != nil {
|
||||
r.logger.Error("获取过期签署合同记录失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取过期签署合同记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.ContractRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, nil
|
||||
}
|
||||
|
||||
// GetExpiredContracts 获取已过期的合同记录(通用方法)
|
||||
func (r *GormContractRecordRepository) GetExpiredContracts(ctx context.Context, limit int) ([]*entities.ContractRecord, error) {
|
||||
return r.GetExpiredSigningContracts(ctx, limit)
|
||||
}
|
||||
@@ -0,0 +1,596 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,315 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,356 @@
|
||||
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
|
||||
}
|
||||
@@ -1,394 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// GormFaceVerifyRecordRepository GORM人脸识别记录仓储实现
|
||||
type GormFaceVerifyRecordRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.FaceVerifyRecordRepository = (*GormFaceVerifyRecordRepository)(nil)
|
||||
|
||||
// NewGormFaceVerifyRecordRepository 创建GORM人脸识别记录仓储
|
||||
func NewGormFaceVerifyRecordRepository(db *gorm.DB, logger *zap.Logger) repositories.FaceVerifyRecordRepository {
|
||||
return &GormFaceVerifyRecordRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// ================ 基础CRUD操作 ================
|
||||
|
||||
// Create 创建人脸识别记录
|
||||
func (r *GormFaceVerifyRecordRepository) Create(ctx context.Context, record entities.FaceVerifyRecord) (entities.FaceVerifyRecord, error) {
|
||||
if err := r.db.WithContext(ctx).Create(&record).Error; err != nil {
|
||||
r.logger.Error("创建人脸识别记录失败",
|
||||
zap.String("certification_id", record.CertificationID),
|
||||
zap.String("certify_id", record.CertifyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return entities.FaceVerifyRecord{}, fmt.Errorf("创建人脸识别记录失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("人脸识别记录创建成功",
|
||||
zap.String("id", record.ID),
|
||||
zap.String("certify_id", record.CertifyID),
|
||||
)
|
||||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取人脸识别记录
|
||||
func (r *GormFaceVerifyRecordRepository) GetByID(ctx context.Context, id string) (entities.FaceVerifyRecord, error) {
|
||||
var record entities.FaceVerifyRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).First(&record, "id = ?", id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return entities.FaceVerifyRecord{}, fmt.Errorf("人脸识别记录不存在")
|
||||
}
|
||||
r.logger.Error("获取人脸识别记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return entities.FaceVerifyRecord{}, fmt.Errorf("获取人脸识别记录失败: %w", err)
|
||||
}
|
||||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// Update 更新人脸识别记录
|
||||
func (r *GormFaceVerifyRecordRepository) Update(ctx context.Context, record entities.FaceVerifyRecord) error {
|
||||
if err := r.db.WithContext(ctx).Save(&record).Error; err != nil {
|
||||
r.logger.Error("更新人脸识别记录失败",
|
||||
zap.String("id", record.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("更新人脸识别记录失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除人脸识别记录
|
||||
func (r *GormFaceVerifyRecordRepository) Delete(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Delete(&entities.FaceVerifyRecord{}, "id = ?", id).Error; err != nil {
|
||||
r.logger.Error("删除人脸识别记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("删除人脸识别记录失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SoftDelete 软删除人脸识别记录
|
||||
func (r *GormFaceVerifyRecordRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
return r.Delete(ctx, id)
|
||||
}
|
||||
|
||||
// Restore 恢复人脸识别记录
|
||||
func (r *GormFaceVerifyRecordRepository) Restore(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Unscoped().Model(&entities.FaceVerifyRecord{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
|
||||
r.logger.Error("恢复人脸识别记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("恢复人脸识别记录失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("人脸识别记录恢复成功", zap.String("id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count 统计人脸识别记录数量
|
||||
func (r *GormFaceVerifyRecordRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.FaceVerifyRecord{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("certify_id LIKE ? OR user_id LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
return count, query.Count(&count).Error
|
||||
}
|
||||
|
||||
// Exists 检查人脸识别记录是否存在
|
||||
func (r *GormFaceVerifyRecordRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.FaceVerifyRecord{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建人脸识别记录
|
||||
func (r *GormFaceVerifyRecordRepository) CreateBatch(ctx context.Context, records []entities.FaceVerifyRecord) error {
|
||||
r.logger.Info("批量创建人脸识别记录", zap.Int("count", len(records)))
|
||||
return r.db.WithContext(ctx).Create(&records).Error
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取人脸识别记录
|
||||
func (r *GormFaceVerifyRecordRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.FaceVerifyRecord, error) {
|
||||
var records []entities.FaceVerifyRecord
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&records).Error
|
||||
return records, err
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新人脸识别记录
|
||||
func (r *GormFaceVerifyRecordRepository) UpdateBatch(ctx context.Context, records []entities.FaceVerifyRecord) error {
|
||||
r.logger.Info("批量更新人脸识别记录", zap.Int("count", len(records)))
|
||||
return r.db.WithContext(ctx).Save(&records).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除人脸识别记录
|
||||
func (r *GormFaceVerifyRecordRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
r.logger.Info("批量删除人脸识别记录", zap.Strings("ids", ids))
|
||||
return r.db.WithContext(ctx).Delete(&entities.FaceVerifyRecord{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取人脸识别记录列表
|
||||
func (r *GormFaceVerifyRecordRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.FaceVerifyRecord, error) {
|
||||
var records []entities.FaceVerifyRecord
|
||||
query := r.db.WithContext(ctx).Model(&entities.FaceVerifyRecord{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("certify_id LIKE ? OR user_id LIKE ?", "%"+options.Search+"%", "%"+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 *GormFaceVerifyRecordRepository) WithTx(tx interface{}) interfaces.Repository[entities.FaceVerifyRecord] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormFaceVerifyRecordRepository{
|
||||
db: gormTx,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ================ 业务方法 ================
|
||||
|
||||
// GetByCertificationID 根据认证申请ID获取人脸识别记录列表
|
||||
func (r *GormFaceVerifyRecordRepository) GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.FaceVerifyRecord, error) {
|
||||
var records []entities.FaceVerifyRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).Where("certification_id = ?", certificationID).Order("created_at DESC").Find(&records).Error; err != nil {
|
||||
r.logger.Error("根据认证申请ID获取人脸识别记录失败",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取人脸识别记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.FaceVerifyRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, nil
|
||||
}
|
||||
|
||||
// GetLatestByCertificationID 根据认证申请ID获取最新的人脸识别记录
|
||||
func (r *GormFaceVerifyRecordRepository) GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.FaceVerifyRecord, error) {
|
||||
var record entities.FaceVerifyRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).Where("certification_id = ?", certificationID).Order("created_at DESC").First(&record).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("人脸识别记录不存在")
|
||||
}
|
||||
r.logger.Error("根据认证申请ID获取最新人脸识别记录失败",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取人脸识别记录失败: %w", err)
|
||||
}
|
||||
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// ListRecords 获取人脸识别记录列表(带分页和筛选)
|
||||
func (r *GormFaceVerifyRecordRepository) ListRecords(ctx context.Context, query *queries.ListFaceVerifyRecordsQuery) ([]*entities.FaceVerifyRecord, int64, error) {
|
||||
var records []entities.FaceVerifyRecord
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.FaceVerifyRecord{})
|
||||
|
||||
// 应用筛选条件
|
||||
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.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
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
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
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.FaceVerifyRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, total, nil
|
||||
}
|
||||
|
||||
// GetSuccessRate 获取成功率
|
||||
func (r *GormFaceVerifyRecordRepository) GetSuccessRate(ctx context.Context, days int) (float64, error) {
|
||||
var totalCount int64
|
||||
var successCount int64
|
||||
|
||||
// 计算指定天数前的日期
|
||||
startDate := fmt.Sprintf("DATE_SUB(NOW(), INTERVAL %d DAY)", days)
|
||||
|
||||
// 获取总数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.FaceVerifyRecord{}).
|
||||
Where("created_at >= " + startDate).Count(&totalCount).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 获取成功数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.FaceVerifyRecord{}).
|
||||
Where("created_at >= "+startDate+" AND status = ?", "SUCCESS").Count(&successCount).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if totalCount == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return float64(successCount) / float64(totalCount) * 100, nil
|
||||
}
|
||||
|
||||
// GetByCertifyID 根据认证ID获取人脸识别记录
|
||||
func (r *GormFaceVerifyRecordRepository) GetByCertifyID(ctx context.Context, certifyID string) (*entities.FaceVerifyRecord, error) {
|
||||
var record entities.FaceVerifyRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).First(&record, "certify_id = ?", certifyID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("人脸识别记录不存在")
|
||||
}
|
||||
r.logger.Error("根据认证ID获取人脸识别记录失败",
|
||||
zap.String("certify_id", certifyID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取人脸识别记录失败: %w", err)
|
||||
}
|
||||
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// GetByUserID 根据用户ID获取人脸识别记录列表
|
||||
func (r *GormFaceVerifyRecordRepository) GetByUserID(ctx context.Context, userID string, page, pageSize int) ([]*entities.FaceVerifyRecord, int, error) {
|
||||
var records []entities.FaceVerifyRecord
|
||||
var total int64
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&entities.FaceVerifyRecord{}).Where("user_id = ?", userID)
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
r.logger.Error("获取用户人脸识别记录总数失败", zap.Error(err))
|
||||
return nil, 0, fmt.Errorf("获取人脸识别记录总数失败: %w", err)
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&records).Error; err != nil {
|
||||
r.logger.Error("获取用户人脸识别记录列表失败", zap.Error(err))
|
||||
return nil, 0, fmt.Errorf("获取人脸识别记录列表失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.FaceVerifyRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, int(total), nil
|
||||
}
|
||||
|
||||
// GetExpiredRecords 获取已过期的人脸识别记录
|
||||
func (r *GormFaceVerifyRecordRepository) GetExpiredRecords(ctx context.Context, limit int) ([]*entities.FaceVerifyRecord, error) {
|
||||
var records []entities.FaceVerifyRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("expires_at < NOW() AND status = ?", "PROCESSING").
|
||||
Limit(limit).
|
||||
Order("expires_at ASC").
|
||||
Find(&records).Error; err != nil {
|
||||
r.logger.Error("获取过期人脸识别记录失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取过期人脸识别记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.FaceVerifyRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, nil
|
||||
}
|
||||
@@ -1,374 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// GormLicenseUploadRecordRepository GORM营业执照上传记录仓储实现
|
||||
type GormLicenseUploadRecordRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.LicenseUploadRecordRepository = (*GormLicenseUploadRecordRepository)(nil)
|
||||
|
||||
// NewGormLicenseUploadRecordRepository 创建GORM营业执照上传记录仓储
|
||||
func NewGormLicenseUploadRecordRepository(db *gorm.DB, logger *zap.Logger) repositories.LicenseUploadRecordRepository {
|
||||
return &GormLicenseUploadRecordRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// ================ 基础CRUD操作 ================
|
||||
|
||||
// Create 创建上传记录
|
||||
func (r *GormLicenseUploadRecordRepository) Create(ctx context.Context, record entities.LicenseUploadRecord) (entities.LicenseUploadRecord, error) {
|
||||
if err := r.db.WithContext(ctx).Create(&record).Error; err != nil {
|
||||
r.logger.Error("创建上传记录失败",
|
||||
zap.String("user_id", record.UserID),
|
||||
zap.String("file_name", record.OriginalFileName),
|
||||
zap.Error(err),
|
||||
)
|
||||
return entities.LicenseUploadRecord{}, fmt.Errorf("创建上传记录失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("上传记录创建成功",
|
||||
zap.String("id", record.ID),
|
||||
zap.String("file_name", record.OriginalFileName),
|
||||
)
|
||||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取上传记录
|
||||
func (r *GormLicenseUploadRecordRepository) GetByID(ctx context.Context, id string) (entities.LicenseUploadRecord, error) {
|
||||
var record entities.LicenseUploadRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).First(&record, "id = ?", id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return entities.LicenseUploadRecord{}, fmt.Errorf("上传记录不存在")
|
||||
}
|
||||
r.logger.Error("获取上传记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return entities.LicenseUploadRecord{}, fmt.Errorf("获取上传记录失败: %w", err)
|
||||
}
|
||||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// Update 更新上传记录
|
||||
func (r *GormLicenseUploadRecordRepository) Update(ctx context.Context, record entities.LicenseUploadRecord) error {
|
||||
if err := r.db.WithContext(ctx).Save(&record).Error; err != nil {
|
||||
r.logger.Error("更新上传记录失败",
|
||||
zap.String("id", record.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("更新上传记录失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除上传记录
|
||||
func (r *GormLicenseUploadRecordRepository) Delete(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Delete(&entities.LicenseUploadRecord{}, "id = ?", id).Error; err != nil {
|
||||
r.logger.Error("删除上传记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("删除上传记录失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SoftDelete 软删除上传记录
|
||||
func (r *GormLicenseUploadRecordRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
return r.Delete(ctx, id)
|
||||
}
|
||||
|
||||
// Restore 恢复上传记录
|
||||
func (r *GormLicenseUploadRecordRepository) Restore(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Unscoped().Model(&entities.LicenseUploadRecord{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
|
||||
r.logger.Error("恢复上传记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("恢复上传记录失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("上传记录恢复成功", zap.String("id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count 统计上传记录数量
|
||||
func (r *GormLicenseUploadRecordRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.LicenseUploadRecord{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("original_file_name LIKE ? OR user_id LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
return count, query.Count(&count).Error
|
||||
}
|
||||
|
||||
// Exists 检查上传记录是否存在
|
||||
func (r *GormLicenseUploadRecordRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.LicenseUploadRecord{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建上传记录
|
||||
func (r *GormLicenseUploadRecordRepository) CreateBatch(ctx context.Context, records []entities.LicenseUploadRecord) error {
|
||||
r.logger.Info("批量创建上传记录", zap.Int("count", len(records)))
|
||||
return r.db.WithContext(ctx).Create(&records).Error
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取上传记录
|
||||
func (r *GormLicenseUploadRecordRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.LicenseUploadRecord, error) {
|
||||
var records []entities.LicenseUploadRecord
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&records).Error
|
||||
return records, err
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新上传记录
|
||||
func (r *GormLicenseUploadRecordRepository) UpdateBatch(ctx context.Context, records []entities.LicenseUploadRecord) error {
|
||||
r.logger.Info("批量更新上传记录", zap.Int("count", len(records)))
|
||||
return r.db.WithContext(ctx).Save(&records).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除上传记录
|
||||
func (r *GormLicenseUploadRecordRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
r.logger.Info("批量删除上传记录", zap.Strings("ids", ids))
|
||||
return r.db.WithContext(ctx).Delete(&entities.LicenseUploadRecord{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取上传记录列表
|
||||
func (r *GormLicenseUploadRecordRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.LicenseUploadRecord, error) {
|
||||
var records []entities.LicenseUploadRecord
|
||||
query := r.db.WithContext(ctx).Model(&entities.LicenseUploadRecord{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("original_file_name LIKE ? OR user_id LIKE ?", "%"+options.Search+"%", "%"+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 *GormLicenseUploadRecordRepository) WithTx(tx interface{}) interfaces.Repository[entities.LicenseUploadRecord] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormLicenseUploadRecordRepository{
|
||||
db: gormTx,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ================ 业务方法 ================
|
||||
|
||||
// GetByCertificationID 根据认证ID获取上传记录
|
||||
func (r *GormLicenseUploadRecordRepository) GetByCertificationID(ctx context.Context, certificationID string) (*entities.LicenseUploadRecord, error) {
|
||||
var record entities.LicenseUploadRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).First(&record, "certification_id = ?", certificationID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("上传记录不存在")
|
||||
}
|
||||
r.logger.Error("根据认证ID获取上传记录失败",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取上传记录失败: %w", err)
|
||||
}
|
||||
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// ListRecords 获取上传记录列表(带分页和筛选)
|
||||
func (r *GormLicenseUploadRecordRepository) ListRecords(ctx context.Context, query *queries.ListLicenseUploadRecordsQuery) ([]*entities.LicenseUploadRecord, int64, error) {
|
||||
var records []entities.LicenseUploadRecord
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.LicenseUploadRecord{})
|
||||
|
||||
// 应用筛选条件
|
||||
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.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
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
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
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.LicenseUploadRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, total, nil
|
||||
}
|
||||
|
||||
// UpdateOCRResult 更新OCR结果
|
||||
func (r *GormLicenseUploadRecordRepository) UpdateOCRResult(ctx context.Context, recordID string, ocrResult string, confidence float64) error {
|
||||
updates := map[string]interface{}{
|
||||
"ocr_result": ocrResult,
|
||||
"ocr_confidence": confidence,
|
||||
"ocr_processed": true,
|
||||
"ocr_success": true,
|
||||
}
|
||||
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.LicenseUploadRecord{}).
|
||||
Where("id = ?", recordID).
|
||||
Updates(updates).Error; err != nil {
|
||||
r.logger.Error("更新OCR结果失败",
|
||||
zap.String("record_id", recordID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("更新OCR结果失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("OCR结果更新成功",
|
||||
zap.String("record_id", recordID),
|
||||
zap.Float64("confidence", confidence),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByUserID 根据用户ID获取上传记录列表
|
||||
func (r *GormLicenseUploadRecordRepository) GetByUserID(ctx context.Context, userID string, page, pageSize int) ([]*entities.LicenseUploadRecord, int, error) {
|
||||
var records []entities.LicenseUploadRecord
|
||||
var total int64
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&entities.LicenseUploadRecord{}).Where("user_id = ?", userID)
|
||||
|
||||
// 获取总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
r.logger.Error("获取用户上传记录总数失败", zap.Error(err))
|
||||
return nil, 0, fmt.Errorf("获取上传记录总数失败: %w", err)
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
offset := (page - 1) * pageSize
|
||||
if err := query.Offset(offset).Limit(pageSize).Order("created_at DESC").Find(&records).Error; err != nil {
|
||||
r.logger.Error("获取用户上传记录列表失败", zap.Error(err))
|
||||
return nil, 0, fmt.Errorf("获取上传记录列表失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.LicenseUploadRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, int(total), nil
|
||||
}
|
||||
|
||||
// GetByQiNiuKey 根据七牛云Key获取上传记录
|
||||
func (r *GormLicenseUploadRecordRepository) GetByQiNiuKey(ctx context.Context, key string) (*entities.LicenseUploadRecord, error) {
|
||||
var record entities.LicenseUploadRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).First(&record, "qiniu_key = ?", key).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("上传记录不存在")
|
||||
}
|
||||
r.logger.Error("根据七牛云Key获取上传记录失败",
|
||||
zap.String("qiniu_key", key),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取上传记录失败: %w", err)
|
||||
}
|
||||
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// GetPendingOCR 获取待OCR处理的上传记录
|
||||
func (r *GormLicenseUploadRecordRepository) GetPendingOCR(ctx context.Context, limit int) ([]*entities.LicenseUploadRecord, error) {
|
||||
var records []entities.LicenseUploadRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("ocr_processed = ? OR (ocr_processed = ? AND ocr_success = ?)", false, true, false).
|
||||
Limit(limit).
|
||||
Order("created_at ASC").
|
||||
Find(&records).Error; err != nil {
|
||||
r.logger.Error("获取待OCR处理记录失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取待OCR处理记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.LicenseUploadRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, nil
|
||||
}
|
||||
@@ -1,344 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// GormNotificationRecordRepository GORM通知记录仓储实现
|
||||
type GormNotificationRecordRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.NotificationRecordRepository = (*GormNotificationRecordRepository)(nil)
|
||||
|
||||
// NewGormNotificationRecordRepository 创建GORM通知记录仓储
|
||||
func NewGormNotificationRecordRepository(db *gorm.DB, logger *zap.Logger) repositories.NotificationRecordRepository {
|
||||
return &GormNotificationRecordRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// ================ 基础CRUD操作 ================
|
||||
|
||||
// Create 创建通知记录
|
||||
func (r *GormNotificationRecordRepository) Create(ctx context.Context, record entities.NotificationRecord) (entities.NotificationRecord, error) {
|
||||
if err := r.db.WithContext(ctx).Create(&record).Error; err != nil {
|
||||
r.logger.Error("创建通知记录失败",
|
||||
zap.String("user_id", *record.UserID),
|
||||
zap.String("type", record.NotificationType),
|
||||
zap.Error(err),
|
||||
)
|
||||
return entities.NotificationRecord{}, fmt.Errorf("创建通知记录失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("通知记录创建成功",
|
||||
zap.String("id", record.ID),
|
||||
zap.String("type", record.NotificationType),
|
||||
)
|
||||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取通知记录
|
||||
func (r *GormNotificationRecordRepository) GetByID(ctx context.Context, id string) (entities.NotificationRecord, error) {
|
||||
var record entities.NotificationRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).First(&record, "id = ?", id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return entities.NotificationRecord{}, fmt.Errorf("通知记录不存在")
|
||||
}
|
||||
r.logger.Error("获取通知记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return entities.NotificationRecord{}, fmt.Errorf("获取通知记录失败: %w", err)
|
||||
}
|
||||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
// Update 更新通知记录
|
||||
func (r *GormNotificationRecordRepository) Update(ctx context.Context, record entities.NotificationRecord) error {
|
||||
if err := r.db.WithContext(ctx).Save(&record).Error; err != nil {
|
||||
r.logger.Error("更新通知记录失败",
|
||||
zap.String("id", record.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("更新通知记录失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除通知记录
|
||||
func (r *GormNotificationRecordRepository) Delete(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Delete(&entities.NotificationRecord{}, "id = ?", id).Error; err != nil {
|
||||
r.logger.Error("删除通知记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("删除通知记录失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SoftDelete 软删除通知记录
|
||||
func (r *GormNotificationRecordRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
return r.Delete(ctx, id)
|
||||
}
|
||||
|
||||
// Restore 恢复通知记录
|
||||
func (r *GormNotificationRecordRepository) Restore(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Unscoped().Model(&entities.NotificationRecord{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
|
||||
r.logger.Error("恢复通知记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("恢复通知记录失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("通知记录恢复成功", zap.String("id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count 统计通知记录数量
|
||||
func (r *GormNotificationRecordRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.NotificationRecord{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("title LIKE ? OR content LIKE ? OR user_id LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
return count, query.Count(&count).Error
|
||||
}
|
||||
|
||||
// Exists 检查通知记录是否存在
|
||||
func (r *GormNotificationRecordRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.NotificationRecord{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建通知记录
|
||||
func (r *GormNotificationRecordRepository) CreateBatch(ctx context.Context, records []entities.NotificationRecord) error {
|
||||
r.logger.Info("批量创建通知记录", zap.Int("count", len(records)))
|
||||
return r.db.WithContext(ctx).Create(&records).Error
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取通知记录
|
||||
func (r *GormNotificationRecordRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.NotificationRecord, error) {
|
||||
var records []entities.NotificationRecord
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&records).Error
|
||||
return records, err
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新通知记录
|
||||
func (r *GormNotificationRecordRepository) UpdateBatch(ctx context.Context, records []entities.NotificationRecord) error {
|
||||
r.logger.Info("批量更新通知记录", zap.Int("count", len(records)))
|
||||
return r.db.WithContext(ctx).Save(&records).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除通知记录
|
||||
func (r *GormNotificationRecordRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
r.logger.Info("批量删除通知记录", zap.Strings("ids", ids))
|
||||
return r.db.WithContext(ctx).Delete(&entities.NotificationRecord{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取通知记录列表
|
||||
func (r *GormNotificationRecordRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.NotificationRecord, error) {
|
||||
var records []entities.NotificationRecord
|
||||
query := r.db.WithContext(ctx).Model(&entities.NotificationRecord{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("title LIKE ? OR content LIKE ? OR user_id LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%", "%"+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 *GormNotificationRecordRepository) WithTx(tx interface{}) interfaces.Repository[entities.NotificationRecord] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormNotificationRecordRepository{
|
||||
db: gormTx,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ================ 业务方法 ================
|
||||
|
||||
// GetByCertificationID 根据认证申请ID获取通知记录列表
|
||||
func (r *GormNotificationRecordRepository) GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.NotificationRecord, error) {
|
||||
var records []entities.NotificationRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).Where("certification_id = ?", certificationID).Order("created_at DESC").Find(&records).Error; err != nil {
|
||||
r.logger.Error("根据认证申请ID获取通知记录失败",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取通知记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.NotificationRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, nil
|
||||
}
|
||||
|
||||
// GetUnreadByUserID 根据用户ID获取未读通知记录列表
|
||||
func (r *GormNotificationRecordRepository) GetUnreadByUserID(ctx context.Context, userID string) ([]*entities.NotificationRecord, error) {
|
||||
var records []entities.NotificationRecord
|
||||
|
||||
if err := r.db.WithContext(ctx).Where("user_id = ? AND is_read = ?", userID, false).Order("created_at DESC").Find(&records).Error; err != nil {
|
||||
r.logger.Error("根据用户ID获取未读通知记录失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取未读通知记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.NotificationRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, nil
|
||||
}
|
||||
|
||||
// ListRecords 获取通知记录列表(带分页和筛选)
|
||||
func (r *GormNotificationRecordRepository) ListRecords(ctx context.Context, query *queries.ListNotificationRecordsQuery) ([]*entities.NotificationRecord, int64, error) {
|
||||
var records []entities.NotificationRecord
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.NotificationRecord{})
|
||||
|
||||
// 应用筛选条件
|
||||
if query.CertificationID != "" {
|
||||
dbQuery = dbQuery.Where("certification_id = ?", query.CertificationID)
|
||||
}
|
||||
if query.UserID != "" {
|
||||
dbQuery = dbQuery.Where("user_id = ?", query.UserID)
|
||||
}
|
||||
if query.Type != "" {
|
||||
dbQuery = dbQuery.Where("type = ?", query.Type)
|
||||
}
|
||||
if query.IsRead != nil {
|
||||
dbQuery = dbQuery.Where("is_read = ?", *query.IsRead)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
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
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
recordPtrs := make([]*entities.NotificationRecord, len(records))
|
||||
for i := range records {
|
||||
recordPtrs[i] = &records[i]
|
||||
}
|
||||
|
||||
return recordPtrs, total, nil
|
||||
}
|
||||
|
||||
// BatchCreate 批量创建通知记录
|
||||
func (r *GormNotificationRecordRepository) BatchCreate(ctx context.Context, records []entities.NotificationRecord) error {
|
||||
r.logger.Info("批量创建通知记录", zap.Int("count", len(records)))
|
||||
return r.db.WithContext(ctx).Create(&records).Error
|
||||
}
|
||||
|
||||
// MarkAsRead 标记通知记录为已读
|
||||
func (r *GormNotificationRecordRepository) MarkAsRead(ctx context.Context, recordIDs []string) error {
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.NotificationRecord{}).
|
||||
Where("id IN ?", recordIDs).
|
||||
Update("is_read", true).Error; err != nil {
|
||||
r.logger.Error("标记通知记录为已读失败",
|
||||
zap.Strings("record_ids", recordIDs),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("标记通知记录为已读失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("通知记录标记为已读成功", zap.Strings("record_ids", recordIDs))
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkAllAsReadByUser 标记用户所有通知记录为已读
|
||||
func (r *GormNotificationRecordRepository) MarkAllAsReadByUser(ctx context.Context, userID string) error {
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.NotificationRecord{}).
|
||||
Where("user_id = ? AND is_read = ?", userID, false).
|
||||
Update("is_read", true).Error; err != nil {
|
||||
r.logger.Error("标记用户所有通知记录为已读失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("标记用户所有通知记录为已读失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("用户所有通知记录标记为已读成功", zap.String("user_id", userID))
|
||||
return nil
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
@@ -42,6 +43,12 @@ func (r *GormWalletRepository) Create(ctx context.Context, wallet entities.Walle
|
||||
func (r *GormWalletRepository) GetByID(ctx context.Context, id string) (entities.Wallet, error) {
|
||||
var wallet entities.Wallet
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&wallet).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return entities.Wallet{}, gorm.ErrRecordNotFound
|
||||
}
|
||||
return entities.Wallet{}, err
|
||||
}
|
||||
return wallet, err
|
||||
}
|
||||
|
||||
@@ -199,6 +206,9 @@ func (r *GormWalletRepository) GetByUserID(ctx context.Context, userID string) (
|
||||
var wallet entities.Wallet
|
||||
err := r.db.WithContext(ctx).Where("user_id = ?", userID).First(&wallet).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &wallet, nil
|
||||
@@ -209,6 +219,9 @@ func (r *GormWalletRepository) GetByWalletAddress(ctx context.Context, walletAdd
|
||||
var wallet entities.Wallet
|
||||
err := r.db.WithContext(ctx).Where("wallet_address = ?", walletAddress).First(&wallet).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &wallet, nil
|
||||
@@ -219,6 +232,9 @@ func (r *GormWalletRepository) GetByWalletType(ctx context.Context, userID strin
|
||||
var wallet entities.Wallet
|
||||
err := r.db.WithContext(ctx).Where("user_id = ? AND wallet_type = ?", userID, walletType).First(&wallet).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &wallet, nil
|
||||
@@ -384,6 +400,12 @@ func (r *GormUserSecretsRepository) Create(ctx context.Context, secrets entities
|
||||
func (r *GormUserSecretsRepository) GetByID(ctx context.Context, id string) (entities.UserSecrets, error) {
|
||||
var secrets entities.UserSecrets
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&secrets).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return entities.UserSecrets{}, gorm.ErrRecordNotFound
|
||||
}
|
||||
return entities.UserSecrets{}, err
|
||||
}
|
||||
return secrets, err
|
||||
}
|
||||
|
||||
@@ -503,21 +525,27 @@ func (r *GormUserSecretsRepository) WithTx(tx interface{}) interfaces.Repository
|
||||
return r
|
||||
}
|
||||
|
||||
// FindByUserID 根据用户ID查找密钥
|
||||
// FindByUserID 根据用户ID查找用户密钥
|
||||
func (r *GormUserSecretsRepository) FindByUserID(ctx context.Context, userID string) (*entities.UserSecrets, error) {
|
||||
var secrets entities.UserSecrets
|
||||
err := r.db.WithContext(ctx).Where("user_id = ?", userID).First(&secrets).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &secrets, nil
|
||||
}
|
||||
|
||||
// FindByAccessID 根据访问ID查找密钥
|
||||
// FindByAccessID 根据访问ID查找用户密钥
|
||||
func (r *GormUserSecretsRepository) FindByAccessID(ctx context.Context, accessID string) (*entities.UserSecrets, error) {
|
||||
var secrets entities.UserSecrets
|
||||
err := r.db.WithContext(ctx).Where("access_id = ?", accessID).First(&secrets).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &secrets, nil
|
||||
@@ -575,16 +603,22 @@ func (r *GormUserSecretsRepository) GetByUserID(ctx context.Context, userID stri
|
||||
var secrets entities.UserSecrets
|
||||
err := r.db.WithContext(ctx).Where("user_id = ?", userID).First(&secrets).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &secrets, nil
|
||||
}
|
||||
|
||||
// GetBySecretType 根据密钥类型获取用户密钥
|
||||
// GetBySecretType 根据用户ID和密钥类型获取用户密钥
|
||||
func (r *GormUserSecretsRepository) GetBySecretType(ctx context.Context, userID string, secretType string) (*entities.UserSecrets, error) {
|
||||
var secrets entities.UserSecrets
|
||||
err := r.db.WithContext(ctx).Where("user_id = ? AND secret_type = ?", userID, secretType).First(&secrets).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &secrets, nil
|
||||
|
||||
@@ -2,13 +2,15 @@ package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/product/entities"
|
||||
"tyapi-server/internal/domains/product/repositories"
|
||||
"tyapi-server/internal/domains/product/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// GormProductCategoryRepository GORM产品分类仓储实现
|
||||
@@ -39,7 +41,13 @@ func (r *GormProductCategoryRepository) Create(ctx context.Context, entity entit
|
||||
func (r *GormProductCategoryRepository) GetByID(ctx context.Context, id string) (entities.ProductCategory, error) {
|
||||
var entity entities.ProductCategory
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&entity).Error
|
||||
return entity, err
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return entities.ProductCategory{}, gorm.ErrRecordNotFound
|
||||
}
|
||||
return entities.ProductCategory{}, err
|
||||
}
|
||||
return entity, nil
|
||||
}
|
||||
|
||||
// Update 更新产品分类
|
||||
@@ -59,40 +67,14 @@ func (r *GormProductCategoryRepository) FindByCode(ctx context.Context, code str
|
||||
var entity entities.ProductCategory
|
||||
err := r.db.WithContext(ctx).Where("code = ?", code).First(&entity).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return &entity, nil
|
||||
}
|
||||
|
||||
// FindByParentID 根据父级ID查找产品分类
|
||||
func (r *GormProductCategoryRepository) FindByParentID(ctx context.Context, parentID *string) ([]*entities.ProductCategory, error) {
|
||||
var categories []entities.ProductCategory
|
||||
query := r.db.WithContext(ctx)
|
||||
|
||||
if parentID == nil {
|
||||
query = query.Where("parent_id IS NULL")
|
||||
} else {
|
||||
query = query.Where("parent_id = ?", *parentID)
|
||||
}
|
||||
|
||||
err := query.Find(&categories).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.ProductCategory, len(categories))
|
||||
for i := range categories {
|
||||
result[i] = &categories[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindRootCategories 查找根分类
|
||||
func (r *GormProductCategoryRepository) FindRootCategories(ctx context.Context) ([]*entities.ProductCategory, error) {
|
||||
return r.FindByParentID(ctx, nil)
|
||||
}
|
||||
|
||||
// FindVisible 查找可见分类
|
||||
func (r *GormProductCategoryRepository) FindVisible(ctx context.Context) ([]*entities.ProductCategory, error) {
|
||||
var categories []entities.ProductCategory
|
||||
@@ -133,9 +115,6 @@ func (r *GormProductCategoryRepository) ListCategories(ctx context.Context, quer
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.ProductCategory{})
|
||||
|
||||
// 应用筛选条件
|
||||
if query.ParentID != nil {
|
||||
dbQuery = dbQuery.Where("parent_id = ?", *query.ParentID)
|
||||
}
|
||||
if query.IsEnabled != nil {
|
||||
dbQuery = dbQuery.Where("is_enabled = ?", *query.IsEnabled)
|
||||
}
|
||||
@@ -158,7 +137,8 @@ func (r *GormProductCategoryRepository) ListCategories(ctx context.Context, quer
|
||||
}
|
||||
dbQuery = dbQuery.Order(order)
|
||||
} else {
|
||||
dbQuery = dbQuery.Order("sort_order ASC, created_at DESC")
|
||||
// 默认按排序字段和创建时间排序
|
||||
dbQuery = dbQuery.Order("sort ASC, created_at ASC")
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
@@ -181,66 +161,6 @@ func (r *GormProductCategoryRepository) ListCategories(ctx context.Context, quer
|
||||
return result, total, nil
|
||||
}
|
||||
|
||||
// GetCategoryTree 获取分类树
|
||||
func (r *GormProductCategoryRepository) GetCategoryTree(ctx context.Context) ([]*entities.ProductCategory, error) {
|
||||
var categories []entities.ProductCategory
|
||||
err := r.db.WithContext(ctx).Where("is_enabled = ?", true).Order("sort_order ASC, created_at ASC").Find(&categories).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.ProductCategory, len(categories))
|
||||
for i := range categories {
|
||||
result[i] = &categories[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindCategoriesByLevel 根据层级查找分类
|
||||
func (r *GormProductCategoryRepository) FindCategoriesByLevel(ctx context.Context, level int) ([]*entities.ProductCategory, error) {
|
||||
var categories []entities.ProductCategory
|
||||
err := r.db.WithContext(ctx).Where("level = ? AND is_enabled = ?", level, true).Find(&categories).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.ProductCategory, len(categories))
|
||||
for i := range categories {
|
||||
result[i] = &categories[i]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// FindCategoryPath 查找分类路径
|
||||
func (r *GormProductCategoryRepository) FindCategoryPath(ctx context.Context, categoryID string) ([]*entities.ProductCategory, error) {
|
||||
// 这里需要递归查找父级分类,简化实现
|
||||
var entity entities.ProductCategory
|
||||
err := r.db.WithContext(ctx).Where("id = ?", categoryID).First(&entity).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := []*entities.ProductCategory{&entity}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CountByParent 统计父级下的分类数量
|
||||
func (r *GormProductCategoryRepository) CountByParent(ctx context.Context, parentID *string) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.ProductCategory{})
|
||||
|
||||
if parentID == nil {
|
||||
query = query.Where("parent_id IS NULL")
|
||||
} else {
|
||||
query = query.Where("parent_id = ?", *parentID)
|
||||
}
|
||||
|
||||
err := query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// CountEnabled 统计启用分类数量
|
||||
func (r *GormProductCategoryRepository) CountEnabled(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user