diff --git a/cmd/server/main.go b/cmd/server/main.go index 0a54262..2f98dd5 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -32,8 +32,6 @@ import ( "github.com/gin-gonic/gin" "go.uber.org/zap" - - _ "carrotskin/docs" // Swagger docs ) func main() { diff --git a/internal/handler/helpers.go b/internal/handler/helpers.go index e52c4ac..ae835d4 100644 --- a/internal/handler/helpers.go +++ b/internal/handler/helpers.go @@ -87,22 +87,28 @@ func ProfilesToProfileInfos(profiles []*model.Profile) []*types.ProfileInfo { // TextureToTextureInfo 将 Texture 模型转换为 TextureInfo 响应 func TextureToTextureInfo(texture *model.Texture) *types.TextureInfo { + uploaderUsername := "" + if texture.Uploader != nil { + uploaderUsername = texture.Uploader.Username + } + return &types.TextureInfo{ - ID: texture.ID, - UploaderID: texture.UploaderID, - Name: texture.Name, - Description: texture.Description, - Type: types.TextureType(texture.Type), - URL: texture.URL, - Hash: texture.Hash, - Size: texture.Size, - IsPublic: texture.IsPublic, - DownloadCount: texture.DownloadCount, - FavoriteCount: texture.FavoriteCount, - IsSlim: texture.IsSlim, - Status: texture.Status, - CreatedAt: texture.CreatedAt, - UpdatedAt: texture.UpdatedAt, + ID: texture.ID, + UploaderID: texture.UploaderID, + UploaderUsername: uploaderUsername, + Name: texture.Name, + Description: texture.Description, + Type: types.TextureType(texture.Type), + URL: texture.URL, + Hash: texture.Hash, + Size: texture.Size, + IsPublic: texture.IsPublic, + DownloadCount: texture.DownloadCount, + FavoriteCount: texture.FavoriteCount, + IsSlim: texture.IsSlim, + Status: texture.Status, + CreatedAt: texture.CreatedAt, + UpdatedAt: texture.UpdatedAt, } } diff --git a/internal/repository/texture_repository.go b/internal/repository/texture_repository.go index d062d50..6382d31 100644 --- a/internal/repository/texture_repository.go +++ b/internal/repository/texture_repository.go @@ -29,13 +29,13 @@ func (r *textureRepository) FindByID(ctx context.Context, id int64) (*model.Text func (r *textureRepository) FindByHash(ctx context.Context, hash string) (*model.Texture, error) { var texture model.Texture - err := r.db.WithContext(ctx).Where("hash = ?", hash).First(&texture).Error + err := r.db.WithContext(ctx).Preload("Uploader").Where("hash = ?", hash).First(&texture).Error return handleNotFoundResult(&texture, err) } func (r *textureRepository) FindByHashAndUploaderID(ctx context.Context, hash string, uploaderID int64) (*model.Texture, error) { var texture model.Texture - err := r.db.WithContext(ctx).Where("hash = ? AND uploader_id = ?", hash, uploaderID).First(&texture).Error + err := r.db.WithContext(ctx).Preload("Uploader").Where("hash = ? AND uploader_id = ?", hash, uploaderID).First(&texture).Error return handleNotFoundResult(&texture, err) } diff --git a/internal/service/texture_service.go b/internal/service/texture_service.go index 0f92d41..c6690dd 100644 --- a/internal/service/texture_service.go +++ b/internal/service/texture_service.go @@ -55,6 +55,22 @@ func (s *textureService) GetByID(ctx context.Context, id int64) (*model.Texture, if texture.Status == -1 { return nil, errors.New("材质已删除") } + // 如果缓存中没有 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 + } return &texture, nil } @@ -365,7 +381,8 @@ func (s *textureService) UploadTexture(ctx context.Context, uploaderID int64, na // 清除用户的 texture 列表缓存(所有分页) s.cacheInv.BatchInvalidate(ctx, fmt.Sprintf("texture:user:%d:*", uploaderID)) - return texture, nil + // 重新查询以预加载 Uploader 关联 + return s.textureRepo.FindByID(ctx, texture.ID) } // parseTextureTypeInternal 解析材质类型 diff --git a/internal/types/common.go b/internal/types/common.go index 38fc5bf..dc0fc50 100644 --- a/internal/types/common.go +++ b/internal/types/common.go @@ -121,21 +121,22 @@ const ( // TextureInfo 材质信息 // @Description 材质详细信息 type TextureInfo struct { - ID int64 `json:"id" example:"1"` - UploaderID int64 `json:"uploader_id" example:"1"` - Name string `json:"name" example:"My Skin"` - Description string `json:"description,omitempty" example:"A cool skin"` - Type TextureType `json:"type" example:"SKIN"` - URL string `json:"url" example:"https://rustfs.example.com/textures/xxx.png"` - Hash string `json:"hash" example:"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"` - Size int `json:"size" example:"2048"` - IsPublic bool `json:"is_public" example:"true"` - DownloadCount int `json:"download_count" example:"100"` - FavoriteCount int `json:"favorite_count" example:"50"` - IsSlim bool `json:"is_slim" example:"false"` - Status int16 `json:"status" example:"1"` - CreatedAt time.Time `json:"created_at" example:"2025-10-01T10:00:00Z"` - UpdatedAt time.Time `json:"updated_at" example:"2025-10-01T10:00:00Z"` + ID int64 `json:"id" example:"1"` + UploaderID int64 `json:"uploader_id" example:"1"` + UploaderUsername string `json:"uploader_username" example:"testuser"` + Name string `json:"name" example:"My Skin"` + Description string `json:"description,omitempty" example:"A cool skin"` + Type TextureType `json:"type" example:"SKIN"` + URL string `json:"url" example:"https://rustfs.example.com/textures/xxx.png"` + Hash string `json:"hash" example:"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"` + Size int `json:"size" example:"2048"` + IsPublic bool `json:"is_public" example:"true"` + DownloadCount int `json:"download_count" example:"100"` + FavoriteCount int `json:"favorite_count" example:"50"` + IsSlim bool `json:"is_slim" example:"false"` + Status int16 `json:"status" example:"1"` + CreatedAt time.Time `json:"created_at" example:"2025-10-01T10:00:00Z"` + UpdatedAt time.Time `json:"updated_at" example:"2025-10-01T10:00:00Z"` } // ProfileInfo 角色信息 diff --git a/pkg/config/config.go b/pkg/config/config.go index 48fba01..a1c27de 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -131,13 +131,14 @@ type SecurityConfig struct { // Load 加载配置 - 完全从环境变量加载,不依赖YAML文件 func Load() (*Config, error) { // 加载.env文件(如果存在) - _ = godotenv.Load(".env") + if err := godotenv.Load(".env"); err != nil { + fmt.Printf("警告: 无法加载 .env 文件: %v\n", err) + } // 设置默认值 setDefaults() - // 设置环境变量前缀 - viper.SetEnvPrefix("CARROTSKIN") + // 自动读取环境变量(不设置前缀,因为 BindEnv 已经明确指定了变量名) viper.AutomaticEnv() // 手动设置环境变量映射 @@ -302,6 +303,7 @@ func setupEnvMappings() { // overrideFromEnv 从环境变量中覆盖配置 func overrideFromEnv(config *Config) { + // 处理RustFS存储桶配置 if texturesBucket := os.Getenv("RUSTFS_BUCKET_TEXTURES"); texturesBucket != "" { if config.RustFS.Buckets == nil { @@ -342,6 +344,24 @@ func overrideFromEnv(config *Config) { } } + // 处理Redis基本配置 + if host := os.Getenv("REDIS_HOST"); host != "" { + config.Redis.Host = host + } + if port := os.Getenv("REDIS_PORT"); port != "" { + if val, err := strconv.Atoi(port); err == nil { + config.Redis.Port = val + } + } + if password := os.Getenv("REDIS_PASSWORD"); password != "" { + config.Redis.Password = password + } + if database := os.Getenv("REDIS_DATABASE"); database != "" { + if val, err := strconv.Atoi(database); err == nil { + config.Redis.Database = val + } + } + // 处理Redis连接池配置 if poolSize := os.Getenv("REDIS_POOL_SIZE"); poolSize != "" { if val, err := strconv.Atoi(poolSize); err == nil { diff --git a/pkg/storage/minio_test.go b/pkg/storage/minio_test.go index 6f3c32d..ea2d562 100644 --- a/pkg/storage/minio_test.go +++ b/pkg/storage/minio_test.go @@ -1,9 +1,7 @@ package storage import ( - "context" "testing" - "time" "carrotskin/pkg/config" @@ -41,4 +39,3 @@ func TestNewStorage_SkipConnectWhenNoCreds(t *testing.T) { t.Fatalf("NewStorage should not error when creds empty: %v", err) } } -