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:
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ export default {
|
|||||||
port: '端口',
|
port: '端口',
|
||||||
password: '密码(可选)',
|
password: '密码(可选)',
|
||||||
database: '数据库',
|
database: '数据库',
|
||||||
passwordPlaceholder: '密码'
|
passwordPlaceholder: '密码',
|
||||||
|
enableTls: '启用 TLS',
|
||||||
|
enableTlsHint: '连接 Redis 时使用 TLS(公共 CA 证书)'
|
||||||
},
|
},
|
||||||
admin: {
|
admin: {
|
||||||
title: '管理员账户',
|
title: '管理员账户',
|
||||||
|
|||||||
@@ -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: '',
|
||||||
|
|||||||
Reference in New Issue
Block a user