110 lines
2.4 KiB
Go
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()
|
|
}
|