Files
hunjing/mcp/web-search/internal/config/reloader.go
T
2026-08-21 16:00:13 +08:00

110 lines
2.4 KiB
Go

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()
}