Merge pull request #436 from iBenzene/feat/redis-tls-support

feat: add support for using TLS to connect to Redis
This commit is contained in:
Wesley Liddick
2026-02-02 10:02:25 +08:00
committed by GitHub
15 changed files with 91 additions and 22 deletions

View File

@@ -415,6 +415,8 @@ type RedisConfig struct {
PoolSize int `mapstructure:"pool_size"` PoolSize int `mapstructure:"pool_size"`
// MinIdleConns: 最小空闲连接数,保持热连接减少冷启动延迟 // MinIdleConns: 最小空闲连接数,保持热连接减少冷启动延迟
MinIdleConns int `mapstructure:"min_idle_conns"` MinIdleConns int `mapstructure:"min_idle_conns"`
// EnableTLS: 是否启用 TLS/SSL 连接
EnableTLS bool `mapstructure:"enable_tls"`
} }
func (r *RedisConfig) Address() string { func (r *RedisConfig) Address() string {
@@ -762,6 +764,7 @@ func setDefaults() {
viper.SetDefault("redis.write_timeout_seconds", 3) viper.SetDefault("redis.write_timeout_seconds", 3)
viper.SetDefault("redis.pool_size", 128) viper.SetDefault("redis.pool_size", 128)
viper.SetDefault("redis.min_idle_conns", 10) viper.SetDefault("redis.min_idle_conns", 10)
viper.SetDefault("redis.enable_tls", false)
// Ops (vNext) // Ops (vNext)
viper.SetDefault("ops.enabled", true) viper.SetDefault("ops.enabled", true)

View File

@@ -1,6 +1,7 @@
package repository package repository
import ( import (
"crypto/tls"
"time" "time"
"github.com/Wei-Shaw/sub2api/internal/config" "github.com/Wei-Shaw/sub2api/internal/config"
@@ -26,7 +27,7 @@ func InitRedis(cfg *config.Config) *redis.Client {
// buildRedisOptions 构建 Redis 连接选项 // buildRedisOptions 构建 Redis 连接选项
// 从配置文件读取连接池和超时参数,支持生产环境调优 // 从配置文件读取连接池和超时参数,支持生产环境调优
func buildRedisOptions(cfg *config.Config) *redis.Options { func buildRedisOptions(cfg *config.Config) *redis.Options {
return &redis.Options{ opts := &redis.Options{
Addr: cfg.Redis.Address(), Addr: cfg.Redis.Address(),
Password: cfg.Redis.Password, Password: cfg.Redis.Password,
DB: cfg.Redis.DB, DB: cfg.Redis.DB,
@@ -36,4 +37,13 @@ func buildRedisOptions(cfg *config.Config) *redis.Options {
PoolSize: cfg.Redis.PoolSize, // 连接池大小 PoolSize: cfg.Redis.PoolSize, // 连接池大小
MinIdleConns: cfg.Redis.MinIdleConns, // 最小空闲连接 MinIdleConns: cfg.Redis.MinIdleConns, // 最小空闲连接
} }
if cfg.Redis.EnableTLS {
opts.TLSConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: cfg.Redis.Host,
}
}
return opts
} }

View File

@@ -32,4 +32,16 @@ func TestBuildRedisOptions(t *testing.T) {
require.Equal(t, 4*time.Second, opts.WriteTimeout) require.Equal(t, 4*time.Second, opts.WriteTimeout)
require.Equal(t, 100, opts.PoolSize) require.Equal(t, 100, opts.PoolSize)
require.Equal(t, 10, opts.MinIdleConns) 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)
} }

View File

@@ -149,6 +149,8 @@ func RunCLI() error {
fmt.Println(" Invalid Redis DB. Must be between 0 and 15.") fmt.Println(" Invalid Redis DB. Must be between 0 and 15.")
} }
cfg.Redis.EnableTLS = promptConfirm(reader, "Enable Redis TLS?")
fmt.Println() fmt.Println()
fmt.Print("Testing Redis connection... ") fmt.Print("Testing Redis connection... ")
if err := TestRedisConnection(&cfg.Redis); err != nil { if err := TestRedisConnection(&cfg.Redis); err != nil {
@@ -205,6 +207,7 @@ func RunCLI() error {
fmt.Println("── Configuration Summary ──") 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("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: %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("Admin: %s\n", cfg.Admin.Email)
fmt.Printf("Server: :%d\n", cfg.Server.Port) fmt.Printf("Server: :%d\n", cfg.Server.Port)
fmt.Println() fmt.Println()

View File

@@ -176,10 +176,11 @@ func testDatabase(c *gin.Context) {
// TestRedisRequest represents Redis test request // TestRedisRequest represents Redis test request
type TestRedisRequest struct { type TestRedisRequest struct {
Host string `json:"host" binding:"required"` Host string `json:"host" binding:"required"`
Port int `json:"port" binding:"required"` Port int `json:"port" binding:"required"`
Password string `json:"password"` Password string `json:"password"`
DB int `json:"db"` DB int `json:"db"`
EnableTLS bool `json:"enable_tls"`
} }
// testRedis tests Redis connection // testRedis tests Redis connection
@@ -205,10 +206,11 @@ func testRedis(c *gin.Context) {
} }
cfg := &RedisConfig{ cfg := &RedisConfig{
Host: req.Host, Host: req.Host,
Port: req.Port, Port: req.Port,
Password: req.Password, Password: req.Password,
DB: req.DB, DB: req.DB,
EnableTLS: req.EnableTLS,
} }
if err := TestRedisConnection(cfg); err != nil { if err := TestRedisConnection(cfg); err != nil {

View File

@@ -3,6 +3,7 @@ package setup
import ( import (
"context" "context"
"crypto/rand" "crypto/rand"
"crypto/tls"
"database/sql" "database/sql"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
@@ -79,10 +80,11 @@ type DatabaseConfig struct {
} }
type RedisConfig struct { type RedisConfig struct {
Host string `json:"host" yaml:"host"` Host string `json:"host" yaml:"host"`
Port int `json:"port" yaml:"port"` Port int `json:"port" yaml:"port"`
Password string `json:"password" yaml:"password"` Password string `json:"password" yaml:"password"`
DB int `json:"db" yaml:"db"` DB int `json:"db" yaml:"db"`
EnableTLS bool `json:"enable_tls" yaml:"enable_tls"`
} }
type AdminConfig struct { type AdminConfig struct {
@@ -199,11 +201,20 @@ func TestDatabaseConnection(cfg *DatabaseConfig) error {
// TestRedisConnection tests the Redis connection // TestRedisConnection tests the Redis connection
func TestRedisConnection(cfg *RedisConfig) error { func TestRedisConnection(cfg *RedisConfig) error {
rdb := redis.NewClient(&redis.Options{ opts := &redis.Options{
Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port),
Password: cfg.Password, Password: cfg.Password,
DB: cfg.DB, DB: cfg.DB,
}) }
if cfg.EnableTLS {
opts.TLSConfig = &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: cfg.Host,
}
}
rdb := redis.NewClient(opts)
defer func() { defer func() {
if err := rdb.Close(); err != nil { if err := rdb.Close(); err != nil {
log.Printf("failed to close redis client: %v", err) log.Printf("failed to close redis client: %v", err)
@@ -485,10 +496,11 @@ func AutoSetupFromEnv() error {
SSLMode: getEnvOrDefault("DATABASE_SSLMODE", "disable"), SSLMode: getEnvOrDefault("DATABASE_SSLMODE", "disable"),
}, },
Redis: RedisConfig{ Redis: RedisConfig{
Host: getEnvOrDefault("REDIS_HOST", "localhost"), Host: getEnvOrDefault("REDIS_HOST", "localhost"),
Port: getEnvIntOrDefault("REDIS_PORT", 6379), Port: getEnvIntOrDefault("REDIS_PORT", 6379),
Password: getEnvOrDefault("REDIS_PASSWORD", ""), Password: getEnvOrDefault("REDIS_PASSWORD", ""),
DB: getEnvIntOrDefault("REDIS_DB", 0), DB: getEnvIntOrDefault("REDIS_DB", 0),
EnableTLS: getEnvOrDefault("REDIS_ENABLE_TLS", "false") == "true",
}, },
Admin: AdminConfig{ Admin: AdminConfig{
Email: getEnvOrDefault("ADMIN_EMAIL", "admin@sub2api.local"), Email: getEnvOrDefault("ADMIN_EMAIL", "admin@sub2api.local"),

View File

@@ -322,6 +322,9 @@ redis:
# Database number (0-15) # Database number (0-15)
# 数据库编号0-15 # 数据库编号0-15
db: 0 db: 0
# Enable TLS/SSL connection
# 是否启用 TLS/SSL 连接
enable_tls: false
# ============================================================================= # =============================================================================
# Ops Monitoring (Optional) # Ops Monitoring (Optional)

View File

@@ -40,6 +40,7 @@ POSTGRES_DB=sub2api
# Leave empty for no password (default for local development) # Leave empty for no password (default for local development)
REDIS_PASSWORD= REDIS_PASSWORD=
REDIS_DB=0 REDIS_DB=0
REDIS_ENABLE_TLS=false
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# Admin Account # Admin Account

View File

@@ -376,6 +376,9 @@ redis:
# Database number (0-15) # Database number (0-15)
# 数据库编号0-15 # 数据库编号0-15
db: 0 db: 0
# Enable TLS/SSL connection
# 是否启用 TLS/SSL 连接
enable_tls: false
# ============================================================================= # =============================================================================
# Ops Monitoring (Optional) # Ops Monitoring (Optional)

View File

@@ -56,6 +56,7 @@ services:
- REDIS_PORT=${REDIS_PORT:-6379} - REDIS_PORT=${REDIS_PORT:-6379}
- REDIS_PASSWORD=${REDIS_PASSWORD:-} - REDIS_PASSWORD=${REDIS_PASSWORD:-}
- REDIS_DB=${REDIS_DB:-0} - REDIS_DB=${REDIS_DB:-0}
- REDIS_ENABLE_TLS=${REDIS_ENABLE_TLS:-false}
# ======================================================================= # =======================================================================
# Admin Account (auto-created on first run) # Admin Account (auto-created on first run)

View File

@@ -62,6 +62,7 @@ services:
- REDIS_PORT=6379 - REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD:-} - REDIS_PASSWORD=${REDIS_PASSWORD:-}
- REDIS_DB=${REDIS_DB:-0} - REDIS_DB=${REDIS_DB:-0}
- REDIS_ENABLE_TLS=${REDIS_ENABLE_TLS:-false}
# ======================================================================= # =======================================================================
# Admin Account (auto-created on first run) # Admin Account (auto-created on first run)

View File

@@ -31,6 +31,7 @@ export interface RedisConfig {
port: number port: number
password: string password: string
db: number db: number
enable_tls: boolean
} }
export interface AdminConfig { export interface AdminConfig {

View File

@@ -69,7 +69,9 @@ export default {
port: 'Port', port: 'Port',
password: 'Password (optional)', password: 'Password (optional)',
database: 'Database', database: 'Database',
passwordPlaceholder: 'Password' passwordPlaceholder: 'Password',
enableTls: 'Enable TLS',
enableTlsHint: 'Use TLS when connecting to Redis (public CA certs)'
}, },
admin: { admin: {
title: 'Admin Account', title: 'Admin Account',

View File

@@ -66,7 +66,9 @@ export default {
port: '端口', port: '端口',
password: '密码(可选)', password: '密码(可选)',
database: '数据库', database: '数据库',
passwordPlaceholder: '密码' passwordPlaceholder: '密码',
enableTls: '启用 TLS',
enableTlsHint: '连接 Redis 时使用 TLS公共 CA 证书)'
}, },
admin: { admin: {
title: '管理员账户', title: '管理员账户',

View File

@@ -91,6 +91,18 @@
</div> </div>
</div> </div>
<div class="flex items-center justify-between rounded-xl border border-gray-200 p-3 dark:border-dark-700">
<div>
<p class="text-sm font-medium text-gray-900 dark:text-white">
{{ t("setup.redis.enableTls") }}
</p>
<p class="text-xs text-gray-500 dark:text-dark-400">
{{ t("setup.redis.enableTlsHint") }}
</p>
</div>
<Toggle v-model="formData.redis.enable_tls" />
</div>
<div class="grid grid-cols-2 gap-4"> <div class="grid grid-cols-2 gap-4">
<div> <div>
<label class="input-label">{{ t('setup.database.username') }}</label> <label class="input-label">{{ t('setup.database.username') }}</label>
@@ -517,7 +529,8 @@ const formData = reactive<InstallRequest>({
host: 'localhost', host: 'localhost',
port: 6379, port: 6379,
password: '', password: '',
db: 0 db: 0,
enable_tls: false
}, },
admin: { admin: {
email: '', email: '',