修改了皮肤收藏部分

This commit is contained in:
2026-01-13 18:34:21 +08:00
parent 3e8b7d150d
commit 116612ffec
7 changed files with 74 additions and 98 deletions

View File

@@ -56,17 +56,12 @@ type TextureRepository interface {
Delete(ctx context.Context, id int64) error Delete(ctx context.Context, id int64) error
BatchDelete(ctx context.Context, ids []int64) (int64, error) // 批量删除 BatchDelete(ctx context.Context, ids []int64) (int64, error) // 批量删除
IncrementDownloadCount(ctx context.Context, id int64) error IncrementDownloadCount(ctx context.Context, id int64) error
IncrementFavoriteCount(ctx context.Context, id int64) error
DecrementFavoriteCount(ctx context.Context, id int64) error
CreateDownloadLog(ctx context.Context, log *model.TextureDownloadLog) error CreateDownloadLog(ctx context.Context, log *model.TextureDownloadLog) error
IsFavorited(ctx context.Context, userID, textureID int64) (bool, error) ToggleFavorite(ctx context.Context, userID, textureID int64) (bool, error)
AddFavorite(ctx context.Context, userID, textureID int64) error
RemoveFavorite(ctx context.Context, userID, textureID int64) error
GetUserFavorites(ctx context.Context, userID int64, page, pageSize int) ([]*model.Texture, int64, error) GetUserFavorites(ctx context.Context, userID int64, page, pageSize int) ([]*model.Texture, int64, error)
CountByUploaderID(ctx context.Context, uploaderID int64) (int64, error) CountByUploaderID(ctx context.Context, uploaderID int64) (int64, error)
} }
// YggdrasilRepository Yggdrasil仓储接口 // YggdrasilRepository Yggdrasil仓储接口
type YggdrasilRepository interface { type YggdrasilRepository interface {
GetPasswordByID(ctx context.Context, id int64) (string, error) GetPasswordByID(ctx context.Context, id int64) (string, error)

View File

@@ -98,7 +98,6 @@ func TestProfileRepository_Basic(t *testing.T) {
t.Fatalf("CountByUserID mismatch: %d err=%v", count, err) t.Fatalf("CountByUserID mismatch: %d err=%v", count, err)
} }
if err := profileRepo.UpdateLastUsedAt(ctx, "p-uuid"); err != nil { if err := profileRepo.UpdateLastUsedAt(ctx, "p-uuid"); err != nil {
t.Fatalf("UpdateLastUsedAt err: %v", err) t.Fatalf("UpdateLastUsedAt err: %v", err)
} }
@@ -150,22 +149,20 @@ func TestTextureRepository_Basic(t *testing.T) {
t.Fatalf("FindByHashAndUploaderID mismatch") t.Fatalf("FindByHashAndUploaderID mismatch")
} }
_ = textureRepo.IncrementFavoriteCount(ctx, tex.ID) _, _ = textureRepo.ToggleFavorite(ctx, u.ID, tex.ID)
_ = textureRepo.DecrementFavoriteCount(ctx, tex.ID) favList, _, _ := textureRepo.GetUserFavorites(ctx, u.ID, 1, 10)
if len(favList) == 0 {
t.Fatalf("GetUserFavorites expected at least 1 favorite")
}
_, _ = textureRepo.ToggleFavorite(ctx, u.ID, tex.ID)
favList, _, _ = textureRepo.GetUserFavorites(ctx, u.ID, 1, 10)
if len(favList) != 0 {
t.Fatalf("GetUserFavorites expected 0 favorites after toggle off")
}
_ = textureRepo.IncrementDownloadCount(ctx, tex.ID) _ = textureRepo.IncrementDownloadCount(ctx, tex.ID)
_ = textureRepo.CreateDownloadLog(ctx, &model.TextureDownloadLog{TextureID: tex.ID, UserID: &u.ID, IPAddress: "127.0.0.1"}) _ = textureRepo.CreateDownloadLog(ctx, &model.TextureDownloadLog{TextureID: tex.ID, UserID: &u.ID, IPAddress: "127.0.0.1"})
// 收藏
_ = textureRepo.AddFavorite(ctx, u.ID, tex.ID)
if fav, err := textureRepo.IsFavorited(ctx, u.ID, tex.ID); err == nil {
if !fav {
t.Fatalf("IsFavorited expected true")
}
} else {
t.Skipf("IsFavorited not supported by sqlite: %v", err)
}
_ = textureRepo.RemoveFavorite(ctx, u.ID, tex.ID)
// 批量更新与删除 // 批量更新与删除
if affected, err := textureRepo.BatchUpdate(ctx, []int64{tex.ID}, map[string]interface{}{"name": "tex-new"}); err != nil || affected != 1 { if affected, err := textureRepo.BatchUpdate(ctx, []int64{tex.ID}, map[string]interface{}{"name": "tex-new"}); err != nil || affected != 1 {
t.Fatalf("BatchUpdate mismatch, affected=%d err=%v", affected, err) t.Fatalf("BatchUpdate mismatch, affected=%d err=%v", affected, err)
@@ -187,7 +184,7 @@ func TestTextureRepository_Basic(t *testing.T) {
if list, total, err := textureRepo.Search(ctx, "search", model.TextureTypeCape, true, 1, 10); err != nil || total == 0 || len(list) == 0 { if list, total, err := textureRepo.Search(ctx, "search", model.TextureTypeCape, true, 1, 10); err != nil || total == 0 || len(list) == 0 {
t.Fatalf("Search mismatch, total=%d len=%d err=%v", total, len(list), err) t.Fatalf("Search mismatch, total=%d len=%d err=%v", total, len(list), err)
} }
_ = textureRepo.AddFavorite(ctx, u.ID, tex.ID+1) _, _ = textureRepo.ToggleFavorite(ctx, u.ID, tex.ID+1)
if favList, total, err := textureRepo.GetUserFavorites(ctx, u.ID, 1, 10); err != nil || total == 0 || len(favList) == 0 { if favList, total, err := textureRepo.GetUserFavorites(ctx, u.ID, 1, 10); err != nil || total == 0 || len(favList) == 0 {
t.Fatalf("GetUserFavorites mismatch, total=%d len=%d err=%v", total, len(favList), err) t.Fatalf("GetUserFavorites mismatch, total=%d len=%d err=%v", total, len(favList), err)
} }
@@ -206,7 +203,6 @@ func TestTextureRepository_Basic(t *testing.T) {
_ = textureRepo.Delete(ctx, tex.ID) _ = textureRepo.Delete(ctx, tex.ID)
} }
func TestClientRepository_Basic(t *testing.T) { func TestClientRepository_Basic(t *testing.T) {
db := testutil.NewTestDB(t) db := testutil.NewTestDB(t)
repo := NewClientRepository(db) repo := NewClientRepository(db)

View File

@@ -138,42 +138,52 @@ func (r *textureRepository) IncrementDownloadCount(ctx context.Context, id int64
UpdateColumn("download_count", gorm.Expr("download_count + ?", 1)).Error UpdateColumn("download_count", gorm.Expr("download_count + ?", 1)).Error
} }
func (r *textureRepository) IncrementFavoriteCount(ctx context.Context, id int64) error {
return r.db.WithContext(ctx).Model(&model.Texture{}).Where("id = ?", id).
UpdateColumn("favorite_count", gorm.Expr("favorite_count + ?", 1)).Error
}
func (r *textureRepository) DecrementFavoriteCount(ctx context.Context, id int64) error {
return r.db.WithContext(ctx).Model(&model.Texture{}).Where("id = ?", id).
UpdateColumn("favorite_count", gorm.Expr("favorite_count - ?", 1)).Error
}
func (r *textureRepository) CreateDownloadLog(ctx context.Context, log *model.TextureDownloadLog) error { func (r *textureRepository) CreateDownloadLog(ctx context.Context, log *model.TextureDownloadLog) error {
return r.db.WithContext(ctx).Create(log).Error return r.db.WithContext(ctx).Create(log).Error
} }
func (r *textureRepository) IsFavorited(ctx context.Context, userID, textureID int64) (bool, error) { func (r *textureRepository) ToggleFavorite(ctx context.Context, userID, textureID int64) (bool, error) {
var isAdded bool
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var count int64 var count int64
// 使用 Select("1") 优化,只查询是否存在,不需要查询所有字段 err := tx.Model(&model.UserTextureFavorite{}).
err := r.db.WithContext(ctx).Model(&model.UserTextureFavorite{}).
Select("1").
Where("user_id = ? AND texture_id = ?", userID, textureID). Where("user_id = ? AND texture_id = ?", userID, textureID).
Limit(1).
Count(&count).Error Count(&count).Error
return count > 0, err if err != nil {
return err
}
if count > 0 {
result := tx.Where("user_id = ? AND texture_id = ?", userID, textureID).
Delete(&model.UserTextureFavorite{})
if result.Error != nil {
return result.Error
}
if result.RowsAffected > 0 {
if err := tx.Model(&model.Texture{}).Where("id = ?", textureID).
UpdateColumn("favorite_count", gorm.Expr("GREATEST(favorite_count - 1, 0)")).Error; err != nil {
return err
}
}
isAdded = false
return nil
} }
func (r *textureRepository) AddFavorite(ctx context.Context, userID, textureID int64) error {
favorite := &model.UserTextureFavorite{ favorite := &model.UserTextureFavorite{
UserID: userID, UserID: userID,
TextureID: textureID, TextureID: textureID,
} }
return r.db.WithContext(ctx).Create(favorite).Error if err := tx.Create(favorite).Error; err != nil {
return err
} }
if err := tx.Model(&model.Texture{}).Where("id = ?", textureID).
func (r *textureRepository) RemoveFavorite(ctx context.Context, userID, textureID int64) error { UpdateColumn("favorite_count", gorm.Expr("favorite_count + 1")).Error; err != nil {
return r.db.WithContext(ctx).Where("user_id = ? AND texture_id = ?", userID, textureID). return err
Delete(&model.UserTextureFavorite{}).Error }
isAdded = true
return nil
})
return isAdded, err
} }
func (r *textureRepository) GetUserFavorites(ctx context.Context, userID int64, page, pageSize int) ([]*model.Texture, int64, error) { func (r *textureRepository) GetUserFavorites(ctx context.Context, userID int64, page, pageSize int) ([]*model.Texture, int64, error) {

View File

@@ -391,37 +391,24 @@ func (m *MockTextureRepository) IncrementFavoriteCount(ctx context.Context, id i
return nil return nil
} }
func (m *MockTextureRepository) DecrementFavoriteCount(ctx context.Context, id int64) error {
if texture, ok := m.textures[id]; ok && texture.FavoriteCount > 0 {
texture.FavoriteCount--
}
return nil
}
func (m *MockTextureRepository) CreateDownloadLog(ctx context.Context, log *model.TextureDownloadLog) error { func (m *MockTextureRepository) CreateDownloadLog(ctx context.Context, log *model.TextureDownloadLog) error {
return nil return nil
} }
func (m *MockTextureRepository) IsFavorited(ctx context.Context, userID, textureID int64) (bool, error) { func (m *MockTextureRepository) ToggleFavorite(ctx context.Context, userID, textureID int64) (bool, error) {
if userFavs, ok := m.favorites[userID]; ok {
return userFavs[textureID], nil
}
return false, nil
}
func (m *MockTextureRepository) AddFavorite(ctx context.Context, userID, textureID int64) error {
if m.favorites[userID] == nil { if m.favorites[userID] == nil {
m.favorites[userID] = make(map[int64]bool) m.favorites[userID] = make(map[int64]bool)
} }
m.favorites[userID][textureID] = true isFavorited := m.favorites[userID][textureID]
return nil m.favorites[userID][textureID] = !isFavorited
if texture, ok := m.textures[textureID]; ok {
if !isFavorited {
texture.FavoriteCount++
} else if texture.FavoriteCount > 0 {
texture.FavoriteCount--
} }
func (m *MockTextureRepository) RemoveFavorite(ctx context.Context, userID, textureID int64) error {
if userFavs, ok := m.favorites[userID]; ok {
delete(userFavs, textureID)
} }
return nil return !isFavorited, nil
} }
func (m *MockTextureRepository) GetUserFavorites(ctx context.Context, userID int64, page, pageSize int) ([]*model.Texture, int64, error) { func (m *MockTextureRepository) GetUserFavorites(ctx context.Context, userID int64, page, pageSize int) ([]*model.Texture, int64, error) {
@@ -474,7 +461,6 @@ func (m *MockTextureRepository) BatchDelete(ctx context.Context, ids []int64) (i
return deleted, nil return deleted, nil
} }
// ============================================================================ // ============================================================================
// Service Mocks // Service Mocks
// ============================================================================ // ============================================================================

View File

@@ -219,7 +219,6 @@ func (s *textureService) Delete(ctx context.Context, textureID, uploaderID int64
} }
func (s *textureService) ToggleFavorite(ctx context.Context, userID, textureID int64) (bool, error) { func (s *textureService) ToggleFavorite(ctx context.Context, userID, textureID int64) (bool, error) {
// 确保材质存在
texture, err := s.textureRepo.FindByID(ctx, textureID) texture, err := s.textureRepo.FindByID(ctx, textureID)
if err != nil { if err != nil {
return false, err return false, err
@@ -228,30 +227,14 @@ func (s *textureService) ToggleFavorite(ctx context.Context, userID, textureID i
return false, ErrTextureNotFound return false, ErrTextureNotFound
} }
isFavorited, err := s.textureRepo.IsFavorited(ctx, userID, textureID) isAdded, err := s.textureRepo.ToggleFavorite(ctx, userID, textureID)
if err != nil { if err != nil {
return false, err return false, err
} }
if isFavorited { s.cacheInv.BatchInvalidate(ctx, s.cacheKeys.UserFavoritesPattern(userID))
// 已收藏 -> 取消收藏
if err := s.textureRepo.RemoveFavorite(ctx, userID, textureID); err != nil {
return false, err
}
if err := s.textureRepo.DecrementFavoriteCount(ctx, textureID); err != nil {
return false, err
}
return false, nil
}
// 未收藏 -> 添加收藏 return isAdded, nil
if err := s.textureRepo.AddFavorite(ctx, userID, textureID); err != nil {
return false, err
}
if err := s.textureRepo.IncrementFavoriteCount(ctx, textureID); err != nil {
return false, err
}
return true, nil
} }
func (s *textureService) GetUserFavorites(ctx context.Context, userID int64, page, pageSize int) ([]*model.Texture, int64, error) { func (s *textureService) GetUserFavorites(ctx context.Context, userID int64, page, pageSize int) ([]*model.Texture, int64, error) {

View File

@@ -3,6 +3,7 @@ package service
import ( import (
"carrotskin/internal/model" "carrotskin/internal/model"
"context" "context"
"strings"
"testing" "testing"
"go.uber.org/zap" "go.uber.org/zap"
@@ -564,7 +565,7 @@ func TestTextureServiceImpl_Create(t *testing.T) {
ctx := context.Background() ctx := context.Background()
// UploadTexture需要文件数据这里创建一个简单的测试数据 // UploadTexture需要文件数据这里创建一个简单的测试数据
fileData := []byte("fake png data for testing") fileData := []byte(strings.Repeat("x", 512))
texture, err := textureService.UploadTexture( texture, err := textureService.UploadTexture(
ctx, ctx,
tt.uploaderID, tt.uploaderID,
@@ -760,7 +761,7 @@ func TestTextureServiceImpl_FavoritesAndLimit(t *testing.T) {
UploaderID: 1, UploaderID: 1,
Name: "T", Name: "T",
}) })
_ = textureRepo.AddFavorite(context.Background(), 1, i) _, _ = textureRepo.ToggleFavorite(context.Background(), 1, i)
} }
cacheManager := NewMockCacheManager() cacheManager := NewMockCacheManager()

View File

@@ -369,6 +369,11 @@ func (b *CacheKeyBuilder) ProfilePattern(userID int64) string {
return fmt.Sprintf("%sprofile:*:%d*", b.prefix, userID) return fmt.Sprintf("%sprofile:*:%d*", b.prefix, userID)
} }
// UserFavoritesPattern 用户收藏相关的所有缓存键模式
func (b *CacheKeyBuilder) UserFavoritesPattern(userID int64) string {
return fmt.Sprintf("%sfavorites:*:%d*", b.prefix, userID)
}
// Exists 检查缓存键是否存在 // Exists 检查缓存键是否存在
func (cm *CacheManager) Exists(ctx context.Context, key string) (bool, error) { func (cm *CacheManager) Exists(ctx context.Context, key string) (bool, error) {
if !cm.config.Enabled || cm.redis == nil { if !cm.config.Enabled || cm.redis == nil {