refactor: 重构服务层和仓库层

This commit is contained in:
lan
2025-12-03 10:58:39 +08:00
parent 034e02e93a
commit e873c58af9
32 changed files with 1445 additions and 5213 deletions

View File

@@ -47,15 +47,23 @@ type DatabaseConfig struct {
MaxIdleConns int `mapstructure:"max_idle_conns"`
MaxOpenConns int `mapstructure:"max_open_conns"`
ConnMaxLifetime time.Duration `mapstructure:"conn_max_lifetime"`
ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"` // 连接最大空闲时间
}
// RedisConfig Redis配置
type RedisConfig struct {
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Password string `mapstructure:"password"`
Database int `mapstructure:"database"`
PoolSize int `mapstructure:"pool_size"`
Host string `mapstructure:"host"`
Port int `mapstructure:"port"`
Password string `mapstructure:"password"`
Database int `mapstructure:"database"`
PoolSize int `mapstructure:"pool_size"` // 连接池大小
MinIdleConns int `mapstructure:"min_idle_conns"` // 最小空闲连接数
MaxRetries int `mapstructure:"max_retries"` // 最大重试次数
DialTimeout time.Duration `mapstructure:"dial_timeout"` // 连接超时
ReadTimeout time.Duration `mapstructure:"read_timeout"` // 读取超时
WriteTimeout time.Duration `mapstructure:"write_timeout"` // 写入超时
PoolTimeout time.Duration `mapstructure:"pool_timeout"` // 连接池超时
ConnMaxIdleTime time.Duration `mapstructure:"conn_max_idle_time"` // 连接最大空闲时间
}
// RustFSConfig RustFS对象存储配置 (S3兼容)
@@ -159,12 +167,20 @@ func setDefaults() {
viper.SetDefault("database.max_idle_conns", 10)
viper.SetDefault("database.max_open_conns", 100)
viper.SetDefault("database.conn_max_lifetime", "1h")
viper.SetDefault("database.conn_max_idle_time", "10m")
// Redis默认配置
viper.SetDefault("redis.host", "localhost")
viper.SetDefault("redis.port", 6379)
viper.SetDefault("redis.database", 0)
viper.SetDefault("redis.pool_size", 10)
viper.SetDefault("redis.min_idle_conns", 5)
viper.SetDefault("redis.max_retries", 3)
viper.SetDefault("redis.dial_timeout", "5s")
viper.SetDefault("redis.read_timeout", "3s")
viper.SetDefault("redis.write_timeout", "3s")
viper.SetDefault("redis.pool_timeout", "4s")
viper.SetDefault("redis.conn_max_idle_time", "30m")
// RustFS默认配置
viper.SetDefault("rustfs.endpoint", "127.0.0.1:9000")
@@ -219,12 +235,24 @@ func setupEnvMappings() {
viper.BindEnv("database.database", "DATABASE_NAME")
viper.BindEnv("database.ssl_mode", "DATABASE_SSL_MODE")
viper.BindEnv("database.timezone", "DATABASE_TIMEZONE")
viper.BindEnv("database.max_idle_conns", "DATABASE_MAX_IDLE_CONNS")
viper.BindEnv("database.max_open_conns", "DATABASE_MAX_OPEN_CONNS")
viper.BindEnv("database.conn_max_lifetime", "DATABASE_CONN_MAX_LIFETIME")
viper.BindEnv("database.conn_max_idle_time", "DATABASE_CONN_MAX_IDLE_TIME")
// Redis配置
viper.BindEnv("redis.host", "REDIS_HOST")
viper.BindEnv("redis.port", "REDIS_PORT")
viper.BindEnv("redis.password", "REDIS_PASSWORD")
viper.BindEnv("redis.database", "REDIS_DATABASE")
viper.BindEnv("redis.pool_size", "REDIS_POOL_SIZE")
viper.BindEnv("redis.min_idle_conns", "REDIS_MIN_IDLE_CONNS")
viper.BindEnv("redis.max_retries", "REDIS_MAX_RETRIES")
viper.BindEnv("redis.dial_timeout", "REDIS_DIAL_TIMEOUT")
viper.BindEnv("redis.read_timeout", "REDIS_READ_TIMEOUT")
viper.BindEnv("redis.write_timeout", "REDIS_WRITE_TIMEOUT")
viper.BindEnv("redis.pool_timeout", "REDIS_POOL_TIMEOUT")
viper.BindEnv("redis.conn_max_idle_time", "REDIS_CONN_MAX_IDLE_TIME")
// RustFS配置
viper.BindEnv("rustfs.endpoint", "RUSTFS_ENDPOINT")
@@ -287,13 +315,61 @@ func overrideFromEnv(config *Config) {
}
}
// 处理Redis池大小
if connMaxIdleTime := os.Getenv("DATABASE_CONN_MAX_IDLE_TIME"); connMaxIdleTime != "" {
if val, err := time.ParseDuration(connMaxIdleTime); err == nil {
config.Database.ConnMaxIdleTime = val
}
}
// 处理Redis连接池配置
if poolSize := os.Getenv("REDIS_POOL_SIZE"); poolSize != "" {
if val, err := strconv.Atoi(poolSize); err == nil {
config.Redis.PoolSize = val
}
}
if minIdleConns := os.Getenv("REDIS_MIN_IDLE_CONNS"); minIdleConns != "" {
if val, err := strconv.Atoi(minIdleConns); err == nil {
config.Redis.MinIdleConns = val
}
}
if maxRetries := os.Getenv("REDIS_MAX_RETRIES"); maxRetries != "" {
if val, err := strconv.Atoi(maxRetries); err == nil {
config.Redis.MaxRetries = val
}
}
if dialTimeout := os.Getenv("REDIS_DIAL_TIMEOUT"); dialTimeout != "" {
if val, err := time.ParseDuration(dialTimeout); err == nil {
config.Redis.DialTimeout = val
}
}
if readTimeout := os.Getenv("REDIS_READ_TIMEOUT"); readTimeout != "" {
if val, err := time.ParseDuration(readTimeout); err == nil {
config.Redis.ReadTimeout = val
}
}
if writeTimeout := os.Getenv("REDIS_WRITE_TIMEOUT"); writeTimeout != "" {
if val, err := time.ParseDuration(writeTimeout); err == nil {
config.Redis.WriteTimeout = val
}
}
if poolTimeout := os.Getenv("REDIS_POOL_TIMEOUT"); poolTimeout != "" {
if val, err := time.ParseDuration(poolTimeout); err == nil {
config.Redis.PoolTimeout = val
}
}
if connMaxIdleTime := os.Getenv("REDIS_CONN_MAX_IDLE_TIME"); connMaxIdleTime != "" {
if val, err := time.ParseDuration(connMaxIdleTime); err == nil {
config.Redis.ConnMaxIdleTime = val
}
}
// 处理文件上传配置
if maxSize := os.Getenv("UPLOAD_MAX_SIZE"); maxSize != "" {
if val, err := strconv.ParseInt(maxSize, 10, 64); err == nil {

View File

@@ -296,6 +296,11 @@ func (b *CacheKeyBuilder) Texture(textureID int64) string {
return fmt.Sprintf("%stexture:id:%d", b.prefix, textureID)
}
// TextureByHash 构建材质hash缓存键
func (b *CacheKeyBuilder) TextureByHash(hash string) string {
return fmt.Sprintf("%stexture:hash:%s", b.prefix, hash)
}
// TextureList 构建材质列表缓存键
func (b *CacheKeyBuilder) TextureList(userID int64, page int) string {
return fmt.Sprintf("%stexture:user:%d:page:%d", b.prefix, userID, page)

View File

@@ -69,10 +69,15 @@ func New(cfg config.DatabaseConfig) (*gorm.DB, error) {
connMaxLifetime = 1 * time.Hour
}
connMaxIdleTime := cfg.ConnMaxIdleTime
if connMaxIdleTime <= 0 {
connMaxIdleTime = 10 * time.Minute
}
sqlDB.SetMaxIdleConns(maxIdleConns)
sqlDB.SetMaxOpenConns(maxOpenConns)
sqlDB.SetConnMaxLifetime(connMaxLifetime)
sqlDB.SetConnMaxIdleTime(10 * time.Minute)
sqlDB.SetConnMaxIdleTime(connMaxIdleTime)
// 测试连接
if err := sqlDB.Ping(); err != nil {

View File

@@ -20,15 +20,60 @@ type Client struct {
// New 创建Redis客户端
func New(cfg config.RedisConfig, logger *zap.Logger) (*Client, error) {
// 设置默认值
poolSize := cfg.PoolSize
if poolSize <= 0 {
poolSize = 10
}
minIdleConns := cfg.MinIdleConns
if minIdleConns <= 0 {
minIdleConns = 5
}
maxRetries := cfg.MaxRetries
if maxRetries <= 0 {
maxRetries = 3
}
dialTimeout := cfg.DialTimeout
if dialTimeout <= 0 {
dialTimeout = 5 * time.Second
}
readTimeout := cfg.ReadTimeout
if readTimeout <= 0 {
readTimeout = 3 * time.Second
}
writeTimeout := cfg.WriteTimeout
if writeTimeout <= 0 {
writeTimeout = 3 * time.Second
}
poolTimeout := cfg.PoolTimeout
if poolTimeout <= 0 {
poolTimeout = 4 * time.Second
}
connMaxIdleTime := cfg.ConnMaxIdleTime
if connMaxIdleTime <= 0 {
connMaxIdleTime = 30 * time.Minute
}
// 创建Redis客户端
rdb := redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
Password: cfg.Password,
DB: cfg.Database,
PoolSize: cfg.PoolSize,
DialTimeout: 5 * time.Second,
ReadTimeout: 3 * time.Second,
WriteTimeout: 3 * time.Second,
Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
Password: cfg.Password,
DB: cfg.Database,
PoolSize: poolSize,
MinIdleConns: minIdleConns,
MaxRetries: maxRetries,
DialTimeout: dialTimeout,
ReadTimeout: readTimeout,
WriteTimeout: writeTimeout,
PoolTimeout: poolTimeout,
ConnMaxIdleTime: connMaxIdleTime,
})
// 测试连接

View File

@@ -3,6 +3,9 @@ package storage
import (
"context"
"fmt"
"io"
"net/url"
"strings"
"time"
"carrotskin/pkg/config"
@@ -137,3 +140,64 @@ func (s *StorageClient) BuildFileURL(bucketName, objectName string) string {
func (s *StorageClient) GetPublicURL() string {
return s.publicURL
}
// ObjectInfo 对象信息
type ObjectInfo struct {
Size int64
LastModified time.Time
ContentType string
ETag string
}
// GetObject 获取对象内容和信息
func (s *StorageClient) GetObject(ctx context.Context, bucketName, objectName string) (io.ReadCloser, *ObjectInfo, error) {
obj, err := s.client.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
if err != nil {
return nil, nil, fmt.Errorf("获取对象失败: %w", err)
}
stat, err := obj.Stat()
if err != nil {
obj.Close()
return nil, nil, fmt.Errorf("获取对象信息失败: %w", err)
}
info := &ObjectInfo{
Size: stat.Size,
LastModified: stat.LastModified,
ContentType: stat.ContentType,
ETag: stat.ETag,
}
return obj, info, nil
}
// ParseFileURL 从文件URL中解析出bucket和objectName
// URL格式: {publicURL}/{bucket}/{objectName}
func (s *StorageClient) ParseFileURL(fileURL string) (bucket, objectName string, err error) {
// 移除 publicURL 前缀
if !strings.HasPrefix(fileURL, s.publicURL) {
return "", "", fmt.Errorf("URL格式不正确必须以 %s 开头", s.publicURL)
}
// 移除 publicURL 前缀和开头的 /
path := strings.TrimPrefix(fileURL, s.publicURL)
path = strings.TrimPrefix(path, "/")
// 解析路径
parts := strings.SplitN(path, "/", 2)
if len(parts) < 2 {
return "", "", fmt.Errorf("URL格式不正确无法解析bucket和objectName")
}
bucket = parts[0]
objectName = parts[1]
// URL解码 objectName
decoded, err := url.PathUnescape(objectName)
if err == nil {
objectName = decoded
}
return bucket, objectName, nil
}