package config import ( "log/slog" "sync" "time" "github.com/fsnotify/fsnotify" ) // Reloader 配置文件热重载器。 // 监听 YAML 文件变更,通过 onChange 回调通知调用方。 type Reloader struct { path string cfg *Config watcher *fsnotify.Watcher onChange func(old, new *Config) error mu sync.Mutex debounce *time.Timer done chan struct{} } // NewReloader 创建并启动配置热重载。 // onChange 在配置变更时被调用,接收 old/new 两份配置。 func NewReloader(path string, cfg *Config, onChange func(old, new *Config) error) (*Reloader, error) { w, err := fsnotify.NewWatcher() if err != nil { return nil, err } if err := w.Add(path); err != nil { w.Close() return nil, err } r := &Reloader{ path: path, cfg: cfg, watcher: w, onChange: onChange, debounce: time.NewTimer(0), done: make(chan struct{}), } // 停止初始化的空 timer if !r.debounce.Stop() { <-r.debounce.C } go r.loop() slog.Info("配置热重载已启动", "path", path) return r, nil } func (r *Reloader) loop() { for { select { case event := <-r.watcher.Events: if event.Op&(fsnotify.Write|fsnotify.Create|fsnotify.Rename) == 0 { continue } slog.Debug("配置文件变更", "event", event.Name) r.debounce.Reset(500 * time.Millisecond) case <-r.debounce.C: r.reload() case err := <-r.watcher.Errors: slog.Error("配置热重载监听异常", "error", err) case <-r.done: return } } } func (r *Reloader) reload() { r.mu.Lock() defer r.mu.Unlock() old := r.cfg // 只重新加载 YAML 部分(API Keys 仍由环境变量管理) updated := *old // 浅拷贝 if err := loadFromFile(r.path, &updated); err != nil { slog.Error("配置热重载读取失败", "error", err) return } // 保留环境变量注入的值 updated.Providers.Tavily.APIKeys = old.Providers.Tavily.APIKeys updated.Providers.Brave.APIKeys = old.Providers.Brave.APIKeys updated.Cache.Redis.Enabled = old.Cache.Redis.Enabled updated.Cache.Redis.Addr = old.Cache.Redis.Addr updated.Cache.Redis.Password = old.Cache.Redis.Password updated.Cache.Redis.DB = old.Cache.Redis.DB if err := r.onChange(r.cfg, &updated); err != nil { slog.Error("配置热重载应用失败", "error", err) return } r.cfg = &updated slog.Info("配置已热重载") } // Stop 停止配置热重载。 func (r *Reloader) Stop() { close(r.done) r.watcher.Close() }