✨ feat(cache): 新增 Redis 缓存与 PostgreSQL 后端支持
- 新增 cache.redis 配置(独立 host/port/password/db 字段),启用后正文缓存与音频代理缓存走 Redis,键前缀 fq:cache:/fq:audio:,TTL 自动过期 - 新增 cache.backend: sqlite|postgres 切换,PG 连接用 net/url 构造 DSN,密码特殊字符自动转义;SQLite 顺手启用 WAL 与 busy_timeout - SQL 方言收敛:q() 将 ? 占位符转换为 $n、INSERT OR IGNORE 与查询再插入统一为 ON CONFLICT、LastInsertId 改为 RETURNING id、建表 DDL 双版本分派 - PG 下 VACUUM 走 autovacuum 说明、状态页大小改查 pg_database_size - 修复 PG 下设备保存失败:is_valid 列为 INTEGER,写入点补 boolToInt 转换 - 新增 .env 支持:敏感配置(密码/主机/访问密钥)由环境变量覆盖 config.yaml,.env 进 gitignore,仓库附 .env.example 模板 - 删除死代码 InitDevicePoolTable;新增 rebind_test.go 单测 - 更新 README 与 config.yaml 配置示例,go.mod 新增 go-redis/v9 与 pgx/v5 依赖
This commit is contained in:
1 parent
d710d382ee
commit
cf16c37f32
14 files changed
+671
-210
No files matched your search
@@ -51,10 +51,32 @@ type MangaConfig struct {
|
||||
|
||||
// CacheConfig 缓存配置
|
||||
type CacheConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
SQLitePath string `mapstructure:"sqlite_path"`
|
||||
CleanupEnabled bool `mapstructure:"cleanup_enabled"` // 是否启用缓存清理
|
||||
CleanupSeconds int `mapstructure:"cleanup_seconds"` // 缓存清理时间(秒)
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Backend string `mapstructure:"backend"` // 存储后端: sqlite | postgres
|
||||
SQLitePath string `mapstructure:"sqlite_path"`
|
||||
Postgres PostgresConfig `mapstructure:"postgres"`
|
||||
Redis RedisConfig `mapstructure:"redis"`
|
||||
CleanupEnabled bool `mapstructure:"cleanup_enabled"` // 是否启用缓存清理
|
||||
CleanupSeconds int `mapstructure:"cleanup_seconds"` // 缓存清理时间(秒)
|
||||
}
|
||||
|
||||
// PostgresConfig PostgreSQL 连接配置
|
||||
type PostgresConfig struct {
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
User string `mapstructure:"user"`
|
||||
Password string `mapstructure:"password"`
|
||||
Database string `mapstructure:"database"`
|
||||
SSLMode string `mapstructure:"sslmode"` // disable | prefer | require
|
||||
}
|
||||
|
||||
// RedisConfig Redis 缓存配置(启用后正文缓存与音频代理缓存走 Redis,统计数据与设备池仍在 SQL 库)
|
||||
type RedisConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Host string `mapstructure:"host"`
|
||||
Port int `mapstructure:"port"`
|
||||
Password string `mapstructure:"password"`
|
||||
DB int `mapstructure:"db"`
|
||||
}
|
||||
|
||||
// DeviceKeyPoolConfig 设备密钥池配置
|
||||
@@ -98,6 +120,9 @@ func LoadConfig(configPath string) error {
|
||||
manager.configPath = configPath
|
||||
}
|
||||
|
||||
// 加载 .env(若存在),敏感配置可由环境变量覆盖
|
||||
loadDotenv("./.env")
|
||||
|
||||
viper.SetConfigFile(configPath)
|
||||
viper.SetConfigType("yaml")
|
||||
|
||||
@@ -112,6 +137,7 @@ func LoadConfig(configPath string) error {
|
||||
if err := viper.Unmarshal(newConfig); err != nil {
|
||||
return fmt.Errorf("解析配置文件失败: %w", err)
|
||||
}
|
||||
applyEnvOverrides(newConfig)
|
||||
|
||||
// 更新配置
|
||||
appConfig.Store(newConfig)
|
||||
@@ -144,10 +170,26 @@ func setDefaults() {
|
||||
|
||||
// 缓存配置
|
||||
viper.SetDefault("cache.enabled", true)
|
||||
viper.SetDefault("cache.backend", "sqlite")
|
||||
viper.SetDefault("cache.sqlite_path", "./cache.db")
|
||||
viper.SetDefault("cache.cleanup_enabled", false)
|
||||
viper.SetDefault("cache.cleanup_seconds", 86400)
|
||||
|
||||
// PostgreSQL 配置
|
||||
viper.SetDefault("cache.postgres.host", "127.0.0.1")
|
||||
viper.SetDefault("cache.postgres.port", 5432)
|
||||
viper.SetDefault("cache.postgres.user", "postgres")
|
||||
viper.SetDefault("cache.postgres.password", "")
|
||||
viper.SetDefault("cache.postgres.database", "fanqie")
|
||||
viper.SetDefault("cache.postgres.sslmode", "disable")
|
||||
|
||||
// Redis 配置
|
||||
viper.SetDefault("cache.redis.enabled", false)
|
||||
viper.SetDefault("cache.redis.host", "127.0.0.1")
|
||||
viper.SetDefault("cache.redis.port", 6379)
|
||||
viper.SetDefault("cache.redis.password", "")
|
||||
viper.SetDefault("cache.redis.db", 0)
|
||||
|
||||
// 设备密钥池配置
|
||||
viper.SetDefault("device_key_pool.enabled", true)
|
||||
viper.SetDefault("device_key_pool.initial_size", 3)
|
||||
@@ -188,6 +230,7 @@ func ReloadConfig() error {
|
||||
if err := viper.Unmarshal(newConfig); err != nil {
|
||||
return fmt.Errorf("重新解析配置文件失败: %w", err)
|
||||
}
|
||||
applyEnvOverrides(newConfig)
|
||||
|
||||
// 更新配置
|
||||
manager.mu.Lock()
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// loadDotenv 加载 .env 文件到环境变量(文件不存在则跳过;不覆盖已有环境变量)
|
||||
func loadDotenv(path string) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
k, v, ok := strings.Cut(line, "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
k = strings.TrimSpace(k)
|
||||
v = strings.Trim(strings.TrimSpace(v), `"'`)
|
||||
if os.Getenv(k) == "" {
|
||||
os.Setenv(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// applyEnvOverrides 用环境变量覆盖敏感配置(配合 .env 使用,真实密码不进 config.yaml)
|
||||
func applyEnvOverrides(c *Config) {
|
||||
for _, o := range []struct {
|
||||
env string
|
||||
dst *string
|
||||
}{
|
||||
{"FQ_SERVER_ACCESS_KEY", &c.Server.AccessKey},
|
||||
{"FQ_PG_HOST", &c.Cache.Postgres.Host},
|
||||
{"FQ_PG_USER", &c.Cache.Postgres.User},
|
||||
{"FQ_PG_PASSWORD", &c.Cache.Postgres.Password},
|
||||
{"FQ_PG_DATABASE", &c.Cache.Postgres.Database},
|
||||
{"FQ_REDIS_HOST", &c.Cache.Redis.Host},
|
||||
{"FQ_REDIS_PASSWORD", &c.Cache.Redis.Password},
|
||||
} {
|
||||
if v := os.Getenv(o.env); v != "" {
|
||||
*o.dst = v
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user