diff --git a/backend/internal/config/config.go b/backend/internal/config/config.go index 477cb59d..84be445b 100644 --- a/backend/internal/config/config.go +++ b/backend/internal/config/config.go @@ -415,6 +415,8 @@ type RedisConfig struct { PoolSize int `mapstructure:"pool_size"` // MinIdleConns: 最小空闲连接数,保持热连接减少冷启动延迟 MinIdleConns int `mapstructure:"min_idle_conns"` + // EnableTLS: 是否启用 TLS/SSL 连接 + EnableTLS bool `mapstructure:"enable_tls"` } func (r *RedisConfig) Address() string { @@ -762,6 +764,7 @@ func setDefaults() { viper.SetDefault("redis.write_timeout_seconds", 3) viper.SetDefault("redis.pool_size", 128) viper.SetDefault("redis.min_idle_conns", 10) + viper.SetDefault("redis.enable_tls", false) // Ops (vNext) viper.SetDefault("ops.enabled", true) diff --git a/backend/internal/repository/redis.go b/backend/internal/repository/redis.go index f3606ad9..2b4ee4e6 100644 --- a/backend/internal/repository/redis.go +++ b/backend/internal/repository/redis.go @@ -1,6 +1,7 @@ package repository import ( + "crypto/tls" "time" "github.com/Wei-Shaw/sub2api/internal/config" @@ -26,7 +27,7 @@ func InitRedis(cfg *config.Config) *redis.Client { // buildRedisOptions 构建 Redis 连接选项 // 从配置文件读取连接池和超时参数,支持生产环境调优 func buildRedisOptions(cfg *config.Config) *redis.Options { - return &redis.Options{ + opts := &redis.Options{ Addr: cfg.Redis.Address(), Password: cfg.Redis.Password, DB: cfg.Redis.DB, @@ -36,4 +37,13 @@ func buildRedisOptions(cfg *config.Config) *redis.Options { PoolSize: cfg.Redis.PoolSize, // 连接池大小 MinIdleConns: cfg.Redis.MinIdleConns, // 最小空闲连接 } + + if cfg.Redis.EnableTLS { + opts.TLSConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + ServerName: cfg.Redis.Host, + } + } + + return opts } diff --git a/backend/internal/repository/redis_test.go b/backend/internal/repository/redis_test.go index 756a63dc..7cb31002 100644 --- a/backend/internal/repository/redis_test.go +++ b/backend/internal/repository/redis_test.go @@ -32,4 +32,16 @@ func TestBuildRedisOptions(t *testing.T) { require.Equal(t, 4*time.Second, opts.WriteTimeout) require.Equal(t, 100, opts.PoolSize) require.Equal(t, 10, opts.MinIdleConns) + require.Nil(t, opts.TLSConfig) + + // Test case with TLS enabled + cfgTLS := &config.Config{ + Redis: config.RedisConfig{ + Host: "localhost", + EnableTLS: true, + }, + } + optsTLS := buildRedisOptions(cfgTLS) + require.NotNil(t, optsTLS.TLSConfig) + require.Equal(t, "localhost", optsTLS.TLSConfig.ServerName) } diff --git a/backend/internal/setup/cli.go b/backend/internal/setup/cli.go index 03ac3f66..2b323acf 100644 --- a/backend/internal/setup/cli.go +++ b/backend/internal/setup/cli.go @@ -149,6 +149,8 @@ func RunCLI() error { fmt.Println(" Invalid Redis DB. Must be between 0 and 15.") } + cfg.Redis.EnableTLS = promptConfirm(reader, "Enable Redis TLS?") + fmt.Println() fmt.Print("Testing Redis connection... ") if err := TestRedisConnection(&cfg.Redis); err != nil { @@ -205,6 +207,7 @@ func RunCLI() error { fmt.Println("── Configuration Summary ──") fmt.Printf("Database: %s@%s:%d/%s\n", cfg.Database.User, cfg.Database.Host, cfg.Database.Port, cfg.Database.DBName) fmt.Printf("Redis: %s:%d\n", cfg.Redis.Host, cfg.Redis.Port) + fmt.Printf("Redis TLS: %s\n", map[bool]string{true: "enabled", false: "disabled"}[cfg.Redis.EnableTLS]) fmt.Printf("Admin: %s\n", cfg.Admin.Email) fmt.Printf("Server: :%d\n", cfg.Server.Port) fmt.Println() diff --git a/backend/internal/setup/handler.go b/backend/internal/setup/handler.go index 1c613dfd..1531c97b 100644 --- a/backend/internal/setup/handler.go +++ b/backend/internal/setup/handler.go @@ -176,10 +176,11 @@ func testDatabase(c *gin.Context) { // TestRedisRequest represents Redis test request type TestRedisRequest struct { - Host string `json:"host" binding:"required"` - Port int `json:"port" binding:"required"` - Password string `json:"password"` - DB int `json:"db"` + Host string `json:"host" binding:"required"` + Port int `json:"port" binding:"required"` + Password string `json:"password"` + DB int `json:"db"` + EnableTLS bool `json:"enable_tls"` } // testRedis tests Redis connection @@ -205,10 +206,11 @@ func testRedis(c *gin.Context) { } cfg := &RedisConfig{ - Host: req.Host, - Port: req.Port, - Password: req.Password, - DB: req.DB, + Host: req.Host, + Port: req.Port, + Password: req.Password, + DB: req.DB, + EnableTLS: req.EnableTLS, } if err := TestRedisConnection(cfg); err != nil { diff --git a/backend/internal/setup/setup.go b/backend/internal/setup/setup.go index 65118161..f81f75cf 100644 --- a/backend/internal/setup/setup.go +++ b/backend/internal/setup/setup.go @@ -3,6 +3,7 @@ package setup import ( "context" "crypto/rand" + "crypto/tls" "database/sql" "encoding/hex" "fmt" @@ -79,10 +80,11 @@ type DatabaseConfig struct { } type RedisConfig struct { - Host string `json:"host" yaml:"host"` - Port int `json:"port" yaml:"port"` - Password string `json:"password" yaml:"password"` - DB int `json:"db" yaml:"db"` + Host string `json:"host" yaml:"host"` + Port int `json:"port" yaml:"port"` + Password string `json:"password" yaml:"password"` + DB int `json:"db" yaml:"db"` + EnableTLS bool `json:"enable_tls" yaml:"enable_tls"` } type AdminConfig struct { @@ -199,11 +201,20 @@ func TestDatabaseConnection(cfg *DatabaseConfig) error { // TestRedisConnection tests the Redis connection func TestRedisConnection(cfg *RedisConfig) error { - rdb := redis.NewClient(&redis.Options{ + opts := &redis.Options{ Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), Password: cfg.Password, DB: cfg.DB, - }) + } + + if cfg.EnableTLS { + opts.TLSConfig = &tls.Config{ + MinVersion: tls.VersionTLS12, + ServerName: cfg.Host, + } + } + + rdb := redis.NewClient(opts) defer func() { if err := rdb.Close(); err != nil { log.Printf("failed to close redis client: %v", err) @@ -485,10 +496,11 @@ func AutoSetupFromEnv() error { SSLMode: getEnvOrDefault("DATABASE_SSLMODE", "disable"), }, Redis: RedisConfig{ - Host: getEnvOrDefault("REDIS_HOST", "localhost"), - Port: getEnvIntOrDefault("REDIS_PORT", 6379), - Password: getEnvOrDefault("REDIS_PASSWORD", ""), - DB: getEnvIntOrDefault("REDIS_DB", 0), + Host: getEnvOrDefault("REDIS_HOST", "localhost"), + Port: getEnvIntOrDefault("REDIS_PORT", 6379), + Password: getEnvOrDefault("REDIS_PASSWORD", ""), + DB: getEnvIntOrDefault("REDIS_DB", 0), + EnableTLS: getEnvOrDefault("REDIS_ENABLE_TLS", "false") == "true", }, Admin: AdminConfig{ Email: getEnvOrDefault("ADMIN_EMAIL", "admin@sub2api.local"), diff --git a/config.yaml b/config.yaml index 5e7513fb..19f77221 100644 --- a/config.yaml +++ b/config.yaml @@ -322,6 +322,9 @@ redis: # Database number (0-15) # 数据库编号(0-15) db: 0 + # Enable TLS/SSL connection + # 是否启用 TLS/SSL 连接 + enable_tls: false # ============================================================================= # Ops Monitoring (Optional) diff --git a/deploy/.env.example b/deploy/.env.example index 1e9395a0..25096c3d 100644 --- a/deploy/.env.example +++ b/deploy/.env.example @@ -40,6 +40,7 @@ POSTGRES_DB=sub2api # Leave empty for no password (default for local development) REDIS_PASSWORD= REDIS_DB=0 +REDIS_ENABLE_TLS=false # ----------------------------------------------------------------------------- # Admin Account diff --git a/deploy/config.example.yaml b/deploy/config.example.yaml index 98aba8f5..6f5e9744 100644 --- a/deploy/config.example.yaml +++ b/deploy/config.example.yaml @@ -376,6 +376,9 @@ redis: # Database number (0-15) # 数据库编号(0-15) db: 0 + # Enable TLS/SSL connection + # 是否启用 TLS/SSL 连接 + enable_tls: false # ============================================================================= # Ops Monitoring (Optional) diff --git a/deploy/docker-compose.standalone.yml b/deploy/docker-compose.standalone.yml index 1bf247c7..97903bc5 100644 --- a/deploy/docker-compose.standalone.yml +++ b/deploy/docker-compose.standalone.yml @@ -56,6 +56,7 @@ services: - REDIS_PORT=${REDIS_PORT:-6379} - REDIS_PASSWORD=${REDIS_PASSWORD:-} - REDIS_DB=${REDIS_DB:-0} + - REDIS_ENABLE_TLS=${REDIS_ENABLE_TLS:-false} # ======================================================================= # Admin Account (auto-created on first run) diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index ac6008d2..033731ac 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -62,6 +62,7 @@ services: - REDIS_PORT=6379 - REDIS_PASSWORD=${REDIS_PASSWORD:-} - REDIS_DB=${REDIS_DB:-0} + - REDIS_ENABLE_TLS=${REDIS_ENABLE_TLS:-false} # ======================================================================= # Admin Account (auto-created on first run) diff --git a/frontend/src/api/setup.ts b/frontend/src/api/setup.ts index 8b744590..1097c95b 100644 --- a/frontend/src/api/setup.ts +++ b/frontend/src/api/setup.ts @@ -31,6 +31,7 @@ export interface RedisConfig { port: number password: string db: number + enable_tls: boolean } export interface AdminConfig { diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index dc93d37c..64b589df 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -69,7 +69,9 @@ export default { port: 'Port', password: 'Password (optional)', database: 'Database', - passwordPlaceholder: 'Password' + passwordPlaceholder: 'Password', + enableTls: 'Enable TLS', + enableTlsHint: 'Use TLS when connecting to Redis (public CA certs)' }, admin: { title: 'Admin Account', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 4b6a9be6..19378915 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -66,7 +66,9 @@ export default { port: '端口', password: '密码(可选)', database: '数据库', - passwordPlaceholder: '密码' + passwordPlaceholder: '密码', + enableTls: '启用 TLS', + enableTlsHint: '连接 Redis 时使用 TLS(公共 CA 证书)' }, admin: { title: '管理员账户', diff --git a/frontend/src/views/setup/SetupWizardView.vue b/frontend/src/views/setup/SetupWizardView.vue index 2be837f5..00f437ba 100644 --- a/frontend/src/views/setup/SetupWizardView.vue +++ b/frontend/src/views/setup/SetupWizardView.vue @@ -91,6 +91,18 @@ +
+
+

+ {{ t("setup.redis.enableTls") }} +

+

+ {{ t("setup.redis.enableTlsHint") }} +

+
+ +
+
@@ -517,7 +529,8 @@ const formData = reactive({ host: 'localhost', port: 6379, password: '', - db: 0 + db: 0, + enable_tls: false }, admin: { email: '',