feat(架构): 完善基础架构设计
This commit is contained in:
120
internal/domains/user/repositories/sms_code_repository.go
Normal file
120
internal/domains/user/repositories/sms_code_repository.go
Normal file
@@ -0,0 +1,120 @@
|
||||
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
|
||||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package repositories
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -12,6 +13,12 @@ import (
|
||||
"tyapi-server/internal/shared/interfaces"
|
||||
)
|
||||
|
||||
// 定义错误常量
|
||||
var (
|
||||
// ErrUserNotFound 用户不存在错误
|
||||
ErrUserNotFound = errors.New("用户不存在")
|
||||
)
|
||||
|
||||
// UserRepository 用户仓储实现
|
||||
type UserRepository struct {
|
||||
db *gorm.DB
|
||||
@@ -29,311 +36,150 @@ func NewUserRepository(db *gorm.DB, cache interfaces.CacheService, logger *zap.L
|
||||
}
|
||||
|
||||
// Create 创建用户
|
||||
func (r *UserRepository) Create(ctx context.Context, entity *entities.User) error {
|
||||
if err := r.db.WithContext(ctx).Create(entity).Error; err != nil {
|
||||
r.logger.Error("Failed to create user", zap.Error(err))
|
||||
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.invalidateUserCaches(ctx, entity.ID)
|
||||
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 := r.GetCacheKey(id)
|
||||
// 尝试从缓存获取
|
||||
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 = ? AND is_deleted = false", id).First(&user).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
// 从数据库查询
|
||||
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, 1*time.Hour)
|
||||
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, entity *entities.User) error {
|
||||
if err := r.db.WithContext(ctx).Save(entity).Error; err != nil {
|
||||
r.logger.Error("Failed to update user", zap.Error(err))
|
||||
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.invalidateUserCaches(ctx, entity.ID)
|
||||
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("Failed to delete user", zap.Error(err))
|
||||
r.logger.Error("删除用户失败", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.invalidateUserCaches(ctx, id)
|
||||
r.deleteCacheByID(ctx, id)
|
||||
r.deleteCacheByPhone(ctx, user.Phone)
|
||||
|
||||
r.logger.Info("用户删除成功", zap.String("user_id", id))
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateBatch 批量创建用户
|
||||
func (r *UserRepository) CreateBatch(ctx context.Context, entities []*entities.User) error {
|
||||
if err := r.db.WithContext(ctx).CreateInBatches(entities, 100).Error; err != nil {
|
||||
r.logger.Error("Failed to create users in batch", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除列表缓存
|
||||
r.cache.DeletePattern(ctx, "users:list:*")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByIDs 根据ID列表获取用户
|
||||
func (r *UserRepository) GetByIDs(ctx context.Context, ids []string) ([]*entities.User, error) {
|
||||
var users []entities.User
|
||||
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("id IN ? AND is_deleted = false", ids).
|
||||
Find(&users).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
result := make([]*entities.User, len(users))
|
||||
for i := range users {
|
||||
result[i] = &users[i]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// UpdateBatch 批量更新用户
|
||||
func (r *UserRepository) UpdateBatch(ctx context.Context, entities []*entities.User) error {
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
for _, entity := range entities {
|
||||
if err := tx.Save(entity).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteBatch 批量删除用户
|
||||
func (r *UserRepository) DeleteBatch(ctx context.Context, ids []string) error {
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("id IN ?", ids).
|
||||
Delete(&entities.User{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
for _, id := range ids {
|
||||
r.invalidateUserCaches(ctx, id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List 获取用户列表
|
||||
func (r *UserRepository) List(ctx context.Context, options interfaces.ListOptions) ([]*entities.User, error) {
|
||||
// 尝试从缓存获取
|
||||
cacheKey := fmt.Sprintf("users:list:%d:%d:%s", options.Page, options.PageSize, options.Sort)
|
||||
// List 分页获取用户列表
|
||||
func (r *UserRepository) List(ctx context.Context, offset, limit int) ([]*entities.User, error) {
|
||||
var users []*entities.User
|
||||
|
||||
if err := r.cache.Get(ctx, cacheKey, &users); err == nil {
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// 从数据库查询
|
||||
query := r.db.WithContext(ctx).Where("is_deleted = false")
|
||||
|
||||
// 应用过滤条件
|
||||
if options.Search != "" {
|
||||
query = query.Where("username ILIKE ? OR email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?",
|
||||
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
// 应用排序
|
||||
if options.Sort != "" {
|
||||
order := options.Order
|
||||
if order == "" {
|
||||
order = "asc"
|
||||
}
|
||||
query = query.Order(fmt.Sprintf("%s %s", options.Sort, order))
|
||||
} else {
|
||||
query = query.Order("created_at desc")
|
||||
}
|
||||
|
||||
// 应用分页
|
||||
if options.Page > 0 && options.PageSize > 0 {
|
||||
offset := (options.Page - 1) * options.PageSize
|
||||
query = query.Offset(offset).Limit(options.PageSize)
|
||||
}
|
||||
|
||||
var userEntities []entities.User
|
||||
if err := query.Find(&userEntities).Error; err != nil {
|
||||
if err := r.db.WithContext(ctx).Offset(offset).Limit(limit).Find(&users).Error; err != nil {
|
||||
r.logger.Error("查询用户列表失败", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 转换为指针切片
|
||||
users = make([]*entities.User, len(userEntities))
|
||||
for i := range userEntities {
|
||||
users[i] = &userEntities[i]
|
||||
}
|
||||
|
||||
// 缓存结果
|
||||
r.cache.Set(ctx, cacheKey, users, 30*time.Minute)
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
// Count 统计用户数量
|
||||
func (r *UserRepository) Count(ctx context.Context, options interfaces.CountOptions) (int64, error) {
|
||||
query := r.db.WithContext(ctx).Model(&entities.User{}).Where("is_deleted = false")
|
||||
|
||||
// 应用过滤条件
|
||||
if options.Search != "" {
|
||||
query = query.Where("username ILIKE ? OR email ILIKE ? OR first_name ILIKE ? OR last_name ILIKE ?",
|
||||
"%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%", "%"+options.Search+"%")
|
||||
}
|
||||
|
||||
// Count 获取用户总数
|
||||
func (r *UserRepository) Count(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
if err := query.Count(&count).Error; err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// Exists 检查用户是否存在
|
||||
func (r *UserRepository) Exists(ctx context.Context, id string) (bool, error) {
|
||||
// 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("id = ? AND is_deleted = false", id).
|
||||
Count(&count).Error; err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// SoftDelete 软删除用户
|
||||
func (r *UserRepository) SoftDelete(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.User{}).
|
||||
Where("id = ?", id).
|
||||
Update("is_deleted", true).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// 私有辅助方法
|
||||
|
||||
// 清除相关缓存
|
||||
r.invalidateUserCaches(ctx, id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Restore 恢复用户
|
||||
func (r *UserRepository) Restore(ctx context.Context, id string) error {
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&entities.User{}).
|
||||
Where("id = ?", id).
|
||||
Update("is_deleted", false).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 清除相关缓存
|
||||
r.invalidateUserCaches(ctx, id)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithTx 使用事务
|
||||
func (r *UserRepository) WithTx(tx interface{}) interfaces.Repository[*entities.User] {
|
||||
gormTx, ok := tx.(*gorm.DB)
|
||||
if !ok {
|
||||
return r
|
||||
}
|
||||
|
||||
return &UserRepository{
|
||||
db: gormTx,
|
||||
cache: r.cache,
|
||||
logger: r.logger,
|
||||
// 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))
|
||||
}
|
||||
}
|
||||
|
||||
// InvalidateCache 清除缓存
|
||||
func (r *UserRepository) InvalidateCache(ctx context.Context, keys ...string) error {
|
||||
return r.cache.Delete(ctx, keys...)
|
||||
}
|
||||
|
||||
// WarmupCache 预热缓存
|
||||
func (r *UserRepository) WarmupCache(ctx context.Context) error {
|
||||
// 预热热门用户数据
|
||||
// 这里可以实现具体的预热逻辑
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCacheKey 获取缓存键
|
||||
func (r *UserRepository) GetCacheKey(id string) string {
|
||||
return fmt.Sprintf("user:%s", id)
|
||||
}
|
||||
|
||||
// FindByUsername 根据用户名查找用户
|
||||
func (r *UserRepository) FindByUsername(ctx context.Context, username string) (*entities.User, error) {
|
||||
var user entities.User
|
||||
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("username = ? AND is_deleted = false", username).
|
||||
First(&user).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
return nil, 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))
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// FindByEmail 根据邮箱查找用户
|
||||
func (r *UserRepository) FindByEmail(ctx context.Context, email string) (*entities.User, error) {
|
||||
var user entities.User
|
||||
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("email = ? AND is_deleted = false", email).
|
||||
First(&user).Error; err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return nil, fmt.Errorf("user not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
// invalidateUserCaches 清除用户相关缓存
|
||||
func (r *UserRepository) invalidateUserCaches(ctx context.Context, userID string) {
|
||||
keys := []string{
|
||||
r.GetCacheKey(userID),
|
||||
}
|
||||
|
||||
r.cache.Delete(ctx, keys...)
|
||||
r.cache.DeletePattern(ctx, "users:list:*")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user