refactor: introduce Wire dependency injection and interface-based architecture
- Add Google Wire for compile-time dependency injection - Replace concrete service types with interfaces across handlers - Remove global state (DB, cache) in favor of constructor injection - Split monolithic files into focused modules: - config: separate files for each config domain - dto: converters split by domain (user, post, message, group) - cache: separate metrics.go and redis_cache.go - Introduce unified apperrors package with string-based error codes - Add transaction support with context-aware repository methods - Upgrade golang.org/x/crypto from 0.17.0 to 0.26.0
This commit is contained in:
70
internal/config/cache.go
Normal file
70
internal/config/cache.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
// ConversationCacheConfig 会话缓存配置
|
||||
type ConversationCacheConfig struct {
|
||||
// TTL 配置
|
||||
DetailTTL string `mapstructure:"detail_ttl"`
|
||||
ListTTL string `mapstructure:"list_ttl"`
|
||||
ParticipantTTL string `mapstructure:"participant_ttl"`
|
||||
UnreadTTL string `mapstructure:"unread_ttl"`
|
||||
|
||||
// 消息缓存配置
|
||||
MessageDetailTTL string `mapstructure:"message_detail_ttl"`
|
||||
MessageListTTL string `mapstructure:"message_list_ttl"`
|
||||
MessageIndexTTL string `mapstructure:"message_index_ttl"`
|
||||
MessageCountTTL string `mapstructure:"message_count_ttl"`
|
||||
|
||||
// 批量写入配置
|
||||
BatchInterval string `mapstructure:"batch_interval"`
|
||||
BatchThreshold int `mapstructure:"batch_threshold"`
|
||||
BatchMaxSize int `mapstructure:"batch_max_size"`
|
||||
BufferMaxSize int `mapstructure:"buffer_max_size"`
|
||||
}
|
||||
|
||||
// ConversationCacheSettings 会话缓存运行时配置(用于传递给 cache 包)
|
||||
type ConversationCacheSettings struct {
|
||||
DetailTTL time.Duration
|
||||
ListTTL time.Duration
|
||||
ParticipantTTL time.Duration
|
||||
UnreadTTL time.Duration
|
||||
MessageDetailTTL time.Duration
|
||||
MessageListTTL time.Duration
|
||||
MessageIndexTTL time.Duration
|
||||
MessageCountTTL time.Duration
|
||||
BatchInterval time.Duration
|
||||
BatchThreshold int
|
||||
BatchMaxSize int
|
||||
BufferMaxSize int
|
||||
}
|
||||
|
||||
// ToSettings 将 ConversationCacheConfig 转换为 ConversationCacheSettings
|
||||
func (c *ConversationCacheConfig) ToSettings() *ConversationCacheSettings {
|
||||
return &ConversationCacheSettings{
|
||||
DetailTTL: parseDuration(c.DetailTTL, 5*time.Minute),
|
||||
ListTTL: parseDuration(c.ListTTL, 60*time.Second),
|
||||
ParticipantTTL: parseDuration(c.ParticipantTTL, 5*time.Minute),
|
||||
UnreadTTL: parseDuration(c.UnreadTTL, 30*time.Second),
|
||||
MessageDetailTTL: parseDuration(c.MessageDetailTTL, 30*time.Minute),
|
||||
MessageListTTL: parseDuration(c.MessageListTTL, 5*time.Minute),
|
||||
MessageIndexTTL: parseDuration(c.MessageIndexTTL, 30*time.Minute),
|
||||
MessageCountTTL: parseDuration(c.MessageCountTTL, 30*time.Minute),
|
||||
BatchInterval: parseDuration(c.BatchInterval, 5*time.Second),
|
||||
BatchThreshold: c.BatchThreshold,
|
||||
BatchMaxSize: c.BatchMaxSize,
|
||||
BufferMaxSize: c.BufferMaxSize,
|
||||
}
|
||||
}
|
||||
|
||||
// parseDuration 解析持续时间字符串,如果解析失败则返回默认值
|
||||
func parseDuration(s string, defaultVal time.Duration) time.Duration {
|
||||
if s == "" {
|
||||
return defaultVal
|
||||
}
|
||||
d, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return defaultVal
|
||||
}
|
||||
return d
|
||||
}
|
||||
@@ -1,19 +1,16 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Config 主配置结构体
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Database DatabaseConfig `mapstructure:"database"`
|
||||
@@ -30,217 +27,7 @@ type Config struct {
|
||||
ConversationCache ConversationCacheConfig `mapstructure:"conversation_cache"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Mode string `mapstructure:"mode"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
Type string `mapstructure:"type"`
|
||||
SQLite SQLiteConfig `mapstructure:"sqlite"`
|
||||
Postgres PostgresConfig `mapstructure:"postgres"`
|
||||
MaxIdleConns int `mapstructure:"max_idle_conns"`
|
||||
MaxOpenConns int `mapstructure:"max_open_conns"`
|
||||
LogLevel string `mapstructure:"log_level"`
|
||||
SlowThresholdMs int `mapstructure:"slow_threshold_ms"`
|
||||
IgnoreRecordNotFound bool `mapstructure:"ignore_record_not_found"`
|
||||
ParameterizedQueries bool `mapstructure:"parameterized_queries"`
|
||||
}
|
||||
|
||||
type SQLiteConfig struct {
|
||||
Path string `mapstructure:"path"`
|
||||
}
|
||||
|
||||
type PostgresConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
DBName string `mapstructure:"dbname"`
|
||||
SSLMode string `mapstructure:"sslmode"`
|
||||
}
|
||||
|
||||
func (d PostgresConfig) DSN() string {
|
||||
return fmt.Sprintf(
|
||||
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||
d.Host, d.Port, d.User, d.Password, d.DBName, d.SSLMode,
|
||||
)
|
||||
}
|
||||
|
||||
type RedisConfig struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Redis RedisServerConfig `mapstructure:"redis"`
|
||||
Miniredis MiniredisConfig `mapstructure:"miniredis"`
|
||||
PoolSize int `mapstructure:"pool_size"`
|
||||
}
|
||||
|
||||
type CacheConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
KeyPrefix string `mapstructure:"key_prefix"`
|
||||
DefaultTTL int `mapstructure:"default_ttl"`
|
||||
NullTTL int `mapstructure:"null_ttl"`
|
||||
JitterRatio float64 `mapstructure:"jitter_ratio"`
|
||||
DisableFlushDB bool `mapstructure:"disable_flushdb"`
|
||||
Modules CacheModuleTTL `mapstructure:"modules"`
|
||||
}
|
||||
|
||||
type CacheModuleTTL struct {
|
||||
PostList int `mapstructure:"post_list_ttl"`
|
||||
Conversation int `mapstructure:"conversation_ttl"`
|
||||
UnreadCount int `mapstructure:"unread_count_ttl"`
|
||||
GroupMembers int `mapstructure:"group_members_ttl"`
|
||||
}
|
||||
|
||||
type RedisServerConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Password string `mapstructure:"password"`
|
||||
DB int `mapstructure:"db"`
|
||||
}
|
||||
|
||||
func (r RedisServerConfig) Addr() string {
|
||||
return fmt.Sprintf("%s:%d", r.Host, r.Port)
|
||||
}
|
||||
|
||||
type MiniredisConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
}
|
||||
|
||||
type S3Config struct {
|
||||
Endpoint string `mapstructure:"endpoint"`
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
Bucket string `mapstructure:"bucket"`
|
||||
UseSSL bool `mapstructure:"use_ssl"`
|
||||
Region string `mapstructure:"region"`
|
||||
Domain string `mapstructure:"domain"` // 自定义域名,如 s3.carrot.skin
|
||||
}
|
||||
|
||||
type JWTConfig struct {
|
||||
Secret string `mapstructure:"secret"`
|
||||
AccessTokenExpire time.Duration `mapstructure:"access_token_expire"`
|
||||
RefreshTokenExpire time.Duration `mapstructure:"refresh_token_expire"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Level string `mapstructure:"level"`
|
||||
Encoding string `mapstructure:"encoding"`
|
||||
OutputPaths []string `mapstructure:"output_paths"`
|
||||
}
|
||||
|
||||
type RateLimitConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
RequestsPerMinute int `mapstructure:"requests_per_minute"`
|
||||
}
|
||||
|
||||
type UploadConfig struct {
|
||||
MaxFileSize int64 `mapstructure:"max_file_size"`
|
||||
AllowedTypes []string `mapstructure:"allowed_types"`
|
||||
}
|
||||
|
||||
type GorseConfig struct {
|
||||
Address string `mapstructure:"address"`
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Dashboard string `mapstructure:"dashboard"`
|
||||
ImportPassword string `mapstructure:"import_password"`
|
||||
EmbeddingAPIKey string `mapstructure:"embedding_api_key"`
|
||||
EmbeddingURL string `mapstructure:"embedding_url"`
|
||||
EmbeddingModel string `mapstructure:"embedding_model"`
|
||||
}
|
||||
|
||||
type OpenAIConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
BaseURL string `mapstructure:"base_url"`
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
ModerationModel string `mapstructure:"moderation_model"`
|
||||
ModerationMaxImagesPerRequest int `mapstructure:"moderation_max_images_per_request"`
|
||||
RequestTimeout int `mapstructure:"request_timeout"`
|
||||
StrictModeration bool `mapstructure:"strict_moderation"`
|
||||
}
|
||||
|
||||
type EmailConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
FromAddress string `mapstructure:"from_address"`
|
||||
FromName string `mapstructure:"from_name"`
|
||||
UseTLS bool `mapstructure:"use_tls"`
|
||||
InsecureSkipVerify bool `mapstructure:"insecure_skip_verify"`
|
||||
Timeout int `mapstructure:"timeout"`
|
||||
}
|
||||
|
||||
// ConversationCacheConfig 会话缓存配置
|
||||
type ConversationCacheConfig struct {
|
||||
// TTL 配置
|
||||
DetailTTL string `mapstructure:"detail_ttl"`
|
||||
ListTTL string `mapstructure:"list_ttl"`
|
||||
ParticipantTTL string `mapstructure:"participant_ttl"`
|
||||
UnreadTTL string `mapstructure:"unread_ttl"`
|
||||
|
||||
// 消息缓存配置
|
||||
MessageDetailTTL string `mapstructure:"message_detail_ttl"`
|
||||
MessageListTTL string `mapstructure:"message_list_ttl"`
|
||||
MessageIndexTTL string `mapstructure:"message_index_ttl"`
|
||||
MessageCountTTL string `mapstructure:"message_count_ttl"`
|
||||
|
||||
// 批量写入配置
|
||||
BatchInterval string `mapstructure:"batch_interval"`
|
||||
BatchThreshold int `mapstructure:"batch_threshold"`
|
||||
BatchMaxSize int `mapstructure:"batch_max_size"`
|
||||
BufferMaxSize int `mapstructure:"buffer_max_size"`
|
||||
}
|
||||
|
||||
// ConversationCacheSettings 会话缓存运行时配置(用于传递给 cache 包)
|
||||
type ConversationCacheSettings struct {
|
||||
DetailTTL time.Duration
|
||||
ListTTL time.Duration
|
||||
ParticipantTTL time.Duration
|
||||
UnreadTTL time.Duration
|
||||
MessageDetailTTL time.Duration
|
||||
MessageListTTL time.Duration
|
||||
MessageIndexTTL time.Duration
|
||||
MessageCountTTL time.Duration
|
||||
BatchInterval time.Duration
|
||||
BatchThreshold int
|
||||
BatchMaxSize int
|
||||
BufferMaxSize int
|
||||
}
|
||||
|
||||
// ToSettings 将 ConversationCacheConfig 转换为 ConversationCacheSettings
|
||||
func (c *ConversationCacheConfig) ToSettings() *ConversationCacheSettings {
|
||||
return &ConversationCacheSettings{
|
||||
DetailTTL: parseDuration(c.DetailTTL, 5*time.Minute),
|
||||
ListTTL: parseDuration(c.ListTTL, 60*time.Second),
|
||||
ParticipantTTL: parseDuration(c.ParticipantTTL, 5*time.Minute),
|
||||
UnreadTTL: parseDuration(c.UnreadTTL, 30*time.Second),
|
||||
MessageDetailTTL: parseDuration(c.MessageDetailTTL, 30*time.Minute),
|
||||
MessageListTTL: parseDuration(c.MessageListTTL, 5*time.Minute),
|
||||
MessageIndexTTL: parseDuration(c.MessageIndexTTL, 30*time.Minute),
|
||||
MessageCountTTL: parseDuration(c.MessageCountTTL, 30*time.Minute),
|
||||
BatchInterval: parseDuration(c.BatchInterval, 5*time.Second),
|
||||
BatchThreshold: c.BatchThreshold,
|
||||
BatchMaxSize: c.BatchMaxSize,
|
||||
BufferMaxSize: c.BufferMaxSize,
|
||||
}
|
||||
}
|
||||
|
||||
// parseDuration 解析持续时间字符串,如果解析失败则返回默认值
|
||||
func parseDuration(s string, defaultVal time.Duration) time.Duration {
|
||||
if s == "" {
|
||||
return defaultVal
|
||||
}
|
||||
d, err := time.ParseDuration(s)
|
||||
if err != nil {
|
||||
return defaultVal
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// Load 加载配置文件
|
||||
func Load(configPath string) (*Config, error) {
|
||||
viper.SetConfigFile(configPath)
|
||||
viper.SetConfigType("yaml")
|
||||
@@ -426,49 +213,3 @@ func getEnvOrDefault(key, defaultValue string) string {
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// NewRedis 创建Redis客户端(真实Redis)
|
||||
func NewRedis(cfg *RedisConfig) (*redis.Client, error) {
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: cfg.Redis.Addr(),
|
||||
Password: cfg.Redis.Password,
|
||||
DB: cfg.Redis.DB,
|
||||
PoolSize: cfg.PoolSize,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to redis: %w", err)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// NewS3 创建S3客户端
|
||||
func NewS3(cfg *S3Config) (*minio.Client, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
client, err := minio.New(cfg.Endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""),
|
||||
Secure: cfg.UseSSL,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create S3 client: %w", err)
|
||||
}
|
||||
|
||||
exists, err := client.BucketExists(ctx, cfg.Bucket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check bucket: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
if err := client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{
|
||||
Region: cfg.Region,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("failed to create bucket: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
39
internal/config/database.go
Normal file
39
internal/config/database.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package config
|
||||
|
||||
import "fmt"
|
||||
|
||||
// DatabaseConfig 数据库配置
|
||||
type DatabaseConfig struct {
|
||||
Type string `mapstructure:"type"`
|
||||
SQLite SQLiteConfig `mapstructure:"sqlite"`
|
||||
Postgres PostgresConfig `mapstructure:"postgres"`
|
||||
MaxIdleConns int `mapstructure:"max_idle_conns"`
|
||||
MaxOpenConns int `mapstructure:"max_open_conns"`
|
||||
LogLevel string `mapstructure:"log_level"`
|
||||
SlowThresholdMs int `mapstructure:"slow_threshold_ms"`
|
||||
IgnoreRecordNotFound bool `mapstructure:"ignore_record_not_found"`
|
||||
ParameterizedQueries bool `mapstructure:"parameterized_queries"`
|
||||
}
|
||||
|
||||
// SQLiteConfig SQLite 配置
|
||||
type SQLiteConfig struct {
|
||||
Path string `mapstructure:"path"`
|
||||
}
|
||||
|
||||
// PostgresConfig PostgreSQL 配置
|
||||
type PostgresConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
DBName string `mapstructure:"dbname"`
|
||||
SSLMode string `mapstructure:"sslmode"`
|
||||
}
|
||||
|
||||
// DSN 返回 PostgreSQL 连接字符串
|
||||
func (d PostgresConfig) DSN() string {
|
||||
return fmt.Sprintf(
|
||||
"host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||
d.Host, d.Port, d.User, d.Password, d.DBName, d.SSLMode,
|
||||
)
|
||||
}
|
||||
15
internal/config/email.go
Normal file
15
internal/config/email.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package config
|
||||
|
||||
// EmailConfig 邮件配置
|
||||
type EmailConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
FromAddress string `mapstructure:"from_address"`
|
||||
FromName string `mapstructure:"from_name"`
|
||||
UseTLS bool `mapstructure:"use_tls"`
|
||||
InsecureSkipVerify bool `mapstructure:"insecure_skip_verify"`
|
||||
Timeout int `mapstructure:"timeout"`
|
||||
}
|
||||
24
internal/config/external.go
Normal file
24
internal/config/external.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package config
|
||||
|
||||
// GorseConfig Gorse 推荐系统配置
|
||||
type GorseConfig struct {
|
||||
Address string `mapstructure:"address"`
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Dashboard string `mapstructure:"dashboard"`
|
||||
ImportPassword string `mapstructure:"import_password"`
|
||||
EmbeddingAPIKey string `mapstructure:"embedding_api_key"`
|
||||
EmbeddingURL string `mapstructure:"embedding_url"`
|
||||
EmbeddingModel string `mapstructure:"embedding_model"`
|
||||
}
|
||||
|
||||
// OpenAIConfig OpenAI 配置
|
||||
type OpenAIConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
BaseURL string `mapstructure:"base_url"`
|
||||
APIKey string `mapstructure:"api_key"`
|
||||
ModerationModel string `mapstructure:"moderation_model"`
|
||||
ModerationMaxImagesPerRequest int `mapstructure:"moderation_max_images_per_request"`
|
||||
RequestTimeout int `mapstructure:"request_timeout"`
|
||||
StrictModeration bool `mapstructure:"strict_moderation"`
|
||||
}
|
||||
10
internal/config/jwt.go
Normal file
10
internal/config/jwt.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
// JWTConfig JWT 配置
|
||||
type JWTConfig struct {
|
||||
Secret string `mapstructure:"secret"`
|
||||
AccessTokenExpire time.Duration `mapstructure:"access_token_expire"`
|
||||
RefreshTokenExpire time.Duration `mapstructure:"refresh_token_expire"`
|
||||
}
|
||||
71
internal/config/redis.go
Normal file
71
internal/config/redis.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
// RedisConfig Redis 配置
|
||||
type RedisConfig struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Redis RedisServerConfig `mapstructure:"redis"`
|
||||
Miniredis MiniredisConfig `mapstructure:"miniredis"`
|
||||
PoolSize int `mapstructure:"pool_size"`
|
||||
}
|
||||
|
||||
// CacheConfig 缓存配置
|
||||
type CacheConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
KeyPrefix string `mapstructure:"key_prefix"`
|
||||
DefaultTTL int `mapstructure:"default_ttl"`
|
||||
NullTTL int `mapstructure:"null_ttl"`
|
||||
JitterRatio float64 `mapstructure:"jitter_ratio"`
|
||||
DisableFlushDB bool `mapstructure:"disable_flushdb"`
|
||||
Modules CacheModuleTTL `mapstructure:"modules"`
|
||||
}
|
||||
|
||||
// CacheModuleTTL 缓存模块 TTL 配置
|
||||
type CacheModuleTTL struct {
|
||||
PostList int `mapstructure:"post_list_ttl"`
|
||||
Conversation int `mapstructure:"conversation_ttl"`
|
||||
UnreadCount int `mapstructure:"unread_count_ttl"`
|
||||
GroupMembers int `mapstructure:"group_members_ttl"`
|
||||
}
|
||||
|
||||
// RedisServerConfig Redis 服务器配置
|
||||
type RedisServerConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Password string `mapstructure:"password"`
|
||||
DB int `mapstructure:"db"`
|
||||
}
|
||||
|
||||
// Addr 返回 Redis 服务器地址
|
||||
func (r RedisServerConfig) Addr() string {
|
||||
return fmt.Sprintf("%s:%d", r.Host, r.Port)
|
||||
}
|
||||
|
||||
// MiniredisConfig Miniredis 配置(用于测试)
|
||||
type MiniredisConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
}
|
||||
|
||||
// NewRedis 创建 Redis 客户端(真实 Redis)
|
||||
func NewRedis(cfg *RedisConfig) (*redis.Client, error) {
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: cfg.Redis.Addr(),
|
||||
Password: cfg.Redis.Password,
|
||||
DB: cfg.Redis.DB,
|
||||
PoolSize: cfg.PoolSize,
|
||||
})
|
||||
|
||||
ctx := context.Background()
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to redis: %w", err)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
27
internal/config/server.go
Normal file
27
internal/config/server.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package config
|
||||
|
||||
// ServerConfig 服务器配置
|
||||
type ServerConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Mode string `mapstructure:"mode"`
|
||||
}
|
||||
|
||||
// LogConfig 日志配置
|
||||
type LogConfig struct {
|
||||
Level string `mapstructure:"level"`
|
||||
Encoding string `mapstructure:"encoding"`
|
||||
OutputPaths []string `mapstructure:"output_paths"`
|
||||
}
|
||||
|
||||
// RateLimitConfig 限流配置
|
||||
type RateLimitConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
RequestsPerMinute int `mapstructure:"requests_per_minute"`
|
||||
}
|
||||
|
||||
// UploadConfig 上传配置
|
||||
type UploadConfig struct {
|
||||
MaxFileSize int64 `mapstructure:"max_file_size"`
|
||||
AllowedTypes []string `mapstructure:"allowed_types"`
|
||||
}
|
||||
50
internal/config/storage.go
Normal file
50
internal/config/storage.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
)
|
||||
|
||||
// S3Config S3 存储配置
|
||||
type S3Config struct {
|
||||
Endpoint string `mapstructure:"endpoint"`
|
||||
AccessKey string `mapstructure:"access_key"`
|
||||
SecretKey string `mapstructure:"secret_key"`
|
||||
Bucket string `mapstructure:"bucket"`
|
||||
UseSSL bool `mapstructure:"use_ssl"`
|
||||
Region string `mapstructure:"region"`
|
||||
Domain string `mapstructure:"domain"` // 自定义域名,如 s3.carrot.skin
|
||||
}
|
||||
|
||||
// NewS3 创建 S3 客户端
|
||||
func NewS3(cfg *S3Config) (*minio.Client, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
defer cancel()
|
||||
|
||||
client, err := minio.New(cfg.Endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(cfg.AccessKey, cfg.SecretKey, ""),
|
||||
Secure: cfg.UseSSL,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create S3 client: %w", err)
|
||||
}
|
||||
|
||||
exists, err := client.BucketExists(ctx, cfg.Bucket)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check bucket: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
if err := client.MakeBucket(ctx, cfg.Bucket, minio.MakeBucketOptions{
|
||||
Region: cfg.Region,
|
||||
}); err != nil {
|
||||
return nil, fmt.Errorf("failed to create bucket: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
Reference in New Issue
Block a user