基础架构
This commit is contained in:
@@ -21,6 +21,7 @@ import (
|
||||
|
||||
// 管理员域实体
|
||||
adminEntities "tyapi-server/internal/domains/admin/entities"
|
||||
"tyapi-server/internal/infrastructure/database"
|
||||
)
|
||||
|
||||
// Application 应用程序结构
|
||||
@@ -165,7 +166,23 @@ func (a *Application) waitForShutdown() error {
|
||||
|
||||
// createDatabaseConnection 创建数据库连接
|
||||
func (a *Application) createDatabaseConnection() (*gorm.DB, error) {
|
||||
return container.NewDatabase(a.config, a.logger)
|
||||
dbCfg := database.Config{
|
||||
Host: a.config.Database.Host,
|
||||
Port: a.config.Database.Port,
|
||||
User: a.config.Database.User,
|
||||
Password: a.config.Database.Password,
|
||||
Name: a.config.Database.Name,
|
||||
SSLMode: a.config.Database.SSLMode,
|
||||
Timezone: a.config.Database.Timezone,
|
||||
MaxOpenConns: a.config.Database.MaxOpenConns,
|
||||
MaxIdleConns: a.config.Database.MaxIdleConns,
|
||||
ConnMaxLifetime: a.config.Database.ConnMaxLifetime,
|
||||
}
|
||||
db, err := database.NewConnection(dbCfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db.DB, nil
|
||||
}
|
||||
|
||||
// autoMigrate 自动迁移
|
||||
@@ -190,12 +207,14 @@ func (a *Application) autoMigrate(db *gorm.DB) error {
|
||||
|
||||
// 认证域
|
||||
&certEntities.Certification{},
|
||||
&certEntities.Enterprise{},
|
||||
&certEntities.LicenseUploadRecord{},
|
||||
&certEntities.FaceVerifyRecord{},
|
||||
&certEntities.ContractRecord{},
|
||||
&certEntities.NotificationRecord{},
|
||||
|
||||
// 用户域 - 企业信息
|
||||
&entities.EnterpriseInfo{},
|
||||
|
||||
// 财务域
|
||||
&financeEntities.Wallet{},
|
||||
&financeEntities.UserSecrets{},
|
||||
|
||||
21
internal/application/admin/admin_application_service.go
Normal file
21
internal/application/admin/admin_application_service.go
Normal file
@@ -0,0 +1,21 @@
|
||||
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)
|
||||
}
|
||||
164
internal/application/admin/admin_application_service_impl.go
Normal file
164
internal/application/admin/admin_application_service_impl.go
Normal file
@@ -0,0 +1,164 @@
|
||||
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
|
||||
}
|
||||
44
internal/application/admin/dto/commands/admin_commands.go
Normal file
44
internal/application/admin/dto/commands/admin_commands.go
Normal file
@@ -0,0 +1,44 @@
|
||||
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:"-"`
|
||||
}
|
||||
16
internal/application/admin/dto/queries/admin_queries.go
Normal file
16
internal/application/admin/dto/queries/admin_queries.go
Normal file
@@ -0,0 +1,16 @@
|
||||
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"`
|
||||
}
|
||||
45
internal/application/admin/dto/responses/admin_responses.go
Normal file
45
internal/application/admin/dto/responses/admin_responses.go
Normal file
@@ -0,0 +1,45 @@
|
||||
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"`
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package certification
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tyapi-server/internal/application/certification/dto/commands"
|
||||
"tyapi-server/internal/application/certification/dto/queries"
|
||||
"tyapi-server/internal/application/certification/dto/responses"
|
||||
)
|
||||
|
||||
// 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)
|
||||
|
||||
// 营业执照上传
|
||||
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)
|
||||
|
||||
// 合同管理
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,608 @@
|
||||
package certification
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"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/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"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
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, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("检查企业信息失败: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("统一社会信用代码已存在")
|
||||
}
|
||||
|
||||
// 创建企业信息
|
||||
enterpriseInfo := &user_entities.EnterpriseInfo{
|
||||
UserID: cmd.UserID,
|
||||
CompanyName: cmd.CompanyName,
|
||||
UnifiedSocialCode: cmd.UnifiedSocialCode,
|
||||
LegalPersonName: cmd.LegalPersonName,
|
||||
LegalPersonID: cmd.LegalPersonID,
|
||||
}
|
||||
|
||||
createdEnterpriseInfo, err := s.enterpriseInfoRepo.Create(ctx, *enterpriseInfo)
|
||||
if err != nil {
|
||||
s.logger.Error("创建企业信息失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建企业信息失败: %w", err)
|
||||
}
|
||||
s.logger.Info("企业信息创建成功",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("enterprise_id", enterpriseInfo.ID),
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户尚未创建认证申请: %w", err)
|
||||
}
|
||||
|
||||
// 设置认证ID
|
||||
cmd.CertificationID = certification.ID
|
||||
|
||||
// 调用领域服务提交企业信息
|
||||
if err := s.certService.SubmitEnterpriseInfo(ctx, certification.ID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建企业信息
|
||||
enterpriseInfo := &user_entities.EnterpriseInfo{
|
||||
UserID: cmd.UserID,
|
||||
CompanyName: cmd.CompanyName,
|
||||
UnifiedSocialCode: cmd.UnifiedSocialCode,
|
||||
LegalPersonName: cmd.LegalPersonName,
|
||||
LegalPersonID: cmd.LegalPersonID,
|
||||
}
|
||||
|
||||
*enterpriseInfo, err = s.enterpriseInfoRepo.Create(ctx, *enterpriseInfo)
|
||||
if err != nil {
|
||||
s.logger.Error("创建企业信息失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("企业信息提交成功",
|
||||
zap.String("user_id", cmd.UserID),
|
||||
zap.String("certification_id", certification.ID),
|
||||
zap.String("enterprise_id", enterpriseInfo.ID),
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
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("用户尚未创建认证申请")
|
||||
}
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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 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
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
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"`
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package queries
|
||||
|
||||
// GetCertificationStatusQuery 获取认证状态查询
|
||||
// 用于查询用户当前认证申请的进度状态
|
||||
type GetCertificationStatusQuery struct {
|
||||
UserID string `json:"user_id" binding:"required" comment:"用户唯一标识,用于查询该用户的认证申请状态"`
|
||||
}
|
||||
|
||||
// GetCertificationDetailsQuery 获取认证详情查询
|
||||
// 用于查询用户认证申请的详细信息,包括所有相关记录
|
||||
type GetCertificationDetailsQuery struct {
|
||||
UserID string `json:"user_id" binding:"required" comment:"用户唯一标识,用于查询该用户的认证申请详细信息"`
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/certification/enums"
|
||||
)
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package responses
|
||||
|
||||
import "time"
|
||||
|
||||
// BusinessLicenseResult 营业执照识别结果
|
||||
type BusinessLicenseResult struct {
|
||||
CompanyName string `json:"company_name"` // 企业名称
|
||||
UnifiedSocialCode string `json:"unified_social_code"` // 统一社会信用代码
|
||||
LegalPersonName string `json:"legal_person_name"` // 法定代表人姓名
|
||||
LegalPersonID string `json:"legal_person_id"` // 法定代表人身份证号
|
||||
RegisteredCapital string `json:"registered_capital"` // 注册资本
|
||||
BusinessScope string `json:"business_scope"` // 经营范围
|
||||
Address string `json:"address"` // 企业地址
|
||||
IssueDate string `json:"issue_date"` // 发证日期
|
||||
ValidPeriod string `json:"valid_period"` // 有效期
|
||||
Confidence float64 `json:"confidence"` // 识别置信度
|
||||
ProcessedAt time.Time `json:"processed_at"` // 处理时间
|
||||
}
|
||||
|
||||
// IDCardResult 身份证识别结果
|
||||
type IDCardResult struct {
|
||||
Name string `json:"name"` // 姓名
|
||||
IDCardNumber string `json:"id_card_number"` // 身份证号
|
||||
Gender string `json:"gender"` // 性别
|
||||
Nation string `json:"nation"` // 民族
|
||||
Birthday string `json:"birthday"` // 出生日期
|
||||
Address string `json:"address"` // 住址
|
||||
IssuingAgency string `json:"issuing_agency"` // 签发机关
|
||||
ValidPeriod string `json:"valid_period"` // 有效期限
|
||||
Side string `json:"side"` // 身份证面(front/back)
|
||||
Confidence float64 `json:"confidence"` // 识别置信度
|
||||
ProcessedAt time.Time `json:"processed_at"` // 处理时间
|
||||
}
|
||||
|
||||
// GeneralTextResult 通用文字识别结果
|
||||
type GeneralTextResult struct {
|
||||
Words []TextLine `json:"words"` // 识别的文字行
|
||||
Confidence float64 `json:"confidence"` // 整体置信度
|
||||
ProcessedAt time.Time `json:"processed_at"` // 处理时间
|
||||
}
|
||||
|
||||
// TextLine 文字行
|
||||
type TextLine struct {
|
||||
Text string `json:"text"` // 文字内容
|
||||
Confidence float64 `json:"confidence"` // 置信度
|
||||
Position Position `json:"position"` // 位置信息
|
||||
}
|
||||
|
||||
// Position 位置信息
|
||||
type Position struct {
|
||||
X int `json:"x"` // X坐标
|
||||
Y int `json:"y"` // Y坐标
|
||||
Width int `json:"width"` // 宽度
|
||||
Height int `json:"height"` // 高度
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// CreateWalletCommand 创建钱包命令
|
||||
type CreateWalletCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
}
|
||||
|
||||
// UpdateWalletCommand 更新钱包命令
|
||||
type UpdateWalletCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
Balance decimal.Decimal `json:"balance"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
}
|
||||
|
||||
// RechargeWalletCommand 充值钱包命令
|
||||
type RechargeWalletCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required"`
|
||||
}
|
||||
|
||||
// RechargeCommand 充值命令
|
||||
type RechargeCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required"`
|
||||
}
|
||||
|
||||
// WithdrawWalletCommand 提现钱包命令
|
||||
type WithdrawWalletCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required"`
|
||||
}
|
||||
|
||||
// WithdrawCommand 提现命令
|
||||
type WithdrawCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
Amount decimal.Decimal `json:"amount" binding:"required"`
|
||||
}
|
||||
|
||||
// CreateUserSecretsCommand 创建用户密钥命令
|
||||
type CreateUserSecretsCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
// RegenerateAccessKeyCommand 重新生成访问密钥命令
|
||||
type RegenerateAccessKeyCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
ExpiresAt *time.Time `json:"expires_at"`
|
||||
}
|
||||
|
||||
// DeactivateUserSecretsCommand 停用用户密钥命令
|
||||
type DeactivateUserSecretsCommand struct {
|
||||
UserID string `json:"user_id" binding:"required"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
21
internal/application/finance/dto/queries/finance_queries.go
Normal file
21
internal/application/finance/dto/queries/finance_queries.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package queries
|
||||
|
||||
// GetWalletInfoQuery 获取钱包信息查询
|
||||
type GetWalletInfoQuery struct {
|
||||
UserID string `form:"user_id" binding:"required"`
|
||||
}
|
||||
|
||||
// GetWalletQuery 获取钱包查询
|
||||
type GetWalletQuery struct {
|
||||
UserID string `form:"user_id" binding:"required"`
|
||||
}
|
||||
|
||||
// GetWalletStatsQuery 获取钱包统计查询
|
||||
type GetWalletStatsQuery struct {
|
||||
UserID string `form:"user_id" binding:"required"`
|
||||
}
|
||||
|
||||
// GetUserSecretsQuery 获取用户密钥查询
|
||||
type GetUserSecretsQuery struct {
|
||||
UserID string `form:"user_id" binding:"required"`
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// WalletResponse 钱包响应
|
||||
type WalletResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
IsActive bool `json:"is_active"`
|
||||
Balance decimal.Decimal `json:"balance"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// TransactionResponse 交易响应
|
||||
type TransactionResponse struct {
|
||||
TransactionID string `json:"transaction_id"`
|
||||
FromUserID string `json:"from_user_id"`
|
||||
ToUserID string `json:"to_user_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"`
|
||||
}
|
||||
|
||||
// UserSecretsResponse 用户密钥响应
|
||||
type UserSecretsResponse struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
AccessID string `json:"access_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"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
24
internal/application/finance/finance_application_service.go
Normal file
24
internal/application/finance/finance_application_service.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package finance
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tyapi-server/internal/application/finance/dto/commands"
|
||||
"tyapi-server/internal/application/finance/dto/queries"
|
||||
"tyapi-server/internal/application/finance/dto/responses"
|
||||
)
|
||||
|
||||
// FinanceApplicationService 财务应用服务接口
|
||||
type FinanceApplicationService interface {
|
||||
CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error)
|
||||
GetWallet(ctx context.Context, query *queries.GetWalletInfoQuery) (*responses.WalletResponse, error)
|
||||
UpdateWallet(ctx context.Context, cmd *commands.UpdateWalletCommand) error
|
||||
Recharge(ctx context.Context, cmd *commands.RechargeWalletCommand) (*responses.TransactionResponse, error)
|
||||
Withdraw(ctx context.Context, cmd *commands.WithdrawWalletCommand) (*responses.TransactionResponse, error)
|
||||
CreateUserSecrets(ctx context.Context, cmd *commands.CreateUserSecretsCommand) (*responses.UserSecretsResponse, error)
|
||||
GetUserSecrets(ctx context.Context, query *queries.GetUserSecretsQuery) (*responses.UserSecretsResponse, error)
|
||||
RegenerateAccessKey(ctx context.Context, cmd *commands.RegenerateAccessKeyCommand) (*responses.UserSecretsResponse, error)
|
||||
DeactivateUserSecrets(ctx context.Context, cmd *commands.DeactivateUserSecretsCommand) error
|
||||
WalletTransaction(ctx context.Context, cmd *commands.WalletTransactionCommand) (*responses.TransactionResponse, error)
|
||||
GetWalletStats(ctx context.Context) (*responses.WalletStatsResponse, error)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package finance
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/application/finance/dto/commands"
|
||||
"tyapi-server/internal/application/finance/dto/queries"
|
||||
"tyapi-server/internal/application/finance/dto/responses"
|
||||
"tyapi-server/internal/domains/finance/repositories"
|
||||
)
|
||||
|
||||
// FinanceApplicationServiceImpl 财务应用服务实现
|
||||
type FinanceApplicationServiceImpl struct {
|
||||
walletRepo repositories.WalletRepository
|
||||
userSecretsRepo repositories.UserSecretsRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewFinanceApplicationService 创建财务应用服务
|
||||
func NewFinanceApplicationService(
|
||||
walletRepo repositories.WalletRepository,
|
||||
userSecretsRepo repositories.UserSecretsRepository,
|
||||
logger *zap.Logger,
|
||||
) FinanceApplicationService {
|
||||
return &FinanceApplicationServiceImpl{
|
||||
walletRepo: walletRepo,
|
||||
userSecretsRepo: userSecretsRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) CreateWallet(ctx context.Context, cmd *commands.CreateWalletCommand) (*responses.WalletResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) GetWallet(ctx context.Context, query *queries.GetWalletInfoQuery) (*responses.WalletResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) UpdateWallet(ctx context.Context, cmd *commands.UpdateWalletCommand) error {
|
||||
// ... implementation from old service
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) Recharge(ctx context.Context, cmd *commands.RechargeWalletCommand) (*responses.TransactionResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) Withdraw(ctx context.Context, cmd *commands.WithdrawWalletCommand) (*responses.TransactionResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) CreateUserSecrets(ctx context.Context, cmd *commands.CreateUserSecretsCommand) (*responses.UserSecretsResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) GetUserSecrets(ctx context.Context, query *queries.GetUserSecretsQuery) (*responses.UserSecretsResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) RegenerateAccessKey(ctx context.Context, cmd *commands.RegenerateAccessKeyCommand) (*responses.UserSecretsResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) DeactivateUserSecrets(ctx context.Context, cmd *commands.DeactivateUserSecretsCommand) error {
|
||||
// ... implementation from old service
|
||||
return fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) WalletTransaction(ctx context.Context, cmd *commands.WalletTransactionCommand) (*responses.TransactionResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func (s *FinanceApplicationServiceImpl) GetWalletStats(ctx context.Context) (*responses.WalletStatsResponse, error) {
|
||||
// ... implementation from old service
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
57
internal/application/user/dto/commands/user_commands.go
Normal file
57
internal/application/user/dto/commands/user_commands.go
Normal file
@@ -0,0 +1,57 @@
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// LoginWithSMSCommand 短信验证码登录命令
|
||||
// @Description 使用短信验证码进行用户登录请求参数
|
||||
type LoginWithSMSCommand struct {
|
||||
Phone string `json:"phone" binding:"required,len=11" example:"13800138000"`
|
||||
Code string `json:"code" binding:"required,len=6" example:"123456"`
|
||||
}
|
||||
|
||||
// ChangePasswordCommand 修改密码命令
|
||||
// @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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// UpdateProfileCommand 更新用户信息命令
|
||||
// @Description 更新用户基本信息请求参数
|
||||
type UpdateProfileCommand struct {
|
||||
UserID string `json:"-"`
|
||||
Phone string `json:"phone" binding:"omitempty,len=11" example:"13800138000"`
|
||||
// 可以在这里添加更多用户信息字段,如昵称、头像等
|
||||
}
|
||||
|
||||
// VerifyCodeCommand 验证验证码命令
|
||||
// @Description 验证短信验证码请求参数
|
||||
type VerifyCodeCommand struct {
|
||||
Phone string `json:"phone" binding:"required,len=11" 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"`
|
||||
}
|
||||
6
internal/application/user/dto/queries/get_user_query.go
Normal file
6
internal/application/user/dto/queries/get_user_query.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package queries
|
||||
|
||||
// GetUserQuery 获取用户信息查询
|
||||
type GetUserQuery struct {
|
||||
UserID string `json:"user_id"`
|
||||
}
|
||||
63
internal/application/user/dto/responses/user_responses.go
Normal file
63
internal/application/user/dto/responses/user_responses.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package responses
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// RegisterUserResponse 用户注册响应
|
||||
// @Description 用户注册成功响应
|
||||
type RegisterUserResponse struct {
|
||||
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
|
||||
Phone string `json:"phone" example:"13800138000"`
|
||||
}
|
||||
|
||||
// EnterpriseInfoResponse 企业信息响应
|
||||
// @Description 企业信息响应
|
||||
type EnterpriseInfoResponse struct {
|
||||
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
|
||||
CompanyName string `json:"company_name" example:"示例企业有限公司"`
|
||||
UnifiedSocialCode string `json:"unified_social_code" example:"91110000123456789X"`
|
||||
LegalPersonName string `json:"legal_person_name" example:"张三"`
|
||||
LegalPersonID string `json:"legal_person_id" example:"110101199001011234"`
|
||||
IsOCRVerified bool `json:"is_ocr_verified" example:"false"`
|
||||
IsFaceVerified bool `json:"is_face_verified" example:"false"`
|
||||
IsCertified bool `json:"is_certified" example:"false"`
|
||||
CertifiedAt *time.Time `json:"certified_at,omitempty" example:"2024-01-01T00:00:00Z"`
|
||||
CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"`
|
||||
UpdatedAt time.Time `json:"updated_at" example:"2024-01-01T00:00:00Z"`
|
||||
}
|
||||
|
||||
// LoginUserResponse 用户登录响应
|
||||
// @Description 用户登录成功响应
|
||||
type LoginUserResponse struct {
|
||||
User *UserProfileResponse `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"`
|
||||
}
|
||||
|
||||
// UserProfileResponse 用户信息响应
|
||||
// @Description 用户基本信息
|
||||
type UserProfileResponse struct {
|
||||
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
|
||||
Phone string `json:"phone" example:"13800138000"`
|
||||
EnterpriseInfo *EnterpriseInfoResponse `json:"enterprise_info,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at" example:"2024-01-01T00:00:00Z"`
|
||||
UpdatedAt time.Time `json:"updated_at" example:"2024-01-01T00:00:00Z"`
|
||||
}
|
||||
|
||||
// SendCodeResponse 发送验证码响应
|
||||
// @Description 发送短信验证码成功响应
|
||||
type SendCodeResponse struct {
|
||||
Message string `json:"message" example:"验证码发送成功"`
|
||||
ExpiresAt time.Time `json:"expires_at" example:"2024-01-01T00:05:00Z"`
|
||||
}
|
||||
|
||||
// UpdateProfileResponse 更新用户信息响应
|
||||
// @Description 更新用户信息成功响应
|
||||
type UpdateProfileResponse struct {
|
||||
ID string `json:"id" example:"123e4567-e89b-12d3-a456-426614174000"`
|
||||
Phone string `json:"phone" example:"13800138000"`
|
||||
UpdatedAt time.Time `json:"updated_at" example:"2024-01-01T00:00:00Z"`
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tyapi-server/internal/application/user/dto/commands"
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
)
|
||||
|
||||
func (s *UserApplicationServiceImpl) SendCode(ctx context.Context, cmd *commands.SendCodeCommand, clientIP, userAgent string) error {
|
||||
// 1. 检查频率限制
|
||||
if err := s.smsCodeService.CheckRateLimit(ctx, cmd.Phone, entities.SMSScene(cmd.Scene)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err := s.smsCodeService.SendCode(ctx, cmd.Phone, entities.SMSScene(cmd.Scene), clientIP, userAgent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
18
internal/application/user/user_application_service.go
Normal file
18
internal/application/user/user_application_service.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tyapi-server/internal/application/user/dto/commands"
|
||||
"tyapi-server/internal/application/user/dto/responses"
|
||||
)
|
||||
|
||||
// UserApplicationService 用户应用服务接口
|
||||
type UserApplicationService interface {
|
||||
Register(ctx context.Context, cmd *commands.RegisterUserCommand) (*responses.RegisterUserResponse, error)
|
||||
LoginWithPassword(ctx context.Context, cmd *commands.LoginWithPasswordCommand) (*responses.LoginUserResponse, error)
|
||||
LoginWithSMS(ctx context.Context, cmd *commands.LoginWithSMSCommand) (*responses.LoginUserResponse, error)
|
||||
ChangePassword(ctx context.Context, cmd *commands.ChangePasswordCommand) error
|
||||
GetUserProfile(ctx context.Context, userID string) (*responses.UserProfileResponse, error)
|
||||
SendCode(ctx context.Context, cmd *commands.SendCodeCommand, clientIP, userAgent string) error
|
||||
}
|
||||
225
internal/application/user/user_application_service_impl.go
Normal file
225
internal/application/user/user_application_service_impl.go
Normal file
@@ -0,0 +1,225 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/application/user/dto/commands"
|
||||
"tyapi-server/internal/application/user/dto/queries"
|
||||
"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
|
||||
}
|
||||
|
||||
// NewUserApplicationService 创建用户应用服务
|
||||
func NewUserApplicationService(
|
||||
userRepo repositories.UserRepository,
|
||||
enterpriseInfoRepo repositories.EnterpriseInfoRepository,
|
||||
smsCodeService *user_service.SMSCodeService,
|
||||
eventBus interfaces.EventBus,
|
||||
jwtAuth *middleware.JWTAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
) UserApplicationService {
|
||||
return &UserApplicationServiceImpl{
|
||||
userRepo: userRepo,
|
||||
enterpriseInfoRepo: enterpriseInfoRepo,
|
||||
smsCodeService: smsCodeService,
|
||||
eventBus: eventBus,
|
||||
jwtAuth: jwtAuth,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
func (s *UserApplicationServiceImpl) Register(ctx context.Context, cmd *commands.RegisterUserCommand) (*responses.RegisterUserResponse, error) {
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
event := events.NewUserRegisteredEvent(user, "")
|
||||
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 &responses.RegisterUserResponse{
|
||||
ID: createdUser.ID,
|
||||
Phone: user.Phone,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LoginWithPassword 密码登录
|
||||
func (s *UserApplicationServiceImpl) LoginWithPassword(ctx context.Context, cmd *commands.LoginWithPasswordCommand) (*responses.LoginUserResponse, error) {
|
||||
user, err := s.userRepo.GetByPhone(ctx, cmd.Phone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户名或密码错误")
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
|
||||
return &responses.LoginUserResponse{
|
||||
User: userProfile,
|
||||
AccessToken: accessToken,
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: 86400, // 24h
|
||||
LoginMethod: "password",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// LoginWithSMS 短信验证码登录
|
||||
func (s *UserApplicationServiceImpl) LoginWithSMS(ctx context.Context, cmd *commands.LoginWithSMSCommand) (*responses.LoginUserResponse, error) {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在")
|
||||
}
|
||||
|
||||
if !user.CanLogin() {
|
||||
return nil, fmt.Errorf("用户状态异常,无法登录")
|
||||
}
|
||||
|
||||
accessToken, err := s.jwtAuth.GenerateToken(user.ID, user.Phone, user.Phone)
|
||||
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)
|
||||
}
|
||||
|
||||
return &responses.LoginUserResponse{
|
||||
User: userProfile,
|
||||
AccessToken: accessToken,
|
||||
TokenType: "Bearer",
|
||||
ExpiresIn: 86400, // 24h
|
||||
LoginMethod: "sms",
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
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
|
||||
}
|
||||
|
||||
// GetUserProfile 获取用户信息
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", 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))
|
||||
} 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,
|
||||
}
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *UserApplicationServiceImpl) GetUser(ctx context.Context, query *queries.GetUserQuery) (*responses.UserProfileResponse, error) {
|
||||
// ... implementation
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
@@ -13,6 +13,8 @@ type Config struct {
|
||||
Logger LoggerConfig `mapstructure:"logger"`
|
||||
JWT JWTConfig `mapstructure:"jwt"`
|
||||
SMS SMSConfig `mapstructure:"sms"`
|
||||
Storage StorageConfig `mapstructure:"storage"`
|
||||
OCR OCRConfig `mapstructure:"ocr"`
|
||||
RateLimit RateLimitConfig `mapstructure:"ratelimit"`
|
||||
Monitoring MonitoringConfig `mapstructure:"monitoring"`
|
||||
Health HealthConfig `mapstructure:"health"`
|
||||
@@ -194,3 +196,17 @@ type WechatWorkConfig struct {
|
||||
WebhookURL string `mapstructure:"webhook_url"`
|
||||
Secret string `mapstructure:"secret"`
|
||||
}
|
||||
|
||||
// StorageConfig 存储服务配置
|
||||
type StorageConfig struct {
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
Bucket string `mapstructure:"bucket"`
|
||||
Domain string `mapstructure:"domain"`
|
||||
}
|
||||
|
||||
// OCRConfig OCR服务配置
|
||||
type OCRConfig struct {
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,178 +0,0 @@
|
||||
package dto
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"tyapi-server/internal/domains/admin/entities"
|
||||
)
|
||||
|
||||
// AdminLoginRequest 管理员登录请求
|
||||
type AdminLoginRequest struct {
|
||||
Username string `json:"username" binding:"required"` // 用户名
|
||||
Password string `json:"password" binding:"required"` // 密码
|
||||
}
|
||||
|
||||
// AdminLoginResponse 管理员登录响应
|
||||
type AdminLoginResponse struct {
|
||||
Token string `json:"token"` // JWT令牌
|
||||
ExpiresAt time.Time `json:"expires_at"` // 过期时间
|
||||
Admin AdminInfo `json:"admin"` // 管理员信息
|
||||
}
|
||||
|
||||
// AdminInfo 管理员信息
|
||||
type AdminInfo struct {
|
||||
ID string `json:"id"` // 管理员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"` // 创建时间
|
||||
}
|
||||
|
||||
// AdminCreateRequest 创建管理员请求
|
||||
type AdminCreateRequest 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 entities.AdminRole `json:"role" binding:"required"` // 角色
|
||||
Permissions []string `json:"permissions"` // 权限列表
|
||||
}
|
||||
|
||||
// AdminUpdateRequest 更新管理员请求
|
||||
type AdminUpdateRequest struct {
|
||||
Email string `json:"email" binding:"email"` // 邮箱
|
||||
Phone string `json:"phone"` // 手机号
|
||||
RealName string `json:"real_name"` // 真实姓名
|
||||
Role entities.AdminRole `json:"role"` // 角色
|
||||
IsActive *bool `json:"is_active"` // 是否激活
|
||||
Permissions []string `json:"permissions"` // 权限列表
|
||||
}
|
||||
|
||||
// AdminPasswordChangeRequest 修改密码请求
|
||||
type AdminPasswordChangeRequest struct {
|
||||
OldPassword string `json:"old_password" binding:"required"` // 旧密码
|
||||
NewPassword string `json:"new_password" binding:"required"` // 新密码
|
||||
}
|
||||
|
||||
// AdminListRequest 管理员列表请求
|
||||
type AdminListRequest 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"` // 状态筛选
|
||||
}
|
||||
|
||||
// AdminListResponse 管理员列表响应
|
||||
type AdminListResponse struct {
|
||||
Total int64 `json:"total"` // 总数
|
||||
Page int `json:"page"` // 当前页
|
||||
Size int `json:"size"` // 每页数量
|
||||
Admins []AdminInfo `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"` // 总操作数
|
||||
}
|
||||
|
||||
// AdminOperationLogRequest 操作日志请求
|
||||
type AdminOperationLogRequest struct {
|
||||
Page int `form:"page" binding:"min=1"` // 页码
|
||||
PageSize int `form:"page_size" binding:"min=1,max=100"` // 每页数量
|
||||
AdminID string `form:"admin_id"` // 管理员ID
|
||||
Action string `form:"action"` // 操作类型
|
||||
Resource string `form:"resource"` // 操作资源
|
||||
Status string `form:"status"` // 操作状态
|
||||
StartTime time.Time `form:"start_time"` // 开始时间
|
||||
EndTime time.Time `form:"end_time"` // 结束时间
|
||||
}
|
||||
|
||||
// AdminOperationLogResponse 操作日志响应
|
||||
type AdminOperationLogResponse struct {
|
||||
Total int64 `json:"total"` // 总数
|
||||
Page int `json:"page"` // 当前页
|
||||
Size int `json:"size"` // 每页数量
|
||||
Logs []AdminOperationLogInfo `json:"logs"` // 日志列表
|
||||
}
|
||||
|
||||
// AdminOperationLogInfo 操作日志信息
|
||||
type AdminOperationLogInfo struct {
|
||||
ID string `json:"id"` // 日志ID
|
||||
AdminID string `json:"admin_id"` // 管理员ID
|
||||
Username string `json:"username"` // 用户名
|
||||
Action string `json:"action"` // 操作类型
|
||||
Resource string `json:"resource"` // 操作资源
|
||||
ResourceID string `json:"resource_id"` // 资源ID
|
||||
Details string `json:"details"` // 操作详情
|
||||
IP string `json:"ip"` // IP地址
|
||||
UserAgent string `json:"user_agent"` // 用户代理
|
||||
Status string `json:"status"` // 操作状态
|
||||
Message string `json:"message"` // 操作消息
|
||||
CreatedAt time.Time `json:"created_at"` // 创建时间
|
||||
}
|
||||
|
||||
// AdminLoginLogRequest 登录日志请求
|
||||
type AdminLoginLogRequest struct {
|
||||
Page int `form:"page" binding:"min=1"` // 页码
|
||||
PageSize int `form:"page_size" binding:"min=1,max=100"` // 每页数量
|
||||
AdminID string `form:"admin_id"` // 管理员ID
|
||||
Username string `form:"username"` // 用户名
|
||||
Status string `form:"status"` // 登录状态
|
||||
StartTime time.Time `form:"start_time"` // 开始时间
|
||||
EndTime time.Time `form:"end_time"` // 结束时间
|
||||
}
|
||||
|
||||
// AdminLoginLogResponse 登录日志响应
|
||||
type AdminLoginLogResponse struct {
|
||||
Total int64 `json:"total"` // 总数
|
||||
Page int `json:"page"` // 当前页
|
||||
Size int `json:"size"` // 每页数量
|
||||
Logs []AdminLoginLogInfo `json:"logs"` // 日志列表
|
||||
}
|
||||
|
||||
// AdminLoginLogInfo 登录日志信息
|
||||
type AdminLoginLogInfo struct {
|
||||
ID string `json:"id"` // 日志ID
|
||||
AdminID string `json:"admin_id"` // 管理员ID
|
||||
Username string `json:"username"` // 用户名
|
||||
IP string `json:"ip"` // IP地址
|
||||
UserAgent string `json:"user_agent"` // 用户代理
|
||||
Status string `json:"status"` // 登录状态
|
||||
Message string `json:"message"` // 登录消息
|
||||
CreatedAt time.Time `json:"created_at"` // 创建时间
|
||||
}
|
||||
|
||||
// PermissionInfo 权限信息
|
||||
type PermissionInfo struct {
|
||||
ID string `json:"id"` // 权限ID
|
||||
Name string `json:"name"` // 权限名称
|
||||
Code string `json:"code"` // 权限代码
|
||||
Description string `json:"description"` // 权限描述
|
||||
Module string `json:"module"` // 所属模块
|
||||
IsActive bool `json:"is_active"` // 是否激活
|
||||
CreatedAt time.Time `json:"created_at"` // 创建时间
|
||||
}
|
||||
|
||||
// RolePermissionRequest 角色权限请求
|
||||
type RolePermissionRequest struct {
|
||||
Role entities.AdminRole `json:"role" binding:"required"` // 角色
|
||||
PermissionIDs []string `json:"permission_ids" binding:"required"` // 权限ID列表
|
||||
}
|
||||
|
||||
// RolePermissionResponse 角色权限响应
|
||||
type RolePermissionResponse struct {
|
||||
Role entities.AdminRole `json:"role"` // 角色
|
||||
Permissions []PermissionInfo `json:"permissions"` // 权限列表
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package entities
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -145,3 +146,11 @@ func (a *Admin) Deactivate() {
|
||||
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,313 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/admin/dto"
|
||||
"tyapi-server/internal/domains/admin/services"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// AdminHandler 管理员HTTP处理器
|
||||
type AdminHandler struct {
|
||||
adminService *services.AdminService
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewAdminHandler 创建管理员HTTP处理器
|
||||
func NewAdminHandler(
|
||||
adminService *services.AdminService,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
logger *zap.Logger,
|
||||
) *AdminHandler {
|
||||
return &AdminHandler{
|
||||
adminService: adminService,
|
||||
responseBuilder: responseBuilder,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Login 管理员登录
|
||||
// @Summary 管理员登录
|
||||
// @Description 管理员登录接口
|
||||
// @Tags 管理员认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.AdminLoginRequest true "登录请求"
|
||||
// @Success 200 {object} dto.AdminLoginResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Failure 401 {object} interfaces.ErrorResponse
|
||||
// @Router /admin/login [post]
|
||||
func (h *AdminHandler) Login(c *gin.Context) {
|
||||
var req dto.AdminLoginRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("管理员登录参数验证失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取客户端信息
|
||||
clientIP := c.ClientIP()
|
||||
userAgent := c.GetHeader("User-Agent")
|
||||
|
||||
// 调用服务
|
||||
response, err := h.adminService.Login(c.Request.Context(), &req, clientIP, userAgent)
|
||||
if err != nil {
|
||||
h.logger.Error("管理员登录失败", zap.Error(err))
|
||||
h.responseBuilder.Unauthorized(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "登录成功")
|
||||
}
|
||||
|
||||
// CreateAdmin 创建管理员
|
||||
// @Summary 创建管理员
|
||||
// @Description 创建新管理员账户
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.AdminCreateRequest true "创建管理员请求"
|
||||
// @Success 201 {object} interfaces.SuccessResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Failure 403 {object} interfaces.ErrorResponse
|
||||
// @Router /admin [post]
|
||||
func (h *AdminHandler) CreateAdmin(c *gin.Context) {
|
||||
var req dto.AdminCreateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("创建管理员参数验证失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前操作的管理员ID(从JWT中解析)
|
||||
operatorID := h.getCurrentAdminID(c)
|
||||
|
||||
// 调用服务
|
||||
err := h.adminService.CreateAdmin(c.Request.Context(), &req, operatorID)
|
||||
if err != nil {
|
||||
h.logger.Error("创建管理员失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Created(c, nil, "管理员创建成功")
|
||||
}
|
||||
|
||||
// UpdateAdmin 更新管理员
|
||||
// @Summary 更新管理员
|
||||
// @Description 更新管理员信息
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "管理员ID"
|
||||
// @Param request body dto.AdminUpdateRequest true "更新管理员请求"
|
||||
// @Success 200 {object} interfaces.SuccessResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Failure 404 {object} interfaces.ErrorResponse
|
||||
// @Router /admin/{id} [put]
|
||||
func (h *AdminHandler) UpdateAdmin(c *gin.Context) {
|
||||
adminID := c.Param("id")
|
||||
if adminID == "" {
|
||||
h.responseBuilder.BadRequest(c, "管理员ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.AdminUpdateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("更新管理员参数验证失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前操作的管理员ID
|
||||
operatorID := h.getCurrentAdminID(c)
|
||||
|
||||
// 调用服务
|
||||
err := h.adminService.UpdateAdmin(c.Request.Context(), adminID, &req, operatorID)
|
||||
if err != nil {
|
||||
h.logger.Error("更新管理员失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "管理员更新成功")
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
// @Summary 修改密码
|
||||
// @Description 管理员修改自己的密码
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.AdminPasswordChangeRequest true "修改密码请求"
|
||||
// @Success 200 {object} interfaces.SuccessResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Router /admin/change-password [post]
|
||||
func (h *AdminHandler) ChangePassword(c *gin.Context) {
|
||||
var req dto.AdminPasswordChangeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("修改密码参数验证失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前管理员ID
|
||||
adminID := h.getCurrentAdminID(c)
|
||||
|
||||
// 调用服务
|
||||
err := h.adminService.ChangePassword(c.Request.Context(), adminID, &req)
|
||||
if err != nil {
|
||||
h.logger.Error("修改密码失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "密码修改成功")
|
||||
}
|
||||
|
||||
// ListAdmins 获取管理员列表
|
||||
// @Summary 获取管理员列表
|
||||
// @Description 分页获取管理员列表
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param page_size query int false "每页数量" default(10)
|
||||
// @Param username query string false "用户名搜索"
|
||||
// @Param email query string false "邮箱搜索"
|
||||
// @Param role query string false "角色筛选"
|
||||
// @Param is_active query bool false "状态筛选"
|
||||
// @Success 200 {object} dto.AdminListResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Router /admin [get]
|
||||
func (h *AdminHandler) ListAdmins(c *gin.Context) {
|
||||
var req dto.AdminListRequest
|
||||
|
||||
// 解析查询参数
|
||||
if page, err := strconv.Atoi(c.DefaultQuery("page", "1")); err == nil {
|
||||
req.Page = page
|
||||
} else {
|
||||
req.Page = 1
|
||||
}
|
||||
|
||||
if pageSize, err := strconv.Atoi(c.DefaultQuery("page_size", "10")); err == nil {
|
||||
req.PageSize = pageSize
|
||||
} else {
|
||||
req.PageSize = 10
|
||||
}
|
||||
|
||||
req.Username = c.Query("username")
|
||||
req.Email = c.Query("email")
|
||||
req.Role = c.Query("role")
|
||||
|
||||
if isActiveStr := c.Query("is_active"); isActiveStr != "" {
|
||||
if isActive, err := strconv.ParseBool(isActiveStr); err == nil {
|
||||
req.IsActive = &isActive
|
||||
}
|
||||
}
|
||||
|
||||
// 调用服务
|
||||
response, err := h.adminService.ListAdmins(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
h.logger.Error("获取管理员列表失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取管理员列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "获取管理员列表成功")
|
||||
}
|
||||
|
||||
// GetAdminByID 根据ID获取管理员
|
||||
// @Summary 获取管理员详情
|
||||
// @Description 根据ID获取管理员详细信息
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "管理员ID"
|
||||
// @Success 200 {object} dto.AdminInfo
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Failure 404 {object} interfaces.ErrorResponse
|
||||
// @Router /admin/{id} [get]
|
||||
func (h *AdminHandler) GetAdminByID(c *gin.Context) {
|
||||
adminID := c.Param("id")
|
||||
if adminID == "" {
|
||||
h.responseBuilder.BadRequest(c, "管理员ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 调用服务
|
||||
admin, err := h.adminService.GetAdminByID(c.Request.Context(), adminID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取管理员详情失败", zap.Error(err))
|
||||
h.responseBuilder.NotFound(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, admin, "获取管理员详情成功")
|
||||
}
|
||||
|
||||
// DeleteAdmin 删除管理员
|
||||
// @Summary 删除管理员
|
||||
// @Description 软删除管理员账户
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "管理员ID"
|
||||
// @Success 200 {object} interfaces.SuccessResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Failure 404 {object} interfaces.ErrorResponse
|
||||
// @Router /admin/{id} [delete]
|
||||
func (h *AdminHandler) DeleteAdmin(c *gin.Context) {
|
||||
adminID := c.Param("id")
|
||||
if adminID == "" {
|
||||
h.responseBuilder.BadRequest(c, "管理员ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取当前操作的管理员ID
|
||||
operatorID := h.getCurrentAdminID(c)
|
||||
|
||||
// 调用服务
|
||||
err := h.adminService.DeleteAdmin(c.Request.Context(), adminID, operatorID)
|
||||
if err != nil {
|
||||
h.logger.Error("删除管理员失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "管理员删除成功")
|
||||
}
|
||||
|
||||
// GetAdminStats 获取管理员统计信息
|
||||
// @Summary 获取管理员统计
|
||||
// @Description 获取管理员相关的统计信息
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} dto.AdminStatsResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Router /admin/stats [get]
|
||||
func (h *AdminHandler) GetAdminStats(c *gin.Context) {
|
||||
// 调用服务
|
||||
stats, err := h.adminService.GetAdminStats(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("获取管理员统计失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取统计信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, stats, "获取统计信息成功")
|
||||
}
|
||||
|
||||
// getCurrentAdminID 获取当前管理员ID
|
||||
func (h *AdminHandler) getCurrentAdminID(c *gin.Context) string {
|
||||
// 这里应该从JWT令牌中解析出管理员ID
|
||||
// 为了简化,这里返回一个模拟的ID
|
||||
// 实际实现中应该从中间件中获取
|
||||
return "current_admin_id"
|
||||
}
|
||||
@@ -2,12 +2,19 @@ package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tyapi-server/internal/domains/admin/dto"
|
||||
"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]
|
||||
@@ -17,8 +24,8 @@ type AdminRepository interface {
|
||||
FindByEmail(ctx context.Context, email string) (*entities.Admin, error)
|
||||
|
||||
// 管理员管理
|
||||
ListAdmins(ctx context.Context, req *dto.AdminListRequest) (*dto.AdminListResponse, error)
|
||||
GetStats(ctx context.Context) (*dto.AdminStatsResponse, 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)
|
||||
@@ -34,7 +41,7 @@ type AdminLoginLogRepository interface {
|
||||
interfaces.Repository[entities.AdminLoginLog]
|
||||
|
||||
// 日志查询
|
||||
ListLogs(ctx context.Context, req *dto.AdminLoginLogRequest) (*dto.AdminLoginLogResponse, error)
|
||||
ListLogs(ctx context.Context, query *queries.ListAdminLoginLogQuery) ([]*entities.AdminLoginLog, int64, error)
|
||||
|
||||
// 统计查询
|
||||
GetTodayLoginCount(ctx context.Context) (int64, error)
|
||||
@@ -46,7 +53,7 @@ type AdminOperationLogRepository interface {
|
||||
interfaces.Repository[entities.AdminOperationLog]
|
||||
|
||||
// 日志查询
|
||||
ListLogs(ctx context.Context, req *dto.AdminOperationLogRequest) (*dto.AdminOperationLogResponse, error)
|
||||
ListLogs(ctx context.Context, query *queries.ListAdminOperationLogQuery) ([]*entities.AdminOperationLog, int64, error)
|
||||
|
||||
// 统计查询
|
||||
GetTotalOperations(ctx context.Context) (int64, error)
|
||||
@@ -0,0 +1,9 @@
|
||||
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"`
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
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"`
|
||||
}
|
||||
16
internal/domains/admin/repositories/queries/admin_queries.go
Normal file
16
internal/domains/admin/repositories/queries/admin_queries.go
Normal file
@@ -0,0 +1,16 @@
|
||||
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,29 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"tyapi-server/internal/domains/admin/handlers"
|
||||
)
|
||||
|
||||
// RegisterAdminRoutes 注册管理员路由
|
||||
func RegisterAdminRoutes(router *gin.Engine, adminHandler *handlers.AdminHandler) {
|
||||
// 管理员路由组
|
||||
adminGroup := router.Group("/api/admin")
|
||||
{
|
||||
// 认证相关路由(无需认证)
|
||||
authGroup := adminGroup.Group("/auth")
|
||||
{
|
||||
authGroup.POST("/login", adminHandler.Login)
|
||||
}
|
||||
|
||||
// 管理员管理路由(需要认证)
|
||||
adminGroup.POST("", adminHandler.CreateAdmin) // 创建管理员
|
||||
adminGroup.GET("", adminHandler.ListAdmins) // 获取管理员列表
|
||||
adminGroup.GET("/stats", adminHandler.GetAdminStats) // 获取统计信息
|
||||
adminGroup.GET("/:id", adminHandler.GetAdminByID) // 获取管理员详情
|
||||
adminGroup.PUT("/:id", adminHandler.UpdateAdmin) // 更新管理员
|
||||
adminGroup.DELETE("/:id", adminHandler.DeleteAdmin) // 删除管理员
|
||||
adminGroup.POST("/change-password", adminHandler.ChangePassword) // 修改密码
|
||||
}
|
||||
}
|
||||
@@ -2,353 +2,36 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"tyapi-server/internal/domains/admin/dto"
|
||||
"tyapi-server/internal/domains/admin/entities"
|
||||
"tyapi-server/internal/domains/admin/repositories"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// AdminService 管理员服务
|
||||
// AdminService 管理员领域服务
|
||||
type AdminService struct {
|
||||
adminRepo repositories.AdminRepository
|
||||
loginLogRepo repositories.AdminLoginLogRepository
|
||||
operationLogRepo repositories.AdminOperationLogRepository
|
||||
permissionRepo repositories.AdminPermissionRepository
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
logger *zap.Logger
|
||||
adminRepo repositories.AdminRepository
|
||||
permissionRepo repositories.AdminPermissionRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewAdminService 创建管理员服务
|
||||
// NewAdminService 创建管理员领域服务
|
||||
func NewAdminService(
|
||||
adminRepo repositories.AdminRepository,
|
||||
loginLogRepo repositories.AdminLoginLogRepository,
|
||||
operationLogRepo repositories.AdminOperationLogRepository,
|
||||
permissionRepo repositories.AdminPermissionRepository,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
logger *zap.Logger,
|
||||
) *AdminService {
|
||||
return &AdminService{
|
||||
adminRepo: adminRepo,
|
||||
loginLogRepo: loginLogRepo,
|
||||
operationLogRepo: operationLogRepo,
|
||||
permissionRepo: permissionRepo,
|
||||
responseBuilder: responseBuilder,
|
||||
logger: logger,
|
||||
adminRepo: adminRepo,
|
||||
permissionRepo: permissionRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Login 管理员登录
|
||||
func (s *AdminService) Login(ctx context.Context, req *dto.AdminLoginRequest, clientIP, userAgent string) (*dto.AdminLoginResponse, error) {
|
||||
s.logger.Info("管理员登录", zap.String("username", req.Username))
|
||||
|
||||
// 查找管理员
|
||||
admin, err := s.adminRepo.FindByUsername(ctx, req.Username)
|
||||
if err != nil {
|
||||
s.logger.Warn("管理员登录失败:用户不存在", zap.String("username", req.Username))
|
||||
s.recordLoginLog(ctx, req.Username, clientIP, userAgent, "failed", "用户不存在")
|
||||
return nil, fmt.Errorf("用户名或密码错误")
|
||||
}
|
||||
|
||||
// 检查管理员状态
|
||||
if !admin.IsActive {
|
||||
s.logger.Warn("管理员登录失败:账户已禁用", zap.String("username", req.Username))
|
||||
s.recordLoginLog(ctx, req.Username, clientIP, userAgent, "failed", "账户已禁用")
|
||||
return nil, fmt.Errorf("账户已被禁用,请联系管理员")
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(admin.Password), []byte(req.Password)); err != nil {
|
||||
s.logger.Warn("管理员登录失败:密码错误", zap.String("username", req.Username))
|
||||
s.recordLoginLog(ctx, req.Username, clientIP, userAgent, "failed", "密码错误")
|
||||
return nil, fmt.Errorf("用户名或密码错误")
|
||||
}
|
||||
|
||||
// 更新登录统计
|
||||
if err := s.adminRepo.UpdateLoginStats(ctx, admin.ID); err != nil {
|
||||
s.logger.Error("更新登录统计失败", zap.Error(err))
|
||||
}
|
||||
|
||||
// 记录登录日志
|
||||
s.recordLoginLog(ctx, req.Username, clientIP, userAgent, "success", "登录成功")
|
||||
|
||||
// 生成JWT令牌
|
||||
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 := dto.AdminInfo{
|
||||
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", req.Username))
|
||||
return &dto.AdminLoginResponse{
|
||||
Token: token,
|
||||
ExpiresAt: expiresAt,
|
||||
Admin: adminInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateAdmin 创建管理员
|
||||
func (s *AdminService) CreateAdmin(ctx context.Context, req *dto.AdminCreateRequest, operatorID string) error {
|
||||
s.logger.Info("创建管理员", zap.String("username", req.Username))
|
||||
|
||||
// 检查用户名是否已存在
|
||||
if _, err := s.adminRepo.FindByUsername(ctx, req.Username); err == nil {
|
||||
return fmt.Errorf("用户名已存在")
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
if _, err := s.adminRepo.FindByEmail(ctx, req.Email); err == nil {
|
||||
return fmt.Errorf("邮箱已存在")
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return fmt.Errorf("密码加密失败: %w", err)
|
||||
}
|
||||
|
||||
// 序列化权限
|
||||
permissionsJSON := "[]"
|
||||
if len(req.Permissions) > 0 {
|
||||
permissionsBytes, err := json.Marshal(req.Permissions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("权限序列化失败: %w", err)
|
||||
}
|
||||
permissionsJSON = string(permissionsBytes)
|
||||
}
|
||||
|
||||
// 创建管理员
|
||||
admin := entities.Admin{
|
||||
ID: s.generateID(),
|
||||
Username: req.Username,
|
||||
Password: string(hashedPassword),
|
||||
Email: req.Email,
|
||||
Phone: req.Phone,
|
||||
RealName: req.RealName,
|
||||
Role: req.Role,
|
||||
IsActive: true,
|
||||
Permissions: permissionsJSON,
|
||||
}
|
||||
|
||||
if err := s.adminRepo.Create(ctx, admin); err != nil {
|
||||
return fmt.Errorf("创建管理员失败: %w", err)
|
||||
}
|
||||
|
||||
// 记录操作日志
|
||||
s.recordOperationLog(ctx, operatorID, "create", "admin", admin.ID, map[string]interface{}{
|
||||
"username": req.Username,
|
||||
"email": req.Email,
|
||||
"role": req.Role,
|
||||
}, "success", "创建管理员成功")
|
||||
|
||||
s.logger.Info("管理员创建成功", zap.String("username", req.Username))
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateAdmin 更新管理员
|
||||
func (s *AdminService) UpdateAdmin(ctx context.Context, adminID string, req *dto.AdminUpdateRequest, operatorID string) error {
|
||||
s.logger.Info("更新管理员", zap.String("admin_id", adminID))
|
||||
|
||||
// 获取管理员
|
||||
admin, err := s.adminRepo.GetByID(ctx, adminID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("管理员不存在")
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
if req.Email != "" {
|
||||
// 检查邮箱是否被其他管理员使用
|
||||
if existingAdmin, err := s.adminRepo.FindByEmail(ctx, req.Email); err == nil && existingAdmin.ID != adminID {
|
||||
return fmt.Errorf("邮箱已被其他管理员使用")
|
||||
}
|
||||
admin.Email = req.Email
|
||||
}
|
||||
|
||||
if req.Phone != "" {
|
||||
admin.Phone = req.Phone
|
||||
}
|
||||
|
||||
if req.RealName != "" {
|
||||
admin.RealName = req.RealName
|
||||
}
|
||||
|
||||
if req.Role != "" {
|
||||
admin.Role = req.Role
|
||||
}
|
||||
|
||||
if req.IsActive != nil {
|
||||
admin.IsActive = *req.IsActive
|
||||
}
|
||||
|
||||
if len(req.Permissions) > 0 {
|
||||
permissionsJSON, err := json.Marshal(req.Permissions)
|
||||
if err != nil {
|
||||
return fmt.Errorf("权限序列化失败: %w", err)
|
||||
}
|
||||
admin.Permissions = string(permissionsJSON)
|
||||
}
|
||||
|
||||
// 保存更新
|
||||
if err := s.adminRepo.Update(ctx, admin); err != nil {
|
||||
return fmt.Errorf("更新管理员失败: %w", err)
|
||||
}
|
||||
|
||||
// 记录操作日志
|
||||
s.recordOperationLog(ctx, operatorID, "update", "admin", adminID, map[string]interface{}{
|
||||
"email": req.Email,
|
||||
"phone": req.Phone,
|
||||
"real_name": req.RealName,
|
||||
"role": req.Role,
|
||||
"is_active": req.IsActive,
|
||||
}, "success", "更新管理员成功")
|
||||
|
||||
s.logger.Info("管理员更新成功", zap.String("admin_id", adminID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
func (s *AdminService) ChangePassword(ctx context.Context, adminID string, req *dto.AdminPasswordChangeRequest) error {
|
||||
s.logger.Info("修改管理员密码", zap.String("admin_id", adminID))
|
||||
|
||||
// 获取管理员
|
||||
admin, err := s.adminRepo.GetByID(ctx, adminID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("管理员不存在")
|
||||
}
|
||||
|
||||
// 验证旧密码
|
||||
if err := bcrypt.CompareHashAndPassword([]byte(admin.Password), []byte(req.OldPassword)); err != nil {
|
||||
return fmt.Errorf("旧密码错误")
|
||||
}
|
||||
|
||||
// 加密新密码
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.NewPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return fmt.Errorf("密码加密失败: %w", err)
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
admin.Password = string(hashedPassword)
|
||||
if err := s.adminRepo.Update(ctx, admin); err != nil {
|
||||
return fmt.Errorf("更新密码失败: %w", err)
|
||||
}
|
||||
|
||||
// 记录操作日志
|
||||
s.recordOperationLog(ctx, adminID, "change_password", "admin", adminID, nil, "success", "修改密码成功")
|
||||
|
||||
s.logger.Info("管理员密码修改成功", zap.String("admin_id", adminID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListAdmins 获取管理员列表
|
||||
func (s *AdminService) ListAdmins(ctx context.Context, req *dto.AdminListRequest) (*dto.AdminListResponse, error) {
|
||||
s.logger.Info("获取管理员列表", zap.Int("page", req.Page), zap.Int("page_size", req.PageSize))
|
||||
|
||||
response, err := s.adminRepo.ListAdmins(ctx, req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取管理员列表失败: %w", err)
|
||||
}
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
// GetAdminStats 获取管理员统计信息
|
||||
func (s *AdminService) GetAdminStats(ctx context.Context) (*dto.AdminStatsResponse, error) {
|
||||
s.logger.Info("获取管理员统计信息")
|
||||
|
||||
stats, err := s.adminRepo.GetStats(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取统计信息失败: %w", err)
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// GetAdminByID 根据ID获取管理员
|
||||
func (s *AdminService) GetAdminByID(ctx context.Context, adminID string) (*dto.AdminInfo, error) {
|
||||
s.logger.Info("获取管理员信息", zap.String("admin_id", adminID))
|
||||
|
||||
admin, err := s.adminRepo.GetByID(ctx, adminID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("管理员不存在")
|
||||
}
|
||||
|
||||
// 获取权限列表
|
||||
permissions, err := s.getAdminPermissions(ctx, &admin)
|
||||
if err != nil {
|
||||
s.logger.Error("获取管理员权限失败", zap.Error(err))
|
||||
permissions = []string{}
|
||||
}
|
||||
|
||||
adminInfo := dto.AdminInfo{
|
||||
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,
|
||||
}
|
||||
|
||||
return &adminInfo, nil
|
||||
}
|
||||
|
||||
// DeleteAdmin 删除管理员
|
||||
func (s *AdminService) DeleteAdmin(ctx context.Context, adminID string, operatorID string) error {
|
||||
s.logger.Info("删除管理员", zap.String("admin_id", adminID))
|
||||
|
||||
// 检查管理员是否存在
|
||||
if _, err := s.adminRepo.GetByID(ctx, adminID); err != nil {
|
||||
return fmt.Errorf("管理员不存在")
|
||||
}
|
||||
|
||||
// 软删除管理员
|
||||
if err := s.adminRepo.SoftDelete(ctx, adminID); err != nil {
|
||||
return fmt.Errorf("删除管理员失败: %w", err)
|
||||
}
|
||||
|
||||
// 记录操作日志
|
||||
s.recordOperationLog(ctx, operatorID, "delete", "admin", adminID, nil, "success", "删除管理员成功")
|
||||
|
||||
s.logger.Info("管理员删除成功", zap.String("admin_id", adminID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// getAdminPermissions 获取管理员权限
|
||||
func (s *AdminService) getAdminPermissions(ctx context.Context, admin *entities.Admin) ([]string, error) {
|
||||
// GetAdminPermissions 获取管理员权限
|
||||
func (s *AdminService) GetAdminPermissions(ctx context.Context, admin *entities.Admin) ([]string, error) {
|
||||
// 首先从角色获取权限
|
||||
rolePermissions, err := s.adminRepo.GetPermissionsByRole(ctx, admin.Role)
|
||||
if err != nil {
|
||||
@@ -371,61 +54,3 @@ func (s *AdminService) getAdminPermissions(ctx context.Context, admin *entities.
|
||||
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
// generateJWTToken 生成JWT令牌
|
||||
func (s *AdminService) generateJWTToken(admin *entities.Admin) (string, time.Time, error) {
|
||||
// 这里应该使用JWT库生成令牌
|
||||
// 为了简化,这里返回一个模拟的令牌
|
||||
token := fmt.Sprintf("admin_token_%s_%d", admin.ID, time.Now().Unix())
|
||||
expiresAt := time.Now().Add(24 * time.Hour)
|
||||
|
||||
return token, expiresAt, nil
|
||||
}
|
||||
|
||||
// generateID 生成ID
|
||||
func (s *AdminService) generateID() string {
|
||||
bytes := make([]byte, 16)
|
||||
rand.Read(bytes)
|
||||
return hex.EncodeToString(bytes)
|
||||
}
|
||||
|
||||
// recordLoginLog 记录登录日志
|
||||
func (s *AdminService) recordLoginLog(ctx context.Context, username, ip, userAgent, status, message string) {
|
||||
log := entities.AdminLoginLog{
|
||||
ID: s.generateID(),
|
||||
Username: username,
|
||||
IP: ip,
|
||||
UserAgent: userAgent,
|
||||
Status: status,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
if err := s.loginLogRepo.Create(ctx, log); err != nil {
|
||||
s.logger.Error("记录登录日志失败", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// recordOperationLog 记录操作日志
|
||||
func (s *AdminService) recordOperationLog(ctx context.Context, adminID, action, resource, resourceID string, details map[string]interface{}, status, message string) {
|
||||
detailsJSON := "{}"
|
||||
if details != nil {
|
||||
if bytes, err := json.Marshal(details); err == nil {
|
||||
detailsJSON = string(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
log := entities.AdminOperationLog{
|
||||
ID: s.generateID(),
|
||||
AdminID: adminID,
|
||||
Action: action,
|
||||
Resource: resource,
|
||||
ResourceID: resourceID,
|
||||
Details: detailsJSON,
|
||||
Status: status,
|
||||
Message: message,
|
||||
}
|
||||
|
||||
if err := s.operationLogRepo.Create(ctx, log); err != nil {
|
||||
s.logger.Error("记录操作日志失败", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
|
||||
"tyapi-server/internal/domains/certification/enums"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -13,10 +15,9 @@ import (
|
||||
// 包含认证状态、时间节点、审核信息、合同信息等核心数据
|
||||
type Certification struct {
|
||||
// 基础信息
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"认证申请唯一标识"`
|
||||
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"申请用户ID"`
|
||||
EnterpriseID *string `gorm:"type:varchar(36);index" json:"enterprise_id" comment:"关联的企业信息ID"`
|
||||
Status enums.CertificationStatus `gorm:"type:varchar(50);not null;index" json:"status" comment:"当前认证状态"`
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"认证申请唯一标识"`
|
||||
UserID string `gorm:"type:varchar(36);not null;index" json:"user_id" comment:"申请用户ID"`
|
||||
Status enums.CertificationStatus `gorm:"type:varchar(50);not null;index" json:"status" comment:"当前认证状态"`
|
||||
|
||||
// 流程节点时间戳 - 记录每个关键步骤的完成时间
|
||||
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
|
||||
@@ -45,7 +46,6 @@ type Certification struct {
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
|
||||
|
||||
// 关联关系 - 与其他实体的关联
|
||||
Enterprise *Enterprise `gorm:"foreignKey:EnterpriseID" json:"enterprise,omitempty" 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:"关联的合同记录列表"`
|
||||
@@ -57,6 +57,14 @@ func (Certification) TableName() string {
|
||||
return "certifications"
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (c *Certification) BeforeCreate(tx *gorm.DB) error {
|
||||
if c.ID == "" {
|
||||
c.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsStatusChangeable 检查状态是否可以变更
|
||||
// 只有非最终状态(完成/拒绝)的认证申请才能进行状态变更
|
||||
func (c *Certification) IsStatusChangeable() bool {
|
||||
|
||||
@@ -3,6 +3,7 @@ package entities
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -96,3 +97,11 @@ func (c *ContractRecord) GetStatusName() string {
|
||||
}
|
||||
return c.Status
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (c *ContractRecord) BeforeCreate(tx *gorm.DB) error {
|
||||
if c.ID == "" {
|
||||
c.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package entities
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -87,3 +88,11 @@ func (f *FaceVerifyRecord) GetStatusName() string {
|
||||
}
|
||||
return f.Status
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (f *FaceVerifyRecord) BeforeCreate(tx *gorm.DB) error {
|
||||
if f.ID == "" {
|
||||
f.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package entities
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -68,3 +69,11 @@ func (l *LicenseUploadRecord) IsValidForOCR() bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (l *LicenseUploadRecord) BeforeCreate(tx *gorm.DB) error {
|
||||
if l.ID == "" {
|
||||
l.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package entities
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -49,6 +50,14 @@ 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 {
|
||||
|
||||
@@ -5,6 +5,7 @@ type CertificationStatus string
|
||||
|
||||
const (
|
||||
// 主流程状态
|
||||
StatusNotStarted CertificationStatus = "not_started" // 未开始认证
|
||||
StatusPending CertificationStatus = "pending" // 待开始
|
||||
StatusInfoSubmitted CertificationStatus = "info_submitted" // 企业信息已提交
|
||||
StatusFaceVerified CertificationStatus = "face_verified" // 人脸识别完成
|
||||
@@ -23,7 +24,7 @@ const (
|
||||
// IsValidStatus 检查状态是否有效
|
||||
func IsValidStatus(status CertificationStatus) bool {
|
||||
validStatuses := []CertificationStatus{
|
||||
StatusPending, StatusInfoSubmitted, StatusFaceVerified,
|
||||
StatusNotStarted, StatusPending, StatusInfoSubmitted, StatusFaceVerified,
|
||||
StatusContractApplied, StatusContractPending, StatusContractApproved,
|
||||
StatusContractSigned, StatusCompleted, StatusFaceFailed,
|
||||
StatusSignFailed, StatusRejected,
|
||||
@@ -40,6 +41,7 @@ func IsValidStatus(status CertificationStatus) bool {
|
||||
// GetStatusName 获取状态的中文名称
|
||||
func GetStatusName(status CertificationStatus) string {
|
||||
statusNames := map[CertificationStatus]string{
|
||||
StatusNotStarted: "未开始认证",
|
||||
StatusPending: "待开始",
|
||||
StatusInfoSubmitted: "企业信息已提交",
|
||||
StatusFaceVerified: "人脸识别完成",
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/infrastructure/external/notification"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
"tyapi-server/internal/shared/notification"
|
||||
)
|
||||
|
||||
// CertificationEventHandler 认证事件处理器
|
||||
|
||||
@@ -1,536 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/certification/dto"
|
||||
"tyapi-server/internal/domains/certification/services"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// CertificationHandler 认证处理器
|
||||
type CertificationHandler struct {
|
||||
certificationService *services.CertificationService
|
||||
response interfaces.ResponseBuilder
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCertificationHandler 创建认证处理器
|
||||
func NewCertificationHandler(
|
||||
certificationService *services.CertificationService,
|
||||
response interfaces.ResponseBuilder,
|
||||
logger *zap.Logger,
|
||||
) *CertificationHandler {
|
||||
return &CertificationHandler{
|
||||
certificationService: certificationService,
|
||||
response: response,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCertification 创建认证申请
|
||||
// @Summary 创建认证申请
|
||||
// @Description 用户创建企业认证申请
|
||||
// @Tags 认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} dto.CertificationCreateResponse
|
||||
// @Failure 400 {object} interfaces.APIResponse
|
||||
// @Failure 500 {object} interfaces.APIResponse
|
||||
// @Router /api/v1/certification/create [post]
|
||||
func (h *CertificationHandler) CreateCertification(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.certificationService.CreateCertification(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("创建认证申请失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.response.InternalError(c, "创建认证申请失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "认证申请创建成功")
|
||||
}
|
||||
|
||||
// UploadLicense 上传营业执照
|
||||
// @Summary 上传营业执照
|
||||
// @Description 上传营业执照文件并进行OCR识别
|
||||
// @Tags 认证
|
||||
// @Accept multipart/form-data
|
||||
// @Produce json
|
||||
// @Param file formData file true "营业执照文件"
|
||||
// @Success 200 {object} dto.UploadLicenseResponse
|
||||
// @Failure 400 {object} interfaces.APIResponse
|
||||
// @Failure 500 {object} interfaces.APIResponse
|
||||
// @Router /api/v1/certification/upload-license [post]
|
||||
func (h *CertificationHandler) UploadLicense(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取上传的文件
|
||||
file, header, err := c.Request.FormFile("file")
|
||||
if err != nil {
|
||||
h.logger.Error("获取上传文件失败", zap.Error(err))
|
||||
h.response.BadRequest(c, "请选择要上传的文件")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// 检查文件类型
|
||||
fileName := header.Filename
|
||||
ext := strings.ToLower(filepath.Ext(fileName))
|
||||
allowedExts := []string{".jpg", ".jpeg", ".png", ".pdf"}
|
||||
|
||||
isAllowed := false
|
||||
for _, allowedExt := range allowedExts {
|
||||
if ext == allowedExt {
|
||||
isAllowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !isAllowed {
|
||||
h.response.BadRequest(c, "文件格式不支持,仅支持 JPG、PNG、PDF 格式")
|
||||
return
|
||||
}
|
||||
|
||||
// 检查文件大小(限制为10MB)
|
||||
const maxFileSize = 10 * 1024 * 1024 // 10MB
|
||||
if header.Size > maxFileSize {
|
||||
h.response.BadRequest(c, "文件大小不能超过10MB")
|
||||
return
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
fileBytes, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
h.logger.Error("读取文件内容失败", zap.Error(err))
|
||||
h.response.InternalError(c, "文件读取失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 调用服务上传文件
|
||||
result, err := h.certificationService.UploadLicense(c.Request.Context(), userID, fileBytes, fileName)
|
||||
if err != nil {
|
||||
h.logger.Error("上传营业执照失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("file_name", fileName),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.response.InternalError(c, "上传失败,请稍后重试")
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "营业执照上传成功")
|
||||
}
|
||||
|
||||
// SubmitEnterpriseInfo 提交企业信息
|
||||
// @Summary 提交企业信息
|
||||
// @Description 确认并提交企业四要素信息
|
||||
// @Tags 认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "认证申请ID"
|
||||
// @Param request body dto.SubmitEnterpriseInfoRequest true "企业信息"
|
||||
// @Success 200 {object} dto.SubmitEnterpriseInfoResponse
|
||||
// @Failure 400 {object} interfaces.APIResponse
|
||||
// @Failure 500 {object} interfaces.APIResponse
|
||||
// @Router /api/v1/certification/{id}/submit-info [put]
|
||||
func (h *CertificationHandler) SubmitEnterpriseInfo(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
certificationID := c.Param("id")
|
||||
if certificationID == "" {
|
||||
h.response.BadRequest(c, "认证申请ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.SubmitEnterpriseInfoRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Error("参数绑定失败", zap.Error(err))
|
||||
h.response.BadRequest(c, "请求参数格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 验证企业信息
|
||||
if req.CompanyName == "" {
|
||||
h.response.BadRequest(c, "企业名称不能为空")
|
||||
return
|
||||
}
|
||||
if req.UnifiedSocialCode == "" {
|
||||
h.response.BadRequest(c, "统一社会信用代码不能为空")
|
||||
return
|
||||
}
|
||||
if req.LegalPersonName == "" {
|
||||
h.response.BadRequest(c, "法定代表人姓名不能为空")
|
||||
return
|
||||
}
|
||||
if req.LegalPersonID == "" {
|
||||
h.response.BadRequest(c, "法定代表人身份证号不能为空")
|
||||
return
|
||||
}
|
||||
if req.LicenseUploadRecordID == "" {
|
||||
h.response.BadRequest(c, "营业执照上传记录ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.certificationService.SubmitEnterpriseInfo(c.Request.Context(), certificationID, &req)
|
||||
if err != nil {
|
||||
h.logger.Error("提交企业信息失败",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
if strings.Contains(err.Error(), "已被使用") || strings.Contains(err.Error(), "不允许") {
|
||||
h.response.BadRequest(c, err.Error())
|
||||
} else {
|
||||
h.response.InternalError(c, "提交失败,请稍后重试")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "企业信息提交成功")
|
||||
}
|
||||
|
||||
// InitiateFaceVerify 初始化人脸识别
|
||||
// @Summary 初始化人脸识别
|
||||
// @Description 开始人脸识别认证流程
|
||||
// @Tags 认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "认证申请ID"
|
||||
// @Param request body dto.FaceVerifyRequest true "人脸识别请求"
|
||||
// @Success 200 {object} dto.FaceVerifyResponse
|
||||
// @Failure 400 {object} interfaces.APIResponse
|
||||
// @Failure 500 {object} interfaces.APIResponse
|
||||
// @Router /api/v1/certification/{id}/face-verify [post]
|
||||
func (h *CertificationHandler) InitiateFaceVerify(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
certificationID := c.Param("id")
|
||||
if certificationID == "" {
|
||||
h.response.BadRequest(c, "认证申请ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var req dto.FaceVerifyRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Error("参数绑定失败", zap.Error(err))
|
||||
h.response.BadRequest(c, "请求参数格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 验证请求参数
|
||||
if req.RealName == "" {
|
||||
h.response.BadRequest(c, "真实姓名不能为空")
|
||||
return
|
||||
}
|
||||
if req.IDCardNumber == "" {
|
||||
h.response.BadRequest(c, "身份证号不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.certificationService.InitiateFaceVerify(c.Request.Context(), certificationID, &req)
|
||||
if err != nil {
|
||||
h.logger.Error("初始化人脸识别失败",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
if strings.Contains(err.Error(), "不允许") {
|
||||
h.response.BadRequest(c, err.Error())
|
||||
} else {
|
||||
h.response.InternalError(c, "初始化失败,请稍后重试")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "人脸识别初始化成功")
|
||||
}
|
||||
|
||||
// ApplyContract 申请电子合同
|
||||
// @Summary 申请电子合同
|
||||
// @Description 申请生成企业认证电子合同
|
||||
// @Tags 认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "认证申请ID"
|
||||
// @Success 200 {object} dto.ApplyContractResponse
|
||||
// @Failure 400 {object} interfaces.APIResponse
|
||||
// @Failure 500 {object} interfaces.APIResponse
|
||||
// @Router /api/v1/certification/{id}/apply-contract [post]
|
||||
func (h *CertificationHandler) ApplyContract(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
certificationID := c.Param("id")
|
||||
if certificationID == "" {
|
||||
h.response.BadRequest(c, "认证申请ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.certificationService.ApplyContract(c.Request.Context(), certificationID)
|
||||
if err != nil {
|
||||
h.logger.Error("申请电子合同失败",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
if strings.Contains(err.Error(), "不允许") {
|
||||
h.response.BadRequest(c, err.Error())
|
||||
} else {
|
||||
h.response.InternalError(c, "申请失败,请稍后重试")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "合同申请提交成功,请等待管理员审核")
|
||||
}
|
||||
|
||||
// GetCertificationStatus 获取认证状态
|
||||
// @Summary 获取认证状态
|
||||
// @Description 查询当前用户的认证申请状态和进度
|
||||
// @Tags 认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} dto.CertificationStatusResponse
|
||||
// @Failure 400 {object} interfaces.APIResponse
|
||||
// @Failure 500 {object} interfaces.APIResponse
|
||||
// @Router /api/v1/certification/status [get]
|
||||
func (h *CertificationHandler) GetCertificationStatus(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.certificationService.GetCertificationStatus(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取认证状态失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
if strings.Contains(err.Error(), "不存在") {
|
||||
h.response.NotFound(c, "未找到认证申请记录")
|
||||
} else {
|
||||
h.response.InternalError(c, "查询失败,请稍后重试")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "查询成功")
|
||||
}
|
||||
|
||||
// GetCertificationDetails 获取认证详情
|
||||
// @Summary 获取认证详情
|
||||
// @Description 获取指定认证申请的详细信息
|
||||
// @Tags 认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "认证申请ID"
|
||||
// @Success 200 {object} dto.CertificationStatusResponse
|
||||
// @Failure 400 {object} interfaces.APIResponse
|
||||
// @Failure 500 {object} interfaces.APIResponse
|
||||
// @Router /api/v1/certification/{id} [get]
|
||||
func (h *CertificationHandler) GetCertificationDetails(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
certificationID := c.Param("id")
|
||||
if certificationID == "" {
|
||||
h.response.BadRequest(c, "认证申请ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// 通过用户ID获取状态来确保用户只能查看自己的认证记录
|
||||
result, err := h.certificationService.GetCertificationStatus(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取认证详情失败",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
if strings.Contains(err.Error(), "不存在") {
|
||||
h.response.NotFound(c, "未找到认证申请记录")
|
||||
} else {
|
||||
h.response.InternalError(c, "查询失败,请稍后重试")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 检查是否是用户自己的认证记录
|
||||
if result.ID != certificationID {
|
||||
h.response.Forbidden(c, "无权访问此认证记录")
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "查询成功")
|
||||
}
|
||||
|
||||
// RetryStep 重试认证步骤
|
||||
// @Summary 重试认证步骤
|
||||
// @Description 重试失败的认证步骤(如人脸识别失败、签署失败等)
|
||||
// @Tags 认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "认证申请ID"
|
||||
// @Param step query string true "重试步骤(face_verify, sign_contract)"
|
||||
// @Success 200 {object} interfaces.APIResponse
|
||||
// @Failure 400 {object} interfaces.APIResponse
|
||||
// @Failure 500 {object} interfaces.APIResponse
|
||||
// @Router /api/v1/certification/{id}/retry [post]
|
||||
func (h *CertificationHandler) RetryStep(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
certificationID := c.Param("id")
|
||||
if certificationID == "" {
|
||||
h.response.BadRequest(c, "认证申请ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
step := c.Query("step")
|
||||
if step == "" {
|
||||
h.response.BadRequest(c, "重试步骤不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: 实现重试逻辑
|
||||
// 这里需要根据不同的步骤调用状态机进行状态重置
|
||||
|
||||
h.logger.Info("重试认证步骤",
|
||||
zap.String("certification_id", certificationID),
|
||||
zap.String("user_id", userID),
|
||||
zap.String("step", step),
|
||||
)
|
||||
|
||||
h.response.Success(c, gin.H{
|
||||
"certification_id": certificationID,
|
||||
"step": step,
|
||||
"message": "重试操作已提交",
|
||||
}, "重试操作成功")
|
||||
}
|
||||
|
||||
// GetProgressStats 获取进度统计
|
||||
// @Summary 获取进度统计
|
||||
// @Description 获取用户认证申请的进度统计信息
|
||||
// @Tags 认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]interface{}
|
||||
// @Failure 400 {object} interfaces.APIResponse
|
||||
// @Failure 500 {object} interfaces.APIResponse
|
||||
// @Router /api/v1/certification/progress [get]
|
||||
func (h *CertificationHandler) GetProgressStats(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取认证状态
|
||||
status, err := h.certificationService.GetCertificationStatus(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "不存在") {
|
||||
h.response.Success(c, gin.H{
|
||||
"has_certification": false,
|
||||
"progress": 0,
|
||||
"status": "",
|
||||
"next_steps": []string{"开始企业认证"},
|
||||
}, "查询成功")
|
||||
return
|
||||
}
|
||||
h.response.InternalError(c, "查询失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 构建进度统计
|
||||
nextSteps := []string{}
|
||||
if status.IsUserActionRequired {
|
||||
switch status.Status {
|
||||
case "pending":
|
||||
nextSteps = append(nextSteps, "上传营业执照")
|
||||
case "info_submitted":
|
||||
nextSteps = append(nextSteps, "进行人脸识别")
|
||||
case "face_verified":
|
||||
nextSteps = append(nextSteps, "申请电子合同")
|
||||
case "contract_approved":
|
||||
nextSteps = append(nextSteps, "签署电子合同")
|
||||
case "face_failed":
|
||||
nextSteps = append(nextSteps, "重新进行人脸识别")
|
||||
case "sign_failed":
|
||||
nextSteps = append(nextSteps, "重新签署合同")
|
||||
}
|
||||
} else if status.IsAdminActionRequired {
|
||||
nextSteps = append(nextSteps, "等待管理员审核")
|
||||
} else {
|
||||
nextSteps = append(nextSteps, "认证流程已完成")
|
||||
}
|
||||
|
||||
result := gin.H{
|
||||
"has_certification": true,
|
||||
"certification_id": status.ID,
|
||||
"progress": status.Progress,
|
||||
"status": status.Status,
|
||||
"status_name": status.StatusName,
|
||||
"is_user_action_required": status.IsUserActionRequired,
|
||||
"is_admin_action_required": status.IsAdminActionRequired,
|
||||
"next_steps": nextSteps,
|
||||
"created_at": status.CreatedAt,
|
||||
"updated_at": status.UpdatedAt,
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "查询成功")
|
||||
}
|
||||
|
||||
// parsePageParams 解析分页参数
|
||||
func (h *CertificationHandler) parsePageParams(c *gin.Context) (int, int) {
|
||||
page := 1
|
||||
pageSize := 20
|
||||
|
||||
if pageStr := c.Query("page"); pageStr != "" {
|
||||
if p, err := strconv.Atoi(pageStr); err == nil && p > 0 {
|
||||
page = p
|
||||
}
|
||||
}
|
||||
|
||||
if sizeStr := c.Query("page_size"); sizeStr != "" {
|
||||
if s, err := strconv.Atoi(sizeStr); err == nil && s > 0 && s <= 100 {
|
||||
pageSize = s
|
||||
}
|
||||
}
|
||||
|
||||
return page, pageSize
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/domains/certification/entities"
|
||||
"tyapi-server/internal/domains/certification/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// CertificationStats 认证统计信息
|
||||
type CertificationStats struct {
|
||||
TotalCertifications int64
|
||||
PendingCertifications int64
|
||||
CompletedCertifications int64
|
||||
RejectedCertifications int64
|
||||
TodaySubmissions int64
|
||||
}
|
||||
|
||||
// CertificationRepository 认证申请仓储接口
|
||||
type CertificationRepository interface {
|
||||
interfaces.Repository[entities.Certification]
|
||||
|
||||
// 基础查询 - 直接使用实体
|
||||
GetByUserID(ctx context.Context, userID string) (*entities.Certification, error)
|
||||
GetByStatus(ctx context.Context, status string) ([]*entities.Certification, error)
|
||||
GetPendingCertifications(ctx context.Context) ([]*entities.Certification, error)
|
||||
|
||||
// 复杂查询 - 使用查询参数
|
||||
ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) ([]*entities.Certification, int64, error)
|
||||
|
||||
// 业务操作
|
||||
UpdateStatus(ctx context.Context, certificationID string, status string, adminID *string, notes 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]
|
||||
|
||||
// 基础查询 - 直接使用实体
|
||||
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.FaceVerifyRecord, error)
|
||||
GetLatestByCertificationID(ctx context.Context, certificationID string) (*entities.FaceVerifyRecord, 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)
|
||||
|
||||
// 业务操作
|
||||
UpdateContractStatus(ctx context.Context, recordID string, status string, adminID *string, notes string) error
|
||||
}
|
||||
|
||||
// LicenseUploadRecordRepository 营业执照上传记录仓储接口
|
||||
type LicenseUploadRecordRepository interface {
|
||||
interfaces.Repository[entities.LicenseUploadRecord]
|
||||
|
||||
// 基础查询 - 直接使用实体
|
||||
GetByCertificationID(ctx context.Context, certificationID string) (*entities.LicenseUploadRecord, error)
|
||||
|
||||
// 复杂查询 - 使用查询参数
|
||||
ListRecords(ctx context.Context, query *queries.ListLicenseUploadRecordsQuery) ([]*entities.LicenseUploadRecord, int64, error)
|
||||
|
||||
// 业务操作
|
||||
UpdateOCRResult(ctx context.Context, recordID string, ocrResult string, confidence float64) error
|
||||
}
|
||||
|
||||
// NotificationRecordRepository 通知记录仓储接口
|
||||
type NotificationRecordRepository interface {
|
||||
interfaces.Repository[entities.NotificationRecord]
|
||||
|
||||
// 基础查询 - 直接使用实体
|
||||
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.NotificationRecord, error)
|
||||
GetUnreadByUserID(ctx context.Context, userID string) ([]*entities.NotificationRecord, error)
|
||||
|
||||
// 复杂查询 - 使用查询参数
|
||||
ListRecords(ctx context.Context, query *queries.ListNotificationRecordsQuery) ([]*entities.NotificationRecord, int64, error)
|
||||
|
||||
// 批量操作
|
||||
BatchCreate(ctx context.Context, records []entities.NotificationRecord) error
|
||||
MarkAsRead(ctx context.Context, recordIDs []string) error
|
||||
MarkAllAsReadByUser(ctx context.Context, userID string) error
|
||||
}
|
||||
@@ -1,223 +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/enums"
|
||||
)
|
||||
|
||||
// GormCertificationRepository GORM认证仓储实现
|
||||
type GormCertificationRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewGormCertificationRepository 创建GORM认证仓储
|
||||
func NewGormCertificationRepository(db *gorm.DB, logger *zap.Logger) CertificationRepository {
|
||||
return &GormCertificationRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建认证记录
|
||||
func (r *GormCertificationRepository) Create(ctx context.Context, cert *entities.Certification) error {
|
||||
if err := r.db.WithContext(ctx).Create(cert).Error; err != nil {
|
||||
r.logger.Error("创建认证记录失败",
|
||||
zap.String("user_id", cert.UserID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("创建认证记录失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("认证记录创建成功",
|
||||
zap.String("id", cert.ID),
|
||||
zap.String("user_id", cert.UserID),
|
||||
zap.String("status", string(cert.Status)),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取认证记录
|
||||
func (r *GormCertificationRepository) GetByID(ctx context.Context, id string) (*entities.Certification, error) {
|
||||
var cert entities.Certification
|
||||
|
||||
if err := r.db.WithContext(ctx).First(&cert, "id = ?", id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("认证记录不存在")
|
||||
}
|
||||
r.logger.Error("获取认证记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取认证记录失败: %w", err)
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// GetByUserID 根据用户ID获取认证记录
|
||||
func (r *GormCertificationRepository) GetByUserID(ctx context.Context, userID string) (*entities.Certification, error) {
|
||||
var cert entities.Certification
|
||||
|
||||
if err := r.db.WithContext(ctx).First(&cert, "user_id = ?", userID).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("用户认证记录不存在")
|
||||
}
|
||||
r.logger.Error("获取用户认证记录失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取用户认证记录失败: %w", err)
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// Update 更新认证记录
|
||||
func (r *GormCertificationRepository) Update(ctx context.Context, cert *entities.Certification) error {
|
||||
if err := r.db.WithContext(ctx).Save(cert).Error; err != nil {
|
||||
r.logger.Error("更新认证记录失败",
|
||||
zap.String("id", cert.ID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("更新认证记录失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("认证记录更新成功",
|
||||
zap.String("id", cert.ID),
|
||||
zap.String("status", string(cert.Status)),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除认证记录(软删除)
|
||||
func (r *GormCertificationRepository) Delete(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Delete(&entities.Certification{}, "id = ?", id).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
|
||||
}
|
||||
|
||||
// List 获取认证记录列表
|
||||
func (r *GormCertificationRepository) List(ctx context.Context, page, pageSize int, status enums.CertificationStatus) ([]*entities.Certification, int, error) {
|
||||
var certs []*entities.Certification
|
||||
var total int64
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&entities.Certification{})
|
||||
|
||||
// 如果指定了状态,添加状态过滤
|
||||
if status != "" {
|
||||
query = query.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(&certs).Error; err != nil {
|
||||
r.logger.Error("获取认证记录列表失败", zap.Error(err))
|
||||
return nil, 0, fmt.Errorf("获取认证记录列表失败: %w", err)
|
||||
}
|
||||
|
||||
return certs, int(total), nil
|
||||
}
|
||||
|
||||
// GetByStatus 根据状态获取认证记录
|
||||
func (r *GormCertificationRepository) GetByStatus(ctx context.Context, status enums.CertificationStatus, page, pageSize int) ([]*entities.Certification, int, error) {
|
||||
return r.List(ctx, page, pageSize, status)
|
||||
}
|
||||
|
||||
// GetPendingApprovals 获取待审核的认证申请
|
||||
func (r *GormCertificationRepository) GetPendingApprovals(ctx context.Context, page, pageSize int) ([]*entities.Certification, int, error) {
|
||||
return r.GetByStatus(ctx, enums.StatusContractPending, page, pageSize)
|
||||
}
|
||||
|
||||
// GetWithEnterprise 获取包含企业信息的认证记录
|
||||
func (r *GormCertificationRepository) GetWithEnterprise(ctx context.Context, id string) (*entities.Certification, error) {
|
||||
var cert entities.Certification
|
||||
|
||||
if err := r.db.WithContext(ctx).Preload("Enterprise").First(&cert, "id = ?", id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("认证记录不存在")
|
||||
}
|
||||
r.logger.Error("获取认证记录(含企业信息)失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取认证记录失败: %w", err)
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// GetWithAllRelations 获取包含所有关联关系的认证记录
|
||||
func (r *GormCertificationRepository) GetWithAllRelations(ctx context.Context, id string) (*entities.Certification, error) {
|
||||
var cert entities.Certification
|
||||
|
||||
if err := r.db.WithContext(ctx).
|
||||
Preload("Enterprise").
|
||||
Preload("LicenseUploadRecord").
|
||||
Preload("FaceVerifyRecords").
|
||||
Preload("ContractRecords").
|
||||
Preload("NotificationRecords").
|
||||
First(&cert, "id = ?", id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("认证记录不存在")
|
||||
}
|
||||
r.logger.Error("获取认证记录(含所有关联)失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取认证记录失败: %w", err)
|
||||
}
|
||||
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// CountByStatus 根据状态统计认证记录数量
|
||||
func (r *GormCertificationRepository) CountByStatus(ctx context.Context, status enums.CertificationStatus) (int64, error) {
|
||||
var count int64
|
||||
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("status = ?", status).Count(&count).Error; err != nil {
|
||||
r.logger.Error("统计认证记录数量失败",
|
||||
zap.String("status", string(status)),
|
||||
zap.Error(err),
|
||||
)
|
||||
return 0, fmt.Errorf("统计认证记录数量失败: %w", err)
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// CountByUserID 根据用户ID统计认证记录数量
|
||||
func (r *GormCertificationRepository) CountByUserID(ctx context.Context, userID string) (int64, error) {
|
||||
var count int64
|
||||
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("user_id = ?", userID).Count(&count).Error; err != nil {
|
||||
r.logger.Error("统计用户认证记录数量失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
return 0, fmt.Errorf("统计用户认证记录数量失败: %w", err)
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
@@ -1,175 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/certification/entities"
|
||||
)
|
||||
|
||||
// GormContractRecordRepository GORM合同记录仓储实现
|
||||
type GormContractRecordRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewGormContractRecordRepository 创建GORM合同记录仓储
|
||||
func NewGormContractRecordRepository(db *gorm.DB, logger *zap.Logger) ContractRecordRepository {
|
||||
return &GormContractRecordRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建合同记录
|
||||
func (r *GormContractRecordRepository) Create(ctx context.Context, record *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 fmt.Errorf("创建合同记录失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("合同记录创建成功",
|
||||
zap.String("id", record.ID),
|
||||
zap.String("contract_type", record.ContractType),
|
||||
)
|
||||
|
||||
return 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 nil, fmt.Errorf("合同记录不存在")
|
||||
}
|
||||
r.logger.Error("获取合同记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取合同记录失败: %w", err)
|
||||
}
|
||||
|
||||
return &record, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
return records, 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
return records, 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)
|
||||
}
|
||||
|
||||
return records, 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)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// GetExpiredContracts 获取已过期的合同记录(通用方法)
|
||||
func (r *GormContractRecordRepository) GetExpiredContracts(ctx context.Context, limit int) ([]*entities.ContractRecord, error) {
|
||||
return r.GetExpiredSigningContracts(ctx, limit)
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/certification/entities"
|
||||
)
|
||||
|
||||
// GormEnterpriseRepository GORM企业信息仓储实现
|
||||
type GormEnterpriseRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewGormEnterpriseRepository 创建GORM企业信息仓储
|
||||
func NewGormEnterpriseRepository(db *gorm.DB, logger *zap.Logger) EnterpriseRepository {
|
||||
return &GormEnterpriseRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建企业信息
|
||||
func (r *GormEnterpriseRepository) Create(ctx context.Context, enterprise *entities.Enterprise) error {
|
||||
if err := r.db.WithContext(ctx).Create(enterprise).Error; err != nil {
|
||||
r.logger.Error("创建企业信息失败",
|
||||
zap.String("certification_id", enterprise.CertificationID),
|
||||
zap.String("company_name", enterprise.CompanyName),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("创建企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("企业信息创建成功",
|
||||
zap.String("id", enterprise.ID),
|
||||
zap.String("company_name", enterprise.CompanyName),
|
||||
zap.String("unified_social_code", enterprise.UnifiedSocialCode),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取企业信息
|
||||
func (r *GormEnterpriseRepository) GetByID(ctx context.Context, id string) (*entities.Enterprise, error) {
|
||||
var enterprise entities.Enterprise
|
||||
|
||||
if err := r.db.WithContext(ctx).First(&enterprise, "id = ?", id).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("企业信息不存在")
|
||||
}
|
||||
r.logger.Error("获取企业信息失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
return &enterprise, nil
|
||||
}
|
||||
|
||||
// GetByCertificationID 根据认证ID获取企业信息
|
||||
func (r *GormEnterpriseRepository) GetByCertificationID(ctx context.Context, certificationID string) (*entities.Enterprise, error) {
|
||||
var enterprise entities.Enterprise
|
||||
|
||||
if err := r.db.WithContext(ctx).First(&enterprise, "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 &enterprise, nil
|
||||
}
|
||||
|
||||
// Update 更新企业信息
|
||||
func (r *GormEnterpriseRepository) Update(ctx context.Context, enterprise *entities.Enterprise) error {
|
||||
if err := r.db.WithContext(ctx).Save(enterprise).Error; err != nil {
|
||||
r.logger.Error("更新企业信息失败",
|
||||
zap.String("id", enterprise.ID),
|
||||
zap.String("company_name", enterprise.CompanyName),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("更新企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("企业信息更新成功",
|
||||
zap.String("id", enterprise.ID),
|
||||
zap.String("company_name", enterprise.CompanyName),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除企业信息(软删除)
|
||||
func (r *GormEnterpriseRepository) Delete(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Delete(&entities.Enterprise{}, "id = ?", id).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
|
||||
}
|
||||
|
||||
// GetByUnifiedSocialCode 根据统一社会信用代码获取企业信息
|
||||
func (r *GormEnterpriseRepository) GetByUnifiedSocialCode(ctx context.Context, code string) (*entities.Enterprise, error) {
|
||||
var enterprise entities.Enterprise
|
||||
|
||||
if err := r.db.WithContext(ctx).First(&enterprise, "unified_social_code = ?", code).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("企业信息不存在")
|
||||
}
|
||||
r.logger.Error("根据统一社会信用代码获取企业信息失败",
|
||||
zap.String("unified_social_code", code),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
return &enterprise, nil
|
||||
}
|
||||
|
||||
// ExistsByUnifiedSocialCode 检查统一社会信用代码是否已存在
|
||||
func (r *GormEnterpriseRepository) ExistsByUnifiedSocialCode(ctx context.Context, code string) (bool, error) {
|
||||
var count int64
|
||||
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Enterprise{}).
|
||||
Where("unified_social_code = ?", code).Count(&count).Error; err != nil {
|
||||
r.logger.Error("检查统一社会信用代码是否存在失败",
|
||||
zap.String("unified_social_code", code),
|
||||
zap.Error(err),
|
||||
)
|
||||
return false, fmt.Errorf("检查统一社会信用代码失败: %w", err)
|
||||
}
|
||||
|
||||
return count > 0, nil
|
||||
}
|
||||
@@ -1,160 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/certification/entities"
|
||||
)
|
||||
|
||||
// GormFaceVerifyRecordRepository GORM人脸识别记录仓储实现
|
||||
type GormFaceVerifyRecordRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewGormFaceVerifyRecordRepository 创建GORM人脸识别记录仓储
|
||||
func NewGormFaceVerifyRecordRepository(db *gorm.DB, logger *zap.Logger) FaceVerifyRecordRepository {
|
||||
return &GormFaceVerifyRecordRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建人脸识别记录
|
||||
func (r *GormFaceVerifyRecordRepository) Create(ctx context.Context, record *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 fmt.Errorf("创建人脸识别记录失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("人脸识别记录创建成功",
|
||||
zap.String("id", record.ID),
|
||||
zap.String("certify_id", record.CertifyID),
|
||||
)
|
||||
|
||||
return 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 nil, fmt.Errorf("人脸识别记录不存在")
|
||||
}
|
||||
r.logger.Error("获取人脸识别记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取人脸识别记录失败: %w", err)
|
||||
}
|
||||
|
||||
return &record, 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
return records, 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
return records, 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)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/certification/entities"
|
||||
)
|
||||
|
||||
// GormLicenseUploadRecordRepository GORM营业执照上传记录仓储实现
|
||||
type GormLicenseUploadRecordRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewGormLicenseUploadRecordRepository 创建GORM营业执照上传记录仓储
|
||||
func NewGormLicenseUploadRecordRepository(db *gorm.DB, logger *zap.Logger) LicenseUploadRecordRepository {
|
||||
return &GormLicenseUploadRecordRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建上传记录
|
||||
func (r *GormLicenseUploadRecordRepository) Create(ctx context.Context, record *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 fmt.Errorf("创建上传记录失败: %w", err)
|
||||
}
|
||||
|
||||
r.logger.Info("上传记录创建成功",
|
||||
zap.String("id", record.ID),
|
||||
zap.String("file_name", record.OriginalFileName),
|
||||
)
|
||||
|
||||
return 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 nil, fmt.Errorf("上传记录不存在")
|
||||
}
|
||||
r.logger.Error("获取上传记录失败",
|
||||
zap.String("id", id),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("获取上传记录失败: %w", err)
|
||||
}
|
||||
|
||||
return &record, 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)
|
||||
}
|
||||
|
||||
return records, int(total), nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tyapi-server/internal/domains/certification/entities"
|
||||
"tyapi-server/internal/domains/certification/enums"
|
||||
)
|
||||
|
||||
// CertificationRepository 认证仓储接口
|
||||
type CertificationRepository interface {
|
||||
// 基础CRUD操作
|
||||
Create(ctx context.Context, cert *entities.Certification) error
|
||||
GetByID(ctx context.Context, id string) (*entities.Certification, error)
|
||||
GetByUserID(ctx context.Context, userID string) (*entities.Certification, error)
|
||||
Update(ctx context.Context, cert *entities.Certification) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
|
||||
// 查询操作
|
||||
List(ctx context.Context, page, pageSize int, status enums.CertificationStatus) ([]*entities.Certification, int, error)
|
||||
GetByStatus(ctx context.Context, status enums.CertificationStatus, page, pageSize int) ([]*entities.Certification, int, error)
|
||||
GetPendingApprovals(ctx context.Context, page, pageSize int) ([]*entities.Certification, int, error)
|
||||
|
||||
// 关联查询
|
||||
GetWithEnterprise(ctx context.Context, id string) (*entities.Certification, error)
|
||||
GetWithAllRelations(ctx context.Context, id string) (*entities.Certification, error)
|
||||
|
||||
// 统计操作
|
||||
CountByStatus(ctx context.Context, status enums.CertificationStatus) (int64, error)
|
||||
CountByUserID(ctx context.Context, userID string) (int64, error)
|
||||
}
|
||||
|
||||
// EnterpriseRepository 企业信息仓储接口
|
||||
type EnterpriseRepository interface {
|
||||
// 基础CRUD操作
|
||||
Create(ctx context.Context, enterprise *entities.Enterprise) error
|
||||
GetByID(ctx context.Context, id string) (*entities.Enterprise, error)
|
||||
GetByCertificationID(ctx context.Context, certificationID string) (*entities.Enterprise, error)
|
||||
Update(ctx context.Context, enterprise *entities.Enterprise) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
|
||||
// 查询操作
|
||||
GetByUnifiedSocialCode(ctx context.Context, code string) (*entities.Enterprise, error)
|
||||
ExistsByUnifiedSocialCode(ctx context.Context, code string) (bool, error)
|
||||
}
|
||||
|
||||
// LicenseUploadRecordRepository 营业执照上传记录仓储接口
|
||||
type LicenseUploadRecordRepository interface {
|
||||
// 基础CRUD操作
|
||||
Create(ctx context.Context, record *entities.LicenseUploadRecord) error
|
||||
GetByID(ctx context.Context, id string) (*entities.LicenseUploadRecord, error)
|
||||
GetByUserID(ctx context.Context, userID string, page, pageSize int) ([]*entities.LicenseUploadRecord, int, error)
|
||||
GetByCertificationID(ctx context.Context, certificationID string) (*entities.LicenseUploadRecord, error)
|
||||
Update(ctx context.Context, record *entities.LicenseUploadRecord) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
|
||||
// 查询操作
|
||||
GetByQiNiuKey(ctx context.Context, key string) (*entities.LicenseUploadRecord, error)
|
||||
GetPendingOCR(ctx context.Context, limit int) ([]*entities.LicenseUploadRecord, error)
|
||||
}
|
||||
|
||||
// FaceVerifyRecordRepository 人脸识别记录仓储接口
|
||||
type FaceVerifyRecordRepository interface {
|
||||
// 基础CRUD操作
|
||||
Create(ctx context.Context, record *entities.FaceVerifyRecord) error
|
||||
GetByID(ctx context.Context, id string) (*entities.FaceVerifyRecord, error)
|
||||
GetByCertifyID(ctx context.Context, certifyID string) (*entities.FaceVerifyRecord, error)
|
||||
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.FaceVerifyRecord, error)
|
||||
Update(ctx context.Context, record *entities.FaceVerifyRecord) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
|
||||
// 查询操作
|
||||
GetByUserID(ctx context.Context, userID string, page, pageSize int) ([]*entities.FaceVerifyRecord, int, error)
|
||||
GetExpiredRecords(ctx context.Context, limit int) ([]*entities.FaceVerifyRecord, error)
|
||||
}
|
||||
|
||||
// ContractRecordRepository 合同记录仓储接口
|
||||
type ContractRecordRepository interface {
|
||||
// 基础CRUD操作
|
||||
Create(ctx context.Context, record *entities.ContractRecord) error
|
||||
GetByID(ctx context.Context, id string) (*entities.ContractRecord, error)
|
||||
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.ContractRecord, error)
|
||||
Update(ctx context.Context, record *entities.ContractRecord) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
|
||||
// 查询操作
|
||||
GetByUserID(ctx context.Context, userID string, page, pageSize int) ([]*entities.ContractRecord, int, error)
|
||||
GetByStatus(ctx context.Context, status string, page, pageSize int) ([]*entities.ContractRecord, int, error)
|
||||
GetExpiredContracts(ctx context.Context, limit int) ([]*entities.ContractRecord, error)
|
||||
}
|
||||
|
||||
// NotificationRecordRepository 通知记录仓储接口
|
||||
type NotificationRecordRepository interface {
|
||||
// 基础CRUD操作
|
||||
Create(ctx context.Context, record *entities.NotificationRecord) error
|
||||
GetByID(ctx context.Context, id string) (*entities.NotificationRecord, error)
|
||||
GetByCertificationID(ctx context.Context, certificationID string) ([]*entities.NotificationRecord, error)
|
||||
Update(ctx context.Context, record *entities.NotificationRecord) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
|
||||
// 查询操作
|
||||
GetByUserID(ctx context.Context, userID string, page, pageSize int) ([]*entities.NotificationRecord, int, error)
|
||||
GetPendingNotifications(ctx context.Context, limit int) ([]*entities.NotificationRecord, error)
|
||||
GetFailedNotifications(ctx context.Context, limit int) ([]*entities.NotificationRecord, error)
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package queries
|
||||
|
||||
import "tyapi-server/internal/domains/certification/enums"
|
||||
|
||||
// ListCertificationsQuery 认证申请列表查询参数
|
||||
type ListCertificationsQuery struct {
|
||||
Page int `json:"page"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// ListEnterprisesQuery 企业信息列表查询参数
|
||||
type ListEnterprisesQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
UserID string `json:"user_id"`
|
||||
EnterpriseName string `json:"enterprise_name"`
|
||||
LicenseNumber string `json:"license_number"`
|
||||
LegalPersonName string `json:"legal_person_name"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
|
||||
// ListFaceVerifyRecordsQuery 人脸识别记录列表查询参数
|
||||
type ListFaceVerifyRecordsQuery 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"`
|
||||
}
|
||||
|
||||
// ListContractRecordsQuery 合同记录列表查询参数
|
||||
type ListContractRecordsQuery 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"`
|
||||
}
|
||||
|
||||
// ListLicenseUploadRecordsQuery 营业执照上传记录列表查询参数
|
||||
type ListLicenseUploadRecordsQuery 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"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/certification/handlers"
|
||||
"tyapi-server/internal/shared/middleware"
|
||||
)
|
||||
|
||||
// CertificationRoutes 认证路由组
|
||||
type CertificationRoutes struct {
|
||||
certificationHandler *handlers.CertificationHandler
|
||||
authMiddleware *middleware.JWTAuthMiddleware
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCertificationRoutes 创建认证路由
|
||||
func NewCertificationRoutes(
|
||||
certificationHandler *handlers.CertificationHandler,
|
||||
authMiddleware *middleware.JWTAuthMiddleware,
|
||||
logger *zap.Logger,
|
||||
) *CertificationRoutes {
|
||||
return &CertificationRoutes{
|
||||
certificationHandler: certificationHandler,
|
||||
authMiddleware: authMiddleware,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterRoutes 注册认证相关路由
|
||||
func (r *CertificationRoutes) RegisterRoutes(router *gin.Engine) {
|
||||
// 认证相关路由组,需要用户认证
|
||||
certificationGroup := router.Group("/api/v1/certification")
|
||||
certificationGroup.Use(r.authMiddleware.Handle())
|
||||
{
|
||||
// 创建认证申请
|
||||
certificationGroup.POST("/create", r.certificationHandler.CreateCertification)
|
||||
|
||||
// 上传营业执照
|
||||
certificationGroup.POST("/upload-license", r.certificationHandler.UploadLicense)
|
||||
|
||||
// 获取认证状态
|
||||
certificationGroup.GET("/status", r.certificationHandler.GetCertificationStatus)
|
||||
|
||||
// 获取进度统计
|
||||
certificationGroup.GET("/progress", r.certificationHandler.GetProgressStats)
|
||||
|
||||
// 提交企业信息
|
||||
certificationGroup.PUT("/:id/submit-info", r.certificationHandler.SubmitEnterpriseInfo)
|
||||
// 发起人脸识别验证
|
||||
certificationGroup.POST("/:id/face-verify", r.certificationHandler.InitiateFaceVerify)
|
||||
// 申请合同签署
|
||||
certificationGroup.POST("/:id/apply-contract", r.certificationHandler.ApplyContract)
|
||||
// 获取认证详情
|
||||
certificationGroup.GET("/:id", r.certificationHandler.GetCertificationDetails)
|
||||
// 重试认证步骤
|
||||
certificationGroup.POST("/:id/retry", r.certificationHandler.RetryStep)
|
||||
}
|
||||
|
||||
r.logger.Info("认证路由注册完成")
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -124,17 +124,17 @@ func (sm *CertificationStateMachine) TransitionTo(
|
||||
}
|
||||
|
||||
// 执行状态转换前的验证
|
||||
if err := sm.validateTransition(ctx, cert, targetStatus, metadata); err != nil {
|
||||
if err := sm.validateTransition(ctx, &cert, targetStatus, metadata); err != nil {
|
||||
return fmt.Errorf("状态转换验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 更新状态和时间戳
|
||||
oldStatus := cert.Status
|
||||
cert.Status = targetStatus
|
||||
sm.updateTimestamp(cert, targetStatus)
|
||||
sm.updateTimestamp(&cert, targetStatus)
|
||||
|
||||
// 更新其他字段
|
||||
sm.updateCertificationFields(cert, targetStatus, metadata)
|
||||
sm.updateCertificationFields(&cert, targetStatus, metadata)
|
||||
|
||||
// 保存到数据库
|
||||
if err := sm.certRepo.Update(ctx, cert); err != nil {
|
||||
@@ -215,9 +215,9 @@ func (sm *CertificationStateMachine) validateTransition(
|
||||
switch targetStatus {
|
||||
case enums.StatusInfoSubmitted:
|
||||
// 验证企业信息是否完整
|
||||
if cert.EnterpriseID == nil {
|
||||
return fmt.Errorf("企业信息未提交")
|
||||
}
|
||||
// 这里应该检查用户是否有企业信息,通过用户域的企业服务验证
|
||||
// 暂时跳过验证,由应用服务层协调
|
||||
break
|
||||
|
||||
case enums.StatusFaceVerified:
|
||||
// 验证人脸识别是否成功
|
||||
@@ -285,3 +285,164 @@ func (sm *CertificationStateMachine) GetTransitionAction(
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetTransitionHistory 获取状态转换历史
|
||||
func (sm *CertificationStateMachine) GetTransitionHistory(ctx context.Context, certificationID string) ([]map[string]interface{}, error) {
|
||||
cert, err := sm.certRepo.GetByID(ctx, certificationID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取认证记录失败: %w", err)
|
||||
}
|
||||
|
||||
history := []map[string]interface{}{}
|
||||
|
||||
// 添加创建时间
|
||||
history = append(history, map[string]interface{}{
|
||||
"status": "CREATED",
|
||||
"timestamp": cert.CreatedAt,
|
||||
"action": "create",
|
||||
"performer": "system",
|
||||
"metadata": map[string]interface{}{},
|
||||
})
|
||||
|
||||
// 添加各个时间节点的状态转换
|
||||
if cert.InfoSubmittedAt != nil {
|
||||
history = append(history, map[string]interface{}{
|
||||
"status": string(enums.StatusInfoSubmitted),
|
||||
"timestamp": *cert.InfoSubmittedAt,
|
||||
"action": "submit_info",
|
||||
"performer": "user",
|
||||
"metadata": map[string]interface{}{},
|
||||
})
|
||||
}
|
||||
|
||||
if cert.FaceVerifiedAt != nil {
|
||||
history = append(history, map[string]interface{}{
|
||||
"status": string(enums.StatusFaceVerified),
|
||||
"timestamp": *cert.FaceVerifiedAt,
|
||||
"action": "face_verify",
|
||||
"performer": "system",
|
||||
"metadata": map[string]interface{}{},
|
||||
})
|
||||
}
|
||||
|
||||
if cert.ContractAppliedAt != nil {
|
||||
history = append(history, map[string]interface{}{
|
||||
"status": string(enums.StatusContractApplied),
|
||||
"timestamp": *cert.ContractAppliedAt,
|
||||
"action": "apply_contract",
|
||||
"performer": "user",
|
||||
"metadata": map[string]interface{}{},
|
||||
})
|
||||
}
|
||||
|
||||
if cert.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 != "" {
|
||||
metadata["contract_url"] = cert.ContractURL
|
||||
}
|
||||
|
||||
history = append(history, map[string]interface{}{
|
||||
"status": string(enums.StatusContractSigned),
|
||||
"timestamp": *cert.ContractSignedAt,
|
||||
"action": "user_sign",
|
||||
"performer": "user",
|
||||
"metadata": metadata,
|
||||
})
|
||||
}
|
||||
|
||||
if cert.CompletedAt != nil {
|
||||
history = append(history, map[string]interface{}{
|
||||
"status": string(enums.StatusCompleted),
|
||||
"timestamp": *cert.CompletedAt,
|
||||
"action": "system_complete",
|
||||
"performer": "system",
|
||||
"metadata": map[string]interface{}{},
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package entities
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -65,3 +66,11 @@ func (u *UserSecrets) Deactivate() {
|
||||
func (u *UserSecrets) Activate() {
|
||||
u.IsActive = true
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (u *UserSecrets) BeforeCreate(tx *gorm.DB) error {
|
||||
if u.ID == "" {
|
||||
u.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/shopspring/decimal"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -69,3 +70,11 @@ func (w *Wallet) SubtractBalance(amount decimal.Decimal) error {
|
||||
func (w *Wallet) GetFormattedBalance() string {
|
||||
return w.Balance.String()
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (w *Wallet) BeforeCreate(tx *gorm.DB) error {
|
||||
if w.ID == "" {
|
||||
w.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/finance/dto"
|
||||
"tyapi-server/internal/domains/finance/services"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// FinanceHandler 财务HTTP处理器
|
||||
type FinanceHandler struct {
|
||||
financeService *services.FinanceService
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewFinanceHandler 创建财务HTTP处理器
|
||||
func NewFinanceHandler(
|
||||
financeService *services.FinanceService,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
logger *zap.Logger,
|
||||
) *FinanceHandler {
|
||||
return &FinanceHandler{
|
||||
financeService: financeService,
|
||||
responseBuilder: responseBuilder,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWallet 创建钱包
|
||||
// @Summary 创建钱包
|
||||
// @Description 为用户创建钱包
|
||||
// @Tags 财务系统
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.CreateWalletRequest true "创建钱包请求"
|
||||
// @Success 201 {object} dto.CreateWalletResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Router /finance/wallet [post]
|
||||
func (h *FinanceHandler) CreateWallet(c *gin.Context) {
|
||||
var req dto.CreateWalletRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("创建钱包参数验证失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.financeService.CreateWallet(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
h.logger.Error("创建钱包失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Created(c, response, "钱包创建成功")
|
||||
}
|
||||
|
||||
// GetWallet 获取钱包信息
|
||||
// @Summary 获取钱包信息
|
||||
// @Description 获取用户钱包信息
|
||||
// @Tags 财务系统
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param user_id query string true "用户ID"
|
||||
// @Success 200 {object} dto.WalletInfo
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Failure 404 {object} interfaces.ErrorResponse
|
||||
// @Router /finance/wallet [get]
|
||||
func (h *FinanceHandler) GetWallet(c *gin.Context) {
|
||||
userID := c.Query("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.BadRequest(c, "用户ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
wallet, err := h.financeService.GetWallet(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取钱包信息失败", zap.Error(err))
|
||||
h.responseBuilder.NotFound(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, wallet, "获取钱包信息成功")
|
||||
}
|
||||
|
||||
// UpdateWallet 更新钱包
|
||||
// @Summary 更新钱包
|
||||
// @Description 更新用户钱包信息
|
||||
// @Tags 财务系统
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.UpdateWalletRequest true "更新钱包请求"
|
||||
// @Success 200 {object} interfaces.SuccessResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Failure 404 {object} interfaces.ErrorResponse
|
||||
// @Router /finance/wallet [put]
|
||||
func (h *FinanceHandler) UpdateWallet(c *gin.Context) {
|
||||
var req dto.UpdateWalletRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("更新钱包参数验证失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.financeService.UpdateWallet(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
h.logger.Error("更新钱包失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "钱包更新成功")
|
||||
}
|
||||
|
||||
// Recharge 充值
|
||||
// @Summary 钱包充值
|
||||
// @Description 为用户钱包充值
|
||||
// @Tags 财务系统
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.RechargeRequest true "充值请求"
|
||||
// @Success 200 {object} dto.RechargeResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Failure 404 {object} interfaces.ErrorResponse
|
||||
// @Router /finance/wallet/recharge [post]
|
||||
func (h *FinanceHandler) Recharge(c *gin.Context) {
|
||||
var req dto.RechargeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("充值参数验证失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.financeService.Recharge(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
h.logger.Error("充值失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "充值成功")
|
||||
}
|
||||
|
||||
// Withdraw 提现
|
||||
// @Summary 钱包提现
|
||||
// @Description 从用户钱包提现
|
||||
// @Tags 财务系统
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.WithdrawRequest true "提现请求"
|
||||
// @Success 200 {object} dto.WithdrawResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Failure 404 {object} interfaces.ErrorResponse
|
||||
// @Router /finance/wallet/withdraw [post]
|
||||
func (h *FinanceHandler) Withdraw(c *gin.Context) {
|
||||
var req dto.WithdrawRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("提现参数验证失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.financeService.Withdraw(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
h.logger.Error("提现失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "提现成功")
|
||||
}
|
||||
|
||||
// CreateUserSecrets 创建用户密钥
|
||||
// @Summary 创建用户密钥
|
||||
// @Description 为用户创建访问密钥
|
||||
// @Tags 财务系统
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.CreateUserSecretsRequest true "创建密钥请求"
|
||||
// @Success 201 {object} dto.CreateUserSecretsResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Router /finance/secrets [post]
|
||||
func (h *FinanceHandler) CreateUserSecrets(c *gin.Context) {
|
||||
var req dto.CreateUserSecretsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("创建密钥参数验证失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.financeService.CreateUserSecrets(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
h.logger.Error("创建密钥失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Created(c, response, "密钥创建成功")
|
||||
}
|
||||
|
||||
// GetUserSecrets 获取用户密钥
|
||||
// @Summary 获取用户密钥
|
||||
// @Description 获取用户访问密钥信息
|
||||
// @Tags 财务系统
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param user_id query string true "用户ID"
|
||||
// @Success 200 {object} dto.UserSecretsInfo
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Failure 404 {object} interfaces.ErrorResponse
|
||||
// @Router /finance/secrets [get]
|
||||
func (h *FinanceHandler) GetUserSecrets(c *gin.Context) {
|
||||
userID := c.Query("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.BadRequest(c, "用户ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
secrets, err := h.financeService.GetUserSecrets(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取密钥失败", zap.Error(err))
|
||||
h.responseBuilder.NotFound(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, secrets, "获取密钥成功")
|
||||
}
|
||||
|
||||
// RegenerateAccessKey 重新生成访问密钥
|
||||
// @Summary 重新生成访问密钥
|
||||
// @Description 重新生成用户的访问密钥
|
||||
// @Tags 财务系统
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.RegenerateAccessKeyRequest true "重新生成密钥请求"
|
||||
// @Success 200 {object} dto.RegenerateAccessKeyResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Failure 404 {object} interfaces.ErrorResponse
|
||||
// @Router /finance/secrets/regenerate [post]
|
||||
func (h *FinanceHandler) RegenerateAccessKey(c *gin.Context) {
|
||||
var req dto.RegenerateAccessKeyRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("重新生成密钥参数验证失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.financeService.RegenerateAccessKey(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
h.logger.Error("重新生成密钥失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "密钥重新生成成功")
|
||||
}
|
||||
|
||||
// DeactivateUserSecrets 停用用户密钥
|
||||
// @Summary 停用用户密钥
|
||||
// @Description 停用用户的访问密钥
|
||||
// @Tags 财务系统
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.DeactivateUserSecretsRequest true "停用密钥请求"
|
||||
// @Success 200 {object} interfaces.SuccessResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Failure 404 {object} interfaces.ErrorResponse
|
||||
// @Router /finance/secrets/deactivate [post]
|
||||
func (h *FinanceHandler) DeactivateUserSecrets(c *gin.Context) {
|
||||
var req dto.DeactivateUserSecretsRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("停用密钥参数验证失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
err := h.financeService.DeactivateUserSecrets(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
h.logger.Error("停用密钥失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "密钥停用成功")
|
||||
}
|
||||
|
||||
// WalletTransaction 钱包交易
|
||||
// @Summary 钱包交易
|
||||
// @Description 用户间钱包转账
|
||||
// @Tags 财务系统
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body dto.WalletTransactionRequest true "交易请求"
|
||||
// @Success 200 {object} dto.WalletTransactionResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Failure 404 {object} interfaces.ErrorResponse
|
||||
// @Router /finance/wallet/transaction [post]
|
||||
func (h *FinanceHandler) WalletTransaction(c *gin.Context) {
|
||||
var req dto.WalletTransactionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.logger.Warn("交易参数验证失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.financeService.WalletTransaction(c.Request.Context(), &req)
|
||||
if err != nil {
|
||||
h.logger.Error("交易失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "交易成功")
|
||||
}
|
||||
|
||||
// GetWalletStats 获取钱包统计
|
||||
// @Summary 获取钱包统计
|
||||
// @Description 获取钱包系统统计信息
|
||||
// @Tags 财务系统
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} dto.WalletStatsResponse
|
||||
// @Failure 400 {object} interfaces.ErrorResponse
|
||||
// @Router /finance/wallet/stats [get]
|
||||
func (h *FinanceHandler) GetWalletStats(c *gin.Context) {
|
||||
stats, err := h.financeService.GetWalletStats(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("获取钱包统计失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取统计信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, stats, "获取统计信息成功")
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"tyapi-server/internal/domains/finance/entities"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// WalletRepository 钱包仓储接口
|
||||
type WalletRepository interface {
|
||||
interfaces.Repository[entities.Wallet]
|
||||
|
||||
// 钱包管理
|
||||
FindByUserID(ctx context.Context, userID string) (*entities.Wallet, error)
|
||||
ExistsByUserID(ctx context.Context, userID string) (bool, error)
|
||||
|
||||
// 余额操作
|
||||
UpdateBalance(ctx context.Context, userID string, balance interface{}) error
|
||||
AddBalance(ctx context.Context, userID string, amount interface{}) error
|
||||
SubtractBalance(ctx context.Context, userID string, amount interface{}) error
|
||||
|
||||
// 统计查询
|
||||
GetTotalBalance(ctx context.Context) (interface{}, error)
|
||||
GetActiveWalletCount(ctx context.Context) (int64, error)
|
||||
}
|
||||
|
||||
// UserSecretsRepository 用户密钥仓储接口
|
||||
type UserSecretsRepository interface {
|
||||
interfaces.Repository[entities.UserSecrets]
|
||||
|
||||
// 密钥管理
|
||||
FindByUserID(ctx context.Context, userID string) (*entities.UserSecrets, error)
|
||||
FindByAccessID(ctx context.Context, accessID string) (*entities.UserSecrets, error)
|
||||
ExistsByUserID(ctx context.Context, userID string) (bool, error)
|
||||
ExistsByAccessID(ctx context.Context, accessID string) (bool, error)
|
||||
|
||||
// 密钥操作
|
||||
UpdateLastUsedAt(ctx context.Context, accessID string) error
|
||||
DeactivateByUserID(ctx context.Context, userID string) error
|
||||
RegenerateAccessKey(ctx context.Context, userID string, accessID, accessKey string) error
|
||||
|
||||
// 过期密钥清理
|
||||
GetExpiredSecrets(ctx context.Context) ([]entities.UserSecrets, error)
|
||||
DeleteExpiredSecrets(ctx context.Context) error
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/domains/finance/entities"
|
||||
"tyapi-server/internal/domains/finance/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// FinanceStats 财务统计信息
|
||||
type FinanceStats struct {
|
||||
TotalWallets int64
|
||||
ActiveWallets int64
|
||||
TotalBalance string
|
||||
TodayTransactions int64
|
||||
}
|
||||
|
||||
// WalletRepository 钱包仓储接口
|
||||
type WalletRepository interface {
|
||||
interfaces.Repository[entities.Wallet]
|
||||
|
||||
// 基础查询 - 直接使用实体
|
||||
GetByUserID(ctx context.Context, userID string) (*entities.Wallet, error)
|
||||
GetByWalletAddress(ctx context.Context, walletAddress string) (*entities.Wallet, error)
|
||||
GetByWalletType(ctx context.Context, userID string, walletType string) (*entities.Wallet, error)
|
||||
|
||||
// 复杂查询 - 使用查询参数
|
||||
ListWallets(ctx context.Context, query *queries.ListWalletsQuery) ([]*entities.Wallet, int64, error)
|
||||
|
||||
// 业务操作
|
||||
UpdateBalance(ctx context.Context, walletID string, balance string) error
|
||||
AddBalance(ctx context.Context, walletID string, amount string) error
|
||||
SubtractBalance(ctx context.Context, walletID string, amount string) error
|
||||
ActivateWallet(ctx context.Context, walletID string) error
|
||||
DeactivateWallet(ctx context.Context, walletID string) error
|
||||
|
||||
// 统计信息
|
||||
GetStats(ctx context.Context) (*FinanceStats, error)
|
||||
GetUserWalletStats(ctx context.Context, userID string) (*FinanceStats, error)
|
||||
}
|
||||
|
||||
// UserSecretsRepository 用户密钥仓储接口
|
||||
type UserSecretsRepository interface {
|
||||
interfaces.Repository[entities.UserSecrets]
|
||||
|
||||
// 基础查询 - 直接使用实体
|
||||
GetByUserID(ctx context.Context, userID string) (*entities.UserSecrets, error)
|
||||
GetBySecretType(ctx context.Context, userID string, secretType string) (*entities.UserSecrets, error)
|
||||
|
||||
// 复杂查询 - 使用查询参数
|
||||
ListUserSecrets(ctx context.Context, query *queries.ListUserSecretsQuery) ([]*entities.UserSecrets, int64, error)
|
||||
|
||||
// 业务操作
|
||||
UpdateSecret(ctx context.Context, userID string, secretType string, secretValue string) error
|
||||
DeleteSecret(ctx context.Context, userID string, secretType string) error
|
||||
ValidateSecret(ctx context.Context, userID string, secretType string, secretValue string) (bool, error)
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package queries
|
||||
|
||||
// ListWalletsQuery 钱包列表查询参数
|
||||
type ListWalletsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
UserID string `json:"user_id"`
|
||||
WalletType string `json:"wallet_type"`
|
||||
WalletAddress string `json:"wallet_address"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
|
||||
// ListUserSecretsQuery 用户密钥列表查询参数
|
||||
type ListUserSecretsQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
UserID string `json:"user_id"`
|
||||
SecretType string `json:"secret_type"`
|
||||
IsActive *bool `json:"is_active"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"tyapi-server/internal/domains/finance/handlers"
|
||||
)
|
||||
|
||||
// RegisterFinanceRoutes 注册财务路由
|
||||
func RegisterFinanceRoutes(router *gin.Engine, financeHandler *handlers.FinanceHandler) {
|
||||
// 财务路由组
|
||||
financeGroup := router.Group("/api/finance")
|
||||
{
|
||||
// 钱包相关路由
|
||||
walletGroup := financeGroup.Group("/wallet")
|
||||
{
|
||||
walletGroup.POST("", financeHandler.CreateWallet) // 创建钱包
|
||||
walletGroup.GET("", financeHandler.GetWallet) // 获取钱包信息
|
||||
walletGroup.PUT("", financeHandler.UpdateWallet) // 更新钱包
|
||||
walletGroup.POST("/recharge", financeHandler.Recharge) // 充值
|
||||
walletGroup.POST("/withdraw", financeHandler.Withdraw) // 提现
|
||||
walletGroup.POST("/transaction", financeHandler.WalletTransaction) // 钱包交易
|
||||
walletGroup.GET("/stats", financeHandler.GetWalletStats) // 获取钱包统计
|
||||
}
|
||||
|
||||
// 用户密钥相关路由
|
||||
secretsGroup := financeGroup.Group("/secrets")
|
||||
{
|
||||
secretsGroup.POST("", financeHandler.CreateUserSecrets) // 创建用户密钥
|
||||
secretsGroup.GET("", financeHandler.GetUserSecrets) // 获取用户密钥
|
||||
secretsGroup.POST("/regenerate", financeHandler.RegenerateAccessKey) // 重新生成访问密钥
|
||||
secretsGroup.POST("/deactivate", financeHandler.DeactivateUserSecrets) // 停用用户密钥
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,470 +1,24 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/finance/dto"
|
||||
"tyapi-server/internal/domains/finance/entities"
|
||||
"tyapi-server/internal/domains/finance/repositories"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// FinanceService 财务服务
|
||||
// FinanceService 财务领域服务
|
||||
type FinanceService struct {
|
||||
walletRepo repositories.WalletRepository
|
||||
userSecretsRepo repositories.UserSecretsRepository
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
logger *zap.Logger
|
||||
walletRepo repositories.WalletRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewFinanceService 创建财务服务
|
||||
// NewFinanceService 创建财务领域服务
|
||||
func NewFinanceService(
|
||||
walletRepo repositories.WalletRepository,
|
||||
userSecretsRepo repositories.UserSecretsRepository,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
logger *zap.Logger,
|
||||
) *FinanceService {
|
||||
return &FinanceService{
|
||||
walletRepo: walletRepo,
|
||||
userSecretsRepo: userSecretsRepo,
|
||||
responseBuilder: responseBuilder,
|
||||
logger: logger,
|
||||
walletRepo: walletRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWallet 创建钱包
|
||||
func (s *FinanceService) CreateWallet(ctx context.Context, req *dto.CreateWalletRequest) (*dto.CreateWalletResponse, error) {
|
||||
s.logger.Info("创建钱包", zap.String("user_id", req.UserID))
|
||||
|
||||
// 检查用户是否已有钱包
|
||||
exists, err := s.walletRepo.ExistsByUserID(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("检查钱包存在性失败: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("用户已存在钱包")
|
||||
}
|
||||
|
||||
// 创建钱包
|
||||
wallet := entities.Wallet{
|
||||
ID: s.generateID(),
|
||||
UserID: req.UserID,
|
||||
IsActive: true,
|
||||
Balance: decimal.Zero,
|
||||
}
|
||||
|
||||
if err := s.walletRepo.Create(ctx, wallet); err != nil {
|
||||
return nil, fmt.Errorf("创建钱包失败: %w", err)
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
walletInfo := dto.WalletInfo{
|
||||
ID: wallet.ID,
|
||||
UserID: wallet.UserID,
|
||||
IsActive: wallet.IsActive,
|
||||
Balance: wallet.Balance,
|
||||
CreatedAt: wallet.CreatedAt,
|
||||
UpdatedAt: wallet.UpdatedAt,
|
||||
}
|
||||
|
||||
s.logger.Info("钱包创建成功", zap.String("wallet_id", wallet.ID))
|
||||
return &dto.CreateWalletResponse{Wallet: walletInfo}, nil
|
||||
}
|
||||
|
||||
// GetWallet 获取钱包信息
|
||||
func (s *FinanceService) GetWallet(ctx context.Context, userID string) (*dto.WalletInfo, error) {
|
||||
s.logger.Info("获取钱包信息", zap.String("user_id", userID))
|
||||
|
||||
wallet, err := s.walletRepo.FindByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("钱包不存在")
|
||||
}
|
||||
|
||||
walletInfo := dto.WalletInfo{
|
||||
ID: wallet.ID,
|
||||
UserID: wallet.UserID,
|
||||
IsActive: wallet.IsActive,
|
||||
Balance: wallet.Balance,
|
||||
CreatedAt: wallet.CreatedAt,
|
||||
UpdatedAt: wallet.UpdatedAt,
|
||||
}
|
||||
|
||||
return &walletInfo, nil
|
||||
}
|
||||
|
||||
// UpdateWallet 更新钱包
|
||||
func (s *FinanceService) UpdateWallet(ctx context.Context, req *dto.UpdateWalletRequest) error {
|
||||
s.logger.Info("更新钱包", zap.String("user_id", req.UserID))
|
||||
|
||||
wallet, err := s.walletRepo.FindByUserID(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("钱包不存在")
|
||||
}
|
||||
|
||||
// 更新字段
|
||||
if !req.Balance.IsZero() {
|
||||
wallet.Balance = req.Balance
|
||||
}
|
||||
if req.IsActive != nil {
|
||||
wallet.IsActive = *req.IsActive
|
||||
}
|
||||
|
||||
if err := s.walletRepo.Update(ctx, *wallet); err != nil {
|
||||
return fmt.Errorf("更新钱包失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("钱包更新成功", zap.String("user_id", req.UserID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Recharge 充值
|
||||
func (s *FinanceService) Recharge(ctx context.Context, req *dto.RechargeRequest) (*dto.RechargeResponse, error) {
|
||||
s.logger.Info("钱包充值", zap.String("user_id", req.UserID), zap.String("amount", req.Amount.String()))
|
||||
|
||||
// 验证金额
|
||||
if req.Amount.LessThanOrEqual(decimal.Zero) {
|
||||
return nil, fmt.Errorf("充值金额必须大于0")
|
||||
}
|
||||
|
||||
// 获取钱包
|
||||
wallet, err := s.walletRepo.FindByUserID(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("钱包不存在")
|
||||
}
|
||||
|
||||
// 检查钱包状态
|
||||
if !wallet.IsActive {
|
||||
return nil, fmt.Errorf("钱包已被禁用")
|
||||
}
|
||||
|
||||
// 增加余额
|
||||
if err := s.walletRepo.AddBalance(ctx, req.UserID, req.Amount); err != nil {
|
||||
return nil, fmt.Errorf("充值失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取更新后的余额
|
||||
updatedWallet, err := s.walletRepo.FindByUserID(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取更新后余额失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("充值成功", zap.String("user_id", req.UserID), zap.String("amount", req.Amount.String()))
|
||||
return &dto.RechargeResponse{
|
||||
WalletID: updatedWallet.ID,
|
||||
Amount: req.Amount,
|
||||
Balance: updatedWallet.Balance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Withdraw 提现
|
||||
func (s *FinanceService) Withdraw(ctx context.Context, req *dto.WithdrawRequest) (*dto.WithdrawResponse, error) {
|
||||
s.logger.Info("钱包提现", zap.String("user_id", req.UserID), zap.String("amount", req.Amount.String()))
|
||||
|
||||
// 验证金额
|
||||
if req.Amount.LessThanOrEqual(decimal.Zero) {
|
||||
return nil, fmt.Errorf("提现金额必须大于0")
|
||||
}
|
||||
|
||||
// 获取钱包
|
||||
wallet, err := s.walletRepo.FindByUserID(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("钱包不存在")
|
||||
}
|
||||
|
||||
// 检查钱包状态
|
||||
if !wallet.IsActive {
|
||||
return nil, fmt.Errorf("钱包已被禁用")
|
||||
}
|
||||
|
||||
// 检查余额是否足够
|
||||
if wallet.Balance.LessThan(req.Amount) {
|
||||
return nil, fmt.Errorf("余额不足")
|
||||
}
|
||||
|
||||
// 减少余额
|
||||
if err := s.walletRepo.SubtractBalance(ctx, req.UserID, req.Amount); err != nil {
|
||||
return nil, fmt.Errorf("提现失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取更新后的余额
|
||||
updatedWallet, err := s.walletRepo.FindByUserID(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取更新后余额失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("提现成功", zap.String("user_id", req.UserID), zap.String("amount", req.Amount.String()))
|
||||
return &dto.WithdrawResponse{
|
||||
WalletID: updatedWallet.ID,
|
||||
Amount: req.Amount,
|
||||
Balance: updatedWallet.Balance,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateUserSecrets 创建用户密钥
|
||||
func (s *FinanceService) CreateUserSecrets(ctx context.Context, req *dto.CreateUserSecretsRequest) (*dto.CreateUserSecretsResponse, error) {
|
||||
s.logger.Info("创建用户密钥", zap.String("user_id", req.UserID))
|
||||
|
||||
// 检查用户是否已有密钥
|
||||
exists, err := s.userSecretsRepo.ExistsByUserID(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("检查密钥存在性失败: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("用户已存在密钥")
|
||||
}
|
||||
|
||||
// 生成访问ID和密钥
|
||||
accessID := s.generateAccessID()
|
||||
accessKey := s.generateAccessKey()
|
||||
|
||||
// 创建密钥
|
||||
secrets := entities.UserSecrets{
|
||||
ID: s.generateID(),
|
||||
UserID: req.UserID,
|
||||
AccessID: accessID,
|
||||
AccessKey: accessKey,
|
||||
IsActive: true,
|
||||
ExpiresAt: req.ExpiresAt,
|
||||
}
|
||||
|
||||
if err := s.userSecretsRepo.Create(ctx, secrets); err != nil {
|
||||
return nil, fmt.Errorf("创建密钥失败: %w", err)
|
||||
}
|
||||
|
||||
// 构建响应
|
||||
secretsInfo := dto.UserSecretsInfo{
|
||||
ID: secrets.ID,
|
||||
UserID: secrets.UserID,
|
||||
AccessID: secrets.AccessID,
|
||||
AccessKey: secrets.AccessKey,
|
||||
IsActive: secrets.IsActive,
|
||||
LastUsedAt: secrets.LastUsedAt,
|
||||
ExpiresAt: secrets.ExpiresAt,
|
||||
CreatedAt: secrets.CreatedAt,
|
||||
UpdatedAt: secrets.UpdatedAt,
|
||||
}
|
||||
|
||||
s.logger.Info("用户密钥创建成功", zap.String("user_id", req.UserID))
|
||||
return &dto.CreateUserSecretsResponse{Secrets: secretsInfo}, nil
|
||||
}
|
||||
|
||||
// GetUserSecrets 获取用户密钥
|
||||
func (s *FinanceService) GetUserSecrets(ctx context.Context, userID string) (*dto.UserSecretsInfo, error) {
|
||||
s.logger.Info("获取用户密钥", zap.String("user_id", userID))
|
||||
|
||||
secrets, err := s.userSecretsRepo.FindByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("密钥不存在")
|
||||
}
|
||||
|
||||
secretsInfo := dto.UserSecretsInfo{
|
||||
ID: secrets.ID,
|
||||
UserID: secrets.UserID,
|
||||
AccessID: secrets.AccessID,
|
||||
AccessKey: secrets.AccessKey,
|
||||
IsActive: secrets.IsActive,
|
||||
LastUsedAt: secrets.LastUsedAt,
|
||||
ExpiresAt: secrets.ExpiresAt,
|
||||
CreatedAt: secrets.CreatedAt,
|
||||
UpdatedAt: secrets.UpdatedAt,
|
||||
}
|
||||
|
||||
return &secretsInfo, nil
|
||||
}
|
||||
|
||||
// RegenerateAccessKey 重新生成访问密钥
|
||||
func (s *FinanceService) RegenerateAccessKey(ctx context.Context, req *dto.RegenerateAccessKeyRequest) (*dto.RegenerateAccessKeyResponse, error) {
|
||||
s.logger.Info("重新生成访问密钥", zap.String("user_id", req.UserID))
|
||||
|
||||
// 检查密钥是否存在
|
||||
secrets, err := s.userSecretsRepo.FindByUserID(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("密钥不存在")
|
||||
}
|
||||
|
||||
// 生成新的访问ID和密钥
|
||||
newAccessID := s.generateAccessID()
|
||||
newAccessKey := s.generateAccessKey()
|
||||
|
||||
// 更新密钥
|
||||
if err := s.userSecretsRepo.RegenerateAccessKey(ctx, req.UserID, newAccessID, newAccessKey); err != nil {
|
||||
return nil, fmt.Errorf("重新生成密钥失败: %w", err)
|
||||
}
|
||||
|
||||
// 更新过期时间
|
||||
if req.ExpiresAt != nil {
|
||||
secrets.ExpiresAt = req.ExpiresAt
|
||||
if err := s.userSecretsRepo.Update(ctx, *secrets); err != nil {
|
||||
s.logger.Error("更新密钥过期时间失败", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
s.logger.Info("访问密钥重新生成成功", zap.String("user_id", req.UserID))
|
||||
return &dto.RegenerateAccessKeyResponse{
|
||||
AccessID: newAccessID,
|
||||
AccessKey: newAccessKey,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeactivateUserSecrets 停用用户密钥
|
||||
func (s *FinanceService) DeactivateUserSecrets(ctx context.Context, req *dto.DeactivateUserSecretsRequest) error {
|
||||
s.logger.Info("停用用户密钥", zap.String("user_id", req.UserID))
|
||||
|
||||
// 检查密钥是否存在
|
||||
if _, err := s.userSecretsRepo.FindByUserID(ctx, req.UserID); err != nil {
|
||||
return fmt.Errorf("密钥不存在")
|
||||
}
|
||||
|
||||
// 停用密钥
|
||||
if err := s.userSecretsRepo.DeactivateByUserID(ctx, req.UserID); err != nil {
|
||||
return fmt.Errorf("停用密钥失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("用户密钥停用成功", zap.String("user_id", req.UserID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// WalletTransaction 钱包交易
|
||||
func (s *FinanceService) WalletTransaction(ctx context.Context, req *dto.WalletTransactionRequest) (*dto.WalletTransactionResponse, error) {
|
||||
s.logger.Info("钱包交易",
|
||||
zap.String("from_user_id", req.FromUserID),
|
||||
zap.String("to_user_id", req.ToUserID),
|
||||
zap.String("amount", req.Amount.String()))
|
||||
|
||||
// 验证金额
|
||||
if req.Amount.LessThanOrEqual(decimal.Zero) {
|
||||
return nil, fmt.Errorf("交易金额必须大于0")
|
||||
}
|
||||
|
||||
// 验证用户不能给自己转账
|
||||
if req.FromUserID == req.ToUserID {
|
||||
return nil, fmt.Errorf("不能给自己转账")
|
||||
}
|
||||
|
||||
// 获取转出钱包
|
||||
fromWallet, err := s.walletRepo.FindByUserID(ctx, req.FromUserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("转出钱包不存在")
|
||||
}
|
||||
|
||||
// 获取转入钱包
|
||||
toWallet, err := s.walletRepo.FindByUserID(ctx, req.ToUserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("转入钱包不存在")
|
||||
}
|
||||
|
||||
// 检查钱包状态
|
||||
if !fromWallet.IsActive {
|
||||
return nil, fmt.Errorf("转出钱包已被禁用")
|
||||
}
|
||||
if !toWallet.IsActive {
|
||||
return nil, fmt.Errorf("转入钱包已被禁用")
|
||||
}
|
||||
|
||||
// 检查余额是否足够
|
||||
if fromWallet.Balance.LessThan(req.Amount) {
|
||||
return nil, fmt.Errorf("余额不足")
|
||||
}
|
||||
|
||||
// 执行交易(使用事务)
|
||||
// 这里简化处理,实际应该使用数据库事务
|
||||
if err := s.walletRepo.SubtractBalance(ctx, req.FromUserID, req.Amount); err != nil {
|
||||
return nil, fmt.Errorf("扣款失败: %w", err)
|
||||
}
|
||||
|
||||
if err := s.walletRepo.AddBalance(ctx, req.ToUserID, req.Amount); err != nil {
|
||||
return nil, fmt.Errorf("入账失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取更新后的余额
|
||||
updatedFromWallet, err := s.walletRepo.FindByUserID(ctx, req.FromUserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取转出后余额失败: %w", err)
|
||||
}
|
||||
|
||||
updatedToWallet, err := s.walletRepo.FindByUserID(ctx, req.ToUserID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取转入后余额失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("钱包交易成功",
|
||||
zap.String("from_user_id", req.FromUserID),
|
||||
zap.String("to_user_id", req.ToUserID),
|
||||
zap.String("amount", req.Amount.String()))
|
||||
|
||||
return &dto.WalletTransactionResponse{
|
||||
TransactionID: s.generateID(),
|
||||
FromUserID: req.FromUserID,
|
||||
ToUserID: req.ToUserID,
|
||||
Amount: req.Amount,
|
||||
FromBalance: updatedFromWallet.Balance,
|
||||
ToBalance: updatedToWallet.Balance,
|
||||
Notes: req.Notes,
|
||||
CreatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetWalletStats 获取钱包统计
|
||||
func (s *FinanceService) GetWalletStats(ctx context.Context) (*dto.WalletStatsResponse, error) {
|
||||
s.logger.Info("获取钱包统计")
|
||||
|
||||
// 获取总钱包数
|
||||
totalWallets, err := s.walletRepo.Count(ctx, interfaces.CountOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取总钱包数失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取激活钱包数
|
||||
activeWallets, err := s.walletRepo.GetActiveWalletCount(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取激活钱包数失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取总余额
|
||||
totalBalance, err := s.walletRepo.GetTotalBalance(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("获取总余额失败: %w", err)
|
||||
}
|
||||
|
||||
// 这里简化处理,实际应该查询交易记录表
|
||||
todayTransactions := int64(0)
|
||||
todayVolume := decimal.Zero
|
||||
|
||||
return &dto.WalletStatsResponse{
|
||||
TotalWallets: totalWallets,
|
||||
ActiveWallets: activeWallets,
|
||||
TotalBalance: totalBalance.(decimal.Decimal),
|
||||
TodayTransactions: todayTransactions,
|
||||
TodayVolume: todayVolume,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// generateID 生成ID
|
||||
func (s *FinanceService) generateID() string {
|
||||
bytes := make([]byte, 16)
|
||||
rand.Read(bytes)
|
||||
return hex.EncodeToString(bytes)
|
||||
}
|
||||
|
||||
// generateAccessID 生成访问ID
|
||||
func (s *FinanceService) generateAccessID() string {
|
||||
bytes := make([]byte, 20)
|
||||
rand.Read(bytes)
|
||||
return hex.EncodeToString(bytes)
|
||||
}
|
||||
|
||||
// generateAccessKey 生成访问密钥
|
||||
func (s *FinanceService) generateAccessKey() string {
|
||||
bytes := make([]byte, 32)
|
||||
rand.Read(bytes)
|
||||
hash := sha256.Sum256(bytes)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Enterprise 企业信息实体
|
||||
// 存储企业认证的核心信息,包括企业四要素和验证状态
|
||||
// 与认证申请是一对一关系,每个认证申请对应一个企业信息
|
||||
type Enterprise struct {
|
||||
// EnterpriseInfo 企业信息实体
|
||||
// 存储用户在认证过程中验证后的企业信息,认证完成后不可修改
|
||||
// 与用户是一对一关系,每个用户最多对应一个企业信息
|
||||
type EnterpriseInfo 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"`
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"企业信息唯一标识"`
|
||||
UserID string `gorm:"type:varchar(36);not null;uniqueIndex" json:"user_id" comment:"关联用户ID"`
|
||||
|
||||
// 企业四要素 - 企业认证的核心信息
|
||||
CompanyName string `gorm:"type:varchar(255);not null" json:"company_name" comment:"企业名称"`
|
||||
@@ -20,17 +22,18 @@ type Enterprise struct {
|
||||
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:"法定代表人身份证号"`
|
||||
|
||||
// 关联的营业执照上传记录
|
||||
LicenseUploadRecordID string `gorm:"type:varchar(36);not null;index" json:"license_upload_record_id" comment:"关联的营业执照上传记录ID"`
|
||||
// 认证状态 - 各环节的验证结果
|
||||
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)"`
|
||||
|
||||
// 验证状态 - 各环节的验证结果
|
||||
IsOCRVerified bool `gorm:"default:false" json:"is_ocr_verified" comment:"OCR验证是否通过"`
|
||||
IsFaceVerified bool `gorm:"default:false" json:"is_face_verified" comment:"人脸识别是否通过"`
|
||||
VerificationData string `gorm:"type:text" json:"verification_data,omitempty" comment:"验证数据(JSON格式)"`
|
||||
// 认证完成时间
|
||||
CertifiedAt *time.Time `json:"certified_at,omitempty" comment:"认证完成时间"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||
@@ -38,18 +41,17 @@ type Enterprise struct {
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
|
||||
|
||||
// 关联关系
|
||||
Certification *Certification `gorm:"foreignKey:CertificationID" json:"certification,omitempty" comment:"关联的认证申请"`
|
||||
LicenseUploadRecord *LicenseUploadRecord `gorm:"foreignKey:LicenseUploadRecordID" json:"license_upload_record,omitempty" comment:"关联的营业执照上传记录"`
|
||||
User *User `gorm:"foreignKey:UserID" json:"user,omitempty" comment:"关联的用户信息"`
|
||||
}
|
||||
|
||||
// TableName 指定数据库表名
|
||||
func (Enterprise) TableName() string {
|
||||
return "enterprises"
|
||||
func (EnterpriseInfo) TableName() string {
|
||||
return "enterprise_infos"
|
||||
}
|
||||
|
||||
// IsComplete 检查企业四要素是否完整
|
||||
// 验证企业名称、统一社会信用代码、法定代表人姓名、身份证号是否都已填写
|
||||
func (e *Enterprise) IsComplete() bool {
|
||||
func (e *EnterpriseInfo) IsComplete() bool {
|
||||
return e.CompanyName != "" &&
|
||||
e.UnifiedSocialCode != "" &&
|
||||
e.LegalPersonName != "" &&
|
||||
@@ -59,8 +61,48 @@ func (e *Enterprise) IsComplete() bool {
|
||||
// Validate 验证企业信息是否有效
|
||||
// 这里可以添加企业信息的业务验证逻辑
|
||||
// 比如统一社会信用代码格式验证、身份证号格式验证等
|
||||
func (e *Enterprise) Validate() error {
|
||||
func (e *EnterpriseInfo) Validate() error {
|
||||
if !e.IsComplete() {
|
||||
return fmt.Errorf("企业信息不完整")
|
||||
}
|
||||
// 这里可以添加企业信息的业务验证逻辑
|
||||
// 比如统一社会信用代码格式验证、身份证号格式验证等
|
||||
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 {
|
||||
if e.ID == "" {
|
||||
e.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -3,32 +3,35 @@ package entities
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// SMSCode 短信验证码记录实体
|
||||
// 记录用户发送的所有短信验证码,支持多种使用场景
|
||||
// 包含验证码的有效期管理、使用状态跟踪、安全审计等功能
|
||||
// @Description 短信验证码记录实体
|
||||
type SMSCode struct {
|
||||
// 基础标识
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"短信验证码记录唯一标识"`
|
||||
Phone string `gorm:"type:varchar(20);not null;index" json:"phone" comment:"接收手机号"`
|
||||
ID string `gorm:"primaryKey;type:varchar(36)" json:"id" comment:"短信验证码记录唯一标识" example:"123e4567-e89b-12d3-a456-426614174000"`
|
||||
Phone string `gorm:"type:varchar(20);not null;index" json:"phone" comment:"接收手机号" example:"13800138000"`
|
||||
Code string `gorm:"type:varchar(10);not null" json:"-" comment:"验证码内容(不返回给前端)"`
|
||||
Scene SMSScene `gorm:"type:varchar(20);not null" json:"scene" comment:"使用场景"`
|
||||
Used bool `gorm:"default:false" json:"used" comment:"是否已使用"`
|
||||
ExpiresAt time.Time `gorm:"not null" json:"expires_at" comment:"过期时间"`
|
||||
Scene SMSScene `gorm:"type:varchar(20);not null" json:"scene" comment:"使用场景" example:"register"`
|
||||
Used bool `gorm:"default:false" json:"used" comment:"是否已使用" example:"false"`
|
||||
ExpiresAt time.Time `gorm:"not null" json:"expires_at" comment:"过期时间" example:"2024-01-01T00:05:00Z"`
|
||||
UsedAt *time.Time `json:"used_at,omitempty" comment:"使用时间"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间" example:"2024-01-01T00:00:00Z"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间" example:"2024-01-01T00:00:00Z"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
|
||||
|
||||
// 额外信息 - 安全审计相关数据
|
||||
IP string `gorm:"type:varchar(45)" json:"ip" comment:"发送IP地址"`
|
||||
UserAgent string `gorm:"type:varchar(500)" json:"user_agent" comment:"客户端信息"`
|
||||
IP string `gorm:"type:varchar(45)" json:"ip" comment:"发送IP地址" example:"192.168.1.1"`
|
||||
UserAgent string `gorm:"type:varchar(500)" json:"user_agent" comment:"客户端信息" example:"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"`
|
||||
}
|
||||
|
||||
// SMSScene 短信验证码使用场景枚举
|
||||
// 定义系统中所有需要使用短信验证码的业务场景
|
||||
// @Description 短信验证码使用场景
|
||||
type SMSScene string
|
||||
|
||||
const (
|
||||
@@ -40,6 +43,14 @@ const (
|
||||
SMSSceneUnbind SMSScene = "unbind" // 解绑手机号 - 解绑当前手机号
|
||||
)
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (s *SMSCode) BeforeCreate(tx *gorm.DB) error {
|
||||
if s.ID == "" {
|
||||
s.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 实现 Entity 接口 - 提供统一的实体管理接口
|
||||
// GetID 获取实体唯一标识
|
||||
func (s *SMSCode) GetID() string {
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -23,6 +24,17 @@ type User struct {
|
||||
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:"软删除时间"`
|
||||
|
||||
// 关联关系
|
||||
EnterpriseInfo *EnterpriseInfo `gorm:"foreignKey:UserID" json:"enterprise_info,omitempty" comment:"企业信息(认证后获得)"`
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (u *User) BeforeCreate(tx *gorm.DB) error {
|
||||
if u.ID == "" {
|
||||
u.ID = uuid.New().String()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 实现 Entity 接口 - 提供统一的实体管理接口
|
||||
@@ -273,3 +285,39 @@ func IsValidationError(err error) bool {
|
||||
var validationErr *ValidationError
|
||||
return errors.As(err, &validationErr)
|
||||
}
|
||||
|
||||
// UserCache 用户缓存结构体
|
||||
// 专门用于缓存序列化,包含Password字段
|
||||
type UserCache struct {
|
||||
// 基础标识
|
||||
ID string `json:"id" comment:"用户唯一标识"`
|
||||
Phone string `json:"phone" comment:"手机号码(登录账号)"`
|
||||
Password string `json:"password" comment:"登录密码(加密存储)"`
|
||||
|
||||
// 时间戳字段
|
||||
CreatedAt time.Time `json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `json:"updated_at" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" comment:"软删除时间"`
|
||||
}
|
||||
|
||||
// ToCache 转换为缓存结构体
|
||||
func (u *User) ToCache() *UserCache {
|
||||
return &UserCache{
|
||||
ID: u.ID,
|
||||
Phone: u.Phone,
|
||||
Password: u.Password,
|
||||
CreatedAt: u.CreatedAt,
|
||||
UpdatedAt: u.UpdatedAt,
|
||||
DeletedAt: u.DeletedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// FromCache 从缓存结构体转换
|
||||
func (u *User) FromCache(cache *UserCache) {
|
||||
u.ID = cache.ID
|
||||
u.Phone = cache.Phone
|
||||
u.Password = cache.Password
|
||||
u.CreatedAt = cache.CreatedAt
|
||||
u.UpdatedAt = cache.UpdatedAt
|
||||
u.DeletedAt = cache.DeletedAt
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// EnterpriseInfoRepository 企业信息仓储接口
|
||||
type EnterpriseInfoRepository interface {
|
||||
interfaces.Repository[entities.EnterpriseInfo]
|
||||
|
||||
// 基础查询
|
||||
GetByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfo, error)
|
||||
GetByUnifiedSocialCode(ctx context.Context, unifiedSocialCode string) (*entities.EnterpriseInfo, error)
|
||||
|
||||
// 业务操作
|
||||
CheckUnifiedSocialCodeExists(ctx context.Context, unifiedSocialCode string, excludeUserID string) (bool, error)
|
||||
UpdateVerificationStatus(ctx context.Context, userID string, isOCRVerified, isFaceVerified, isCertified bool) error
|
||||
UpdateOCRData(ctx context.Context, userID string, rawData string, confidence float64) error
|
||||
CompleteCertification(ctx context.Context, userID string) error
|
||||
}
|
||||
21
internal/domains/user/repositories/queries/user_queries.go
Normal file
21
internal/domains/user/repositories/queries/user_queries.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package queries
|
||||
|
||||
// ListUsersQuery 用户列表查询参数
|
||||
type ListUsersQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Phone string `json:"phone"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
|
||||
// ListSMSCodesQuery 短信验证码列表查询参数
|
||||
type ListSMSCodesQuery struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
Phone string `json:"phone"`
|
||||
Purpose string `json:"purpose"`
|
||||
Status string `json:"status"`
|
||||
StartDate string `json:"start_date"`
|
||||
EndDate string `json:"end_date"`
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// SMSCodeRepository 短信验证码仓储
|
||||
type SMSCodeRepository struct {
|
||||
db *gorm.DB
|
||||
cache interfaces.CacheService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewSMSCodeRepository 创建短信验证码仓储
|
||||
func NewSMSCodeRepository(db *gorm.DB, cache interfaces.CacheService, logger *zap.Logger) *SMSCodeRepository {
|
||||
return &SMSCodeRepository{
|
||||
db: db,
|
||||
cache: cache,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建短信验证码记录
|
||||
func (r *SMSCodeRepository) Create(ctx context.Context, smsCode *entities.SMSCode) error {
|
||||
if err := r.db.WithContext(ctx).Create(smsCode).Error; err != nil {
|
||||
r.logger.Error("创建短信验证码失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 缓存验证码
|
||||
cacheKey := r.buildCacheKey(smsCode.Phone, smsCode.Scene)
|
||||
r.cache.Set(ctx, cacheKey, smsCode, 5*time.Minute)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValidCode 获取有效的验证码
|
||||
func (r *SMSCodeRepository) GetValidCode(ctx context.Context, phone string, scene entities.SMSScene) (*entities.SMSCode, error) {
|
||||
// 先从缓存查找
|
||||
cacheKey := r.buildCacheKey(phone, scene)
|
||||
var smsCode entities.SMSCode
|
||||
if err := r.cache.Get(ctx, cacheKey, &smsCode); err == nil {
|
||||
return &smsCode, nil
|
||||
}
|
||||
|
||||
// 从数据库查找最新的有效验证码
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("phone = ? AND scene = ? AND expires_at > ? AND used_at IS NULL",
|
||||
phone, scene, time.Now()).
|
||||
Order("created_at DESC").
|
||||
First(&smsCode).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
r.cache.Set(ctx, cacheKey, &smsCode, 5*time.Minute)
|
||||
|
||||
return &smsCode, nil
|
||||
}
|
||||
|
||||
// MarkAsUsed 标记验证码为已使用
|
||||
func (r *SMSCodeRepository) MarkAsUsed(ctx context.Context, id string) error {
|
||||
now := time.Now()
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.SMSCode{}).
|
||||
Where("id = ?", id).
|
||||
Update("used_at", now).Error; err != nil {
|
||||
r.logger.Error("标记验证码为已使用失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("验证码已标记为使用", zap.String("code_id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update 更新验证码记录
|
||||
func (r *SMSCodeRepository) Update(ctx context.Context, smsCode *entities.SMSCode) error {
|
||||
if err := r.db.WithContext(ctx).Save(smsCode).Error; err != nil {
|
||||
r.logger.Error("更新验证码记录失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
cacheKey := r.buildCacheKey(smsCode.Phone, smsCode.Scene)
|
||||
r.cache.Set(ctx, cacheKey, smsCode, 5*time.Minute)
|
||||
|
||||
r.logger.Info("验证码记录更新成功", zap.String("code_id", smsCode.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRecentCode 获取最近的验证码记录(不限制有效性)
|
||||
func (r *SMSCodeRepository) GetRecentCode(ctx context.Context, phone string, scene entities.SMSScene) (*entities.SMSCode, error) {
|
||||
var smsCode entities.SMSCode
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("phone = ? AND scene = ?", phone, scene).
|
||||
Order("created_at DESC").
|
||||
First(&smsCode).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &smsCode, nil
|
||||
}
|
||||
|
||||
// CleanupExpired 清理过期的验证码
|
||||
func (r *SMSCodeRepository) CleanupExpired(ctx context.Context) error {
|
||||
result := r.db.WithContext(ctx).
|
||||
Where("expires_at < ?", time.Now()).
|
||||
Delete(&entities.SMSCode{})
|
||||
|
||||
if result.Error != nil {
|
||||
r.logger.Error("清理过期验证码失败", zap.Error(result.Error))
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected > 0 {
|
||||
r.logger.Info("清理过期验证码完成", zap.Int64("count", result.RowsAffected))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CountRecentCodes 统计最近发送的验证码数量
|
||||
func (r *SMSCodeRepository) CountRecentCodes(ctx context.Context, phone string, scene entities.SMSScene, duration time.Duration) (int64, error) {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.SMSCode{}).
|
||||
Where("phone = ? AND scene = ? AND created_at > ?",
|
||||
phone, scene, time.Now().Add(-duration)).
|
||||
Count(&count).Error; err != nil {
|
||||
r.logger.Error("统计最近验证码数量失败", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// buildCacheKey 构建缓存键
|
||||
func (r *SMSCodeRepository) buildCacheKey(phone string, scene entities.SMSScene) string {
|
||||
return fmt.Sprintf("sms_code:%s:%s", phone, string(scene))
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// 定义错误常量
|
||||
var (
|
||||
// ErrUserNotFound 用户不存在错误
|
||||
ErrUserNotFound = errors.New("用户不存在")
|
||||
)
|
||||
|
||||
// UserRepository 用户仓储实现
|
||||
type UserRepository struct {
|
||||
db *gorm.DB
|
||||
cache interfaces.CacheService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewUserRepository 创建用户仓储
|
||||
func NewUserRepository(db *gorm.DB, cache interfaces.CacheService, logger *zap.Logger) *UserRepository {
|
||||
return &UserRepository{
|
||||
db: db,
|
||||
cache: cache,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建用户
|
||||
func (r *UserRepository) Create(ctx context.Context, user *entities.User) error {
|
||||
if err := r.db.WithContext(ctx).Create(user).Error; err != nil {
|
||||
r.logger.Error("创建用户失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.deleteCacheByPhone(ctx, user.Phone)
|
||||
|
||||
r.logger.Info("用户创建成功", zap.String("user_id", user.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取用户
|
||||
func (r *UserRepository) GetByID(ctx context.Context, id string) (*entities.User, error) {
|
||||
// 尝试从缓存获取
|
||||
cacheKey := fmt.Sprintf("user:id:%s", id)
|
||||
var user entities.User
|
||||
if err := r.cache.Get(ctx, cacheKey, &user); err == nil {
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// 从数据库查询
|
||||
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&user).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
r.logger.Error("根据ID查询用户失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
r.cache.Set(ctx, cacheKey, &user, 10*time.Minute)
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// FindByPhone 根据手机号查找用户
|
||||
func (r *UserRepository) FindByPhone(ctx context.Context, phone string) (*entities.User, error) {
|
||||
// 尝试从缓存获取
|
||||
cacheKey := fmt.Sprintf("user:phone:%s", phone)
|
||||
var user entities.User
|
||||
if err := r.cache.Get(ctx, cacheKey, &user); err == nil {
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// 从数据库查询
|
||||
if err := r.db.WithContext(ctx).Where("phone = ?", phone).First(&user).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
r.logger.Error("根据手机号查询用户失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
r.cache.Set(ctx, cacheKey, &user, 10*time.Minute)
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// Update 更新用户
|
||||
func (r *UserRepository) Update(ctx context.Context, user *entities.User) error {
|
||||
if err := r.db.WithContext(ctx).Save(user).Error; err != nil {
|
||||
r.logger.Error("更新用户失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.deleteCacheByID(ctx, user.ID)
|
||||
r.deleteCacheByPhone(ctx, user.Phone)
|
||||
|
||||
r.logger.Info("用户更新成功", zap.String("user_id", user.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除用户
|
||||
func (r *UserRepository) Delete(ctx context.Context, id string) error {
|
||||
// 先获取用户信息用于清除缓存
|
||||
user, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.db.WithContext(ctx).Delete(&entities.User{}, "id = ?", id).Error; err != nil {
|
||||
r.logger.Error("删除用户失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.deleteCacheByID(ctx, id)
|
||||
r.deleteCacheByPhone(ctx, user.Phone)
|
||||
|
||||
r.logger.Info("用户删除成功", zap.String("user_id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// SoftDelete 软删除用户
|
||||
func (r *UserRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
// 先获取用户信息用于清除缓存
|
||||
user, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.db.WithContext(ctx).Delete(&entities.User{}, "id = ?", id).Error; err != nil {
|
||||
r.logger.Error("软删除用户失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.deleteCacheByID(ctx, id)
|
||||
r.deleteCacheByPhone(ctx, user.Phone)
|
||||
|
||||
r.logger.Info("用户软删除成功", zap.String("user_id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore 恢复软删除的用户
|
||||
func (r *UserRepository) Restore(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Unscoped().Model(&entities.User{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
|
||||
r.logger.Error("恢复用户失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.deleteCacheByID(ctx, id)
|
||||
|
||||
r.logger.Info("用户恢复成功", zap.String("user_id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// List 分页获取用户列表
|
||||
func (r *UserRepository) List(ctx context.Context, offset, limit int) ([]*entities.User, error) {
|
||||
var users []*entities.User
|
||||
if err := r.db.WithContext(ctx).Offset(offset).Limit(limit).Find(&users).Error; err != nil {
|
||||
r.logger.Error("查询用户列表失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// Count 获取用户总数
|
||||
func (r *UserRepository) Count(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).Model(&entities.User{}).Count(&count).Error; err != nil {
|
||||
r.logger.Error("统计用户数量失败", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// ExistsByPhone 检查手机号是否存在
|
||||
func (r *UserRepository) ExistsByPhone(ctx context.Context, phone string) (bool, error) {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).Model(&entities.User{}).Where("phone = ?", phone).Count(&count).Error; err != nil {
|
||||
r.logger.Error("检查手机号是否存在失败", zap.Error(err))
|
||||
return false, err
|
||||
}
|
||||
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// 私有辅助方法
|
||||
|
||||
// deleteCacheByID 根据ID删除缓存
|
||||
func (r *UserRepository) deleteCacheByID(ctx context.Context, id string) {
|
||||
cacheKey := fmt.Sprintf("user:id:%s", id)
|
||||
if err := r.cache.Delete(ctx, cacheKey); err != nil {
|
||||
r.logger.Warn("删除用户ID缓存失败", zap.String("cache_key", cacheKey), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// deleteCacheByPhone 根据手机号删除缓存
|
||||
func (r *UserRepository) deleteCacheByPhone(ctx context.Context, phone string) {
|
||||
cacheKey := fmt.Sprintf("user:phone:%s", phone)
|
||||
if err := r.cache.Delete(ctx, cacheKey); err != nil {
|
||||
r.logger.Warn("删除用户手机号缓存失败", zap.String("cache_key", cacheKey), zap.Error(err))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// UserStats 用户统计信息
|
||||
type UserStats struct {
|
||||
TotalUsers int64
|
||||
ActiveUsers int64
|
||||
TodayRegistrations int64
|
||||
TodayLogins int64
|
||||
}
|
||||
|
||||
// UserRepository 用户仓储接口
|
||||
type UserRepository interface {
|
||||
interfaces.Repository[entities.User]
|
||||
|
||||
// 基础查询 - 直接使用实体
|
||||
GetByPhone(ctx context.Context, phone string) (*entities.User, error)
|
||||
|
||||
// 复杂查询 - 使用查询参数
|
||||
ListUsers(ctx context.Context, query *queries.ListUsersQuery) ([]*entities.User, int64, error)
|
||||
|
||||
// 业务操作
|
||||
ValidateUser(ctx context.Context, phone, password string) (*entities.User, error)
|
||||
UpdateLastLogin(ctx context.Context, userID string) error
|
||||
UpdatePassword(ctx context.Context, userID string, newPassword string) error
|
||||
CheckPassword(ctx context.Context, userID string, password string) (bool, error)
|
||||
ActivateUser(ctx context.Context, userID string) error
|
||||
DeactivateUser(ctx context.Context, userID string) error
|
||||
|
||||
// 统计信息
|
||||
GetStats(ctx context.Context) (*UserStats, error)
|
||||
GetStatsByDateRange(ctx context.Context, startDate, endDate string) (*UserStats, error)
|
||||
}
|
||||
|
||||
// SMSCodeRepository 短信验证码仓储接口
|
||||
type SMSCodeRepository interface {
|
||||
interfaces.Repository[entities.SMSCode]
|
||||
|
||||
// 基础查询 - 直接使用实体
|
||||
GetByPhone(ctx context.Context, phone string) (*entities.SMSCode, error)
|
||||
GetLatestByPhone(ctx context.Context, phone string) (*entities.SMSCode, error)
|
||||
GetValidByPhone(ctx context.Context, phone string) (*entities.SMSCode, error)
|
||||
GetValidByPhoneAndScene(ctx context.Context, phone string, scene entities.SMSScene) (*entities.SMSCode, error)
|
||||
|
||||
// 复杂查询 - 使用查询参数
|
||||
ListSMSCodes(ctx context.Context, query *queries.ListSMSCodesQuery) ([]*entities.SMSCode, int64, error)
|
||||
|
||||
// 业务操作
|
||||
CreateCode(ctx context.Context, phone string, code string, purpose string) (entities.SMSCode, error)
|
||||
ValidateCode(ctx context.Context, phone string, code string, purpose string) (bool, error)
|
||||
InvalidateCode(ctx context.Context, phone string) error
|
||||
CheckSendFrequency(ctx context.Context, phone string, purpose string) (bool, error)
|
||||
GetTodaySendCount(ctx context.Context, phone string) (int64, error)
|
||||
|
||||
// 统计信息
|
||||
GetCodeStats(ctx context.Context, phone string, days int) (*SMSCodeStats, error)
|
||||
}
|
||||
|
||||
// SMSCodeStats 短信验证码统计信息
|
||||
type SMSCodeStats struct {
|
||||
TotalSent int64
|
||||
TotalValidated int64
|
||||
SuccessRate float64
|
||||
TodaySent int64
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"tyapi-server/internal/domains/user/handlers"
|
||||
"tyapi-server/internal/shared/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// UserRoutes 注册用户相关路由
|
||||
func UserRoutes(router *gin.Engine, handler *handlers.UserHandler, authMiddleware *middleware.JWTAuthMiddleware) {
|
||||
// 用户域路由组
|
||||
usersGroup := router.Group("/api/v1/users")
|
||||
{
|
||||
// 公开路由(不需要认证)
|
||||
usersGroup.POST("/send-code", handler.SendCode) // 发送验证码
|
||||
usersGroup.POST("/register", handler.Register) // 用户注册
|
||||
usersGroup.POST("/login-password", handler.LoginWithPassword) // 密码登录
|
||||
usersGroup.POST("/login-sms", handler.LoginWithSMS) // 短信验证码登录
|
||||
|
||||
// 需要认证的路由
|
||||
authenticated := usersGroup.Group("")
|
||||
authenticated.Use(authMiddleware.Handle())
|
||||
{
|
||||
authenticated.GET("/me", handler.GetProfile) // 获取当前用户信息
|
||||
authenticated.PUT("/me/password", handler.ChangePassword) // 修改密码
|
||||
}
|
||||
}
|
||||
}
|
||||
304
internal/domains/user/services/enterprise_service.go
Normal file
304
internal/domains/user/services/enterprise_service.go
Normal file
@@ -0,0 +1,304 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/repositories"
|
||||
)
|
||||
|
||||
// EnterpriseService 企业信息领域服务
|
||||
type EnterpriseService struct {
|
||||
userRepo repositories.UserRepository
|
||||
enterpriseInfoRepo repositories.EnterpriseInfoRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewEnterpriseService 创建企业信息领域服务
|
||||
func NewEnterpriseService(
|
||||
userRepo repositories.UserRepository,
|
||||
enterpriseInfoRepo repositories.EnterpriseInfoRepository,
|
||||
logger *zap.Logger,
|
||||
) *EnterpriseService {
|
||||
return &EnterpriseService{
|
||||
userRepo: userRepo,
|
||||
enterpriseInfoRepo: enterpriseInfoRepo,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateEnterpriseInfo 创建企业信息
|
||||
func (s *EnterpriseService) CreateEnterpriseInfo(ctx context.Context, userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID string) (*entities.EnterpriseInfo, error) {
|
||||
// 检查用户是否存在
|
||||
_, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
// 检查用户是否已有企业信息
|
||||
existingInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err == nil && existingInfo != nil {
|
||||
return nil, fmt.Errorf("用户已有企业信息")
|
||||
}
|
||||
|
||||
// 检查统一社会信用代码是否已存在
|
||||
exists, err := s.enterpriseInfoRepo.CheckUnifiedSocialCodeExists(ctx, unifiedSocialCode, "")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("检查企业信息失败: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("统一社会信用代码已存在")
|
||||
}
|
||||
|
||||
// 创建企业信息
|
||||
enterpriseInfo := &entities.EnterpriseInfo{
|
||||
UserID: userID,
|
||||
CompanyName: companyName,
|
||||
UnifiedSocialCode: unifiedSocialCode,
|
||||
LegalPersonName: legalPersonName,
|
||||
LegalPersonID: legalPersonID,
|
||||
}
|
||||
|
||||
*enterpriseInfo, err = s.enterpriseInfoRepo.Create(ctx, *enterpriseInfo)
|
||||
if err != nil {
|
||||
s.logger.Error("创建企业信息失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("企业信息创建成功",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("enterprise_id", enterpriseInfo.ID),
|
||||
zap.String("company_name", companyName),
|
||||
)
|
||||
|
||||
return enterpriseInfo, nil
|
||||
}
|
||||
|
||||
// GetEnterpriseInfo 获取企业信息
|
||||
func (s *EnterpriseService) GetEnterpriseInfo(ctx context.Context, userID string) (*entities.EnterpriseInfo, error) {
|
||||
// 检查用户是否存在
|
||||
_, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("企业信息不存在: %w", err)
|
||||
}
|
||||
|
||||
return enterpriseInfo, nil
|
||||
}
|
||||
|
||||
// UpdateEnterpriseInfo 更新企业信息(仅限未认证完成的情况)
|
||||
func (s *EnterpriseService) UpdateEnterpriseInfo(ctx context.Context, userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID string) (*entities.EnterpriseInfo, error) {
|
||||
// 检查用户是否存在
|
||||
_, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
// 获取现有企业信息
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("检查企业信息失败: %w", err)
|
||||
}
|
||||
if exists {
|
||||
return nil, fmt.Errorf("统一社会信用代码已存在")
|
||||
}
|
||||
}
|
||||
|
||||
// 更新企业信息
|
||||
enterpriseInfo.CompanyName = companyName
|
||||
enterpriseInfo.UnifiedSocialCode = unifiedSocialCode
|
||||
enterpriseInfo.LegalPersonName = legalPersonName
|
||||
enterpriseInfo.LegalPersonID = legalPersonID
|
||||
|
||||
if err := s.enterpriseInfoRepo.Update(ctx, *enterpriseInfo); err != nil {
|
||||
s.logger.Error("更新企业信息失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("更新企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("企业信息更新成功",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("enterprise_id", enterpriseInfo.ID),
|
||||
)
|
||||
|
||||
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 {
|
||||
// 获取企业信息
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
s.logger.Info("企业认证完成",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("enterprise_id", enterpriseInfo.ID),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckUnifiedSocialCodeExists 检查统一社会信用代码是否存在
|
||||
func (s *EnterpriseService) CheckUnifiedSocialCodeExists(ctx context.Context, unifiedSocialCode, excludeUserID string) (bool, error) {
|
||||
return s.enterpriseInfoRepo.CheckUnifiedSocialCodeExists(ctx, unifiedSocialCode, excludeUserID)
|
||||
}
|
||||
|
||||
// GetUserWithEnterpriseInfo 获取用户信息(包含企业信息)
|
||||
func (s *EnterpriseService) GetUserWithEnterpriseInfo(ctx context.Context, userID string) (*entities.User, error) {
|
||||
// 获取用户信息
|
||||
user, err := s.userRepo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
// 获取企业信息(如果存在)
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
// 企业信息不存在是正常的,不是错误
|
||||
s.logger.Debug("用户暂无企业信息", zap.String("user_id", userID))
|
||||
} else {
|
||||
user.EnterpriseInfo = enterpriseInfo
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// ValidateEnterpriseInfo 验证企业信息完整性
|
||||
func (s *EnterpriseService) ValidateEnterpriseInfo(ctx context.Context, userID string) error {
|
||||
enterpriseInfo, err := s.enterpriseInfoRepo.GetByUserID(ctx, userID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("企业信息不存在: %w", err)
|
||||
}
|
||||
|
||||
if err := enterpriseInfo.Validate(); err != nil {
|
||||
return fmt.Errorf("企业信息验证失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEnterpriseInfoByUnifiedSocialCode 根据统一社会信用代码获取企业信息
|
||||
func (s *EnterpriseService) GetEnterpriseInfoByUnifiedSocialCode(ctx context.Context, unifiedSocialCode string) (*entities.EnterpriseInfo, error) {
|
||||
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)
|
||||
if err != nil {
|
||||
return map[string]interface{}{
|
||||
"has_enterprise_info": false,
|
||||
"is_certified": false,
|
||||
"message": "用户暂无企业信息",
|
||||
}, nil
|
||||
}
|
||||
|
||||
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,
|
||||
"created_at": enterpriseInfo.CreatedAt,
|
||||
"updated_at": enterpriseInfo.UpdatedAt,
|
||||
}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
@@ -5,31 +5,32 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/config"
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/repositories"
|
||||
"tyapi-server/internal/infrastructure/external/sms"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
"tyapi-server/internal/shared/sms"
|
||||
)
|
||||
|
||||
// SMSCodeService 短信验证码服务
|
||||
type SMSCodeService struct {
|
||||
repo *repositories.SMSCodeRepository
|
||||
smsClient sms.Service
|
||||
repo repositories.SMSCodeRepository
|
||||
smsClient *sms.AliSMSService
|
||||
cache interfaces.CacheService
|
||||
config config.SMSConfig
|
||||
appConfig config.AppConfig
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewSMSCodeService 创建短信验证码服务
|
||||
func NewSMSCodeService(
|
||||
repo *repositories.SMSCodeRepository,
|
||||
smsClient sms.Service,
|
||||
repo repositories.SMSCodeRepository,
|
||||
smsClient *sms.AliSMSService,
|
||||
cache interfaces.CacheService,
|
||||
config config.SMSConfig,
|
||||
appConfig config.AppConfig,
|
||||
logger *zap.Logger,
|
||||
) *SMSCodeService {
|
||||
return &SMSCodeService{
|
||||
@@ -37,31 +38,25 @@ func NewSMSCodeService(
|
||||
smsClient: smsClient,
|
||||
cache: cache,
|
||||
config: config,
|
||||
appConfig: appConfig,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// SendCode 发送验证码
|
||||
func (s *SMSCodeService) SendCode(ctx context.Context, phone string, scene entities.SMSScene, clientIP, userAgent string) error {
|
||||
// 1. 检查频率限制
|
||||
if err := s.checkRateLimit(ctx, phone); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 2. 生成验证码
|
||||
// 1. 生成验证码
|
||||
code := s.smsClient.GenerateCode(s.config.CodeLength)
|
||||
|
||||
// 3. 使用工厂方法创建SMS验证码记录
|
||||
// 2. 使用工厂方法创建SMS验证码记录
|
||||
smsCode, err := entities.NewSMSCode(phone, code, scene, s.config.ExpireTime, clientIP, userAgent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("创建验证码记录失败: %w", err)
|
||||
}
|
||||
|
||||
// 4. 设置ID
|
||||
smsCode.ID = uuid.New().String()
|
||||
|
||||
// 5. 保存验证码
|
||||
if err := s.repo.Create(ctx, smsCode); err != nil {
|
||||
// 4. 保存验证码
|
||||
*smsCode, err = s.repo.Create(ctx, *smsCode)
|
||||
if err != nil {
|
||||
s.logger.Error("保存短信验证码失败",
|
||||
zap.String("phone", smsCode.GetMaskedPhone()),
|
||||
zap.String("scene", smsCode.GetSceneName()),
|
||||
@@ -69,7 +64,7 @@ func (s *SMSCodeService) SendCode(ctx context.Context, phone string, scene entit
|
||||
return fmt.Errorf("保存验证码失败: %w", err)
|
||||
}
|
||||
|
||||
// 6. 发送短信
|
||||
// 5. 发送短信
|
||||
if err := s.smsClient.SendVerificationCode(ctx, phone, code); err != nil {
|
||||
// 记录发送失败但不删除验证码记录,让其自然过期
|
||||
s.logger.Error("发送短信验证码失败",
|
||||
@@ -79,8 +74,8 @@ func (s *SMSCodeService) SendCode(ctx context.Context, phone string, scene entit
|
||||
return fmt.Errorf("短信发送失败: %w", err)
|
||||
}
|
||||
|
||||
// 7. 更新发送记录缓存
|
||||
s.updateSendRecord(ctx, phone)
|
||||
// 6. 更新发送记录缓存
|
||||
s.updateSendRecord(ctx, phone, scene)
|
||||
|
||||
s.logger.Info("短信验证码发送成功",
|
||||
zap.String("phone", smsCode.GetMaskedPhone()),
|
||||
@@ -92,19 +87,33 @@ func (s *SMSCodeService) SendCode(ctx context.Context, phone string, scene entit
|
||||
|
||||
// VerifyCode 验证验证码
|
||||
func (s *SMSCodeService) VerifyCode(ctx context.Context, phone, code string, scene entities.SMSScene) error {
|
||||
// 开发模式下跳过验证码校验
|
||||
if s.appConfig.IsDevelopment() {
|
||||
s.logger.Info("开发模式:验证码校验已跳过",
|
||||
zap.String("phone", phone),
|
||||
zap.String("scene", string(scene)),
|
||||
zap.String("code", code))
|
||||
return nil
|
||||
}
|
||||
|
||||
// 1. 根据手机号和场景获取有效的验证码记录
|
||||
smsCode, err := s.repo.GetValidCode(ctx, phone, scene)
|
||||
smsCode, err := s.repo.GetValidByPhoneAndScene(ctx, phone, scene)
|
||||
if err != nil {
|
||||
return fmt.Errorf("验证码无效或已过期")
|
||||
}
|
||||
|
||||
// 2. 使用实体的验证方法
|
||||
// 2. 检查场景是否匹配
|
||||
if smsCode.Scene != scene {
|
||||
return fmt.Errorf("验证码错误或已过期")
|
||||
}
|
||||
|
||||
// 3. 使用实体的验证方法
|
||||
if err := smsCode.VerifyCode(code); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. 保存更新后的验证码状态
|
||||
if err := s.repo.Update(ctx, smsCode); err != nil {
|
||||
// 4. 保存更新后的验证码状态
|
||||
if err := s.repo.Update(ctx, *smsCode); err != nil {
|
||||
s.logger.Error("更新验证码状态失败",
|
||||
zap.String("code_id", smsCode.ID),
|
||||
zap.Error(err))
|
||||
@@ -120,10 +129,10 @@ func (s *SMSCodeService) VerifyCode(ctx context.Context, phone, code string, sce
|
||||
|
||||
// CanResendCode 检查是否可以重新发送验证码
|
||||
func (s *SMSCodeService) CanResendCode(ctx context.Context, phone string, scene entities.SMSScene) (bool, error) {
|
||||
// 1. 获取最近的验证码记录
|
||||
recentCode, err := s.repo.GetRecentCode(ctx, phone, scene)
|
||||
// 1. 获取最近的验证码记录(按场景)
|
||||
recentCode, err := s.repo.GetValidByPhoneAndScene(ctx, phone, scene)
|
||||
if err != nil {
|
||||
// 如果没有记录,可以发送
|
||||
// 如果没有该场景的记录,可以发送
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -144,8 +153,8 @@ func (s *SMSCodeService) CanResendCode(ctx context.Context, phone string, scene
|
||||
|
||||
// GetCodeStatus 获取验证码状态信息
|
||||
func (s *SMSCodeService) GetCodeStatus(ctx context.Context, phone string, scene entities.SMSScene) (map[string]interface{}, error) {
|
||||
// 1. 获取最近的验证码记录
|
||||
recentCode, err := s.repo.GetRecentCode(ctx, phone, scene)
|
||||
// 1. 获取最近的验证码记录(按场景)
|
||||
recentCode, err := s.repo.GetValidByPhoneAndScene(ctx, phone, scene)
|
||||
if err != nil {
|
||||
return map[string]interface{}{
|
||||
"has_code": false,
|
||||
@@ -170,11 +179,11 @@ func (s *SMSCodeService) GetCodeStatus(ctx context.Context, phone string, scene
|
||||
}
|
||||
|
||||
// checkRateLimit 检查发送频率限制
|
||||
func (s *SMSCodeService) checkRateLimit(ctx context.Context, phone string) error {
|
||||
func (s *SMSCodeService) CheckRateLimit(ctx context.Context, phone string, scene entities.SMSScene) error {
|
||||
now := time.Now()
|
||||
|
||||
// 检查最小发送间隔
|
||||
lastSentKey := fmt.Sprintf("sms:last_sent:%s", phone)
|
||||
lastSentKey := fmt.Sprintf("sms:last_sent:%s:%s", scene, phone)
|
||||
var lastSent time.Time
|
||||
if err := s.cache.Get(ctx, lastSentKey, &lastSent); err == nil {
|
||||
if now.Sub(lastSent) < s.config.RateLimit.MinInterval {
|
||||
@@ -204,11 +213,11 @@ func (s *SMSCodeService) checkRateLimit(ctx context.Context, phone string) error
|
||||
}
|
||||
|
||||
// updateSendRecord 更新发送记录
|
||||
func (s *SMSCodeService) updateSendRecord(ctx context.Context, phone string) {
|
||||
func (s *SMSCodeService) updateSendRecord(ctx context.Context, phone string, scene entities.SMSScene) {
|
||||
now := time.Now()
|
||||
|
||||
// 更新最后发送时间
|
||||
lastSentKey := fmt.Sprintf("sms:last_sent:%s", phone)
|
||||
lastSentKey := fmt.Sprintf("sms:last_sent:%s:%s", scene, phone)
|
||||
s.cache.Set(ctx, lastSentKey, now, s.config.RateLimit.MinInterval)
|
||||
|
||||
// 更新每小时计数
|
||||
@@ -232,5 +241,5 @@ func (s *SMSCodeService) updateSendRecord(ctx context.Context, phone string) {
|
||||
|
||||
// CleanExpiredCodes 清理过期验证码
|
||||
func (s *SMSCodeService) CleanExpiredCodes(ctx context.Context) error {
|
||||
return s.repo.CleanupExpired(ctx)
|
||||
return s.repo.DeleteBatch(ctx, []string{})
|
||||
}
|
||||
|
||||
@@ -4,308 +4,126 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/user/dto"
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/events"
|
||||
"tyapi-server/internal/domains/user/repositories"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// UserService 用户服务实现
|
||||
// UserService 用户领域服务
|
||||
type UserService struct {
|
||||
repo *repositories.UserRepository
|
||||
smsCodeService *SMSCodeService
|
||||
eventBus interfaces.EventBus
|
||||
logger *zap.Logger
|
||||
userRepo repositories.UserRepository
|
||||
enterpriseService *EnterpriseService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewUserService 创建用户服务
|
||||
// NewUserService 创建用户领域服务
|
||||
func NewUserService(
|
||||
repo *repositories.UserRepository,
|
||||
smsCodeService *SMSCodeService,
|
||||
eventBus interfaces.EventBus,
|
||||
userRepo repositories.UserRepository,
|
||||
enterpriseService *EnterpriseService,
|
||||
logger *zap.Logger,
|
||||
) *UserService {
|
||||
return &UserService{
|
||||
repo: repo,
|
||||
smsCodeService: smsCodeService,
|
||||
eventBus: eventBus,
|
||||
logger: logger,
|
||||
userRepo: userRepo,
|
||||
enterpriseService: enterpriseService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Name 返回服务名称
|
||||
func (s *UserService) Name() string {
|
||||
return "user-service"
|
||||
}
|
||||
|
||||
// Initialize 初始化服务
|
||||
func (s *UserService) Initialize(ctx context.Context) error {
|
||||
s.logger.Info("用户服务已初始化")
|
||||
return nil
|
||||
}
|
||||
|
||||
// HealthCheck 健康检查
|
||||
func (s *UserService) HealthCheck(ctx context.Context) error {
|
||||
// 简单的健康检查
|
||||
return nil
|
||||
}
|
||||
|
||||
// Shutdown 关闭服务
|
||||
func (s *UserService) Shutdown(ctx context.Context) error {
|
||||
s.logger.Info("用户服务已关闭")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
func (s *UserService) Register(ctx context.Context, registerReq *dto.RegisterRequest) (*entities.User, error) {
|
||||
// 1. 验证短信验证码
|
||||
if err := s.smsCodeService.VerifyCode(ctx, registerReq.Phone, registerReq.Code, entities.SMSSceneRegister); err != nil {
|
||||
return nil, fmt.Errorf("验证码验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 检查手机号是否已存在
|
||||
if err := s.checkPhoneDuplicate(ctx, registerReq.Phone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3. 使用工厂方法创建用户实体(业务规则验证在实体中完成)
|
||||
user, err := entities.NewUser(registerReq.Phone, registerReq.Password)
|
||||
// IsPhoneRegistered 检查手机号是否已注册
|
||||
func (s *UserService) IsPhoneRegistered(ctx context.Context, phone string) (bool, error) {
|
||||
_, err := s.userRepo.GetByPhone(ctx, phone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建用户失败: %w", err)
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 4. 设置用户ID
|
||||
user.ID = uuid.New().String()
|
||||
// GetUserWithEnterpriseInfo 获取用户信息(包含企业信息)
|
||||
func (s *UserService) GetUserWithEnterpriseInfo(ctx context.Context, userID string) (*entities.User, error) {
|
||||
// 通过企业服务获取用户信息(包含企业信息)
|
||||
return s.enterpriseService.GetUserWithEnterpriseInfo(ctx, userID)
|
||||
}
|
||||
|
||||
// 5. 保存用户
|
||||
if err := s.repo.Create(ctx, user); err != nil {
|
||||
s.logger.Error("创建用户失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("创建用户失败: %w", err)
|
||||
// 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
|
||||
}
|
||||
|
||||
// 6. 发布用户注册事件
|
||||
event := events.NewUserRegisteredEvent(user, s.getCorrelationID(ctx))
|
||||
if err := s.eventBus.Publish(ctx, event); err != nil {
|
||||
s.logger.Warn("发布用户注册事件失败", zap.Error(err))
|
||||
// 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)
|
||||
}
|
||||
|
||||
s.logger.Info("用户注册成功",
|
||||
zap.String("user_id", user.ID),
|
||||
zap.String("phone", user.Phone))
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// LoginWithPassword 密码登录
|
||||
func (s *UserService) LoginWithPassword(ctx context.Context, loginReq *dto.LoginWithPasswordRequest) (*entities.User, error) {
|
||||
// 1. 根据手机号查找用户
|
||||
user, err := s.repo.FindByPhone(ctx, loginReq.Phone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户名或密码错误")
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 2. 检查用户是否可以登录(委托给实体)
|
||||
if !user.CanLogin() {
|
||||
return nil, fmt.Errorf("用户状态异常,无法登录")
|
||||
}
|
||||
|
||||
// 3. 验证密码(委托给实体)
|
||||
if !user.CheckPassword(loginReq.Password) {
|
||||
return nil, fmt.Errorf("用户名或密码错误")
|
||||
}
|
||||
|
||||
// 4. 发布用户登录事件
|
||||
event := events.NewUserLoggedInEvent(
|
||||
user.ID, user.Phone,
|
||||
s.getClientIP(ctx), s.getUserAgent(ctx),
|
||||
s.getCorrelationID(ctx))
|
||||
if err := s.eventBus.Publish(ctx, event); err != nil {
|
||||
s.logger.Warn("发布用户登录事件失败", zap.Error(err))
|
||||
}
|
||||
|
||||
s.logger.Info("用户密码登录成功",
|
||||
s.logger.Info("用户信息更新成功",
|
||||
zap.String("user_id", user.ID),
|
||||
zap.String("phone", user.Phone))
|
||||
zap.String("phone", user.Phone),
|
||||
)
|
||||
|
||||
return user, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoginWithSMS 短信验证码登录
|
||||
func (s *UserService) LoginWithSMS(ctx context.Context, loginReq *dto.LoginWithSMSRequest) (*entities.User, error) {
|
||||
// 1. 验证短信验证码
|
||||
if err := s.smsCodeService.VerifyCode(ctx, loginReq.Phone, loginReq.Code, entities.SMSSceneLogin); err != nil {
|
||||
return nil, fmt.Errorf("验证码验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 2. 根据手机号查找用户
|
||||
user, err := s.repo.FindByPhone(ctx, loginReq.Phone)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在")
|
||||
}
|
||||
|
||||
// 3. 检查用户是否可以登录(委托给实体)
|
||||
if !user.CanLogin() {
|
||||
return nil, fmt.Errorf("用户状态异常,无法登录")
|
||||
}
|
||||
|
||||
// 4. 发布用户登录事件
|
||||
event := events.NewUserLoggedInEvent(
|
||||
user.ID, user.Phone,
|
||||
s.getClientIP(ctx), s.getUserAgent(ctx),
|
||||
s.getCorrelationID(ctx))
|
||||
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 user, nil
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
func (s *UserService) ChangePassword(ctx context.Context, userID string, req *dto.ChangePasswordRequest) error {
|
||||
// 1. 获取用户信息
|
||||
user, err := s.repo.GetByID(ctx, userID)
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 2. 验证短信验证码
|
||||
if err := s.smsCodeService.VerifyCode(ctx, user.Phone, req.Code, entities.SMSSceneChangePassword); err != nil {
|
||||
return fmt.Errorf("验证码验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 3. 执行业务逻辑(委托给实体)
|
||||
if err := user.ChangePassword(req.OldPassword, req.NewPassword, req.ConfirmNewPassword); err != nil {
|
||||
if err := user.ChangePassword(oldPassword, newPassword, newPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. 保存用户
|
||||
if err := s.repo.Update(ctx, user); err != nil {
|
||||
return fmt.Errorf("密码更新失败: %w", err)
|
||||
if err := s.userRepo.Update(ctx, user); err != nil {
|
||||
s.logger.Error("密码修改失败", zap.Error(err))
|
||||
return fmt.Errorf("密码修改失败: %w", err)
|
||||
}
|
||||
|
||||
// 5. 发布密码修改事件
|
||||
event := events.NewUserPasswordChangedEvent(user.ID, user.Phone, s.getCorrelationID(ctx))
|
||||
if err := s.eventBus.Publish(ctx, event); err != nil {
|
||||
s.logger.Warn("发布密码修改事件失败", zap.Error(err))
|
||||
}
|
||||
|
||||
s.logger.Info("密码修改成功", zap.String("user_id", userID))
|
||||
s.logger.Info("密码修改成功",
|
||||
zap.String("user_id", userID),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取用户
|
||||
func (s *UserService) GetByID(ctx context.Context, id string) (*entities.User, error) {
|
||||
if id == "" {
|
||||
return nil, fmt.Errorf("用户ID不能为空")
|
||||
}
|
||||
|
||||
user, err := s.repo.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// UpdateUserProfile 更新用户信息
|
||||
func (s *UserService) UpdateUserProfile(ctx context.Context, userID string, req *dto.UpdateProfileRequest) (*entities.User, error) {
|
||||
// 1. 获取用户信息
|
||||
user, err := s.repo.GetByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("用户不存在: %w", err)
|
||||
}
|
||||
|
||||
// 2. 更新手机号(如果需要)
|
||||
if req.Phone != "" && req.Phone != user.Phone {
|
||||
// 检查新手机号是否已存在
|
||||
if err := s.checkPhoneDuplicate(ctx, req.Phone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 使用实体的方法设置手机号
|
||||
if err := user.SetPhone(req.Phone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 保存用户
|
||||
if err := s.repo.Update(ctx, user); err != nil {
|
||||
return nil, fmt.Errorf("更新用户信息失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("用户信息更新成功", zap.String("user_id", userID))
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// DeactivateUser 停用用户
|
||||
func (s *UserService) DeactivateUser(ctx context.Context, userID string) error {
|
||||
// 1. 获取用户信息
|
||||
user, err := s.repo.GetByID(ctx, userID)
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 2. 检查用户状态
|
||||
if user.IsDeleted() {
|
||||
return fmt.Errorf("用户已被停用")
|
||||
// 这里可以添加更多的用户验证逻辑
|
||||
if user.Phone == "" {
|
||||
return fmt.Errorf("用户手机号不能为空")
|
||||
}
|
||||
|
||||
// 3. 软删除用户(这里需要调用仓储的软删除方法)
|
||||
if err := s.repo.SoftDelete(ctx, userID); err != nil {
|
||||
return fmt.Errorf("停用用户失败: %w", err)
|
||||
}
|
||||
|
||||
s.logger.Info("用户停用成功", zap.String("user_id", userID))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ================ 工具方法 ================
|
||||
|
||||
// checkPhoneDuplicate 检查手机号重复
|
||||
func (s *UserService) checkPhoneDuplicate(ctx context.Context, phone string) error {
|
||||
if _, err := s.repo.FindByPhone(ctx, phone); err == nil {
|
||||
return fmt.Errorf("手机号已存在")
|
||||
// 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 nil
|
||||
}
|
||||
|
||||
// getCorrelationID 获取关联ID
|
||||
func (s *UserService) getCorrelationID(ctx context.Context) string {
|
||||
if id := ctx.Value("correlation_id"); id != nil {
|
||||
if strID, ok := id.(string); ok {
|
||||
return strID
|
||||
}
|
||||
}
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
// getClientIP 获取客户端IP
|
||||
func (s *UserService) getClientIP(ctx context.Context) string {
|
||||
if ip := ctx.Value("client_ip"); ip != nil {
|
||||
if strIP, ok := ip.(string); ok {
|
||||
return strIP
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// getUserAgent 获取用户代理
|
||||
func (s *UserService) getUserAgent(ctx context.Context) string {
|
||||
if ua := ctx.Value("user_agent"); ua != nil {
|
||||
if strUA, ok := ua.(string); ok {
|
||||
return strUA
|
||||
}
|
||||
}
|
||||
return ""
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,225 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
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
|
||||
}
|
||||
@@ -9,8 +9,9 @@ import (
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/admin/dto"
|
||||
"tyapi-server/internal/domains/admin/entities"
|
||||
"tyapi-server/internal/domains/admin/repositories"
|
||||
"tyapi-server/internal/domains/admin/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
@@ -20,6 +21,9 @@ type GormAdminRepository struct {
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.AdminRepository = (*GormAdminRepository)(nil)
|
||||
|
||||
// NewGormAdminRepository 创建管理员GORM仓储
|
||||
func NewGormAdminRepository(db *gorm.DB, logger *zap.Logger) *GormAdminRepository {
|
||||
return &GormAdminRepository{
|
||||
@@ -29,9 +33,10 @@ func NewGormAdminRepository(db *gorm.DB, logger *zap.Logger) *GormAdminRepositor
|
||||
}
|
||||
|
||||
// Create 创建管理员
|
||||
func (r *GormAdminRepository) Create(ctx context.Context, admin entities.Admin) error {
|
||||
func (r *GormAdminRepository) Create(ctx context.Context, admin entities.Admin) (entities.Admin, error) {
|
||||
r.logger.Info("创建管理员", zap.String("username", admin.Username))
|
||||
return r.db.WithContext(ctx).Create(&admin).Error
|
||||
err := r.db.WithContext(ctx).Create(&admin).Error
|
||||
return admin, err
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取管理员
|
||||
@@ -186,60 +191,55 @@ func (r *GormAdminRepository) FindByEmail(ctx context.Context, email string) (*e
|
||||
}
|
||||
|
||||
// ListAdmins 获取管理员列表(带分页和筛选)
|
||||
func (r *GormAdminRepository) ListAdmins(ctx context.Context, req *dto.AdminListRequest) (*dto.AdminListResponse, error) {
|
||||
func (r *GormAdminRepository) ListAdmins(ctx context.Context, query *queries.ListAdminsQuery) ([]*entities.Admin, int64, error) {
|
||||
var admins []entities.Admin
|
||||
var total int64
|
||||
|
||||
query := r.db.WithContext(ctx).Model(&entities.Admin{})
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Admin{})
|
||||
|
||||
// 应用筛选条件
|
||||
if req.Username != "" {
|
||||
query = query.Where("username LIKE ?", "%"+req.Username+"%")
|
||||
if query.Username != "" {
|
||||
dbQuery = dbQuery.Where("username LIKE ?", "%"+query.Username+"%")
|
||||
}
|
||||
if req.Email != "" {
|
||||
query = query.Where("email LIKE ?", "%"+req.Email+"%")
|
||||
if query.Email != "" {
|
||||
dbQuery = dbQuery.Where("email LIKE ?", "%"+query.Email+"%")
|
||||
}
|
||||
if req.Role != "" {
|
||||
query = query.Where("role = ?", req.Role)
|
||||
if query.Role != "" {
|
||||
dbQuery = dbQuery.Where("role = ?", query.Role)
|
||||
}
|
||||
if req.IsActive != nil {
|
||||
query = query.Where("is_active = ?", *req.IsActive)
|
||||
if query.IsActive != nil {
|
||||
dbQuery = dbQuery.Where("is_active = ?", *query.IsActive)
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
if err := query.Count(&total).Error; err != nil {
|
||||
return nil, err
|
||||
if err := dbQuery.Count(&total).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
offset := (req.Page - 1) * req.PageSize
|
||||
query = query.Offset(offset).Limit(req.PageSize)
|
||||
offset := (query.Page - 1) * query.PageSize
|
||||
dbQuery = dbQuery.Offset(offset).Limit(query.PageSize)
|
||||
|
||||
// 默认排序
|
||||
query = query.Order("created_at DESC")
|
||||
dbQuery = dbQuery.Order("created_at DESC")
|
||||
|
||||
// 查询数据
|
||||
if err := query.Find(&admins).Error; err != nil {
|
||||
return nil, err
|
||||
if err := dbQuery.Find(&admins).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为DTO
|
||||
adminInfos := make([]dto.AdminInfo, len(admins))
|
||||
for i, admin := range admins {
|
||||
adminInfos[i] = r.convertToAdminInfo(admin)
|
||||
// 转换为指针切片
|
||||
adminPtrs := make([]*entities.Admin, len(admins))
|
||||
for i := range admins {
|
||||
adminPtrs[i] = &admins[i]
|
||||
}
|
||||
|
||||
return &dto.AdminListResponse{
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
Size: req.PageSize,
|
||||
Admins: adminInfos,
|
||||
}, nil
|
||||
return adminPtrs, total, nil
|
||||
}
|
||||
|
||||
// GetStats 获取管理员统计信息
|
||||
func (r *GormAdminRepository) GetStats(ctx context.Context) (*dto.AdminStatsResponse, error) {
|
||||
var stats dto.AdminStatsResponse
|
||||
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 {
|
||||
@@ -317,25 +317,3 @@ func (r *GormAdminRepository) UpdateReviewStats(ctx context.Context, adminID str
|
||||
Where("id = ?", adminID).
|
||||
Updates(updates).Error
|
||||
}
|
||||
|
||||
// convertToAdminInfo 转换为管理员信息DTO
|
||||
func (r *GormAdminRepository) convertToAdminInfo(admin entities.Admin) dto.AdminInfo {
|
||||
var permissions []string
|
||||
if admin.Permissions != "" {
|
||||
json.Unmarshal([]byte(admin.Permissions), &permissions)
|
||||
}
|
||||
|
||||
return dto.AdminInfo{
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,353 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// GormCertificationRepository GORM认证仓储实现
|
||||
type GormCertificationRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ repositories.CertificationRepository = (*GormCertificationRepository)(nil)
|
||||
|
||||
// NewGormCertificationRepository 创建GORM认证仓储
|
||||
func NewGormCertificationRepository(db *gorm.DB, logger *zap.Logger) repositories.CertificationRepository {
|
||||
return &GormCertificationRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// ================ 基础CRUD操作 ================
|
||||
|
||||
// Create 创建认证申请
|
||||
func (r *GormCertificationRepository) Create(ctx context.Context, cert entities.Certification) (entities.Certification, error) {
|
||||
r.logger.Info("创建认证申请", zap.String("user_id", cert.UserID))
|
||||
err := r.db.WithContext(ctx).Create(&cert).Error
|
||||
return cert, err
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取认证申请
|
||||
func (r *GormCertificationRepository) GetByID(ctx context.Context, id string) (entities.Certification, error) {
|
||||
var cert entities.Certification
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&cert).Error
|
||||
return cert, err
|
||||
}
|
||||
|
||||
// Update 更新认证申请
|
||||
func (r *GormCertificationRepository) Update(ctx context.Context, cert entities.Certification) error {
|
||||
r.logger.Info("更新认证申请", zap.String("id", cert.ID))
|
||||
return r.db.WithContext(ctx).Save(&cert).Error
|
||||
}
|
||||
|
||||
// Delete 删除认证申请
|
||||
func (r *GormCertificationRepository) Delete(ctx context.Context, id string) error {
|
||||
r.logger.Info("删除认证申请", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Delete(&entities.Certification{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// SoftDelete 软删除认证申请
|
||||
func (r *GormCertificationRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
r.logger.Info("软删除认证申请", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Delete(&entities.Certification{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
// Restore 恢复认证申请
|
||||
func (r *GormCertificationRepository) Restore(ctx context.Context, id string) error {
|
||||
r.logger.Info("恢复认证申请", zap.String("id", id))
|
||||
return r.db.WithContext(ctx).Unscoped().Model(&entities.Certification{}).Where("id = ?", id).Update("deleted_at", nil).Error
|
||||
}
|
||||
|
||||
// Count 统计认证申请数量
|
||||
func (r *GormCertificationRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.Certification{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("user_id LIKE ?", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
err := query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// Exists 检查认证申请是否存在
|
||||
func (r *GormCertificationRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建认证申请
|
||||
func (r *GormCertificationRepository) CreateBatch(ctx context.Context, certs []entities.Certification) error {
|
||||
r.logger.Info("批量创建认证申请", zap.Int("count", len(certs)))
|
||||
return r.db.WithContext(ctx).Create(&certs).Error
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取认证申请
|
||||
func (r *GormCertificationRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.Certification, error) {
|
||||
var certs []entities.Certification
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&certs).Error
|
||||
return certs, err
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新认证申请
|
||||
func (r *GormCertificationRepository) UpdateBatch(ctx context.Context, certs []entities.Certification) error {
|
||||
r.logger.Info("批量更新认证申请", zap.Int("count", len(certs)))
|
||||
return r.db.WithContext(ctx).Save(&certs).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除认证申请
|
||||
func (r *GormCertificationRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
r.logger.Info("批量删除认证申请", zap.Strings("ids", ids))
|
||||
return r.db.WithContext(ctx).Delete(&entities.Certification{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取认证申请列表
|
||||
func (r *GormCertificationRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.Certification, error) {
|
||||
var certs []entities.Certification
|
||||
query := r.db.WithContext(ctx).Model(&entities.Certification{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("user_id LIKE ?", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
if options.Sort != "" {
|
||||
order := "ASC"
|
||||
if options.Order != "" {
|
||||
order = options.Order
|
||||
}
|
||||
query = query.Order(options.Sort + " " + order)
|
||||
}
|
||||
|
||||
if options.Page > 0 && options.PageSize > 0 {
|
||||
offset := (options.Page - 1) * options.PageSize
|
||||
query = query.Offset(offset).Limit(options.PageSize)
|
||||
}
|
||||
|
||||
return certs, query.Find(&certs).Error
|
||||
}
|
||||
|
||||
// WithTx 使用事务
|
||||
func (r *GormCertificationRepository) WithTx(tx interface{}) interfaces.Repository[entities.Certification] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormCertificationRepository{
|
||||
db: gormTx,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ================ 业务方法 ================
|
||||
|
||||
// ListCertifications 获取认证申请列表(带分页和筛选)
|
||||
func (r *GormCertificationRepository) ListCertifications(ctx context.Context, query *queries.ListCertificationsQuery) ([]*entities.Certification, int64, error) {
|
||||
var certs []entities.Certification
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Certification{})
|
||||
|
||||
// 应用筛选条件
|
||||
if query.UserID != "" {
|
||||
dbQuery = dbQuery.Where("user_id = ?", query.UserID)
|
||||
}
|
||||
if query.Status != "" {
|
||||
dbQuery = dbQuery.Where("status = ?", query.Status)
|
||||
}
|
||||
if query.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 query.EnterpriseName != "" {
|
||||
dbQuery = dbQuery.Joins("JOIN enterprises ON certifications.enterprise_id = enterprises.id").
|
||||
Where("enterprises.enterprise_name LIKE ?", "%"+query.EnterpriseName+"%")
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
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(&certs).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
certPtrs := make([]*entities.Certification, len(certs))
|
||||
for i := range certs {
|
||||
certPtrs[i] = &certs[i]
|
||||
}
|
||||
|
||||
return certPtrs, total, nil
|
||||
}
|
||||
|
||||
// GetByUserID 根据用户ID获取认证申请
|
||||
func (r *GormCertificationRepository) GetByUserID(ctx context.Context, userID string) (*entities.Certification, error) {
|
||||
var cert entities.Certification
|
||||
err := r.db.WithContext(ctx).Where("user_id = ?", userID).First(&cert).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &cert, nil
|
||||
}
|
||||
|
||||
// GetByStatus 根据状态获取认证申请列表
|
||||
func (r *GormCertificationRepository) GetByStatus(ctx context.Context, status string) ([]*entities.Certification, error) {
|
||||
var certs []entities.Certification
|
||||
err := r.db.WithContext(ctx).Where("status = ?", status).Find(&certs).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certPtrs := make([]*entities.Certification, len(certs))
|
||||
for i := range certs {
|
||||
certPtrs[i] = &certs[i]
|
||||
}
|
||||
|
||||
return certPtrs, nil
|
||||
}
|
||||
|
||||
// UpdateStatus 更新认证状态
|
||||
func (r *GormCertificationRepository) UpdateStatus(ctx context.Context, certificationID string, status string, adminID *string, notes string) error {
|
||||
updates := map[string]interface{}{
|
||||
"status": status,
|
||||
}
|
||||
|
||||
if adminID != nil {
|
||||
updates["admin_id"] = *adminID
|
||||
}
|
||||
|
||||
if notes != "" {
|
||||
updates["approval_notes"] = notes
|
||||
}
|
||||
|
||||
// 根据状态设置相应的时间戳
|
||||
switch status {
|
||||
case "INFO_SUBMITTED":
|
||||
updates["info_submitted_at"] = time.Now()
|
||||
case "FACE_VERIFIED":
|
||||
updates["face_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":
|
||||
updates["contract_signed_at"] = time.Now()
|
||||
case "COMPLETED":
|
||||
updates["completed_at"] = time.Now()
|
||||
}
|
||||
|
||||
return r.db.WithContext(ctx).
|
||||
Model(&entities.Certification{}).
|
||||
Where("id = ?", certificationID).
|
||||
Updates(updates).Error
|
||||
}
|
||||
|
||||
// GetPendingCertifications 获取待审核的认证申请
|
||||
func (r *GormCertificationRepository) GetPendingCertifications(ctx context.Context) ([]*entities.Certification, error) {
|
||||
return r.GetByStatus(ctx, "CONTRACT_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 {
|
||||
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 {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// GetStatsByDateRange 根据日期范围获取认证统计信息
|
||||
func (r *GormCertificationRepository) GetStatsByDateRange(ctx context.Context, startDate, endDate string) (*repositories.CertificationStats, error) {
|
||||
var stats repositories.CertificationStats
|
||||
|
||||
// 总认证申请数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("created_at BETWEEN ? AND ?", startDate, endDate).Count(&stats.TotalCertifications).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 待审核认证申请数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Certification{}).Where("status = ? AND created_at BETWEEN ? AND ?", "CONTRACT_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 {
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
@@ -0,0 +1,422 @@
|
||||
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,394 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,344 @@
|
||||
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
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/finance/entities"
|
||||
domain_finance_repo "tyapi-server/internal/domains/finance/repositories"
|
||||
"tyapi-server/internal/domains/finance/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
@@ -18,8 +20,11 @@ type GormWalletRepository struct {
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ domain_finance_repo.WalletRepository = (*GormWalletRepository)(nil)
|
||||
|
||||
// NewGormWalletRepository 创建钱包GORM仓储
|
||||
func NewGormWalletRepository(db *gorm.DB, logger *zap.Logger) *GormWalletRepository {
|
||||
func NewGormWalletRepository(db *gorm.DB, logger *zap.Logger) domain_finance_repo.WalletRepository {
|
||||
return &GormWalletRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
@@ -27,9 +32,10 @@ func NewGormWalletRepository(db *gorm.DB, logger *zap.Logger) *GormWalletReposit
|
||||
}
|
||||
|
||||
// Create 创建钱包
|
||||
func (r *GormWalletRepository) Create(ctx context.Context, wallet entities.Wallet) error {
|
||||
func (r *GormWalletRepository) Create(ctx context.Context, wallet entities.Wallet) (entities.Wallet, error) {
|
||||
r.logger.Info("创建钱包", zap.String("user_id", wallet.UserID))
|
||||
return r.db.WithContext(ctx).Create(&wallet).Error
|
||||
err := r.db.WithContext(ctx).Create(&wallet).Error
|
||||
return wallet, err
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取钱包
|
||||
@@ -172,21 +178,6 @@ func (r *GormWalletRepository) ExistsByUserID(ctx context.Context, userID string
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// UpdateBalance 更新余额
|
||||
func (r *GormWalletRepository) UpdateBalance(ctx context.Context, userID string, balance interface{}) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.Wallet{}).Where("user_id = ?", userID).Update("balance", balance).Error
|
||||
}
|
||||
|
||||
// AddBalance 增加余额
|
||||
func (r *GormWalletRepository) AddBalance(ctx context.Context, userID string, amount interface{}) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.Wallet{}).Where("user_id = ?", userID).Update("balance", gorm.Expr("balance + ?", amount)).Error
|
||||
}
|
||||
|
||||
// SubtractBalance 减少余额
|
||||
func (r *GormWalletRepository) SubtractBalance(ctx context.Context, userID string, amount interface{}) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.Wallet{}).Where("user_id = ?", userID).Update("balance", gorm.Expr("balance - ?", amount)).Error
|
||||
}
|
||||
|
||||
// GetTotalBalance 获取总余额
|
||||
func (r *GormWalletRepository) GetTotalBalance(ctx context.Context) (interface{}, error) {
|
||||
var total decimal.Decimal
|
||||
@@ -201,14 +192,181 @@ func (r *GormWalletRepository) GetActiveWalletCount(ctx context.Context) (int64,
|
||||
return count, err
|
||||
}
|
||||
|
||||
// ================ 接口要求的方法 ================
|
||||
|
||||
// GetByUserID 根据用户ID获取钱包
|
||||
func (r *GormWalletRepository) GetByUserID(ctx context.Context, userID string) (*entities.Wallet, error) {
|
||||
var wallet entities.Wallet
|
||||
err := r.db.WithContext(ctx).Where("user_id = ?", userID).First(&wallet).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wallet, nil
|
||||
}
|
||||
|
||||
// GetByWalletAddress 根据钱包地址获取钱包
|
||||
func (r *GormWalletRepository) GetByWalletAddress(ctx context.Context, walletAddress string) (*entities.Wallet, error) {
|
||||
var wallet entities.Wallet
|
||||
err := r.db.WithContext(ctx).Where("wallet_address = ?", walletAddress).First(&wallet).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wallet, nil
|
||||
}
|
||||
|
||||
// GetByWalletType 根据钱包类型获取钱包
|
||||
func (r *GormWalletRepository) GetByWalletType(ctx context.Context, userID string, walletType string) (*entities.Wallet, error) {
|
||||
var wallet entities.Wallet
|
||||
err := r.db.WithContext(ctx).Where("user_id = ? AND wallet_type = ?", userID, walletType).First(&wallet).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &wallet, nil
|
||||
}
|
||||
|
||||
// ListWallets 获取钱包列表(带分页和筛选)
|
||||
func (r *GormWalletRepository) ListWallets(ctx context.Context, query *queries.ListWalletsQuery) ([]*entities.Wallet, int64, error) {
|
||||
var wallets []entities.Wallet
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.Wallet{})
|
||||
|
||||
// 应用筛选条件
|
||||
if query.UserID != "" {
|
||||
dbQuery = dbQuery.Where("user_id = ?", query.UserID)
|
||||
}
|
||||
if query.WalletType != "" {
|
||||
dbQuery = dbQuery.Where("wallet_type = ?", query.WalletType)
|
||||
}
|
||||
if query.WalletAddress != "" {
|
||||
dbQuery = dbQuery.Where("wallet_address LIKE ?", "%"+query.WalletAddress+"%")
|
||||
}
|
||||
if query.IsActive != nil {
|
||||
dbQuery = dbQuery.Where("is_active = ?", *query.IsActive)
|
||||
}
|
||||
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(&wallets).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
walletPtrs := make([]*entities.Wallet, len(wallets))
|
||||
for i := range wallets {
|
||||
walletPtrs[i] = &wallets[i]
|
||||
}
|
||||
|
||||
return walletPtrs, total, nil
|
||||
}
|
||||
|
||||
// UpdateBalance 更新钱包余额
|
||||
func (r *GormWalletRepository) UpdateBalance(ctx context.Context, walletID string, balance string) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.Wallet{}).Where("id = ?", walletID).Update("balance", balance).Error
|
||||
}
|
||||
|
||||
// AddBalance 增加钱包余额
|
||||
func (r *GormWalletRepository) AddBalance(ctx context.Context, walletID string, amount string) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.Wallet{}).Where("id = ?", walletID).Update("balance", gorm.Expr("balance + ?", amount)).Error
|
||||
}
|
||||
|
||||
// SubtractBalance 减少钱包余额
|
||||
func (r *GormWalletRepository) SubtractBalance(ctx context.Context, walletID string, amount string) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.Wallet{}).Where("id = ?", walletID).Update("balance", gorm.Expr("balance - ?", amount)).Error
|
||||
}
|
||||
|
||||
// ActivateWallet 激活钱包
|
||||
func (r *GormWalletRepository) ActivateWallet(ctx context.Context, walletID string) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.Wallet{}).Where("id = ?", walletID).Update("is_active", true).Error
|
||||
}
|
||||
|
||||
// DeactivateWallet 停用钱包
|
||||
func (r *GormWalletRepository) DeactivateWallet(ctx context.Context, walletID string) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.Wallet{}).Where("id = ?", walletID).Update("is_active", false).Error
|
||||
}
|
||||
|
||||
// GetStats 获取财务统计信息
|
||||
func (r *GormWalletRepository) GetStats(ctx context.Context) (*domain_finance_repo.FinanceStats, error) {
|
||||
var stats domain_finance_repo.FinanceStats
|
||||
|
||||
// 总钱包数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Wallet{}).Count(&stats.TotalWallets).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 激活钱包数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Wallet{}).Where("is_active = ?", true).Count(&stats.ActiveWallets).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 总余额
|
||||
var totalBalance decimal.Decimal
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Wallet{}).Select("COALESCE(SUM(balance), 0)").Scan(&totalBalance).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.TotalBalance = totalBalance.String()
|
||||
|
||||
// 今日交易数(这里需要根据实际业务逻辑实现)
|
||||
stats.TodayTransactions = 0
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// GetUserWalletStats 获取用户钱包统计信息
|
||||
func (r *GormWalletRepository) GetUserWalletStats(ctx context.Context, userID string) (*domain_finance_repo.FinanceStats, error) {
|
||||
var stats domain_finance_repo.FinanceStats
|
||||
|
||||
// 用户钱包数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Wallet{}).Where("user_id = ?", userID).Count(&stats.TotalWallets).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 用户激活钱包数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Wallet{}).Where("user_id = ? AND is_active = ?", userID, true).Count(&stats.ActiveWallets).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 用户总余额
|
||||
var totalBalance decimal.Decimal
|
||||
if err := r.db.WithContext(ctx).Model(&entities.Wallet{}).Where("user_id = ?", userID).Select("COALESCE(SUM(balance), 0)").Scan(&totalBalance).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats.TotalBalance = totalBalance.String()
|
||||
|
||||
// 用户今日交易数(这里需要根据实际业务逻辑实现)
|
||||
stats.TodayTransactions = 0
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// GormUserSecretsRepository 用户密钥GORM仓储实现
|
||||
type GormUserSecretsRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// 编译时检查接口实现
|
||||
var _ domain_finance_repo.UserSecretsRepository = (*GormUserSecretsRepository)(nil)
|
||||
|
||||
// NewGormUserSecretsRepository 创建用户密钥GORM仓储
|
||||
func NewGormUserSecretsRepository(db *gorm.DB, logger *zap.Logger) *GormUserSecretsRepository {
|
||||
func NewGormUserSecretsRepository(db *gorm.DB, logger *zap.Logger) domain_finance_repo.UserSecretsRepository {
|
||||
return &GormUserSecretsRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
@@ -216,9 +374,10 @@ func NewGormUserSecretsRepository(db *gorm.DB, logger *zap.Logger) *GormUserSecr
|
||||
}
|
||||
|
||||
// Create 创建用户密钥
|
||||
func (r *GormUserSecretsRepository) Create(ctx context.Context, secrets entities.UserSecrets) error {
|
||||
func (r *GormUserSecretsRepository) Create(ctx context.Context, secrets entities.UserSecrets) (entities.UserSecrets, error) {
|
||||
r.logger.Info("创建用户密钥", zap.String("user_id", secrets.UserID))
|
||||
return r.db.WithContext(ctx).Create(&secrets).Error
|
||||
err := r.db.WithContext(ctx).Create(&secrets).Error
|
||||
return secrets, err
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取用户密钥
|
||||
@@ -406,5 +565,99 @@ func (r *GormUserSecretsRepository) GetExpiredSecrets(ctx context.Context) ([]en
|
||||
|
||||
// DeleteExpiredSecrets 删除过期的密钥
|
||||
func (r *GormUserSecretsRepository) DeleteExpiredSecrets(ctx context.Context) error {
|
||||
return r.db.WithContext(ctx).Where("expires_at IS NOT NULL AND expires_at < ?", time.Now()).Delete(&entities.UserSecrets{}).Error
|
||||
return r.db.WithContext(ctx).Where("expires_at < ?", time.Now()).Delete(&entities.UserSecrets{}).Error
|
||||
}
|
||||
|
||||
// ================ 接口要求的方法 ================
|
||||
|
||||
// GetByUserID 根据用户ID获取用户密钥
|
||||
func (r *GormUserSecretsRepository) GetByUserID(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 {
|
||||
return nil, err
|
||||
}
|
||||
return &secrets, nil
|
||||
}
|
||||
|
||||
// GetBySecretType 根据密钥类型获取用户密钥
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
return &secrets, nil
|
||||
}
|
||||
|
||||
// ListUserSecrets 获取用户密钥列表(带分页和筛选)
|
||||
func (r *GormUserSecretsRepository) ListUserSecrets(ctx context.Context, query *queries.ListUserSecretsQuery) ([]*entities.UserSecrets, int64, error) {
|
||||
var secrets []entities.UserSecrets
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.UserSecrets{})
|
||||
|
||||
// 应用筛选条件
|
||||
if query.UserID != "" {
|
||||
dbQuery = dbQuery.Where("user_id = ?", query.UserID)
|
||||
}
|
||||
if query.SecretType != "" {
|
||||
dbQuery = dbQuery.Where("secret_type = ?", query.SecretType)
|
||||
}
|
||||
if query.IsActive != nil {
|
||||
dbQuery = dbQuery.Where("is_active = ?", *query.IsActive)
|
||||
}
|
||||
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(&secrets).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
secretPtrs := make([]*entities.UserSecrets, len(secrets))
|
||||
for i := range secrets {
|
||||
secretPtrs[i] = &secrets[i]
|
||||
}
|
||||
|
||||
return secretPtrs, total, nil
|
||||
}
|
||||
|
||||
// UpdateSecret 更新密钥
|
||||
func (r *GormUserSecretsRepository) UpdateSecret(ctx context.Context, userID string, secretType string, secretValue string) error {
|
||||
return r.db.WithContext(ctx).Model(&entities.UserSecrets{}).
|
||||
Where("user_id = ? AND secret_type = ?", userID, secretType).
|
||||
Update("secret_value", secretValue).Error
|
||||
}
|
||||
|
||||
// DeleteSecret 删除密钥
|
||||
func (r *GormUserSecretsRepository) DeleteSecret(ctx context.Context, userID string, secretType string) error {
|
||||
return r.db.WithContext(ctx).Where("user_id = ? AND secret_type = ?", userID, secretType).
|
||||
Delete(&entities.UserSecrets{}).Error
|
||||
}
|
||||
|
||||
// ValidateSecret 验证密钥
|
||||
func (r *GormUserSecretsRepository) ValidateSecret(ctx context.Context, userID string, secretType string, secretValue string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.UserSecrets{}).
|
||||
Where("user_id = ? AND secret_type = ? AND secret_value = ?", userID, secretType, secretValue).
|
||||
Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/repositories"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// GormEnterpriseInfoRepository 企业信息GORM仓储实现
|
||||
type GormEnterpriseInfoRepository struct {
|
||||
db *gorm.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewGormEnterpriseInfoRepository 创建企业信息GORM仓储
|
||||
func NewGormEnterpriseInfoRepository(db *gorm.DB, logger *zap.Logger) repositories.EnterpriseInfoRepository {
|
||||
return &GormEnterpriseInfoRepository{
|
||||
db: db,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建企业信息
|
||||
func (r *GormEnterpriseInfoRepository) Create(ctx context.Context, enterpriseInfo entities.EnterpriseInfo) (entities.EnterpriseInfo, error) {
|
||||
if err := r.db.WithContext(ctx).Create(&enterpriseInfo).Error; err != nil {
|
||||
r.logger.Error("创建企业信息失败", zap.Error(err))
|
||||
return entities.EnterpriseInfo{}, fmt.Errorf("创建企业信息失败: %w", err)
|
||||
}
|
||||
return enterpriseInfo, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取企业信息
|
||||
func (r *GormEnterpriseInfoRepository) GetByID(ctx context.Context, id string) (entities.EnterpriseInfo, error) {
|
||||
var enterpriseInfo entities.EnterpriseInfo
|
||||
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&enterpriseInfo).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return entities.EnterpriseInfo{}, fmt.Errorf("企业信息不存在")
|
||||
}
|
||||
r.logger.Error("获取企业信息失败", zap.Error(err))
|
||||
return entities.EnterpriseInfo{}, fmt.Errorf("获取企业信息失败: %w", err)
|
||||
}
|
||||
return enterpriseInfo, nil
|
||||
}
|
||||
|
||||
// Update 更新企业信息
|
||||
func (r *GormEnterpriseInfoRepository) Update(ctx context.Context, enterpriseInfo entities.EnterpriseInfo) error {
|
||||
// 检查企业信息是否已认证完成,认证完成后不可修改
|
||||
if enterpriseInfo.IsReadOnly() {
|
||||
return fmt.Errorf("企业信息已认证完成,不可修改")
|
||||
}
|
||||
|
||||
if err := r.db.WithContext(ctx).Save(&enterpriseInfo).Error; err != nil {
|
||||
r.logger.Error("更新企业信息失败", zap.Error(err))
|
||||
return fmt.Errorf("更新企业信息失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除企业信息
|
||||
func (r *GormEnterpriseInfoRepository) Delete(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Delete(&entities.EnterpriseInfo{}, "id = ?", id).Error; err != nil {
|
||||
r.logger.Error("删除企业信息失败", zap.Error(err))
|
||||
return fmt.Errorf("删除企业信息失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SoftDelete 软删除企业信息
|
||||
func (r *GormEnterpriseInfoRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Delete(&entities.EnterpriseInfo{}, "id = ?", id).Error; err != nil {
|
||||
r.logger.Error("软删除企业信息失败", zap.Error(err))
|
||||
return fmt.Errorf("软删除企业信息失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore 恢复软删除的企业信息
|
||||
func (r *GormEnterpriseInfoRepository) Restore(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Unscoped().Model(&entities.EnterpriseInfo{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
|
||||
r.logger.Error("恢复企业信息失败", zap.Error(err))
|
||||
return fmt.Errorf("恢复企业信息失败: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByUserID 根据用户ID获取企业信息
|
||||
func (r *GormEnterpriseInfoRepository) GetByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfo, error) {
|
||||
var enterpriseInfo entities.EnterpriseInfo
|
||||
if err := r.db.WithContext(ctx).Where("user_id = ?", userID).First(&enterpriseInfo).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("企业信息不存在")
|
||||
}
|
||||
r.logger.Error("获取企业信息失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取企业信息失败: %w", err)
|
||||
}
|
||||
return &enterpriseInfo, nil
|
||||
}
|
||||
|
||||
// GetByUnifiedSocialCode 根据统一社会信用代码获取企业信息
|
||||
func (r *GormEnterpriseInfoRepository) GetByUnifiedSocialCode(ctx context.Context, unifiedSocialCode string) (*entities.EnterpriseInfo, error) {
|
||||
var enterpriseInfo entities.EnterpriseInfo
|
||||
if err := r.db.WithContext(ctx).Where("unified_social_code = ?", unifiedSocialCode).First(&enterpriseInfo).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("企业信息不存在")
|
||||
}
|
||||
r.logger.Error("获取企业信息失败", zap.Error(err))
|
||||
return nil, fmt.Errorf("获取企业信息失败: %w", err)
|
||||
}
|
||||
return &enterpriseInfo, nil
|
||||
}
|
||||
|
||||
// CheckUnifiedSocialCodeExists 检查统一社会信用代码是否已存在
|
||||
func (r *GormEnterpriseInfoRepository) CheckUnifiedSocialCodeExists(ctx context.Context, unifiedSocialCode string, excludeUserID string) (bool, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.EnterpriseInfo{}).Where("unified_social_code = ?", unifiedSocialCode)
|
||||
|
||||
if excludeUserID != "" {
|
||||
query = query.Where("user_id != ?", excludeUserID)
|
||||
}
|
||||
|
||||
if err := query.Count(&count).Error; err != nil {
|
||||
r.logger.Error("检查统一社会信用代码失败", zap.Error(err))
|
||||
return false, fmt.Errorf("检查统一社会信用代码失败: %w", err)
|
||||
}
|
||||
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// UpdateVerificationStatus 更新验证状态
|
||||
func (r *GormEnterpriseInfoRepository) UpdateVerificationStatus(ctx context.Context, userID string, isOCRVerified, isFaceVerified, isCertified bool) error {
|
||||
updates := map[string]interface{}{
|
||||
"is_ocr_verified": isOCRVerified,
|
||||
"is_face_verified": isFaceVerified,
|
||||
"is_certified": isCertified,
|
||||
}
|
||||
|
||||
if err := r.db.WithContext(ctx).Model(&entities.EnterpriseInfo{}).Where("user_id = ?", userID).Updates(updates).Error; err != nil {
|
||||
r.logger.Error("更新验证状态失败", zap.Error(err))
|
||||
return fmt.Errorf("更新验证状态失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateOCRData 更新OCR数据
|
||||
func (r *GormEnterpriseInfoRepository) UpdateOCRData(ctx context.Context, userID string, rawData string, confidence float64) error {
|
||||
updates := map[string]interface{}{
|
||||
"ocr_raw_data": rawData,
|
||||
"ocr_confidence": confidence,
|
||||
"is_ocr_verified": true,
|
||||
}
|
||||
|
||||
if err := r.db.WithContext(ctx).Model(&entities.EnterpriseInfo{}).Where("user_id = ?", userID).Updates(updates).Error; err != nil {
|
||||
r.logger.Error("更新OCR数据失败", zap.Error(err))
|
||||
return fmt.Errorf("更新OCR数据失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompleteCertification 完成认证
|
||||
func (r *GormEnterpriseInfoRepository) CompleteCertification(ctx context.Context, userID string) error {
|
||||
now := time.Now()
|
||||
updates := map[string]interface{}{
|
||||
"is_certified": true,
|
||||
"certified_at": &now,
|
||||
}
|
||||
|
||||
if err := r.db.WithContext(ctx).Model(&entities.EnterpriseInfo{}).Where("user_id = ?", userID).Updates(updates).Error; err != nil {
|
||||
r.logger.Error("完成认证失败", zap.Error(err))
|
||||
return fmt.Errorf("完成认证失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count 统计企业信息数量
|
||||
func (r *GormEnterpriseInfoRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.EnterpriseInfo{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("company_name LIKE ? OR unified_social_code LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
err := query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// Exists 检查企业信息是否存在
|
||||
func (r *GormEnterpriseInfoRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.EnterpriseInfo{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建企业信息
|
||||
func (r *GormEnterpriseInfoRepository) CreateBatch(ctx context.Context, enterpriseInfos []entities.EnterpriseInfo) error {
|
||||
return r.db.WithContext(ctx).Create(&enterpriseInfos).Error
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取企业信息
|
||||
func (r *GormEnterpriseInfoRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.EnterpriseInfo, error) {
|
||||
var enterpriseInfos []entities.EnterpriseInfo
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&enterpriseInfos).Error
|
||||
return enterpriseInfos, err
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新企业信息
|
||||
func (r *GormEnterpriseInfoRepository) UpdateBatch(ctx context.Context, enterpriseInfos []entities.EnterpriseInfo) error {
|
||||
return r.db.WithContext(ctx).Save(&enterpriseInfos).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除企业信息
|
||||
func (r *GormEnterpriseInfoRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
return r.db.WithContext(ctx).Delete(&entities.EnterpriseInfo{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取企业信息列表
|
||||
func (r *GormEnterpriseInfoRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.EnterpriseInfo, error) {
|
||||
var enterpriseInfos []entities.EnterpriseInfo
|
||||
query := r.db.WithContext(ctx).Model(&entities.EnterpriseInfo{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("company_name LIKE ? OR unified_social_code 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)
|
||||
}
|
||||
|
||||
err := query.Find(&enterpriseInfos).Error
|
||||
return enterpriseInfos, err
|
||||
}
|
||||
|
||||
// WithTx 使用事务
|
||||
func (r *GormEnterpriseInfoRepository) WithTx(tx interface{}) interfaces.Repository[entities.EnterpriseInfo] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormEnterpriseInfoRepository{
|
||||
db: gormTx,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
@@ -0,0 +1,499 @@
|
||||
//go:build !test
|
||||
// +build !test
|
||||
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/repositories"
|
||||
"tyapi-server/internal/domains/user/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// SMSCodeRepository 短信验证码仓储
|
||||
type GormSMSCodeRepository struct {
|
||||
db *gorm.DB
|
||||
cache interfaces.CacheService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewGormSMSCodeRepository 创建短信验证码仓储
|
||||
func NewGormSMSCodeRepository(db *gorm.DB, cache interfaces.CacheService, logger *zap.Logger) repositories.SMSCodeRepository {
|
||||
return &GormSMSCodeRepository{
|
||||
db: db,
|
||||
cache: cache,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// 确保 GormSMSCodeRepository 实现了 SMSCodeRepository 接口
|
||||
var _ repositories.SMSCodeRepository = (*GormSMSCodeRepository)(nil)
|
||||
|
||||
// ================ 基础CRUD操作 ================
|
||||
|
||||
// Create 创建短信验证码记录
|
||||
func (r *GormSMSCodeRepository) Create(ctx context.Context, smsCode entities.SMSCode) (entities.SMSCode, error) {
|
||||
if err := r.db.WithContext(ctx).Create(&smsCode).Error; err != nil {
|
||||
r.logger.Error("创建短信验证码失败", zap.Error(err))
|
||||
return entities.SMSCode{}, err
|
||||
}
|
||||
|
||||
// 缓存验证码
|
||||
cacheKey := r.buildCacheKey(smsCode.Phone, smsCode.Scene)
|
||||
r.cache.Set(ctx, cacheKey, &smsCode, 5*time.Minute)
|
||||
|
||||
return smsCode, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取短信验证码
|
||||
func (r *GormSMSCodeRepository) GetByID(ctx context.Context, id string) (entities.SMSCode, error) {
|
||||
var smsCode entities.SMSCode
|
||||
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&smsCode).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return entities.SMSCode{}, fmt.Errorf("短信验证码不存在")
|
||||
}
|
||||
r.logger.Error("获取短信验证码失败", zap.Error(err))
|
||||
return entities.SMSCode{}, err
|
||||
}
|
||||
|
||||
return smsCode, nil
|
||||
}
|
||||
|
||||
// Update 更新验证码记录
|
||||
func (r *GormSMSCodeRepository) Update(ctx context.Context, smsCode entities.SMSCode) error {
|
||||
if err := r.db.WithContext(ctx).Save(&smsCode).Error; err != nil {
|
||||
r.logger.Error("更新验证码记录失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 更新缓存
|
||||
cacheKey := r.buildCacheKey(smsCode.Phone, smsCode.Scene)
|
||||
r.cache.Set(ctx, cacheKey, &smsCode, 5*time.Minute)
|
||||
|
||||
r.logger.Info("验证码记录更新成功", zap.String("code_id", smsCode.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除短信验证码
|
||||
func (r *GormSMSCodeRepository) Delete(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Delete(&entities.SMSCode{}, "id = ?", id).Error; err != nil {
|
||||
r.logger.Error("删除短信验证码失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("短信验证码删除成功", zap.String("id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// SoftDelete 软删除短信验证码
|
||||
func (r *GormSMSCodeRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
return r.Delete(ctx, id)
|
||||
}
|
||||
|
||||
// Restore 恢复短信验证码
|
||||
func (r *GormSMSCodeRepository) Restore(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Unscoped().Model(&entities.SMSCode{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
|
||||
r.logger.Error("恢复短信验证码失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("短信验证码恢复成功", zap.String("id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count 统计短信验证码数量
|
||||
func (r *GormSMSCodeRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.SMSCode{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("phone LIKE ? OR code LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
err := query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// Exists 检查短信验证码是否存在
|
||||
func (r *GormSMSCodeRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.SMSCode{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建短信验证码
|
||||
func (r *GormSMSCodeRepository) CreateBatch(ctx context.Context, smsCodes []entities.SMSCode) error {
|
||||
r.logger.Info("批量创建短信验证码", zap.Int("count", len(smsCodes)))
|
||||
return r.db.WithContext(ctx).Create(&smsCodes).Error
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取短信验证码
|
||||
func (r *GormSMSCodeRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.SMSCode, error) {
|
||||
var smsCodes []entities.SMSCode
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&smsCodes).Error
|
||||
return smsCodes, err
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新短信验证码
|
||||
func (r *GormSMSCodeRepository) UpdateBatch(ctx context.Context, smsCodes []entities.SMSCode) error {
|
||||
r.logger.Info("批量更新短信验证码", zap.Int("count", len(smsCodes)))
|
||||
return r.db.WithContext(ctx).Save(&smsCodes).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除短信验证码
|
||||
func (r *GormSMSCodeRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
r.logger.Info("批量删除短信验证码", zap.Strings("ids", ids))
|
||||
return r.db.WithContext(ctx).Delete(&entities.SMSCode{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取短信验证码列表
|
||||
func (r *GormSMSCodeRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.SMSCode, error) {
|
||||
var smsCodes []entities.SMSCode
|
||||
query := r.db.WithContext(ctx).Model(&entities.SMSCode{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("phone LIKE ? OR code 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 smsCodes, query.Find(&smsCodes).Error
|
||||
}
|
||||
|
||||
// WithTx 使用事务
|
||||
func (r *GormSMSCodeRepository) WithTx(tx interface{}) interfaces.Repository[entities.SMSCode] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormSMSCodeRepository{
|
||||
db: gormTx,
|
||||
cache: r.cache,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ================ 业务方法 ================
|
||||
|
||||
// GetByPhone 根据手机号获取短信验证码
|
||||
func (r *GormSMSCodeRepository) GetByPhone(ctx context.Context, phone string) (*entities.SMSCode, error) {
|
||||
var smsCode entities.SMSCode
|
||||
if err := r.db.WithContext(ctx).Where("phone = ?", phone).Order("created_at DESC").First(&smsCode).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("短信验证码不存在")
|
||||
}
|
||||
r.logger.Error("根据手机号获取短信验证码失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &smsCode, nil
|
||||
}
|
||||
|
||||
// GetLatestByPhone 根据手机号获取最新的短信验证码
|
||||
func (r *GormSMSCodeRepository) GetLatestByPhone(ctx context.Context, phone string) (*entities.SMSCode, error) {
|
||||
return r.GetByPhone(ctx, phone)
|
||||
}
|
||||
|
||||
// GetValidByPhone 根据手机号获取有效的短信验证码
|
||||
func (r *GormSMSCodeRepository) GetValidByPhone(ctx context.Context, phone string) (*entities.SMSCode, error) {
|
||||
return r.GetValidCode(ctx, phone, "")
|
||||
}
|
||||
|
||||
// GetValidByPhoneAndScene 根据手机号和场景获取有效的验证码
|
||||
func (r *GormSMSCodeRepository) GetValidByPhoneAndScene(ctx context.Context, phone string, scene entities.SMSScene) (*entities.SMSCode, error) {
|
||||
return r.GetValidCode(ctx, phone, scene)
|
||||
}
|
||||
|
||||
// ListSMSCodes 获取短信验证码列表(带分页和筛选)
|
||||
func (r *GormSMSCodeRepository) ListSMSCodes(ctx context.Context, query *queries.ListSMSCodesQuery) ([]*entities.SMSCode, int64, error) {
|
||||
var smsCodes []entities.SMSCode
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.SMSCode{})
|
||||
|
||||
// 应用筛选条件
|
||||
if query.Phone != "" {
|
||||
dbQuery = dbQuery.Where("phone = ?", query.Phone)
|
||||
}
|
||||
if query.Purpose != "" {
|
||||
dbQuery = dbQuery.Where("scene = ?", query.Purpose)
|
||||
}
|
||||
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(&smsCodes).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
smsCodePtrs := make([]*entities.SMSCode, len(smsCodes))
|
||||
for i := range smsCodes {
|
||||
smsCodePtrs[i] = &smsCodes[i]
|
||||
}
|
||||
|
||||
return smsCodePtrs, total, nil
|
||||
}
|
||||
|
||||
// CreateCode 创建验证码
|
||||
func (r *GormSMSCodeRepository) CreateCode(ctx context.Context, phone string, code string, purpose string) (entities.SMSCode, error) {
|
||||
smsCode := entities.SMSCode{
|
||||
Phone: phone,
|
||||
Code: code,
|
||||
Scene: entities.SMSScene(purpose),
|
||||
ExpiresAt: time.Now().Add(5 * time.Minute), // 5分钟过期
|
||||
Used: false,
|
||||
}
|
||||
|
||||
return r.Create(ctx, smsCode)
|
||||
}
|
||||
|
||||
// ValidateCode 验证验证码
|
||||
func (r *GormSMSCodeRepository) ValidateCode(ctx context.Context, phone string, code string, purpose string) (bool, error) {
|
||||
var smsCode entities.SMSCode
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("phone = ? AND code = ? AND scene = ? AND expires_at > ? AND used_at IS NULL",
|
||||
phone, code, purpose, time.Now()).
|
||||
First(&smsCode).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return false, nil
|
||||
}
|
||||
r.logger.Error("验证验证码失败", zap.Error(err))
|
||||
return false, err
|
||||
}
|
||||
|
||||
// 标记为已使用
|
||||
if err := r.MarkAsUsed(ctx, smsCode.ID); err != nil {
|
||||
r.logger.Error("标记验证码为已使用失败", zap.Error(err))
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// InvalidateCode 使验证码失效
|
||||
func (r *GormSMSCodeRepository) InvalidateCode(ctx context.Context, phone string) error {
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.SMSCode{}).
|
||||
Where("phone = ? AND used_at IS NULL", phone).
|
||||
Update("used_at", time.Now()).Error; err != nil {
|
||||
r.logger.Error("使验证码失效失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
cacheKey := r.buildCacheKey(phone, "")
|
||||
r.cache.Delete(ctx, cacheKey)
|
||||
|
||||
r.logger.Info("验证码已失效", zap.String("phone", phone))
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckSendFrequency 检查发送频率
|
||||
func (r *GormSMSCodeRepository) CheckSendFrequency(ctx context.Context, phone string, purpose string) (bool, error) {
|
||||
// 检查最近1分钟内是否已发送
|
||||
oneMinuteAgo := time.Now().Add(-1 * time.Minute)
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.SMSCode{}).
|
||||
Where("phone = ? AND scene = ? AND created_at > ?", phone, purpose, oneMinuteAgo).
|
||||
Count(&count).Error; err != nil {
|
||||
r.logger.Error("检查发送频率失败", zap.Error(err))
|
||||
return false, err
|
||||
}
|
||||
|
||||
return count == 0, nil
|
||||
}
|
||||
|
||||
// GetTodaySendCount 获取今日发送次数
|
||||
func (r *GormSMSCodeRepository) GetTodaySendCount(ctx context.Context, phone string) (int64, error) {
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.SMSCode{}).
|
||||
Where("phone = ? AND created_at >= ?", phone, today).
|
||||
Count(&count).Error; err != nil {
|
||||
r.logger.Error("获取今日发送次数失败", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// GetCodeStats 获取验证码统计信息
|
||||
func (r *GormSMSCodeRepository) GetCodeStats(ctx context.Context, phone string, days int) (*repositories.SMSCodeStats, error) {
|
||||
var stats repositories.SMSCodeStats
|
||||
|
||||
// 计算指定天数前的日期
|
||||
startDate := time.Now().AddDate(0, 0, -days)
|
||||
|
||||
// 总发送数
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.SMSCode{}).
|
||||
Where("phone = ? AND created_at >= ?", phone, startDate).
|
||||
Count(&stats.TotalSent).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 总验证数
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.SMSCode{}).
|
||||
Where("phone = ? AND created_at >= ? AND used_at IS NOT NULL", phone, startDate).
|
||||
Count(&stats.TotalValidated).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 成功率
|
||||
if stats.TotalSent > 0 {
|
||||
stats.SuccessRate = float64(stats.TotalValidated) / float64(stats.TotalSent) * 100
|
||||
}
|
||||
|
||||
// 今日发送数
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.SMSCode{}).
|
||||
Where("phone = ? AND created_at >= ?", phone, today).
|
||||
Count(&stats.TodaySent).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// GetValidCode 获取有效的验证码
|
||||
func (r *GormSMSCodeRepository) GetValidCode(ctx context.Context, phone string, scene entities.SMSScene) (*entities.SMSCode, error) {
|
||||
// 先从缓存查找
|
||||
cacheKey := r.buildCacheKey(phone, scene)
|
||||
var smsCode entities.SMSCode
|
||||
if err := r.cache.Get(ctx, cacheKey, &smsCode); err == nil {
|
||||
return &smsCode, nil
|
||||
}
|
||||
|
||||
// 从数据库查找最新的有效验证码
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("phone = ? AND scene = ? AND expires_at > ? AND used_at IS NULL",
|
||||
phone, scene, time.Now()).
|
||||
Order("created_at DESC").
|
||||
First(&smsCode).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
r.cache.Set(ctx, cacheKey, &smsCode, 5*time.Minute)
|
||||
|
||||
return &smsCode, nil
|
||||
}
|
||||
|
||||
// MarkAsUsed 标记验证码为已使用
|
||||
func (r *GormSMSCodeRepository) MarkAsUsed(ctx context.Context, id string) error {
|
||||
now := time.Now()
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.SMSCode{}).
|
||||
Where("id = ?", id).
|
||||
Update("used_at", now).Error; err != nil {
|
||||
r.logger.Error("标记验证码为已使用失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("验证码已标记为使用", zap.String("code_id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRecentCode 获取最近的验证码记录(不限制有效性)
|
||||
func (r *GormSMSCodeRepository) GetRecentCode(ctx context.Context, phone string, scene entities.SMSScene) (*entities.SMSCode, error) {
|
||||
var smsCode entities.SMSCode
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("phone = ? AND scene = ?", phone, scene).
|
||||
Order("created_at DESC").
|
||||
First(&smsCode).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &smsCode, nil
|
||||
}
|
||||
|
||||
// CleanupExpired 清理过期的验证码
|
||||
func (r *GormSMSCodeRepository) CleanupExpired(ctx context.Context) error {
|
||||
result := r.db.WithContext(ctx).
|
||||
Where("expires_at < ?", time.Now()).
|
||||
Delete(&entities.SMSCode{})
|
||||
|
||||
if result.Error != nil {
|
||||
r.logger.Error("清理过期验证码失败", zap.Error(result.Error))
|
||||
return result.Error
|
||||
}
|
||||
|
||||
if result.RowsAffected > 0 {
|
||||
r.logger.Info("清理过期验证码完成", zap.Int64("count", result.RowsAffected))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CountRecentCodes 统计最近发送的验证码数量
|
||||
func (r *GormSMSCodeRepository) CountRecentCodes(ctx context.Context, phone string, scene entities.SMSScene, duration time.Duration) (int64, error) {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.SMSCode{}).
|
||||
Where("phone = ? AND scene = ? AND created_at > ?",
|
||||
phone, scene, time.Now().Add(-duration)).
|
||||
Count(&count).Error; err != nil {
|
||||
r.logger.Error("统计最近验证码数量失败", zap.Error(err))
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// buildCacheKey 构建缓存键
|
||||
func (r *GormSMSCodeRepository) buildCacheKey(phone string, scene entities.SMSScene) string {
|
||||
return fmt.Sprintf("sms_code:%s:%s", phone, string(scene))
|
||||
}
|
||||
@@ -0,0 +1,511 @@
|
||||
//go:build !test
|
||||
// +build !test
|
||||
|
||||
package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"tyapi-server/internal/domains/user/entities"
|
||||
"tyapi-server/internal/domains/user/repositories"
|
||||
"tyapi-server/internal/domains/user/repositories/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// 定义错误常量
|
||||
var (
|
||||
// ErrUserNotFound 用户不存在错误
|
||||
ErrUserNotFound = errors.New("用户不存在")
|
||||
)
|
||||
|
||||
// UserRepository 用户仓储实现
|
||||
type GormUserRepository struct {
|
||||
db *gorm.DB
|
||||
cache interfaces.CacheService
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewGormUserRepository 创建用户仓储
|
||||
func NewGormUserRepository(db *gorm.DB, cache interfaces.CacheService, logger *zap.Logger) repositories.UserRepository {
|
||||
return &GormUserRepository{
|
||||
db: db,
|
||||
cache: cache,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// 确保 GormUserRepository 实现了 UserRepository 接口
|
||||
var _ repositories.UserRepository = (*GormUserRepository)(nil)
|
||||
|
||||
// ================ 基础CRUD操作 ================
|
||||
|
||||
// Create 创建用户
|
||||
func (r *GormUserRepository) Create(ctx context.Context, user entities.User) (entities.User, error) {
|
||||
if err := r.db.WithContext(ctx).Create(&user).Error; err != nil {
|
||||
r.logger.Error("创建用户失败", zap.Error(err))
|
||||
return entities.User{}, err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.deleteCacheByPhone(ctx, user.Phone)
|
||||
|
||||
r.logger.Info("用户创建成功", zap.String("user_id", user.ID))
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetByID 根据ID获取用户
|
||||
func (r *GormUserRepository) GetByID(ctx context.Context, id string) (entities.User, error) {
|
||||
// 尝试从缓存获取
|
||||
cacheKey := fmt.Sprintf("user:id:%s", id)
|
||||
var userCache entities.UserCache
|
||||
if err := r.cache.Get(ctx, cacheKey, &userCache); err == nil {
|
||||
var user entities.User
|
||||
user.FromCache(&userCache)
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// 从数据库查询
|
||||
var user entities.User
|
||||
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&user).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return entities.User{}, ErrUserNotFound
|
||||
}
|
||||
r.logger.Error("根据ID查询用户失败", zap.Error(err))
|
||||
return entities.User{}, err
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
r.cache.Set(ctx, cacheKey, user.ToCache(), 10*time.Minute)
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// Update 更新用户
|
||||
func (r *GormUserRepository) Update(ctx context.Context, user entities.User) error {
|
||||
if err := r.db.WithContext(ctx).Save(&user).Error; err != nil {
|
||||
r.logger.Error("更新用户失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.deleteCacheByID(ctx, user.ID)
|
||||
r.deleteCacheByPhone(ctx, user.Phone)
|
||||
|
||||
r.logger.Info("用户更新成功", zap.String("user_id", user.ID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete 删除用户
|
||||
func (r *GormUserRepository) Delete(ctx context.Context, id string) error {
|
||||
// 先获取用户信息用于清除缓存
|
||||
user, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.db.WithContext(ctx).Delete(&entities.User{}, "id = ?", id).Error; err != nil {
|
||||
r.logger.Error("删除用户失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.deleteCacheByID(ctx, id)
|
||||
r.deleteCacheByPhone(ctx, user.Phone)
|
||||
|
||||
r.logger.Info("用户删除成功", zap.String("user_id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// SoftDelete 软删除用户
|
||||
func (r *GormUserRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
// 先获取用户信息用于清除缓存
|
||||
user, err := r.GetByID(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.db.WithContext(ctx).Delete(&entities.User{}, "id = ?", id).Error; err != nil {
|
||||
r.logger.Error("软删除用户失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.deleteCacheByID(ctx, id)
|
||||
r.deleteCacheByPhone(ctx, user.Phone)
|
||||
|
||||
r.logger.Info("用户软删除成功", zap.String("user_id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore 恢复软删除的用户
|
||||
func (r *GormUserRepository) Restore(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).Unscoped().Model(&entities.User{}).Where("id = ?", id).Update("deleted_at", nil).Error; err != nil {
|
||||
r.logger.Error("恢复用户失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.deleteCacheByID(ctx, id)
|
||||
|
||||
r.logger.Info("用户恢复成功", zap.String("user_id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Count 统计用户数量
|
||||
func (r *GormUserRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
var count int64
|
||||
query := r.db.WithContext(ctx).Model(&entities.User{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("phone LIKE ? OR nickname LIKE ?", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
err := query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// Exists 检查用户是否存在
|
||||
func (r *GormUserRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&entities.User{}).Where("id = ?", id).Count(&count).Error
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建用户
|
||||
func (r *GormUserRepository) CreateBatch(ctx context.Context, users []entities.User) error {
|
||||
r.logger.Info("批量创建用户", zap.Int("count", len(users)))
|
||||
return r.db.WithContext(ctx).Create(&users).Error
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取用户
|
||||
func (r *GormUserRepository) GetByIDs(ctx context.Context, ids []string) ([]entities.User, error) {
|
||||
var users []entities.User
|
||||
err := r.db.WithContext(ctx).Where("id IN ?", ids).Find(&users).Error
|
||||
return users, err
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新用户
|
||||
func (r *GormUserRepository) UpdateBatch(ctx context.Context, users []entities.User) error {
|
||||
r.logger.Info("批量更新用户", zap.Int("count", len(users)))
|
||||
return r.db.WithContext(ctx).Save(&users).Error
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除用户
|
||||
func (r *GormUserRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
r.logger.Info("批量删除用户", zap.Strings("ids", ids))
|
||||
return r.db.WithContext(ctx).Delete(&entities.User{}, "id IN ?", ids).Error
|
||||
}
|
||||
|
||||
// List 获取用户列表
|
||||
func (r *GormUserRepository) List(ctx context.Context, options interfaces.ListOptions) ([]entities.User, error) {
|
||||
var users []entities.User
|
||||
query := r.db.WithContext(ctx).Model(&entities.User{})
|
||||
|
||||
if options.Filters != nil {
|
||||
for key, value := range options.Filters {
|
||||
query = query.Where(key+" = ?", value)
|
||||
}
|
||||
}
|
||||
|
||||
if options.Search != "" {
|
||||
query = query.Where("phone LIKE ? OR nickname 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 users, query.Find(&users).Error
|
||||
}
|
||||
|
||||
// WithTx 使用事务
|
||||
func (r *GormUserRepository) WithTx(tx interface{}) interfaces.Repository[entities.User] {
|
||||
if gormTx, ok := tx.(*gorm.DB); ok {
|
||||
return &GormUserRepository{
|
||||
db: gormTx,
|
||||
cache: r.cache,
|
||||
logger: r.logger,
|
||||
}
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// ================ 业务方法 ================
|
||||
|
||||
// GetByPhone 根据手机号获取用户
|
||||
func (r *GormUserRepository) GetByPhone(ctx context.Context, phone string) (*entities.User, error) {
|
||||
// 尝试从缓存获取
|
||||
cacheKey := fmt.Sprintf("user:phone:%s", phone)
|
||||
var userCache entities.UserCache
|
||||
if err := r.cache.Get(ctx, cacheKey, &userCache); err == nil {
|
||||
var user entities.User
|
||||
user.FromCache(&userCache)
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// 从数据库查询
|
||||
var user entities.User
|
||||
if err := r.db.WithContext(ctx).Where("phone = ?", phone).First(&user).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
r.logger.Error("根据手机号查询用户失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
r.cache.Set(ctx, cacheKey, user.ToCache(), 10*time.Minute)
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// ListUsers 获取用户列表(带分页和筛选)
|
||||
func (r *GormUserRepository) ListUsers(ctx context.Context, query *queries.ListUsersQuery) ([]*entities.User, int64, error) {
|
||||
var users []entities.User
|
||||
var total int64
|
||||
|
||||
dbQuery := r.db.WithContext(ctx).Model(&entities.User{})
|
||||
|
||||
// 应用筛选条件
|
||||
if query.Phone != "" {
|
||||
dbQuery = dbQuery.Where("phone LIKE ?", "%"+query.Phone+"%")
|
||||
}
|
||||
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(&users).Error; err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
userPtrs := make([]*entities.User, len(users))
|
||||
for i := range users {
|
||||
userPtrs[i] = &users[i]
|
||||
}
|
||||
|
||||
return userPtrs, total, nil
|
||||
}
|
||||
|
||||
// ValidateUser 验证用户
|
||||
func (r *GormUserRepository) ValidateUser(ctx context.Context, phone, password string) (*entities.User, error) {
|
||||
var user entities.User
|
||||
if err := r.db.WithContext(ctx).Where("phone = ? AND password = ?", phone, password).First(&user).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fmt.Errorf("手机号或密码错误")
|
||||
}
|
||||
r.logger.Error("验证用户失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// UpdateLastLogin 更新最后登录时间
|
||||
func (r *GormUserRepository) UpdateLastLogin(ctx context.Context, userID string) error {
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.User{}).
|
||||
Where("id = ?", userID).
|
||||
Update("last_login_at", time.Now()).Error; err != nil {
|
||||
r.logger.Error("更新最后登录时间失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.deleteCacheByID(ctx, userID)
|
||||
|
||||
r.logger.Info("最后登录时间更新成功", zap.String("user_id", userID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdatePassword 更新密码
|
||||
func (r *GormUserRepository) UpdatePassword(ctx context.Context, userID string, newPassword string) error {
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.User{}).
|
||||
Where("id = ?", userID).
|
||||
Update("password", newPassword).Error; err != nil {
|
||||
r.logger.Error("更新密码失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.deleteCacheByID(ctx, userID)
|
||||
|
||||
r.logger.Info("密码更新成功", zap.String("user_id", userID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckPassword 检查密码
|
||||
func (r *GormUserRepository) CheckPassword(ctx context.Context, userID string, password string) (bool, error) {
|
||||
var user entities.User
|
||||
if err := r.db.WithContext(ctx).Where("id = ? AND password = ?", userID, password).First(&user).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return false, nil
|
||||
}
|
||||
r.logger.Error("检查密码失败", zap.Error(err))
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ActivateUser 激活用户
|
||||
func (r *GormUserRepository) ActivateUser(ctx context.Context, userID string) error {
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.User{}).
|
||||
Where("id = ?", userID).
|
||||
Update("status", "ACTIVE").Error; err != nil {
|
||||
r.logger.Error("激活用户失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.deleteCacheByID(ctx, userID)
|
||||
|
||||
r.logger.Info("用户激活成功", zap.String("user_id", userID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeactivateUser 停用用户
|
||||
func (r *GormUserRepository) DeactivateUser(ctx context.Context, userID string) error {
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.User{}).
|
||||
Where("id = ?", userID).
|
||||
Update("status", "INACTIVE").Error; err != nil {
|
||||
r.logger.Error("停用用户失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.deleteCacheByID(ctx, userID)
|
||||
|
||||
r.logger.Info("用户停用成功", zap.String("user_id", userID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStats 获取用户统计信息
|
||||
func (r *GormUserRepository) GetStats(ctx context.Context) (*repositories.UserStats, error) {
|
||||
var stats repositories.UserStats
|
||||
|
||||
// 总用户数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.User{}).Count(&stats.TotalUsers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 活跃用户数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.User{}).Where("status = ?", "ACTIVE").Count(&stats.ActiveUsers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 今日注册数
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
if err := r.db.WithContext(ctx).Model(&entities.User{}).Where("created_at >= ?", today).Count(&stats.TodayRegistrations).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 今日登录数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.User{}).Where("last_login_at >= ?", today).Count(&stats.TodayLogins).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// GetStatsByDateRange 根据日期范围获取用户统计信息
|
||||
func (r *GormUserRepository) GetStatsByDateRange(ctx context.Context, startDate, endDate string) (*repositories.UserStats, error) {
|
||||
var stats repositories.UserStats
|
||||
|
||||
// 总用户数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.User{}).Where("created_at BETWEEN ? AND ?", startDate, endDate).Count(&stats.TotalUsers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 活跃用户数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.User{}).Where("status = ? AND created_at BETWEEN ? AND ?", "ACTIVE", startDate, endDate).Count(&stats.ActiveUsers).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 今日注册数
|
||||
today := time.Now().Truncate(24 * time.Hour)
|
||||
if err := r.db.WithContext(ctx).Model(&entities.User{}).Where("created_at >= ?", today).Count(&stats.TodayRegistrations).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 今日登录数
|
||||
if err := r.db.WithContext(ctx).Model(&entities.User{}).Where("last_login_at >= ?", today).Count(&stats.TodayLogins).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// FindByPhone 根据手机号查找用户(兼容旧方法)
|
||||
func (r *GormUserRepository) FindByPhone(ctx context.Context, phone string) (*entities.User, error) {
|
||||
return r.GetByPhone(ctx, phone)
|
||||
}
|
||||
|
||||
// ExistsByPhone 检查手机号是否存在
|
||||
func (r *GormUserRepository) ExistsByPhone(ctx context.Context, phone string) (bool, error) {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).Model(&entities.User{}).Where("phone = ?", phone).Count(&count).Error; err != nil {
|
||||
r.logger.Error("检查手机号是否存在失败", zap.Error(err))
|
||||
return false, err
|
||||
}
|
||||
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
// 私有辅助方法
|
||||
|
||||
// deleteCacheByID 根据ID删除缓存
|
||||
func (r *GormUserRepository) deleteCacheByID(ctx context.Context, id string) {
|
||||
cacheKey := fmt.Sprintf("user:id:%s", id)
|
||||
if err := r.cache.Delete(ctx, cacheKey); err != nil {
|
||||
r.logger.Warn("删除用户ID缓存失败", zap.String("cache_key", cacheKey), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// deleteCacheByPhone 根据手机号删除缓存
|
||||
func (r *GormUserRepository) deleteCacheByPhone(ctx context.Context, phone string) {
|
||||
cacheKey := fmt.Sprintf("user:phone:%s", phone)
|
||||
if err := r.cache.Delete(ctx, cacheKey); err != nil {
|
||||
r.logger.Warn("删除用户手机号缓存失败", zap.String("cache_key", cacheKey), zap.Error(err))
|
||||
}
|
||||
}
|
||||
@@ -402,8 +402,8 @@ func (s *WeChatWorkService) sendMessage(ctx context.Context, message map[string]
|
||||
|
||||
// 检查错误码
|
||||
if errCode, ok := response["errcode"].(float64); ok && errCode != 0 {
|
||||
errMsg := response["errmsg"].(string)
|
||||
return fmt.Errorf("企业微信API错误: %d - %s", int(errCode), errMsg)
|
||||
errmsg := response["errmsg"].(string)
|
||||
return fmt.Errorf("企业微信API错误: %d - %s", int(errCode), errmsg)
|
||||
}
|
||||
|
||||
s.logger.Info("企业微信消息发送成功", zap.Any("response", response))
|
||||
@@ -8,16 +8,16 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/domains/certification/dto"
|
||||
"tyapi-server/internal/application/certification/dto/responses"
|
||||
)
|
||||
|
||||
// BaiduOCRService 百度OCR服务
|
||||
type BaiduOCRService struct {
|
||||
appID string
|
||||
apiKey string
|
||||
secretKey string
|
||||
endpoint string
|
||||
@@ -26,9 +26,8 @@ type BaiduOCRService struct {
|
||||
}
|
||||
|
||||
// NewBaiduOCRService 创建百度OCR服务
|
||||
func NewBaiduOCRService(appID, apiKey, secretKey string, logger *zap.Logger) *BaiduOCRService {
|
||||
func NewBaiduOCRService(apiKey, secretKey string, logger *zap.Logger) *BaiduOCRService {
|
||||
return &BaiduOCRService{
|
||||
appID: appID,
|
||||
apiKey: apiKey,
|
||||
secretKey: secretKey,
|
||||
endpoint: "https://aip.baidubce.com",
|
||||
@@ -38,7 +37,7 @@ func NewBaiduOCRService(appID, apiKey, secretKey string, logger *zap.Logger) *Ba
|
||||
}
|
||||
|
||||
// RecognizeBusinessLicense 识别营业执照
|
||||
func (s *BaiduOCRService) RecognizeBusinessLicense(ctx context.Context, imageBytes []byte) (*dto.BusinessLicenseResult, error) {
|
||||
func (s *BaiduOCRService) RecognizeBusinessLicense(ctx context.Context, imageBytes []byte) (*responses.BusinessLicenseResult, error) {
|
||||
s.logger.Info("开始识别营业执照", zap.Int("image_size", len(imageBytes)))
|
||||
|
||||
// 获取访问令牌
|
||||
@@ -47,17 +46,16 @@ func (s *BaiduOCRService) RecognizeBusinessLicense(ctx context.Context, imageByt
|
||||
return nil, fmt.Errorf("获取访问令牌失败: %w", err)
|
||||
}
|
||||
|
||||
// 将图片转换为base64
|
||||
// 将图片转换为base64并进行URL编码
|
||||
imageBase64 := base64.StdEncoding.EncodeToString(imageBytes)
|
||||
imageBase64UrlEncoded := url.QueryEscape(imageBase64)
|
||||
|
||||
// 构建请求参数
|
||||
params := url.Values{}
|
||||
params.Set("access_token", accessToken)
|
||||
params.Set("image", imageBase64)
|
||||
// 构建请求URL(只包含access_token)
|
||||
apiURL := fmt.Sprintf("%s/rest/2.0/ocr/v1/business_license?access_token=%s", s.endpoint, accessToken)
|
||||
|
||||
// 发送请求
|
||||
apiURL := fmt.Sprintf("%s/rest/2.0/ocr/v1/business_license?%s", s.endpoint, params.Encode())
|
||||
resp, err := s.sendRequest(ctx, "POST", apiURL, nil)
|
||||
// 构建POST请求体
|
||||
payload := strings.NewReader(fmt.Sprintf("image=%s", imageBase64UrlEncoded))
|
||||
resp, err := s.sendRequest(ctx, "POST", apiURL, payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("营业执照识别请求失败: %w", err)
|
||||
}
|
||||
@@ -79,7 +77,7 @@ func (s *BaiduOCRService) RecognizeBusinessLicense(ctx context.Context, imageByt
|
||||
|
||||
s.logger.Info("营业执照识别成功",
|
||||
zap.String("company_name", licenseResult.CompanyName),
|
||||
zap.String("legal_representative", licenseResult.LegalRepresentative),
|
||||
zap.String("legal_representative", licenseResult.LegalPersonName),
|
||||
zap.String("registered_capital", licenseResult.RegisteredCapital),
|
||||
)
|
||||
|
||||
@@ -87,7 +85,7 @@ func (s *BaiduOCRService) RecognizeBusinessLicense(ctx context.Context, imageByt
|
||||
}
|
||||
|
||||
// RecognizeIDCard 识别身份证
|
||||
func (s *BaiduOCRService) RecognizeIDCard(ctx context.Context, imageBytes []byte, side string) (*dto.IDCardResult, error) {
|
||||
func (s *BaiduOCRService) RecognizeIDCard(ctx context.Context, imageBytes []byte, side string) (*responses.IDCardResult, error) {
|
||||
s.logger.Info("开始识别身份证", zap.String("side", side), zap.Int("image_size", len(imageBytes)))
|
||||
|
||||
// 获取访问令牌
|
||||
@@ -96,18 +94,16 @@ func (s *BaiduOCRService) RecognizeIDCard(ctx context.Context, imageBytes []byte
|
||||
return nil, fmt.Errorf("获取访问令牌失败: %w", err)
|
||||
}
|
||||
|
||||
// 将图片转换为base64
|
||||
// 将图片转换为base64并进行URL编码
|
||||
imageBase64 := base64.StdEncoding.EncodeToString(imageBytes)
|
||||
imageBase64UrlEncoded := url.QueryEscape(imageBase64)
|
||||
|
||||
// 构建请求参数
|
||||
params := url.Values{}
|
||||
params.Set("access_token", accessToken)
|
||||
params.Set("image", imageBase64)
|
||||
params.Set("side", side)
|
||||
// 构建请求URL(只包含access_token)
|
||||
apiURL := fmt.Sprintf("%s/rest/2.0/ocr/v1/idcard?access_token=%s", s.endpoint, accessToken)
|
||||
|
||||
// 发送请求
|
||||
apiURL := fmt.Sprintf("%s/rest/2.0/ocr/v1/idcard?%s", s.endpoint, params.Encode())
|
||||
resp, err := s.sendRequest(ctx, "POST", apiURL, nil)
|
||||
// 构建POST请求体
|
||||
payload := strings.NewReader(fmt.Sprintf("image=%s&side=%s", imageBase64UrlEncoded, side))
|
||||
resp, err := s.sendRequest(ctx, "POST", apiURL, payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("身份证识别请求失败: %w", err)
|
||||
}
|
||||
@@ -129,7 +125,7 @@ func (s *BaiduOCRService) RecognizeIDCard(ctx context.Context, imageBytes []byte
|
||||
|
||||
s.logger.Info("身份证识别成功",
|
||||
zap.String("name", idCardResult.Name),
|
||||
zap.String("id_number", idCardResult.IDNumber),
|
||||
zap.String("id_number", idCardResult.IDCardNumber),
|
||||
zap.String("side", side),
|
||||
)
|
||||
|
||||
@@ -137,7 +133,7 @@ func (s *BaiduOCRService) RecognizeIDCard(ctx context.Context, imageBytes []byte
|
||||
}
|
||||
|
||||
// RecognizeGeneralText 通用文字识别
|
||||
func (s *BaiduOCRService) RecognizeGeneralText(ctx context.Context, imageBytes []byte) (*dto.GeneralTextResult, error) {
|
||||
func (s *BaiduOCRService) RecognizeGeneralText(ctx context.Context, imageBytes []byte) (*responses.GeneralTextResult, error) {
|
||||
s.logger.Info("开始通用文字识别", zap.Int("image_size", len(imageBytes)))
|
||||
|
||||
// 获取访问令牌
|
||||
@@ -146,17 +142,16 @@ func (s *BaiduOCRService) RecognizeGeneralText(ctx context.Context, imageBytes [
|
||||
return nil, fmt.Errorf("获取访问令牌失败: %w", err)
|
||||
}
|
||||
|
||||
// 将图片转换为base64
|
||||
// 将图片转换为base64并进行URL编码
|
||||
imageBase64 := base64.StdEncoding.EncodeToString(imageBytes)
|
||||
imageBase64UrlEncoded := url.QueryEscape(imageBase64)
|
||||
|
||||
// 构建请求参数
|
||||
params := url.Values{}
|
||||
params.Set("access_token", accessToken)
|
||||
params.Set("image", imageBase64)
|
||||
// 构建请求URL(只包含access_token)
|
||||
apiURL := fmt.Sprintf("%s/rest/2.0/ocr/v1/general_basic?access_token=%s", s.endpoint, accessToken)
|
||||
|
||||
// 发送请求
|
||||
apiURL := fmt.Sprintf("%s/rest/2.0/ocr/v1/general_basic?%s", s.endpoint, params.Encode())
|
||||
resp, err := s.sendRequest(ctx, "POST", apiURL, nil)
|
||||
// 构建POST请求体
|
||||
payload := strings.NewReader(fmt.Sprintf("image=%s", imageBase64UrlEncoded))
|
||||
resp, err := s.sendRequest(ctx, "POST", apiURL, payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("通用文字识别请求失败: %w", err)
|
||||
}
|
||||
@@ -282,125 +277,92 @@ func (s *BaiduOCRService) sendRequest(ctx context.Context, method, url string, b
|
||||
}
|
||||
|
||||
// parseBusinessLicenseResult 解析营业执照识别结果
|
||||
func (s *BaiduOCRService) parseBusinessLicenseResult(result map[string]interface{}) *dto.BusinessLicenseResult {
|
||||
// 解析百度OCR返回的结果
|
||||
func (s *BaiduOCRService) parseBusinessLicenseResult(result map[string]interface{}) *responses.BusinessLicenseResult {
|
||||
wordsResult := result["words_result"].(map[string]interface{})
|
||||
|
||||
licenseResult := &dto.BusinessLicenseResult{
|
||||
Confidence: s.extractConfidence(result),
|
||||
Words: s.extractWords(result),
|
||||
// 提取企业信息
|
||||
companyName := ""
|
||||
if companyNameObj, ok := wordsResult["单位名称"].(map[string]interface{}); ok {
|
||||
companyName = companyNameObj["words"].(string)
|
||||
}
|
||||
|
||||
// 提取关键字段
|
||||
if companyName, ok := wordsResult["单位名称"]; ok {
|
||||
if word, ok := companyName.(map[string]interface{}); ok {
|
||||
licenseResult.CompanyName = word["words"].(string)
|
||||
}
|
||||
unifiedSocialCode := ""
|
||||
if socialCreditCodeObj, ok := wordsResult["社会信用代码"].(map[string]interface{}); ok {
|
||||
unifiedSocialCode = socialCreditCodeObj["words"].(string)
|
||||
}
|
||||
|
||||
if legalRep, ok := wordsResult["法人"]; ok {
|
||||
if word, ok := legalRep.(map[string]interface{}); ok {
|
||||
licenseResult.LegalRepresentative = word["words"].(string)
|
||||
}
|
||||
legalPersonName := ""
|
||||
if legalPersonObj, ok := wordsResult["法人"].(map[string]interface{}); ok {
|
||||
legalPersonName = legalPersonObj["words"].(string)
|
||||
}
|
||||
|
||||
if regCapital, ok := wordsResult["注册资本"]; ok {
|
||||
if word, ok := regCapital.(map[string]interface{}); ok {
|
||||
licenseResult.RegisteredCapital = word["words"].(string)
|
||||
}
|
||||
// 提取注册资本等其他信息
|
||||
registeredCapital := ""
|
||||
if registeredCapitalObj, ok := wordsResult["注册资本"].(map[string]interface{}); ok {
|
||||
registeredCapital = registeredCapitalObj["words"].(string)
|
||||
}
|
||||
|
||||
if regAddress, ok := wordsResult["地址"]; ok {
|
||||
if word, ok := regAddress.(map[string]interface{}); ok {
|
||||
licenseResult.RegisteredAddress = word["words"].(string)
|
||||
}
|
||||
}
|
||||
// 计算置信度(这里简化处理,实际应该从OCR结果中获取)
|
||||
confidence := 0.9 // 默认置信度
|
||||
|
||||
if regNumber, ok := wordsResult["社会信用代码"]; ok {
|
||||
if word, ok := regNumber.(map[string]interface{}); ok {
|
||||
licenseResult.RegistrationNumber = word["words"].(string)
|
||||
}
|
||||
return &responses.BusinessLicenseResult{
|
||||
CompanyName: companyName,
|
||||
UnifiedSocialCode: unifiedSocialCode,
|
||||
LegalPersonName: legalPersonName,
|
||||
RegisteredCapital: registeredCapital,
|
||||
Confidence: confidence,
|
||||
}
|
||||
|
||||
if businessScope, ok := wordsResult["经营范围"]; ok {
|
||||
if word, ok := businessScope.(map[string]interface{}); ok {
|
||||
licenseResult.BusinessScope = word["words"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if regDate, ok := wordsResult["成立日期"]; ok {
|
||||
if word, ok := regDate.(map[string]interface{}); ok {
|
||||
licenseResult.RegistrationDate = word["words"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if validDate, ok := wordsResult["营业期限"]; ok {
|
||||
if word, ok := validDate.(map[string]interface{}); ok {
|
||||
licenseResult.ValidDate = word["words"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
return licenseResult
|
||||
}
|
||||
|
||||
// parseIDCardResult 解析身份证识别结果
|
||||
func (s *BaiduOCRService) parseIDCardResult(result map[string]interface{}, side string) *dto.IDCardResult {
|
||||
func (s *BaiduOCRService) parseIDCardResult(result map[string]interface{}, side string) *responses.IDCardResult {
|
||||
wordsResult := result["words_result"].(map[string]interface{})
|
||||
|
||||
idCardResult := &dto.IDCardResult{
|
||||
idCardResult := &responses.IDCardResult{
|
||||
Side: side,
|
||||
Confidence: s.extractConfidence(result),
|
||||
Words: s.extractWords(result),
|
||||
}
|
||||
|
||||
if side == "front" {
|
||||
// 正面信息
|
||||
if name, ok := wordsResult["姓名"]; ok {
|
||||
if word, ok := name.(map[string]interface{}); ok {
|
||||
idCardResult.Name = word["words"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if sex, ok := wordsResult["性别"]; ok {
|
||||
if word, ok := sex.(map[string]interface{}); ok {
|
||||
idCardResult.Sex = word["words"].(string)
|
||||
if gender, ok := wordsResult["性别"]; ok {
|
||||
if word, ok := gender.(map[string]interface{}); ok {
|
||||
idCardResult.Gender = word["words"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if nation, ok := wordsResult["民族"]; ok {
|
||||
if word, ok := nation.(map[string]interface{}); ok {
|
||||
idCardResult.Nation = word["words"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if birth, ok := wordsResult["出生"]; ok {
|
||||
if word, ok := birth.(map[string]interface{}); ok {
|
||||
idCardResult.BirthDate = word["words"].(string)
|
||||
if birthday, ok := wordsResult["出生"]; ok {
|
||||
if word, ok := birthday.(map[string]interface{}); ok {
|
||||
idCardResult.Birthday = word["words"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if address, ok := wordsResult["住址"]; ok {
|
||||
if word, ok := address.(map[string]interface{}); ok {
|
||||
idCardResult.Address = word["words"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if idNumber, ok := wordsResult["公民身份号码"]; ok {
|
||||
if word, ok := idNumber.(map[string]interface{}); ok {
|
||||
idCardResult.IDNumber = word["words"].(string)
|
||||
idCardResult.IDCardNumber = word["words"].(string)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 背面信息
|
||||
if authority, ok := wordsResult["签发机关"]; ok {
|
||||
if word, ok := authority.(map[string]interface{}); ok {
|
||||
idCardResult.IssuingAuthority = word["words"].(string)
|
||||
if issuingAgency, ok := wordsResult["签发机关"]; ok {
|
||||
if word, ok := issuingAgency.(map[string]interface{}); ok {
|
||||
idCardResult.IssuingAgency = word["words"].(string)
|
||||
}
|
||||
}
|
||||
|
||||
if validDate, ok := wordsResult["有效期限"]; ok {
|
||||
if word, ok := validDate.(map[string]interface{}); ok {
|
||||
idCardResult.ValidDate = word["words"].(string)
|
||||
if validPeriod, ok := wordsResult["有效期限"]; ok {
|
||||
if word, ok := validPeriod.(map[string]interface{}); ok {
|
||||
idCardResult.ValidPeriod = word["words"].(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -409,20 +371,21 @@ func (s *BaiduOCRService) parseIDCardResult(result map[string]interface{}, side
|
||||
}
|
||||
|
||||
// parseGeneralTextResult 解析通用文字识别结果
|
||||
func (s *BaiduOCRService) parseGeneralTextResult(result map[string]interface{}) *dto.GeneralTextResult {
|
||||
func (s *BaiduOCRService) parseGeneralTextResult(result map[string]interface{}) *responses.GeneralTextResult {
|
||||
wordsResult := result["words_result"].([]interface{})
|
||||
|
||||
textResult := &dto.GeneralTextResult{
|
||||
textResult := &responses.GeneralTextResult{
|
||||
Confidence: s.extractConfidence(result),
|
||||
Words: make([]string, 0, len(wordsResult)),
|
||||
Words: make([]responses.TextLine, 0, len(wordsResult)),
|
||||
}
|
||||
|
||||
// 提取所有识别的文字
|
||||
for _, word := range wordsResult {
|
||||
if wordMap, ok := word.(map[string]interface{}); ok {
|
||||
if words, ok := wordMap["words"].(string); ok {
|
||||
textResult.Words = append(textResult.Words, words)
|
||||
line := responses.TextLine{
|
||||
Text: wordMap["words"].(string),
|
||||
Confidence: 1.0, // 百度返回的通用文字识别没有单独置信度
|
||||
}
|
||||
textResult.Words = append(textResult.Words, line)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,47 +465,41 @@ func (s *BaiduOCRService) downloadImage(ctx context.Context, imageURL string) ([
|
||||
}
|
||||
|
||||
// ValidateBusinessLicense 验证营业执照识别结果
|
||||
func (s *BaiduOCRService) ValidateBusinessLicense(result *dto.BusinessLicenseResult) error {
|
||||
func (s *BaiduOCRService) ValidateBusinessLicense(result *responses.BusinessLicenseResult) error {
|
||||
if result.Confidence < 0.8 {
|
||||
return fmt.Errorf("识别置信度过低: %.2f", result.Confidence)
|
||||
}
|
||||
|
||||
if result.CompanyName == "" {
|
||||
return fmt.Errorf("未能识别公司名称")
|
||||
}
|
||||
|
||||
if result.LegalRepresentative == "" {
|
||||
if result.LegalPersonName == "" {
|
||||
return fmt.Errorf("未能识别法定代表人")
|
||||
}
|
||||
|
||||
if result.RegistrationNumber == "" {
|
||||
if result.UnifiedSocialCode == "" {
|
||||
return fmt.Errorf("未能识别统一社会信用代码")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateIDCard 验证身份证识别结果
|
||||
func (s *BaiduOCRService) ValidateIDCard(result *dto.IDCardResult) error {
|
||||
func (s *BaiduOCRService) ValidateIDCard(result *responses.IDCardResult) error {
|
||||
if result.Confidence < 0.8 {
|
||||
return fmt.Errorf("识别置信度过低: %.2f", result.Confidence)
|
||||
}
|
||||
|
||||
if result.Side == "front" {
|
||||
if result.Name == "" {
|
||||
return fmt.Errorf("未能识别姓名")
|
||||
}
|
||||
if result.IDNumber == "" {
|
||||
if result.IDCardNumber == "" {
|
||||
return fmt.Errorf("未能识别身份证号码")
|
||||
}
|
||||
} else {
|
||||
if result.IssuingAuthority == "" {
|
||||
if result.IssuingAgency == "" {
|
||||
return fmt.Errorf("未能识别签发机关")
|
||||
}
|
||||
if result.ValidDate == "" {
|
||||
if result.ValidPeriod == "" {
|
||||
return fmt.Errorf("未能识别有效期限")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -12,13 +12,7 @@ import (
|
||||
"tyapi-server/internal/config"
|
||||
)
|
||||
|
||||
// Service 短信服务接口
|
||||
type Service interface {
|
||||
SendVerificationCode(ctx context.Context, phone string, code string) error
|
||||
GenerateCode(length int) string
|
||||
}
|
||||
|
||||
// AliSMSService 阿里云短信服务实现
|
||||
// AliSMSService 阿里云短信服务
|
||||
type AliSMSService struct {
|
||||
client *dysmsapi.Client
|
||||
config config.SMSConfig
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"github.com/qiniu/go-sdk/v7/auth/qbox"
|
||||
"github.com/qiniu/go-sdk/v7/storage"
|
||||
"go.uber.org/zap"
|
||||
|
||||
sharedStorage "tyapi-server/internal/shared/storage"
|
||||
)
|
||||
|
||||
// QiNiuStorageService 七牛云存储服务
|
||||
@@ -22,7 +24,6 @@ type QiNiuStorageService struct {
|
||||
secretKey string
|
||||
bucket string
|
||||
domain string
|
||||
region string
|
||||
logger *zap.Logger
|
||||
mac *qbox.Mac
|
||||
bucketManager *storage.BucketManager
|
||||
@@ -34,17 +35,13 @@ type QiNiuStorageConfig struct {
|
||||
SecretKey string `yaml:"secret_key"`
|
||||
Bucket string `yaml:"bucket"`
|
||||
Domain string `yaml:"domain"`
|
||||
Region string `yaml:"region"`
|
||||
}
|
||||
|
||||
// NewQiNiuStorageService 创建七牛云存储服务
|
||||
func NewQiNiuStorageService(accessKey, secretKey, bucket, domain, region string, logger *zap.Logger) *QiNiuStorageService {
|
||||
func NewQiNiuStorageService(accessKey, secretKey, bucket, domain string, logger *zap.Logger) *QiNiuStorageService {
|
||||
mac := qbox.NewMac(accessKey, secretKey)
|
||||
cfg := storage.Config{
|
||||
Region: &storage.Zone{
|
||||
RsHost: fmt.Sprintf("rs-%s.qiniu.com", region),
|
||||
},
|
||||
}
|
||||
// 使用默认配置,不需要指定region
|
||||
cfg := storage.Config{}
|
||||
bucketManager := storage.NewBucketManager(mac, &cfg)
|
||||
|
||||
return &QiNiuStorageService{
|
||||
@@ -52,7 +49,6 @@ func NewQiNiuStorageService(accessKey, secretKey, bucket, domain, region string,
|
||||
secretKey: secretKey,
|
||||
bucket: bucket,
|
||||
domain: domain,
|
||||
region: region,
|
||||
logger: logger,
|
||||
mac: mac,
|
||||
bucketManager: bucketManager,
|
||||
@@ -60,7 +56,7 @@ func NewQiNiuStorageService(accessKey, secretKey, bucket, domain, region string,
|
||||
}
|
||||
|
||||
// UploadFile 上传文件到七牛云
|
||||
func (s *QiNiuStorageService) UploadFile(ctx context.Context, fileBytes []byte, fileName string) (*UploadResult, error) {
|
||||
func (s *QiNiuStorageService) UploadFile(ctx context.Context, fileBytes []byte, fileName string) (*sharedStorage.UploadResult, error) {
|
||||
s.logger.Info("开始上传文件到七牛云",
|
||||
zap.String("file_name", fileName),
|
||||
zap.Int("file_size", len(fileBytes)),
|
||||
@@ -76,11 +72,7 @@ func (s *QiNiuStorageService) UploadFile(ctx context.Context, fileBytes []byte,
|
||||
upToken := putPolicy.UploadToken(s.mac)
|
||||
|
||||
// 配置上传参数
|
||||
cfg := storage.Config{
|
||||
Region: &storage.Zone{
|
||||
RsHost: fmt.Sprintf("rs-%s.qiniu.com", s.region),
|
||||
},
|
||||
}
|
||||
cfg := storage.Config{}
|
||||
formUploader := storage.NewFormUploader(&cfg)
|
||||
ret := storage.PutRet{}
|
||||
|
||||
@@ -104,7 +96,7 @@ func (s *QiNiuStorageService) UploadFile(ctx context.Context, fileBytes []byte,
|
||||
zap.String("url", fileURL),
|
||||
)
|
||||
|
||||
return &UploadResult{
|
||||
return &sharedStorage.UploadResult{
|
||||
Key: key,
|
||||
URL: fileURL,
|
||||
MimeType: s.getMimeType(fileName),
|
||||
@@ -179,7 +171,7 @@ func (s *QiNiuStorageService) FileExists(ctx context.Context, key string) (bool,
|
||||
}
|
||||
|
||||
// GetFileInfo 获取文件信息
|
||||
func (s *QiNiuStorageService) GetFileInfo(ctx context.Context, key string) (*FileInfo, error) {
|
||||
func (s *QiNiuStorageService) GetFileInfo(ctx context.Context, key string) (*sharedStorage.FileInfo, error) {
|
||||
fileInfo, err := s.bucketManager.Stat(s.bucket, key)
|
||||
if err != nil {
|
||||
s.logger.Error("获取文件信息失败",
|
||||
@@ -189,7 +181,7 @@ func (s *QiNiuStorageService) GetFileInfo(ctx context.Context, key string) (*Fil
|
||||
return nil, fmt.Errorf("获取文件信息失败: %w", err)
|
||||
}
|
||||
|
||||
return &FileInfo{
|
||||
return &sharedStorage.FileInfo{
|
||||
Key: key,
|
||||
Size: fileInfo.Fsize,
|
||||
MimeType: fileInfo.MimeType,
|
||||
@@ -199,7 +191,7 @@ func (s *QiNiuStorageService) GetFileInfo(ctx context.Context, key string) (*Fil
|
||||
}
|
||||
|
||||
// ListFiles 列出文件
|
||||
func (s *QiNiuStorageService) ListFiles(ctx context.Context, prefix string, limit int) ([]*FileInfo, error) {
|
||||
func (s *QiNiuStorageService) ListFiles(ctx context.Context, prefix string, limit int) ([]*sharedStorage.FileInfo, error) {
|
||||
entries, _, _, hasMore, err := s.bucketManager.ListFiles(s.bucket, prefix, "", "", limit)
|
||||
if err != nil {
|
||||
s.logger.Error("列出文件失败",
|
||||
@@ -209,9 +201,9 @@ func (s *QiNiuStorageService) ListFiles(ctx context.Context, prefix string, limi
|
||||
return nil, fmt.Errorf("列出文件失败: %w", err)
|
||||
}
|
||||
|
||||
var fileInfos []*FileInfo
|
||||
var fileInfos []*sharedStorage.FileInfo
|
||||
for _, entry := range entries {
|
||||
fileInfo := &FileInfo{
|
||||
fileInfo := &sharedStorage.FileInfo{
|
||||
Key: entry.Key,
|
||||
Size: entry.Fsize,
|
||||
MimeType: entry.MimeType,
|
||||
@@ -278,55 +270,12 @@ func (s *QiNiuStorageService) generateSignature(data string) string {
|
||||
}
|
||||
|
||||
// UploadFromReader 从Reader上传文件
|
||||
func (s *QiNiuStorageService) UploadFromReader(ctx context.Context, reader io.Reader, fileName string, fileSize int64) (*UploadResult, error) {
|
||||
s.logger.Info("从Reader上传文件到七牛云",
|
||||
zap.String("file_name", fileName),
|
||||
zap.Int64("file_size", fileSize),
|
||||
)
|
||||
|
||||
// 生成唯一的文件key
|
||||
key := s.generateFileKey(fileName)
|
||||
|
||||
// 创建上传凭证
|
||||
putPolicy := storage.PutPolicy{
|
||||
Scope: s.bucket,
|
||||
}
|
||||
upToken := putPolicy.UploadToken(s.mac)
|
||||
|
||||
// 配置上传参数
|
||||
cfg := storage.Config{
|
||||
Region: &storage.Zone{
|
||||
RsHost: fmt.Sprintf("rs-%s.qiniu.com", s.region),
|
||||
},
|
||||
}
|
||||
formUploader := storage.NewFormUploader(&cfg)
|
||||
ret := storage.PutRet{}
|
||||
|
||||
// 上传文件
|
||||
err := formUploader.Put(ctx, &ret, upToken, key, reader, fileSize, &storage.PutExtra{})
|
||||
func (s *QiNiuStorageService) UploadFromReader(ctx context.Context, reader io.Reader, fileName string, fileSize int64) (*sharedStorage.UploadResult, error) {
|
||||
// 读取文件内容
|
||||
fileBytes, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
s.logger.Error("从Reader上传文件失败",
|
||||
zap.String("file_name", fileName),
|
||||
zap.String("key", key),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("文件上传失败: %w", err)
|
||||
return nil, fmt.Errorf("读取文件失败: %w", err)
|
||||
}
|
||||
|
||||
// 构建文件URL
|
||||
fileURL := s.GetFileURL(ctx, key)
|
||||
|
||||
s.logger.Info("从Reader上传文件成功",
|
||||
zap.String("file_name", fileName),
|
||||
zap.String("key", key),
|
||||
zap.String("url", fileURL),
|
||||
)
|
||||
|
||||
return &UploadResult{
|
||||
Key: key,
|
||||
URL: fileURL,
|
||||
MimeType: s.getMimeType(fileName),
|
||||
Size: fileSize,
|
||||
Hash: ret.Hash,
|
||||
}, nil
|
||||
return s.UploadFile(ctx, fileBytes, fileName)
|
||||
}
|
||||
280
internal/infrastructure/http/handlers/admin_handler.go
Normal file
280
internal/infrastructure/http/handlers/admin_handler.go
Normal file
@@ -0,0 +1,280 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/application/admin"
|
||||
"tyapi-server/internal/application/admin/dto/commands"
|
||||
"tyapi-server/internal/application/admin/dto/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// AdminHandler 管理员HTTP处理器
|
||||
type AdminHandler struct {
|
||||
appService admin.AdminApplicationService
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewAdminHandler 创建管理员HTTP处理器
|
||||
func NewAdminHandler(
|
||||
appService admin.AdminApplicationService,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
logger *zap.Logger,
|
||||
) *AdminHandler {
|
||||
return &AdminHandler{
|
||||
appService: appService,
|
||||
responseBuilder: responseBuilder,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Login 管理员登录
|
||||
// @Summary 管理员登录
|
||||
// @Description 使用用户名和密码进行管理员登录,返回JWT令牌
|
||||
// @Tags 管理员认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body commands.AdminLoginCommand true "管理员登录请求"
|
||||
// @Success 200 {object} responses.AdminLoginResponse "登录成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "用户名或密码错误"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/auth/login [post]
|
||||
func (h *AdminHandler) Login(c *gin.Context) {
|
||||
var cmd commands.AdminLoginCommand
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.appService.Login(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("管理员登录失败", zap.Error(err))
|
||||
h.responseBuilder.Unauthorized(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "登录成功")
|
||||
}
|
||||
|
||||
// CreateAdmin 创建管理员
|
||||
// @Summary 创建管理员
|
||||
// @Description 创建新的管理员账户,需要超级管理员权限
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.CreateAdminCommand true "创建管理员请求"
|
||||
// @Success 201 {object} map[string]interface{} "管理员创建成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 403 {object} map[string]interface{} "权限不足"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin [post]
|
||||
func (h *AdminHandler) CreateAdmin(c *gin.Context) {
|
||||
var cmd commands.CreateAdminCommand
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
cmd.OperatorID = h.getCurrentAdminID(c)
|
||||
|
||||
if err := h.appService.CreateAdmin(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("创建管理员失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Created(c, nil, "管理员创建成功")
|
||||
}
|
||||
|
||||
// UpdateAdmin 更新管理员
|
||||
// @Summary 更新管理员信息
|
||||
// @Description 更新指定管理员的基本信息
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "管理员ID"
|
||||
// @Param request body commands.UpdateAdminCommand true "更新管理员请求"
|
||||
// @Success 200 {object} map[string]interface{} "管理员更新成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 403 {object} map[string]interface{} "权限不足"
|
||||
// @Failure 404 {object} map[string]interface{} "管理员不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/{id} [put]
|
||||
func (h *AdminHandler) UpdateAdmin(c *gin.Context) {
|
||||
var cmd commands.UpdateAdminCommand
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
cmd.AdminID = c.Param("id")
|
||||
cmd.OperatorID = h.getCurrentAdminID(c)
|
||||
|
||||
if err := h.appService.UpdateAdmin(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("更新管理员失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "管理员更新成功")
|
||||
}
|
||||
|
||||
// ChangePassword 修改密码
|
||||
// @Summary 修改管理员密码
|
||||
// @Description 修改当前登录管理员的密码
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.ChangeAdminPasswordCommand true "修改密码请求"
|
||||
// @Success 200 {object} map[string]interface{} "密码修改成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/change-password [post]
|
||||
func (h *AdminHandler) ChangePassword(c *gin.Context) {
|
||||
var cmd commands.ChangeAdminPasswordCommand
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
cmd.AdminID = h.getCurrentAdminID(c)
|
||||
|
||||
if err := h.appService.ChangePassword(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("修改密码失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "密码修改成功")
|
||||
}
|
||||
|
||||
// ListAdmins 获取管理员列表
|
||||
// @Summary 获取管理员列表
|
||||
// @Description 分页获取管理员列表,支持搜索和筛选
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param page query int false "页码" default(1)
|
||||
// @Param size query int false "每页数量" default(10)
|
||||
// @Param keyword query string false "搜索关键词"
|
||||
// @Param status query string false "状态筛选"
|
||||
// @Success 200 {object} responses.AdminListResponse "获取管理员列表成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin [get]
|
||||
func (h *AdminHandler) ListAdmins(c *gin.Context) {
|
||||
var query queries.ListAdminsQuery
|
||||
if err := c.ShouldBindQuery(&query); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.appService.ListAdmins(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取管理员列表失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取管理员列表失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, response, "获取管理员列表成功")
|
||||
}
|
||||
|
||||
// GetAdminByID 根据ID获取管理员
|
||||
// @Summary 获取管理员详情
|
||||
// @Description 根据管理员ID获取详细信息
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "管理员ID"
|
||||
// @Success 200 {object} responses.AdminInfoResponse "获取管理员详情成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "管理员不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/{id} [get]
|
||||
func (h *AdminHandler) GetAdminByID(c *gin.Context) {
|
||||
var query queries.GetAdminInfoQuery
|
||||
if err := c.ShouldBindUri(&query); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
admin, err := h.appService.GetAdminByID(c.Request.Context(), &query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取管理员详情失败", zap.Error(err))
|
||||
h.responseBuilder.NotFound(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, admin, "获取管理员详情成功")
|
||||
}
|
||||
|
||||
// DeleteAdmin 删除管理员
|
||||
// @Summary 删除管理员
|
||||
// @Description 删除指定的管理员账户
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param id path string true "管理员ID"
|
||||
// @Success 200 {object} map[string]interface{} "管理员删除成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 403 {object} map[string]interface{} "权限不足"
|
||||
// @Failure 404 {object} map[string]interface{} "管理员不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/{id} [delete]
|
||||
func (h *AdminHandler) DeleteAdmin(c *gin.Context) {
|
||||
var cmd commands.DeleteAdminCommand
|
||||
cmd.AdminID = c.Param("id")
|
||||
cmd.OperatorID = h.getCurrentAdminID(c)
|
||||
|
||||
if err := h.appService.DeleteAdmin(c.Request.Context(), &cmd); err != nil {
|
||||
h.logger.Error("删除管理员失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "管理员删除成功")
|
||||
}
|
||||
|
||||
// GetAdminStats 获取管理员统计信息
|
||||
// @Summary 获取管理员统计信息
|
||||
// @Description 获取管理员相关的统计数据
|
||||
// @Tags 管理员管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.AdminStatsResponse "获取统计信息成功"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/admin/stats [get]
|
||||
func (h *AdminHandler) GetAdminStats(c *gin.Context) {
|
||||
stats, err := h.appService.GetAdminStats(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("获取管理员统计失败", zap.Error(err))
|
||||
h.responseBuilder.InternalError(c, "获取统计信息失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, stats, "获取统计信息成功")
|
||||
}
|
||||
|
||||
// getCurrentAdminID 获取当前管理员ID
|
||||
func (h *AdminHandler) getCurrentAdminID(c *gin.Context) string {
|
||||
if userID, exists := c.Get("user_id"); exists {
|
||||
if id, ok := userID.(string); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
472
internal/infrastructure/http/handlers/certification_handler.go
Normal file
472
internal/infrastructure/http/handlers/certification_handler.go
Normal file
@@ -0,0 +1,472 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/application/certification"
|
||||
"tyapi-server/internal/application/certification/dto/commands"
|
||||
"tyapi-server/internal/application/certification/dto/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// CertificationHandler 认证处理器
|
||||
type CertificationHandler struct {
|
||||
appService certification.CertificationApplicationService
|
||||
response interfaces.ResponseBuilder
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewCertificationHandler 创建认证处理器
|
||||
func NewCertificationHandler(
|
||||
appService certification.CertificationApplicationService,
|
||||
response interfaces.ResponseBuilder,
|
||||
logger *zap.Logger,
|
||||
) *CertificationHandler {
|
||||
return &CertificationHandler{
|
||||
appService: appService,
|
||||
response: response,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCertification 创建认证申请
|
||||
// @Summary 创建认证申请
|
||||
// @Description 为用户创建新的企业认证申请
|
||||
// @Tags 企业认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.CertificationResponse "认证申请创建成功"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certification [post]
|
||||
func (h *CertificationHandler) CreateCertification(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
cmd := &commands.CreateCertificationCommand{UserID: userID}
|
||||
result, err := h.appService.CreateCertification(c.Request.Context(), cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("创建认证申请失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.response.InternalError(c, "创建认证申请失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "认证申请创建成功")
|
||||
}
|
||||
|
||||
// UploadBusinessLicense 上传营业执照并同步OCR识别
|
||||
// @Summary 上传营业执照并同步OCR识别
|
||||
// @Description 上传营业执照文件,立即进行OCR识别并返回结果
|
||||
// @Tags 企业认证
|
||||
// @Accept multipart/form-data
|
||||
// @Produce json
|
||||
// @Param file formData file true "营业执照文件"
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.UploadLicenseResponse "上传成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未授权"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certification/upload-license [post]
|
||||
func (h *CertificationHandler) UploadBusinessLicense(c *gin.Context) {
|
||||
// 获取当前用户ID
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
// 获取上传的文件
|
||||
file, err := c.FormFile("file")
|
||||
if err != nil {
|
||||
h.response.BadRequest(c, "文件上传失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
openedFile, err := file.Open()
|
||||
if err != nil {
|
||||
h.response.BadRequest(c, "无法读取文件")
|
||||
return
|
||||
}
|
||||
defer openedFile.Close()
|
||||
|
||||
fileBytes, err := io.ReadAll(openedFile)
|
||||
if err != nil {
|
||||
h.response.BadRequest(c, "文件读取失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 调用应用服务
|
||||
response, err := h.appService.UploadBusinessLicense(c.Request.Context(), userID.(string), fileBytes, file.Filename)
|
||||
if err != nil {
|
||||
h.logger.Error("营业执照上传失败", zap.Error(err))
|
||||
h.response.InternalError(c, "营业执照上传失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, response, "营业执照上传成功")
|
||||
}
|
||||
|
||||
// GetCertificationStatus 获取认证状态
|
||||
// @Summary 获取认证状态
|
||||
// @Description 获取当前用户的认证申请状态
|
||||
// @Tags 企业认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.CertificationResponse "获取认证状态成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certification/status [get]
|
||||
func (h *CertificationHandler) GetCertificationStatus(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
query := &queries.GetCertificationStatusQuery{UserID: userID}
|
||||
result, err := h.appService.GetCertificationStatus(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取认证状态失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "获取认证状态成功")
|
||||
}
|
||||
|
||||
// GetProgressStats 获取进度统计
|
||||
// @Summary 获取进度统计
|
||||
// @Description 获取认证申请的进度统计数据
|
||||
// @Tags 企业认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} map[string]interface{} "获取进度统计成功"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certification/stats [get]
|
||||
func (h *CertificationHandler) GetProgressStats(c *gin.Context) {
|
||||
// 这里应该实现获取进度统计的逻辑
|
||||
// 暂时返回空数据
|
||||
h.response.Success(c, map[string]interface{}{
|
||||
"total_applications": 0,
|
||||
"pending": 0,
|
||||
"in_progress": 0,
|
||||
"completed": 0,
|
||||
"rejected": 0,
|
||||
}, "获取进度统计成功")
|
||||
}
|
||||
|
||||
// GetCertificationProgress 获取认证进度
|
||||
// @Summary 获取认证进度
|
||||
// @Description 获取当前用户的认证申请详细进度信息
|
||||
// @Tags 企业认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} map[string]interface{} "获取认证进度成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "认证申请不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certification/progress [get]
|
||||
func (h *CertificationHandler) GetCertificationProgress(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.appService.GetCertificationProgress(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取认证进度失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "获取认证进度成功")
|
||||
}
|
||||
|
||||
// SubmitEnterpriseInfo 提交企业信息
|
||||
// @Summary 提交企业信息
|
||||
// @Description 提交企业基本信息,包括企业名称、统一社会信用代码、法定代表人信息等
|
||||
// @Tags 企业认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.SubmitEnterpriseInfoCommand true "企业信息"
|
||||
// @Success 200 {object} responses.CertificationResponse "企业信息提交成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certification/enterprise-info [post]
|
||||
func (h *CertificationHandler) SubmitEnterpriseInfo(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
var cmd commands.SubmitEnterpriseInfoCommand
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.logger.Error("参数绑定失败", zap.Error(err))
|
||||
h.response.BadRequest(c, "请求参数格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
cmd.UserID = userID
|
||||
|
||||
result, err := h.appService.SubmitEnterpriseInfo(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("提交企业信息失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "企业信息提交成功")
|
||||
}
|
||||
|
||||
// InitiateFaceVerify 发起人脸验证
|
||||
// @Summary 发起人脸验证
|
||||
// @Description 发起企业法人人脸验证流程
|
||||
// @Tags 企业认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.InitiateFaceVerifyCommand true "人脸验证请求"
|
||||
// @Success 200 {object} responses.FaceVerifyResponse "人脸验证发起成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certification/face-verify [post]
|
||||
func (h *CertificationHandler) InitiateFaceVerify(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
var cmd commands.InitiateFaceVerifyCommand
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.logger.Error("参数绑定失败", zap.Error(err))
|
||||
h.response.BadRequest(c, "请求参数格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 根据用户ID获取认证申请
|
||||
query := &queries.GetCertificationStatusQuery{UserID: userID}
|
||||
certification, err := h.appService.GetCertificationStatus(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取认证申请失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// 如果用户没有认证申请,返回错误
|
||||
if certification.ID == "" {
|
||||
h.response.BadRequest(c, "用户尚未创建认证申请")
|
||||
return
|
||||
}
|
||||
|
||||
cmd.CertificationID = certification.ID
|
||||
|
||||
result, err := h.appService.InitiateFaceVerify(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("发起人脸验证失败",
|
||||
zap.String("certification_id", certification.ID),
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "人脸验证发起成功")
|
||||
}
|
||||
|
||||
// ApplyContract 申请合同
|
||||
// @Summary 申请合同
|
||||
// @Description 申请企业认证合同
|
||||
// @Tags 企业认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.CertificationResponse "合同申请成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certification/contract [post]
|
||||
func (h *CertificationHandler) ApplyContract(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.appService.ApplyContract(c.Request.Context(), userID)
|
||||
if err != nil {
|
||||
h.logger.Error("申请合同失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "合同申请成功")
|
||||
}
|
||||
|
||||
// GetCertificationDetails 获取认证详情
|
||||
// @Summary 获取认证详情
|
||||
// @Description 获取当前用户的认证申请详细信息
|
||||
// @Tags 企业认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.CertificationResponse "获取认证详情成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "认证申请不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certification/details [get]
|
||||
func (h *CertificationHandler) GetCertificationDetails(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
query := &queries.GetCertificationDetailsQuery{
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
result, err := h.appService.GetCertificationDetails(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取认证详情失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "获取认证详情成功")
|
||||
}
|
||||
|
||||
// RetryStep 重试步骤
|
||||
// @Summary 重试认证步骤
|
||||
// @Description 重新执行指定的认证步骤
|
||||
// @Tags 企业认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param step path string true "步骤名称"
|
||||
// @Success 200 {object} map[string]interface{} "步骤重试成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certification/retry/{step} [post]
|
||||
func (h *CertificationHandler) RetryStep(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
step := c.Param("step")
|
||||
if step == "" {
|
||||
h.response.BadRequest(c, "步骤名称不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
var result interface{}
|
||||
var err error
|
||||
|
||||
switch step {
|
||||
case "face_verify":
|
||||
result, err = h.appService.RetryFaceVerify(c.Request.Context(), userID)
|
||||
case "contract_sign":
|
||||
result, err = h.appService.RetryContractSign(c.Request.Context(), userID)
|
||||
default:
|
||||
h.response.BadRequest(c, "不支持的步骤类型")
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
h.logger.Error("重试认证步骤失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("step", step),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "认证步骤重试成功")
|
||||
}
|
||||
|
||||
// GetLicenseOCRResult 获取营业执照OCR识别结果
|
||||
// @Summary 获取营业执照OCR识别结果
|
||||
// @Description 根据上传记录ID获取OCR识别结果
|
||||
// @Tags 企业认证
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param record_id path string true "上传记录ID"
|
||||
// @Success 200 {object} responses.UploadLicenseResponse "获取OCR结果成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "记录不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/certification/license/{record_id}/ocr-result [get]
|
||||
func (h *CertificationHandler) GetLicenseOCRResult(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.response.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
recordID := c.Param("record_id")
|
||||
if recordID == "" {
|
||||
h.response.BadRequest(c, "上传记录ID不能为空")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.appService.GetLicenseOCRResult(c.Request.Context(), recordID)
|
||||
if err != nil {
|
||||
h.logger.Error("获取OCR结果失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.String("record_id", recordID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.response.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.response.Success(c, result, "获取OCR结果成功")
|
||||
}
|
||||
429
internal/infrastructure/http/handlers/finance_handler.go
Normal file
429
internal/infrastructure/http/handlers/finance_handler.go
Normal file
@@ -0,0 +1,429 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"tyapi-server/internal/application/finance"
|
||||
"tyapi-server/internal/application/finance/dto/commands"
|
||||
"tyapi-server/internal/application/finance/dto/queries"
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// FinanceHandler 财务HTTP处理器
|
||||
type FinanceHandler struct {
|
||||
appService finance.FinanceApplicationService
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewFinanceHandler 创建财务HTTP处理器
|
||||
func NewFinanceHandler(
|
||||
appService finance.FinanceApplicationService,
|
||||
responseBuilder interfaces.ResponseBuilder,
|
||||
logger *zap.Logger,
|
||||
) *FinanceHandler {
|
||||
return &FinanceHandler{
|
||||
appService: appService,
|
||||
responseBuilder: responseBuilder,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateWallet 创建钱包
|
||||
// @Summary 创建钱包
|
||||
// @Description 为用户创建新的钱包账户
|
||||
// @Tags 钱包管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body commands.CreateWalletCommand true "创建钱包请求"
|
||||
// @Success 201 {object} responses.WalletResponse "钱包创建成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 409 {object} map[string]interface{} "钱包已存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/finance/wallet [post]
|
||||
func (h *FinanceHandler) CreateWallet(c *gin.Context) {
|
||||
var cmd commands.CreateWalletCommand
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
response, err := h.appService.CreateWallet(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("创建钱包失败", zap.Error(err))
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Created(c, response, "钱包创建成功")
|
||||
}
|
||||
|
||||
// GetWallet 获取钱包信息
|
||||
// @Summary 获取钱包信息
|
||||
// @Description 获取当前用户的钱包详细信息
|
||||
// @Tags 钱包管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.WalletResponse "获取钱包信息成功"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "钱包不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/finance/wallet [get]
|
||||
func (h *FinanceHandler) GetWallet(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
query := &queries.GetWalletInfoQuery{UserID: userID}
|
||||
result, err := h.appService.GetWallet(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取钱包信息失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取钱包信息成功")
|
||||
}
|
||||
|
||||
// UpdateWallet 更新钱包
|
||||
// @Summary 更新钱包信息
|
||||
// @Description 更新当前用户的钱包基本信息
|
||||
// @Tags 钱包管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.UpdateWalletCommand true "更新钱包请求"
|
||||
// @Success 200 {object} map[string]interface{} "钱包更新成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/finance/wallet [put]
|
||||
func (h *FinanceHandler) UpdateWallet(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
var cmd commands.UpdateWalletCommand
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
cmd.UserID = userID
|
||||
|
||||
err := h.appService.UpdateWallet(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("更新钱包失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "钱包更新成功")
|
||||
}
|
||||
|
||||
// Recharge 充值
|
||||
// @Summary 钱包充值
|
||||
// @Description 为钱包进行充值操作
|
||||
// @Tags 钱包管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.RechargeWalletCommand true "充值请求"
|
||||
// @Success 200 {object} responses.TransactionResponse "充值成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/finance/wallet/recharge [post]
|
||||
func (h *FinanceHandler) Recharge(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
var cmd commands.RechargeWalletCommand
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
cmd.UserID = userID
|
||||
|
||||
result, err := h.appService.Recharge(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("充值失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "充值成功")
|
||||
}
|
||||
|
||||
// Withdraw 提现
|
||||
// @Summary 钱包提现
|
||||
// @Description 从钱包进行提现操作
|
||||
// @Tags 钱包管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.WithdrawWalletCommand true "提现请求"
|
||||
// @Success 200 {object} responses.TransactionResponse "提现申请已提交"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/finance/wallet/withdraw [post]
|
||||
func (h *FinanceHandler) Withdraw(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
var cmd commands.WithdrawWalletCommand
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
cmd.UserID = userID
|
||||
|
||||
result, err := h.appService.Withdraw(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("提现失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "提现申请已提交")
|
||||
}
|
||||
|
||||
// WalletTransaction 钱包交易
|
||||
// @Summary 钱包交易
|
||||
// @Description 执行钱包内部交易操作
|
||||
// @Tags 钱包管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.WalletTransactionCommand true "交易请求"
|
||||
// @Success 200 {object} responses.TransactionResponse "交易成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/finance/wallet/transaction [post]
|
||||
func (h *FinanceHandler) WalletTransaction(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
var cmd commands.WalletTransactionCommand
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
cmd.UserID = userID
|
||||
|
||||
result, err := h.appService.WalletTransaction(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("钱包交易失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "交易成功")
|
||||
}
|
||||
|
||||
// GetWalletStats 获取钱包统计
|
||||
// @Summary 获取钱包统计
|
||||
// @Description 获取钱包相关的统计数据
|
||||
// @Tags 钱包管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.WalletStatsResponse "获取钱包统计成功"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/finance/wallet/stats [get]
|
||||
func (h *FinanceHandler) GetWalletStats(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
result, err := h.appService.GetWalletStats(c.Request.Context())
|
||||
if err != nil {
|
||||
h.logger.Error("获取钱包统计失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.responseBuilder.InternalError(c, "获取钱包统计失败")
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取钱包统计成功")
|
||||
}
|
||||
|
||||
// CreateUserSecrets 创建用户密钥
|
||||
// @Summary 创建用户密钥
|
||||
// @Description 为用户创建API访问密钥
|
||||
// @Tags 用户密钥管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Param request body commands.CreateUserSecretsCommand true "创建密钥请求"
|
||||
// @Success 201 {object} responses.UserSecretsResponse "用户密钥创建成功"
|
||||
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 409 {object} map[string]interface{} "密钥已存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/finance/secrets [post]
|
||||
func (h *FinanceHandler) CreateUserSecrets(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
var cmd commands.CreateUserSecretsCommand
|
||||
if err := c.ShouldBindJSON(&cmd); err != nil {
|
||||
h.responseBuilder.BadRequest(c, "请求参数错误")
|
||||
return
|
||||
}
|
||||
|
||||
cmd.UserID = userID
|
||||
|
||||
result, err := h.appService.CreateUserSecrets(c.Request.Context(), &cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("创建用户密钥失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Created(c, result, "用户密钥创建成功")
|
||||
}
|
||||
|
||||
// GetUserSecrets 获取用户密钥
|
||||
// @Summary 获取用户密钥
|
||||
// @Description 获取当前用户的API访问密钥信息
|
||||
// @Tags 用户密钥管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.UserSecretsResponse "获取用户密钥成功"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "密钥不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/finance/secrets [get]
|
||||
func (h *FinanceHandler) GetUserSecrets(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
query := &queries.GetUserSecretsQuery{UserID: userID}
|
||||
result, err := h.appService.GetUserSecrets(c.Request.Context(), query)
|
||||
if err != nil {
|
||||
h.logger.Error("获取用户密钥失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "获取用户密钥成功")
|
||||
}
|
||||
|
||||
// RegenerateAccessKey 重新生成访问密钥
|
||||
// @Summary 重新生成访问密钥
|
||||
// @Description 重新生成用户的API访问密钥
|
||||
// @Tags 用户密钥管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} responses.UserSecretsResponse "访问密钥重新生成成功"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "密钥不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/finance/secrets/regenerate [post]
|
||||
func (h *FinanceHandler) RegenerateAccessKey(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
cmd := &commands.RegenerateAccessKeyCommand{UserID: userID}
|
||||
result, err := h.appService.RegenerateAccessKey(c.Request.Context(), cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("重新生成访问密钥失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, result, "访问密钥重新生成成功")
|
||||
}
|
||||
|
||||
// DeactivateUserSecrets 停用用户密钥
|
||||
// @Summary 停用用户密钥
|
||||
// @Description 停用用户的API访问密钥
|
||||
// @Tags 用户密钥管理
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security Bearer
|
||||
// @Success 200 {object} map[string]interface{} "用户密钥停用成功"
|
||||
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||
// @Failure 404 {object} map[string]interface{} "密钥不存在"
|
||||
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||
// @Router /api/v1/finance/secrets/deactivate [post]
|
||||
func (h *FinanceHandler) DeactivateUserSecrets(c *gin.Context) {
|
||||
userID := c.GetString("user_id")
|
||||
if userID == "" {
|
||||
h.responseBuilder.Unauthorized(c, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
cmd := &commands.DeactivateUserSecretsCommand{UserID: userID}
|
||||
err := h.appService.DeactivateUserSecrets(c.Request.Context(), cmd)
|
||||
if err != nil {
|
||||
h.logger.Error("停用用户密钥失败",
|
||||
zap.String("user_id", userID),
|
||||
zap.Error(err),
|
||||
)
|
||||
h.responseBuilder.BadRequest(c, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
h.responseBuilder.Success(c, nil, "用户密钥停用成功")
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user