- 新增 SIGINT/SIGTERM 信号处理与 http.Server.Shutdown,退出前确保统计数据保存、数据库正常关闭 - 为 http.Server 设置 ReadHeaderTimeout/ReadTimeout/WriteTimeout/IdleTimeout,防止慢客户端长期占用连接 - 全局配置指针改为 atomic.Pointer 并提供 GetAppConfig 访问器,消除配置热更新时的数据竞争,同时删除冗余的 manager.config 字段 - 请求统计九组计数器重构为 map[string]*atomic.Int64,统一 incrementStat/getStatsSnapshot 访问,精简约 40 行样板代码 - go mod tidy 清理 go.mod 与 go.sum 中的冗余依赖条目
232 lines
6.5 KiB
Go
232 lines
6.5 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
// Config 应用配置结构
|
|
type Config struct {
|
|
Server ServerConfig `mapstructure:"server"`
|
|
Fanqie FanqieConfig `mapstructure:"fanqie"`
|
|
Cache CacheConfig `mapstructure:"cache"`
|
|
DeviceKeyPool DeviceKeyPoolConfig `mapstructure:"device_key_pool"`
|
|
}
|
|
|
|
// ServerConfig 服务器配置
|
|
type ServerConfig struct {
|
|
Port string `mapstructure:"port"`
|
|
Host string `mapstructure:"host"`
|
|
AccessKey string `mapstructure:"access_key"`
|
|
}
|
|
|
|
// FanqieConfig 番茄小说配置
|
|
type FanqieConfig struct {
|
|
Algorithm string `mapstructure:"algorithm"`
|
|
Signature string `mapstructure:"signature"` // 签名方法: sign2 / sign4
|
|
Content ContentConfig `mapstructure:"content"`
|
|
Manga MangaConfig `mapstructure:"manga"`
|
|
}
|
|
|
|
// ContentConfig 内容处理配置
|
|
type ContentConfig struct {
|
|
RemoveImages bool `mapstructure:"remove_images"`
|
|
RemoveImageDesc bool `mapstructure:"remove_image_desc"`
|
|
RemoveBlankLines bool `mapstructure:"remove_blank_lines"`
|
|
RemoveHTMLTags bool `mapstructure:"remove_html_tags"`
|
|
RemoveChapterTitle bool `mapstructure:"remove_chapter_title"`
|
|
AddParagraphIndent bool `mapstructure:"add_paragraph_indent"`
|
|
DoubleNewline bool `mapstructure:"double_newline"` // 段落间使用双换行
|
|
EnableReview bool `mapstructure:"enable_review"` // 是否启用段评功能
|
|
}
|
|
|
|
// MangaConfig 漫画处理配置
|
|
type MangaConfig struct {
|
|
OutputMode string `mapstructure:"output_mode"` // 输出模式: data_uri / base64 / url
|
|
}
|
|
|
|
// CacheConfig 缓存配置
|
|
type CacheConfig struct {
|
|
Enabled bool `mapstructure:"enabled"`
|
|
SQLitePath string `mapstructure:"sqlite_path"`
|
|
CleanupEnabled bool `mapstructure:"cleanup_enabled"` // 是否启用缓存清理
|
|
CleanupSeconds int `mapstructure:"cleanup_seconds"` // 缓存清理时间(秒)
|
|
}
|
|
|
|
// DeviceKeyPoolConfig 设备密钥池配置
|
|
type DeviceKeyPoolConfig struct {
|
|
Enabled bool `mapstructure:"enabled"` // 是否启用设备密钥池
|
|
InitialSize int `mapstructure:"initial_size"` // 初始化设备数量
|
|
MinSize int `mapstructure:"min_size"` // 最小设备数量
|
|
MaxSize int `mapstructure:"max_size"` // 最大设备数量
|
|
}
|
|
|
|
// ConfigManager 配置管理器
|
|
type ConfigManager struct {
|
|
mu sync.RWMutex
|
|
configPath string
|
|
callbacks []func(*Config)
|
|
}
|
|
|
|
var (
|
|
// appConfig 全局配置实例(原子指针,热更新替换时读取侧无数据竞争)
|
|
appConfig atomic.Pointer[Config]
|
|
manager *ConfigManager
|
|
)
|
|
|
|
// GetAppConfig 获取当前配置,未加载时返回 nil
|
|
func GetAppConfig() *Config {
|
|
return appConfig.Load()
|
|
}
|
|
|
|
// NewConfigManager 创建配置管理器
|
|
func NewConfigManager() *ConfigManager {
|
|
return &ConfigManager{
|
|
callbacks: make([]func(*Config), 0),
|
|
}
|
|
}
|
|
|
|
// LoadConfig 加载配置文件
|
|
func LoadConfig(configPath string) error {
|
|
// 初始化管理器
|
|
if manager == nil {
|
|
manager = NewConfigManager()
|
|
manager.configPath = configPath
|
|
}
|
|
|
|
viper.SetConfigFile(configPath)
|
|
viper.SetConfigType("yaml")
|
|
|
|
// 设置默认值
|
|
setDefaults()
|
|
|
|
if err := viper.ReadInConfig(); err != nil {
|
|
return fmt.Errorf("读取配置文件失败: %w", err)
|
|
}
|
|
|
|
newConfig := &Config{}
|
|
if err := viper.Unmarshal(newConfig); err != nil {
|
|
return fmt.Errorf("解析配置文件失败: %w", err)
|
|
}
|
|
|
|
// 更新配置
|
|
appConfig.Store(newConfig)
|
|
|
|
return nil
|
|
}
|
|
|
|
// setDefaults 设置默认值
|
|
func setDefaults() {
|
|
// 服务器配置
|
|
viper.SetDefault("server.port", "40180")
|
|
viper.SetDefault("server.host", "0.0.0.0")
|
|
|
|
// 番茄小说配置
|
|
viper.SetDefault("fanqie.algorithm", "gorgon")
|
|
viper.SetDefault("fanqie.signature", "sign2")
|
|
|
|
// 内容处理配置
|
|
viper.SetDefault("fanqie.content.remove_images", false)
|
|
viper.SetDefault("fanqie.content.remove_image_desc", false)
|
|
viper.SetDefault("fanqie.content.remove_blank_lines", true)
|
|
viper.SetDefault("fanqie.content.remove_html_tags", false)
|
|
viper.SetDefault("fanqie.content.remove_chapter_title", false)
|
|
viper.SetDefault("fanqie.content.add_paragraph_indent", false)
|
|
viper.SetDefault("fanqie.content.double_newline", true)
|
|
viper.SetDefault("fanqie.content.enable_review", true)
|
|
|
|
// 漫画处理配置
|
|
viper.SetDefault("fanqie.manga.output_mode", "url")
|
|
|
|
// 缓存配置
|
|
viper.SetDefault("cache.enabled", true)
|
|
viper.SetDefault("cache.sqlite_path", "./cache.db")
|
|
viper.SetDefault("cache.cleanup_enabled", false)
|
|
viper.SetDefault("cache.cleanup_seconds", 86400)
|
|
|
|
// 设备密钥池配置
|
|
viper.SetDefault("device_key_pool.enabled", true)
|
|
viper.SetDefault("device_key_pool.initial_size", 3)
|
|
viper.SetDefault("device_key_pool.min_size", 2)
|
|
viper.SetDefault("device_key_pool.max_size", 10)
|
|
}
|
|
|
|
// GetConfig 获取配置实例
|
|
func GetConfig() *Config {
|
|
if cfg := appConfig.Load(); cfg != nil {
|
|
return cfg
|
|
}
|
|
log.Fatal("配置未初始化,请先调用 LoadConfig")
|
|
return nil
|
|
}
|
|
|
|
// ReloadConfig 重新加载配置文件
|
|
func ReloadConfig() error {
|
|
if manager == nil {
|
|
return fmt.Errorf("配置管理器未初始化")
|
|
}
|
|
|
|
manager.mu.RLock()
|
|
configPath := manager.configPath
|
|
manager.mu.RUnlock()
|
|
|
|
if configPath == "" {
|
|
return fmt.Errorf("配置文件路径为空")
|
|
}
|
|
|
|
// 重新读取配置文件
|
|
viper.SetConfigFile(configPath)
|
|
if err := viper.ReadInConfig(); err != nil {
|
|
return fmt.Errorf("重新读取配置文件失败: %w", err)
|
|
}
|
|
|
|
newConfig := &Config{}
|
|
if err := viper.Unmarshal(newConfig); err != nil {
|
|
return fmt.Errorf("重新解析配置文件失败: %w", err)
|
|
}
|
|
|
|
// 更新配置
|
|
manager.mu.Lock()
|
|
var oldAlgorithm string
|
|
if old := appConfig.Load(); old != nil {
|
|
oldAlgorithm = old.Fanqie.Algorithm
|
|
}
|
|
appConfig.Store(newConfig)
|
|
callbacks := make([]func(*Config), len(manager.callbacks))
|
|
copy(callbacks, manager.callbacks)
|
|
manager.mu.Unlock()
|
|
|
|
// 通知配置更新
|
|
for _, callback := range callbacks {
|
|
callback(newConfig)
|
|
}
|
|
|
|
log.Printf("配置重新加载成功: %s", configPath)
|
|
if oldAlgorithm != newConfig.Fanqie.Algorithm {
|
|
log.Printf("算法已切换: %s -> %s", oldAlgorithm, newConfig.Fanqie.Algorithm)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RegisterConfigCallback 注册配置更新回调
|
|
func RegisterConfigCallback(callback func(*Config)) {
|
|
if manager == nil {
|
|
log.Printf("警告: 配置管理器未初始化,无法注册回调")
|
|
return
|
|
}
|
|
|
|
manager.mu.Lock()
|
|
defer manager.mu.Unlock()
|
|
manager.callbacks = append(manager.callbacks, callback)
|
|
}
|
|
|
|
// GetConfigManager 获取配置管理器实例
|
|
func GetConfigManager() *ConfigManager {
|
|
return manager
|
|
}
|
|
|