- Updated main.go to initialize email service and include it in the dependency injection container. - Refactored handlers to utilize context in service method calls, improving consistency and error handling. - Introduced new service options for upload, security, and captcha services, enhancing modularity and testability. - Removed unused repository implementations to streamline the codebase. This commit continues the effort to improve the architecture by ensuring all services are properly injected and utilized across the application.
287 lines
7.4 KiB
Go
287 lines
7.4 KiB
Go
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("无效的材质类型")
|
||
}
|
||
}
|