2025-11-28 23:30:49 +08:00
|
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2025-12-04 20:07:30 +08:00
|
|
|
|
"bytes"
|
2025-11-28 23:30:49 +08:00
|
|
|
|
"carrotskin/internal/model"
|
|
|
|
|
|
"carrotskin/internal/repository"
|
2025-12-02 22:52:33 +08:00
|
|
|
|
"carrotskin/pkg/database"
|
2025-12-04 20:07:30 +08:00
|
|
|
|
"carrotskin/pkg/storage"
|
2025-12-02 22:52:33 +08:00
|
|
|
|
"context"
|
2025-12-04 20:07:30 +08:00
|
|
|
|
"crypto/sha256"
|
|
|
|
|
|
"encoding/hex"
|
2025-11-28 23:30:49 +08:00
|
|
|
|
"errors"
|
|
|
|
|
|
"fmt"
|
2025-12-04 20:07:30 +08:00
|
|
|
|
"path/filepath"
|
|
|
|
|
|
"strings"
|
2025-11-28 23:30:49 +08:00
|
|
|
|
|
2025-12-02 19:43:39 +08:00
|
|
|
|
"go.uber.org/zap"
|
2025-11-28 23:30:49 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
// textureService TextureService的实现
|
|
|
|
|
|
type textureService struct {
|
2025-12-02 19:43:39 +08:00
|
|
|
|
textureRepo repository.TextureRepository
|
|
|
|
|
|
userRepo repository.UserRepository
|
2025-12-04 20:07:30 +08:00
|
|
|
|
storage *storage.StorageClient
|
2025-12-02 22:52:33 +08:00
|
|
|
|
cache *database.CacheManager
|
|
|
|
|
|
cacheKeys *database.CacheKeyBuilder
|
|
|
|
|
|
cacheInv *database.CacheInvalidator
|
2025-12-02 19:43:39 +08:00
|
|
|
|
logger *zap.Logger
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewTextureService 创建TextureService实例
|
|
|
|
|
|
func NewTextureService(
|
|
|
|
|
|
textureRepo repository.TextureRepository,
|
|
|
|
|
|
userRepo repository.UserRepository,
|
2025-12-04 20:07:30 +08:00
|
|
|
|
storageClient *storage.StorageClient,
|
2025-12-02 22:52:33 +08:00
|
|
|
|
cacheManager *database.CacheManager,
|
2025-12-02 19:43:39 +08:00
|
|
|
|
logger *zap.Logger,
|
|
|
|
|
|
) TextureService {
|
2025-12-02 22:52:33 +08:00
|
|
|
|
return &textureService{
|
2025-12-02 19:43:39 +08:00
|
|
|
|
textureRepo: textureRepo,
|
|
|
|
|
|
userRepo: userRepo,
|
2025-12-04 20:07:30 +08:00
|
|
|
|
storage: storageClient,
|
2025-12-02 22:52:33 +08:00
|
|
|
|
cache: cacheManager,
|
|
|
|
|
|
cacheKeys: database.NewCacheKeyBuilder(""),
|
|
|
|
|
|
cacheInv: database.NewCacheInvalidator(cacheManager),
|
2025-12-02 19:43:39 +08:00
|
|
|
|
logger: logger,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
func (s *textureService) GetByID(ctx context.Context, id int64) (*model.Texture, error) {
|
|
|
|
|
|
// 尝试从缓存获取
|
|
|
|
|
|
cacheKey := s.cacheKeys.Texture(id)
|
|
|
|
|
|
var texture model.Texture
|
2025-12-24 16:03:46 +08:00
|
|
|
|
if ok, _ := s.cache.TryGet(ctx, cacheKey, &texture); ok {
|
2025-12-02 22:52:33 +08:00
|
|
|
|
if texture.Status == -1 {
|
|
|
|
|
|
return nil, errors.New("材质已删除")
|
|
|
|
|
|
}
|
2026-01-10 03:15:27 +08:00
|
|
|
|
// 如果缓存中没有 Uploader 信息,重新查询数据库
|
|
|
|
|
|
if texture.Uploader == nil {
|
|
|
|
|
|
texture2, err := s.textureRepo.FindByID(ctx, id)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
if texture2 == nil {
|
|
|
|
|
|
return nil, ErrTextureNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
if texture2.Status == -1 {
|
|
|
|
|
|
return nil, errors.New("材质已删除")
|
|
|
|
|
|
}
|
|
|
|
|
|
// 更新缓存
|
|
|
|
|
|
s.cache.SetAsync(context.Background(), cacheKey, texture2, s.cache.Policy.TextureTTL)
|
|
|
|
|
|
return texture2, nil
|
|
|
|
|
|
}
|
2025-12-02 22:52:33 +08:00
|
|
|
|
return &texture, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 缓存未命中,从数据库查询
|
2025-12-03 15:27:12 +08:00
|
|
|
|
texture2, err := s.textureRepo.FindByID(ctx, id)
|
2025-12-02 19:43:39 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-12-02 22:52:33 +08:00
|
|
|
|
if texture2 == nil {
|
2025-12-02 19:43:39 +08:00
|
|
|
|
return nil, ErrTextureNotFound
|
|
|
|
|
|
}
|
2025-12-02 22:52:33 +08:00
|
|
|
|
if texture2.Status == -1 {
|
2025-12-02 19:43:39 +08:00
|
|
|
|
return nil, errors.New("材质已删除")
|
|
|
|
|
|
}
|
2025-12-02 22:52:33 +08:00
|
|
|
|
|
2025-12-24 16:03:46 +08:00
|
|
|
|
// 存入缓存(异步)
|
2026-01-10 03:23:26 +08:00
|
|
|
|
s.cache.SetAsync(context.Background(), cacheKey, texture2, s.cache.Policy.TextureTTL)
|
2025-12-02 22:52:33 +08:00
|
|
|
|
|
|
|
|
|
|
return texture2, nil
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-03 10:58:39 +08:00
|
|
|
|
func (s *textureService) GetByHash(ctx context.Context, hash string) (*model.Texture, error) {
|
|
|
|
|
|
// 尝试从缓存获取
|
|
|
|
|
|
cacheKey := s.cacheKeys.TextureByHash(hash)
|
|
|
|
|
|
var texture model.Texture
|
2025-12-24 16:03:46 +08:00
|
|
|
|
if ok, _ := s.cache.TryGet(ctx, cacheKey, &texture); ok {
|
2025-12-03 10:58:39 +08:00
|
|
|
|
if texture.Status == -1 {
|
|
|
|
|
|
return nil, errors.New("材质已删除")
|
|
|
|
|
|
}
|
|
|
|
|
|
return &texture, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 缓存未命中,从数据库查询
|
2025-12-03 15:27:12 +08:00
|
|
|
|
texture2, err := s.textureRepo.FindByHash(ctx, hash)
|
2025-12-03 10:58:39 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
if texture2 == nil {
|
|
|
|
|
|
return nil, ErrTextureNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
if texture2.Status == -1 {
|
|
|
|
|
|
return nil, errors.New("材质已删除")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 16:03:46 +08:00
|
|
|
|
// 存入缓存(异步)
|
|
|
|
|
|
s.cache.SetAsync(context.Background(), cacheKey, texture2, s.cache.Policy.TextureTTL)
|
2025-12-03 10:58:39 +08:00
|
|
|
|
|
|
|
|
|
|
return texture2, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
func (s *textureService) GetByUserID(ctx context.Context, uploaderID int64, page, pageSize int) ([]*model.Texture, int64, error) {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
page, pageSize = NormalizePagination(page, pageSize)
|
2025-12-02 22:52:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 尝试从缓存获取(包含分页参数)
|
|
|
|
|
|
cacheKey := s.cacheKeys.TextureList(uploaderID, page)
|
|
|
|
|
|
var cachedResult struct {
|
|
|
|
|
|
Textures []*model.Texture
|
|
|
|
|
|
Total int64
|
|
|
|
|
|
}
|
2025-12-24 16:03:46 +08:00
|
|
|
|
if ok, _ := s.cache.TryGet(ctx, cacheKey, &cachedResult); ok {
|
2025-12-02 22:52:33 +08:00
|
|
|
|
return cachedResult.Textures, cachedResult.Total, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 缓存未命中,从数据库查询
|
2025-12-03 15:27:12 +08:00
|
|
|
|
textures, total, err := s.textureRepo.FindByUploaderID(ctx, uploaderID, page, pageSize)
|
2025-12-02 22:52:33 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, 0, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-24 16:03:46 +08:00
|
|
|
|
// 存入缓存(异步)
|
|
|
|
|
|
result := struct {
|
|
|
|
|
|
Textures []*model.Texture
|
|
|
|
|
|
Total int64
|
|
|
|
|
|
}{Textures: textures, Total: total}
|
|
|
|
|
|
s.cache.SetAsync(context.Background(), cacheKey, result, s.cache.Policy.TextureListTTL)
|
2025-12-02 22:52:33 +08:00
|
|
|
|
|
|
|
|
|
|
return textures, total, nil
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
func (s *textureService) Search(ctx context.Context, keyword string, textureType model.TextureType, publicOnly bool, page, pageSize int) ([]*model.Texture, int64, error) {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
page, pageSize = NormalizePagination(page, pageSize)
|
2025-12-03 15:27:12 +08:00
|
|
|
|
return s.textureRepo.Search(ctx, keyword, textureType, publicOnly, page, pageSize)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
func (s *textureService) Update(ctx context.Context, textureID, uploaderID int64, name, description string, isPublic *bool) (*model.Texture, error) {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
// 获取材质并验证权限
|
2025-12-03 15:27:12 +08:00
|
|
|
|
texture, err := s.textureRepo.FindByID(ctx, textureID)
|
2025-12-02 19:43:39 +08:00
|
|
|
|
if err != nil {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
2025-12-02 19:43:39 +08:00
|
|
|
|
if texture == nil {
|
|
|
|
|
|
return nil, ErrTextureNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
if texture.UploaderID != uploaderID {
|
|
|
|
|
|
return nil, ErrTextureNoPermission
|
|
|
|
|
|
}
|
2025-11-28 23:30:49 +08:00
|
|
|
|
|
|
|
|
|
|
// 更新字段
|
|
|
|
|
|
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 {
|
2025-12-03 15:27:12 +08:00
|
|
|
|
if err := s.textureRepo.UpdateFields(ctx, textureID, updates); err != nil {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
// 清除 texture 缓存和用户列表缓存
|
|
|
|
|
|
s.cacheInv.OnUpdate(ctx, s.cacheKeys.Texture(textureID))
|
2025-12-24 16:03:46 +08:00
|
|
|
|
s.cacheInv.BatchInvalidate(ctx, s.cacheKeys.TextureListPattern(uploaderID))
|
2025-12-02 22:52:33 +08:00
|
|
|
|
|
2025-12-03 15:27:12 +08:00
|
|
|
|
return s.textureRepo.FindByID(ctx, textureID)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
func (s *textureService) Delete(ctx context.Context, textureID, uploaderID int64) error {
|
2025-12-02 19:43:39 +08:00
|
|
|
|
// 获取材质并验证权限
|
2025-12-03 15:27:12 +08:00
|
|
|
|
texture, err := s.textureRepo.FindByID(ctx, textureID)
|
2025-12-02 19:43:39 +08:00
|
|
|
|
if err != nil {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return err
|
|
|
|
|
|
}
|
2025-12-02 19:43:39 +08:00
|
|
|
|
if texture == nil {
|
|
|
|
|
|
return ErrTextureNotFound
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
2025-12-02 19:43:39 +08:00
|
|
|
|
if texture.UploaderID != uploaderID {
|
|
|
|
|
|
return ErrTextureNoPermission
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-03 15:27:12 +08:00
|
|
|
|
err = s.textureRepo.Delete(ctx, textureID)
|
2025-12-02 22:52:33 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清除 texture 缓存和用户列表缓存
|
|
|
|
|
|
s.cacheInv.OnDelete(ctx, s.cacheKeys.Texture(textureID))
|
2025-12-24 16:03:46 +08:00
|
|
|
|
s.cacheInv.BatchInvalidate(ctx, s.cacheKeys.TextureListPattern(uploaderID))
|
2025-12-02 22:52:33 +08:00
|
|
|
|
|
|
|
|
|
|
return nil
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
func (s *textureService) ToggleFavorite(ctx context.Context, userID, textureID int64) (bool, error) {
|
2025-12-02 19:43:39 +08:00
|
|
|
|
// 确保材质存在
|
2025-12-03 15:27:12 +08:00
|
|
|
|
texture, err := s.textureRepo.FindByID(ctx, textureID)
|
2025-12-02 19:43:39 +08:00
|
|
|
|
if err != nil {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
2025-12-02 19:43:39 +08:00
|
|
|
|
if texture == nil {
|
|
|
|
|
|
return false, ErrTextureNotFound
|
|
|
|
|
|
}
|
2025-11-28 23:30:49 +08:00
|
|
|
|
|
2025-12-03 15:27:12 +08:00
|
|
|
|
isFavorited, err := s.textureRepo.IsFavorited(ctx, userID, textureID)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if isFavorited {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
// 已收藏 -> 取消收藏
|
2025-12-03 15:27:12 +08:00
|
|
|
|
if err := s.textureRepo.RemoveFavorite(ctx, userID, textureID); err != nil {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
2025-12-03 15:27:12 +08:00
|
|
|
|
if err := s.textureRepo.DecrementFavoriteCount(ctx, textureID); err != nil {
|
2025-11-28 23:30:49 +08:00
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return false, nil
|
|
|
|
|
|
}
|
2025-12-02 19:43:39 +08:00
|
|
|
|
|
|
|
|
|
|
// 未收藏 -> 添加收藏
|
2025-12-03 15:27:12 +08:00
|
|
|
|
if err := s.textureRepo.AddFavorite(ctx, userID, textureID); err != nil {
|
2025-12-02 19:43:39 +08:00
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
2025-12-03 15:27:12 +08:00
|
|
|
|
if err := s.textureRepo.IncrementFavoriteCount(ctx, textureID); err != nil {
|
2025-12-02 19:43:39 +08:00
|
|
|
|
return false, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return true, nil
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
func (s *textureService) GetUserFavorites(ctx context.Context, userID int64, page, pageSize int) ([]*model.Texture, int64, error) {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
page, pageSize = NormalizePagination(page, pageSize)
|
2025-12-03 15:27:12 +08:00
|
|
|
|
return s.textureRepo.GetUserFavorites(ctx, userID, page, pageSize)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 22:52:33 +08:00
|
|
|
|
func (s *textureService) CheckUploadLimit(ctx context.Context, uploaderID int64, maxTextures int) error {
|
2025-12-03 15:27:12 +08:00
|
|
|
|
count, err := s.textureRepo.CountByUploaderID(ctx, uploaderID)
|
2025-11-28 23:30:49 +08:00
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if count >= int64(maxTextures) {
|
|
|
|
|
|
return fmt.Errorf("已达到最大上传数量限制(%d)", maxTextures)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2025-12-02 10:33:19 +08:00
|
|
|
|
|
2025-12-04 20:07:30 +08:00
|
|
|
|
// UploadTexture 直接上传材质文件
|
|
|
|
|
|
func (s *textureService) UploadTexture(ctx context.Context, uploaderID int64, name, description, textureType string, fileData []byte, fileName string, isPublic, isSlim bool) (*model.Texture, error) {
|
|
|
|
|
|
// 验证用户存在
|
|
|
|
|
|
user, err := s.userRepo.FindByID(ctx, uploaderID)
|
|
|
|
|
|
if err != nil || user == nil {
|
|
|
|
|
|
return nil, ErrUserNotFound
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证文件大小和扩展名
|
|
|
|
|
|
fileSize := len(fileData)
|
|
|
|
|
|
const minSize = 512 // 512B
|
|
|
|
|
|
const maxSize = 10 * 1024 * 1024 // 10MB
|
|
|
|
|
|
if int64(fileSize) < minSize || int64(fileSize) > maxSize {
|
|
|
|
|
|
return nil, fmt.Errorf("文件大小必须在 %d 到 %d 字节之间", minSize, maxSize)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证文件扩展名(只支持PNG)
|
|
|
|
|
|
ext := strings.ToLower(filepath.Ext(fileName))
|
|
|
|
|
|
if ext != ".png" {
|
|
|
|
|
|
return nil, fmt.Errorf("不支持的文件格式: %s,仅支持PNG格式", ext)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 验证材质类型
|
|
|
|
|
|
if textureType != "SKIN" && textureType != "CAPE" {
|
|
|
|
|
|
return nil, errors.New("无效的材质类型")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算文件SHA256哈希
|
|
|
|
|
|
hashBytes := sha256.Sum256(fileData)
|
|
|
|
|
|
hash := hex.EncodeToString(hashBytes[:])
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否有任何用户上传过相同Hash的皮肤(复用URL,不重复保存文件)
|
|
|
|
|
|
existingTexture, err := s.textureRepo.FindByHash(ctx, hash)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var finalURL string
|
|
|
|
|
|
if existingTexture != nil {
|
|
|
|
|
|
// 如果已存在相同Hash的皮肤,复用已存在的URL,不重复上传
|
|
|
|
|
|
finalURL = existingTexture.URL
|
|
|
|
|
|
s.logger.Info("复用已存在的材质文件",
|
|
|
|
|
|
zap.String("hash", hash),
|
|
|
|
|
|
zap.String("url", finalURL),
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果不存在,上传到对象存储
|
|
|
|
|
|
if s.storage == nil {
|
|
|
|
|
|
return nil, errors.New("存储服务不可用")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取存储桶名称
|
|
|
|
|
|
bucketName, err := s.storage.GetBucket("textures")
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("获取存储桶失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 生成对象名称(路径)
|
|
|
|
|
|
// 格式: hash/{hash[:2]}/{hash[2:4]}/{hash}.png
|
|
|
|
|
|
// 使用哈希值作为路径,避免重复存储相同文件
|
|
|
|
|
|
textureTypeFolder := strings.ToLower(textureType)
|
|
|
|
|
|
objectName := fmt.Sprintf("%s/%s/%s/%s/%s%s", textureTypeFolder, hash[:2], hash[2:4], hash, hash, ext)
|
|
|
|
|
|
|
|
|
|
|
|
// 上传文件
|
|
|
|
|
|
reader := bytes.NewReader(fileData)
|
|
|
|
|
|
contentType := "image/png"
|
|
|
|
|
|
if err := s.storage.UploadObject(ctx, bucketName, objectName, reader, int64(fileSize), contentType); err != nil {
|
|
|
|
|
|
return nil, fmt.Errorf("上传文件失败: %w", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建文件URL
|
|
|
|
|
|
finalURL = s.storage.BuildFileURL(bucketName, objectName)
|
|
|
|
|
|
s.logger.Info("上传新的材质文件",
|
|
|
|
|
|
zap.String("hash", hash),
|
|
|
|
|
|
zap.String("url", finalURL),
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 转换材质类型
|
|
|
|
|
|
textureTypeEnum, err := parseTextureTypeInternal(textureType)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建材质记录(即使Hash相同,也创建新的数据库记录)
|
|
|
|
|
|
texture := &model.Texture{
|
|
|
|
|
|
UploaderID: uploaderID,
|
|
|
|
|
|
Name: name,
|
|
|
|
|
|
Description: description,
|
|
|
|
|
|
Type: textureTypeEnum,
|
|
|
|
|
|
URL: finalURL,
|
|
|
|
|
|
Hash: hash,
|
|
|
|
|
|
Size: fileSize,
|
|
|
|
|
|
IsPublic: isPublic,
|
|
|
|
|
|
IsSlim: isSlim,
|
|
|
|
|
|
Status: 1,
|
|
|
|
|
|
DownloadCount: 0,
|
|
|
|
|
|
FavoriteCount: 0,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if err := s.textureRepo.Create(ctx, texture); err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 清除用户的 texture 列表缓存(所有分页)
|
|
|
|
|
|
s.cacheInv.BatchInvalidate(ctx, fmt.Sprintf("texture:user:%d:*", uploaderID))
|
|
|
|
|
|
|
2026-01-10 03:15:27 +08:00
|
|
|
|
// 重新查询以预加载 Uploader 关联
|
2026-01-10 03:23:26 +08:00
|
|
|
|
textureWithUploader, err := s.textureRepo.FindByID(ctx, texture.ID)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
// 如果查询失败,返回原始创建的 texture 对象(虽然可能没有 Uploader 信息)
|
|
|
|
|
|
return texture, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
if textureWithUploader == nil {
|
|
|
|
|
|
// 如果查询返回 nil(极端情况,如数据库复制延迟),返回原始创建的 texture 对象
|
|
|
|
|
|
return texture, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
return textureWithUploader, nil
|
2025-12-04 20:07:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-02 19:43:39 +08:00
|
|
|
|
// parseTextureTypeInternal 解析材质类型
|
|
|
|
|
|
func parseTextureTypeInternal(textureType string) (model.TextureType, error) {
|
2025-12-02 10:33:19 +08:00
|
|
|
|
switch textureType {
|
|
|
|
|
|
case "SKIN":
|
|
|
|
|
|
return model.TextureTypeSkin, nil
|
|
|
|
|
|
case "CAPE":
|
|
|
|
|
|
return model.TextureTypeCape, nil
|
|
|
|
|
|
default:
|
|
|
|
|
|
return "", errors.New("无效的材质类型")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|