基础架构

This commit is contained in:
2025-07-13 16:36:20 +08:00
parent e3d64e7485
commit 807004f78d
128 changed files with 17232 additions and 11396 deletions

View File

@@ -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{},

View 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)
}

View 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
}

View 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:"-"`
}

View 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"`
}

View 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"`
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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"`
}

View File

@@ -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:"用户唯一标识,用于查询该用户的认证申请详细信息"`
}

View File

@@ -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"`
}

View File

@@ -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"` // 高度
}

View File

@@ -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"`
}

View 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"`
}

View File

@@ -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"`
}

View 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)
}

View File

@@ -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")
}

View 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"`
}

View File

@@ -0,0 +1,6 @@
package queries
// GetUserQuery 获取用户信息查询
type GetUserQuery struct {
UserID string `json:"user_id"`
}

View 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"`
}

View File

@@ -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
}

View 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
}

View 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")
}

View File

@@ -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

View File

@@ -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"` // 权限列表
}

View File

@@ -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
}

View File

@@ -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"
}

View File

@@ -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)

View File

@@ -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"`
}

View File

@@ -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"`
}

View 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"`
}

View File

@@ -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) // 修改密码
}
}

View File

@@ -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))
}
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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: "人脸识别完成",

View File

@@ -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 认证事件处理器

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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"`
}

View File

@@ -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("认证路由注册完成")
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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, "获取统计信息成功")
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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"`
}

View File

@@ -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) // 停用用户密钥
}
}
}

View File

@@ -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[:])
}

View File

@@ -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
}

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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
}

View 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"`
}

View File

@@ -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))
}

View File

@@ -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))
}
}

View File

@@ -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
}

View File

@@ -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) // 修改密码
}
}
}

View 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
}

View File

@@ -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{})
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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,
}
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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))
}

View File

@@ -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))
}
}

View File

@@ -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))

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)
}

View 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 ""
}

View 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结果成功")
}

View 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