package service import ( "carrotskin/internal/model" "carrotskin/internal/repository" "carrotskin/pkg/database" "context" "errors" "fmt" "time" "go.uber.org/zap" ) // textureService TextureService的实现 type textureService struct { textureRepo repository.TextureRepository userRepo repository.UserRepository cache *database.CacheManager cacheKeys *database.CacheKeyBuilder cacheInv *database.CacheInvalidator logger *zap.Logger } // NewTextureService 创建TextureService实例 func NewTextureService( textureRepo repository.TextureRepository, userRepo repository.UserRepository, cacheManager *database.CacheManager, logger *zap.Logger, ) TextureService { return &textureService{ textureRepo: textureRepo, userRepo: userRepo, cache: cacheManager, cacheKeys: database.NewCacheKeyBuilder(""), cacheInv: database.NewCacheInvalidator(cacheManager), logger: logger, } } func (s *textureService) Create(ctx context.Context, uploaderID int64, name, description, textureType, url, hash string, size int, isPublic, isSlim bool) (*model.Texture, error) { // 验证用户存在 user, err := s.userRepo.FindByID(uploaderID) if err != nil || user == nil { return nil, ErrUserNotFound } // 检查Hash是否已存在 existingTexture, err := s.textureRepo.FindByHash(hash) if err != nil { return nil, err } if existingTexture != nil { return nil, errors.New("该材质已存在") } // 转换材质类型 textureTypeEnum, err := parseTextureTypeInternal(textureType) if err != nil { return nil, err } // 创建材质 texture := &model.Texture{ UploaderID: uploaderID, Name: name, Description: description, Type: textureTypeEnum, URL: url, Hash: hash, Size: size, IsPublic: isPublic, IsSlim: isSlim, Status: 1, DownloadCount: 0, FavoriteCount: 0, } if err := s.textureRepo.Create(texture); err != nil { return nil, err } // 清除用户的 texture 列表缓存(所有分页) s.cacheInv.BatchInvalidate(ctx, fmt.Sprintf("texture:user:%d:*", uploaderID)) return texture, nil } func (s *textureService) GetByID(ctx context.Context, id int64) (*model.Texture, error) { // 尝试从缓存获取 cacheKey := s.cacheKeys.Texture(id) var texture model.Texture if err := s.cache.Get(ctx, cacheKey, &texture); err == nil { if texture.Status == -1 { return nil, errors.New("材质已删除") } return &texture, nil } // 缓存未命中,从数据库查询 texture2, err := s.textureRepo.FindByID(id) if err != nil { return nil, err } if texture2 == nil { return nil, ErrTextureNotFound } if texture2.Status == -1 { return nil, errors.New("材质已删除") } // 存入缓存(异步,5分钟过期) if texture2 != nil { go func() { _ = s.cache.Set(context.Background(), cacheKey, texture2, 5*time.Minute) }() } return texture2, nil } func (s *textureService) GetByUserID(ctx context.Context, uploaderID int64, page, pageSize int) ([]*model.Texture, int64, error) { page, pageSize = NormalizePagination(page, pageSize) // 尝试从缓存获取(包含分页参数) cacheKey := s.cacheKeys.TextureList(uploaderID, page) var cachedResult struct { Textures []*model.Texture Total int64 } if err := s.cache.Get(ctx, cacheKey, &cachedResult); err == nil { return cachedResult.Textures, cachedResult.Total, nil } // 缓存未命中,从数据库查询 textures, total, err := s.textureRepo.FindByUploaderID(uploaderID, page, pageSize) if err != nil { return nil, 0, err } // 存入缓存(异步,2分钟过期) go func() { result := struct { Textures []*model.Texture Total int64 }{Textures: textures, Total: total} _ = s.cache.Set(context.Background(), cacheKey, result, 2*time.Minute) }() return textures, total, nil } func (s *textureService) Search(ctx context.Context, keyword string, textureType model.TextureType, publicOnly bool, page, pageSize int) ([]*model.Texture, int64, error) { page, pageSize = NormalizePagination(page, pageSize) return s.textureRepo.Search(keyword, textureType, publicOnly, page, pageSize) } func (s *textureService) Update(ctx context.Context, textureID, uploaderID int64, name, description string, isPublic *bool) (*model.Texture, error) { // 获取材质并验证权限 texture, err := s.textureRepo.FindByID(textureID) if err != nil { return nil, err } if texture == nil { return nil, ErrTextureNotFound } if texture.UploaderID != uploaderID { return nil, ErrTextureNoPermission } // 更新字段 updates := make(map[string]interface{}) if name != "" { updates["name"] = name } if description != "" { updates["description"] = description } if isPublic != nil { updates["is_public"] = *isPublic } if len(updates) > 0 { if err := s.textureRepo.UpdateFields(textureID, updates); err != nil { return nil, err } } // 清除 texture 缓存和用户列表缓存 s.cacheInv.OnUpdate(ctx, s.cacheKeys.Texture(textureID)) s.cacheInv.BatchInvalidate(ctx, fmt.Sprintf("texture:user:%d:*", uploaderID)) return s.textureRepo.FindByID(textureID) } func (s *textureService) Delete(ctx context.Context, textureID, uploaderID int64) error { // 获取材质并验证权限 texture, err := s.textureRepo.FindByID(textureID) if err != nil { return err } if texture == nil { return ErrTextureNotFound } if texture.UploaderID != uploaderID { return ErrTextureNoPermission } err = s.textureRepo.Delete(textureID) if err != nil { return err } // 清除 texture 缓存和用户列表缓存 s.cacheInv.OnDelete(ctx, s.cacheKeys.Texture(textureID)) s.cacheInv.BatchInvalidate(ctx, fmt.Sprintf("texture:user:%d:*", uploaderID)) return nil } func (s *textureService) ToggleFavorite(ctx context.Context, userID, textureID int64) (bool, error) { // 确保材质存在 texture, err := s.textureRepo.FindByID(textureID) if err != nil { return false, err } if texture == nil { return false, ErrTextureNotFound } isFavorited, err := s.textureRepo.IsFavorited(userID, textureID) if err != nil { return false, err } if isFavorited { // 已收藏 -> 取消收藏 if err := s.textureRepo.RemoveFavorite(userID, textureID); err != nil { return false, err } if err := s.textureRepo.DecrementFavoriteCount(textureID); err != nil { return false, err } return false, nil } // 未收藏 -> 添加收藏 if err := s.textureRepo.AddFavorite(userID, textureID); err != nil { return false, err } if err := s.textureRepo.IncrementFavoriteCount(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) { page, pageSize = NormalizePagination(page, pageSize) return s.textureRepo.GetUserFavorites(userID, page, pageSize) } func (s *textureService) CheckUploadLimit(ctx context.Context, uploaderID int64, maxTextures int) error { count, err := s.textureRepo.CountByUploaderID(uploaderID) if err != nil { return err } if count >= int64(maxTextures) { return fmt.Errorf("已达到最大上传数量限制(%d)", maxTextures) } return nil } // parseTextureTypeInternal 解析材质类型 func parseTextureTypeInternal(textureType string) (model.TextureType, error) { switch textureType { case "SKIN": return model.TextureTypeSkin, nil case "CAPE": return model.TextureTypeCape, nil default: return "", errors.New("无效的材质类型") } }