初始化提交

This commit is contained in:
chenxin committed 2026-08-21 16:00:13 +08:00
commit 887c1f449e
963 files changed
+166869

No files matched your search

+179
View File
@@ -0,0 +1,179 @@
// Package config 管理博闻配置。
package config
import (
"fmt"
"os"
"strconv"
"strings"
"time"
)
// Config 顶层配置。
type Config struct {
Server ServerConfig `yaml:"server"`
Providers ProvidersConfig `yaml:"providers"`
Balancer BalancerConfig `yaml:"balancer"`
Cache CacheConfig `yaml:"cache"`
Metrics MetricsConfig `yaml:"metrics"`
}
// ServerConfig 服务端配置。
type ServerConfig struct {
Port int `yaml:"port"`
ReadTimeout time.Duration `yaml:"read_timeout"`
WriteTimeout time.Duration `yaml:"write_timeout"`
}
// ProvidersConfig 聚合所有搜索提供商配置。
type ProvidersConfig struct {
Tavily ProviderConfig `yaml:"tavily"`
Brave ProviderConfig `yaml:"brave"`
}
// ProviderConfig 单个搜索提供商配置。
type ProviderConfig struct {
Enabled bool `yaml:"enabled"`
Timeout time.Duration `yaml:"timeout"`
MaxResults int `yaml:"max_results"`
HealthCheck HealthCheckConf `yaml:"health_check"`
// APIKeys 从环境变量注入,不在 YAML 中
APIKeys []string `yaml:"-"`
}
// HealthCheckConf 健康检查配置。
type HealthCheckConf struct {
Interval time.Duration `yaml:"interval"`
Timeout time.Duration `yaml:"timeout"`
FailureThreshold int `yaml:"failure_threshold"`
}
// BalancerConfig 负载均衡配置。
type BalancerConfig struct {
Strategy string `yaml:"strategy"`
RetryCount int `yaml:"retry_count"`
CircuitBreaker CircuitBreakerCfg `yaml:"circuit_breaker"`
}
// CircuitBreakerCfg 熔断器配置。
type CircuitBreakerCfg struct {
Enabled bool `yaml:"enabled"`
FailureThreshold int `yaml:"failure_threshold"`
Cooldown time.Duration `yaml:"cooldown"`
}
// CacheConfig 缓存配置。
type CacheConfig struct {
Enabled bool `yaml:"enabled"`
TTL time.Duration `yaml:"ttl"`
MaxItems int `yaml:"max_items"`
Redis RedisConfig `yaml:"redis"`
}
// RedisConfig Redis 缓存配置(二级缓存层,可开关)。
type RedisConfig struct {
Enabled bool `yaml:"enabled"`
Addr string `yaml:"addr"`
Password string `yaml:"password"`
DB int `yaml:"db"`
}
// MetricsConfig 监控指标配置。
type MetricsConfig struct {
Enabled bool `yaml:"enabled"`
Port int `yaml:"port"`
Path string `yaml:"path"`
}
// envVarKeys 定义各 provider 的 API Key 环境变量名。
var envVarKeys = map[string]string{
"tavily": "TAVILY_API_KEYS",
"brave": "BRAVE_API_KEYS",
}
// Load 加载配置,API Keys 从环境变量读取。
// configPath 为可选 YAML 配置文件路径。
func Load(configPath string) (*Config, error) {
cfg := &Config{}
setDefaults(cfg)
// 从 YAML 文件加载(如存在)
if configPath != "" {
if err := loadFromFile(configPath, cfg); err != nil {
return nil, err
}
}
// 从环境变量加载 API Keys
cfg.Providers.Tavily.APIKeys = parseEnvKeys(envVarKeys["tavily"])
cfg.Providers.Brave.APIKeys = parseEnvKeys(envVarKeys["brave"])
// 从环境变量加载 Redis 配置
if env := os.Getenv("MCP_WEB_SEARCH_REDIS_ENABLED"); env != "" {
cfg.Cache.Redis.Enabled, _ = strconv.ParseBool(env)
}
if env := os.Getenv("MCP_WEB_SEARCH_REDIS_ADDR"); env != "" {
cfg.Cache.Redis.Addr = env
}
if env := os.Getenv("MCP_WEB_SEARCH_REDIS_PASSWORD"); env != "" {
cfg.Cache.Redis.Password = env
}
if env := os.Getenv("MCP_WEB_SEARCH_REDIS_DB"); env != "" {
if db, err := strconv.Atoi(env); err == nil {
cfg.Cache.Redis.DB = db
}
}
return cfg, nil
}
func setDefaults(cfg *Config) {
cfg.Server.Port = 8080
cfg.Server.ReadTimeout = 30 * time.Second
cfg.Server.WriteTimeout = 30 * time.Second
// Provider 默认启用(可通过 YAML 中 enabled: false 关闭)
cfg.Providers.Tavily.Enabled = true
cfg.Providers.Tavily.Timeout = 10 * time.Second
cfg.Providers.Tavily.MaxResults = 10
cfg.Providers.Brave.Enabled = true
cfg.Providers.Brave.Timeout = 10 * time.Second
cfg.Providers.Brave.MaxResults = 10
cfg.Balancer.Strategy = "weighted_round_robin"
cfg.Balancer.RetryCount = 2
cfg.Balancer.CircuitBreaker.Enabled = true
cfg.Balancer.CircuitBreaker.FailureThreshold = 5
cfg.Balancer.CircuitBreaker.Cooldown = 30 * time.Second
cfg.Cache.Enabled = true
cfg.Cache.TTL = 5 * time.Minute
cfg.Cache.MaxItems = 1000
cfg.Cache.Redis.Enabled = false
cfg.Cache.Redis.Addr = "127.0.0.1:6379"
cfg.Cache.Redis.Password = ""
cfg.Cache.Redis.DB = 0
cfg.Metrics.Enabled = true
cfg.Metrics.Port = 9090
cfg.Metrics.Path = "/metrics"
}
func loadFromFile(path string, cfg *Config) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("read config: %w", err)
}
return yamlUnmarshal(data, cfg)
}
func parseEnvKeys(envName string) []string {
val := os.Getenv(envName)
if val == "" {
return nil
}
parts := strings.Split(val, ",")
keys := make([]string, 0, len(parts))
for _, p := range parts {
if trimmed := strings.TrimSpace(p); trimmed != "" {
keys = append(keys, trimmed)
}
}
return keys
}
+109
View File
@@ -0,0 +1,109 @@
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()
}
+7
View File
@@ -0,0 +1,7 @@
package config
import "gopkg.in/yaml.v3"
func yamlUnmarshal(data []byte, cfg *Config) error {
return yaml.Unmarshal(data, cfg)
}