Compare commits
2 Commits
dev
...
432b875ba4
| Author | SHA1 | Date | |
|---|---|---|---|
| 432b875ba4 | |||
| 116612ffec |
@@ -1,6 +1,3 @@
|
|||||||
# CarrotSkin 环境配置文件示例
|
|
||||||
# 复制此文件为 .env 并修改相应的配置值
|
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 站点配置
|
# 站点配置
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
@@ -34,10 +31,10 @@ SERVER_SWAGGER_ENABLED=true
|
|||||||
# 数据库配置
|
# 数据库配置
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
DATABASE_DRIVER=postgres
|
DATABASE_DRIVER=postgres
|
||||||
DATABASE_HOST=localhost
|
DATABASE_HOST=120.27.110.94
|
||||||
DATABASE_PORT=5432
|
DATABASE_PORT=5432
|
||||||
DATABASE_USERNAME=postgres
|
DATABASE_USERNAME=user_wc2MbZ
|
||||||
DATABASE_PASSWORD=your_password_here
|
DATABASE_PASSWORD=password_65b5aN
|
||||||
DATABASE_NAME=carrotskin
|
DATABASE_NAME=carrotskin
|
||||||
DATABASE_SSL_MODE=disable
|
DATABASE_SSL_MODE=disable
|
||||||
DATABASE_TIMEZONE=Asia/Shanghai
|
DATABASE_TIMEZONE=Asia/Shanghai
|
||||||
@@ -49,19 +46,19 @@ DATABASE_CONN_MAX_IDLE_TIME=10m
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
# Redis配置
|
# Redis配置
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
REDIS_HOST=localhost
|
REDIS_HOST=120.27.110.94
|
||||||
REDIS_PORT=6379
|
REDIS_PORT=6379
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=redis_ZXjbN5
|
||||||
REDIS_DATABASE=0
|
REDIS_DATABASE=0
|
||||||
REDIS_POOL_SIZE=10
|
REDIS_POOL_SIZE=10
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# RustFS对象存储配置 (S3兼容)
|
# RustFS对象存储配置 (S3兼容)
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
RUSTFS_ENDPOINT=127.0.0.1:9000
|
RUSTFS_ENDPOINT=120.27.110.94:9000
|
||||||
RUSTFS_PUBLIC_URL=http://127.0.0.1:9000
|
RUSTFS_PUBLIC_URL=http://120.27.110.94:9000
|
||||||
RUSTFS_ACCESS_KEY=your_access_key
|
RUSTFS_ACCESS_KEY=ftbulyR6rj0AZ4n5ID7g
|
||||||
RUSTFS_SECRET_KEY=your_secret_key
|
RUSTFS_SECRET_KEY=P8q3VZ1wfMEdGJayu4sxh7NRSAB2H0tkFeTQlXLW
|
||||||
RUSTFS_USE_SSL=false
|
RUSTFS_USE_SSL=false
|
||||||
RUSTFS_BUCKET_TEXTURES=carrot-skin-textures
|
RUSTFS_BUCKET_TEXTURES=carrot-skin-textures
|
||||||
RUSTFS_BUCKET_AVATARS=carrot-skin-avatars
|
RUSTFS_BUCKET_AVATARS=carrot-skin-avatars
|
||||||
@@ -78,6 +75,18 @@ JWT_EXPIRE_HOURS=168
|
|||||||
LOG_LEVEL=info
|
LOG_LEVEL=info
|
||||||
LOG_FORMAT=json
|
LOG_FORMAT=json
|
||||||
LOG_OUTPUT=logs/app.log
|
LOG_OUTPUT=logs/app.log
|
||||||
|
# 保留的旧配置项
|
||||||
|
LOG_MAX_SIZE=100
|
||||||
|
LOG_MAX_BACKUPS=3
|
||||||
|
LOG_MAX_AGE=28
|
||||||
|
LOG_COMPRESS=true
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 文件上传配置 (保留的旧配置项)
|
||||||
|
# =============================================================================
|
||||||
|
UPLOAD_MAX_SIZE=10485760
|
||||||
|
UPLOAD_TEXTURE_MAX_SIZE=2097152
|
||||||
|
UPLOAD_AVATAR_MAX_SIZE=1048576
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 安全配置
|
# 安全配置
|
||||||
@@ -85,15 +94,17 @@ LOG_OUTPUT=logs/app.log
|
|||||||
# CORS 允许的来源,多个用逗号分隔
|
# CORS 允许的来源,多个用逗号分隔
|
||||||
SECURITY_ALLOWED_ORIGINS=*
|
SECURITY_ALLOWED_ORIGINS=*
|
||||||
# 允许的头像/材质URL域名,多个用逗号分隔
|
# 允许的头像/材质URL域名,多个用逗号分隔
|
||||||
SECURITY_ALLOWED_DOMAINS=localhost,127.0.0.1
|
SECURITY_ALLOWED_DOMAINS=localhost,127.0.0.1,120.27.110.94
|
||||||
|
# 保留的旧配置项
|
||||||
|
MAX_LOGIN_ATTEMPTS=5
|
||||||
|
LOGIN_LOCK_DURATION=30m
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# 邮件配置
|
# 邮件配置
|
||||||
# 腾讯企业邮箱SSL配置示例:smtp.exmail.qq.com, 端口465
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
EMAIL_ENABLED=false
|
EMAIL_ENABLED=true
|
||||||
EMAIL_SMTP_HOST=smtp.example.com
|
EMAIL_SMTP_HOST=smtp.exmail.qq.com
|
||||||
EMAIL_SMTP_PORT=587
|
EMAIL_SMTP_PORT=465
|
||||||
EMAIL_USERNAME=noreply@example.com
|
EMAIL_USERNAME=system@qczlit.cn
|
||||||
EMAIL_PASSWORD=your-email-password
|
EMAIL_PASSWORD=545mkewZwMzEWUjD
|
||||||
EMAIL_FROM_NAME=CarrotSkin
|
EMAIL_FROM_NAME=CarrotSkin
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -60,7 +60,7 @@ configs/config.yaml
|
|||||||
.env.production
|
.env.production
|
||||||
|
|
||||||
# Keep example files
|
# Keep example files
|
||||||
!.env.example
|
!.env
|
||||||
|
|
||||||
# Database files
|
# Database files
|
||||||
*.db
|
*.db
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ backend/
|
|||||||
|
|
||||||
3. **配置环境变量**
|
3. **配置环境变量**
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env .env
|
||||||
# 根据实际环境填写数据库、Redis、对象存储、邮件等信息
|
# 根据实际环境填写数据库、Redis、对象存储、邮件等信息
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
_ "time/tzdata"
|
||||||
|
|
||||||
"carrotskin/internal/container"
|
"carrotskin/internal/container"
|
||||||
"carrotskin/internal/handler"
|
"carrotskin/internal/handler"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 count int64
|
var isAdded bool
|
||||||
// 使用 Select("1") 优化,只查询是否存在,不需要查询所有字段
|
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
err := r.db.WithContext(ctx).Model(&model.UserTextureFavorite{}).
|
var count int64
|
||||||
Select("1").
|
err := tx.Model(&model.UserTextureFavorite{}).
|
||||||
Where("user_id = ? AND texture_id = ?", userID, textureID).
|
Where("user_id = ? AND texture_id = ?", userID, textureID).
|
||||||
Limit(1).
|
Count(&count).Error
|
||||||
Count(&count).Error
|
if err != nil {
|
||||||
return count > 0, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *textureRepository) AddFavorite(ctx context.Context, userID, textureID int64) error {
|
if count > 0 {
|
||||||
favorite := &model.UserTextureFavorite{
|
result := tx.Where("user_id = ? AND texture_id = ?", userID, textureID).
|
||||||
UserID: userID,
|
Delete(&model.UserTextureFavorite{})
|
||||||
TextureID: textureID,
|
if result.Error != nil {
|
||||||
}
|
return result.Error
|
||||||
return r.db.WithContext(ctx).Create(favorite).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) RemoveFavorite(ctx context.Context, userID, textureID int64) error {
|
favorite := &model.UserTextureFavorite{
|
||||||
return r.db.WithContext(ctx).Where("user_id = ? AND texture_id = ?", userID, textureID).
|
UserID: userID,
|
||||||
Delete(&model.UserTextureFavorite{}).Error
|
TextureID: textureID,
|
||||||
|
}
|
||||||
|
if err := tx.Create(favorite).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := tx.Model(&model.Texture{}).Where("id = ?", textureID).
|
||||||
|
UpdateColumn("favorite_count", gorm.Expr("favorite_count + 1")).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
func (m *MockTextureRepository) RemoveFavorite(ctx context.Context, userID, textureID int64) error {
|
texture.FavoriteCount++
|
||||||
if userFavs, ok := m.favorites[userID]; ok {
|
} else if texture.FavoriteCount > 0 {
|
||||||
delete(userFavs, textureID)
|
texture.FavoriteCount--
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -219,39 +219,22 @@ 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
|
||||||
}
|
}
|
||||||
if texture == nil {
|
if texture == nil || texture.Status != 1 || !texture.IsPublic {
|
||||||
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) {
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user