初始化提交

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

+160
View File
@@ -0,0 +1,160 @@
package main
import (
"flag"
"fmt"
"log/slog"
"net/http"
"os"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/chenxin/hunjing/mcp/web-search/internal/balancer"
"github.com/chenxin/hunjing/mcp/web-search/internal/cache"
"github.com/chenxin/hunjing/mcp/web-search/internal/config"
"github.com/chenxin/hunjing/mcp/web-search/internal/logger"
"github.com/chenxin/hunjing/mcp/web-search/internal/mcp"
"github.com/chenxin/hunjing/mcp/web-search/internal/metrics"
"github.com/chenxin/hunjing/mcp/web-search/internal/models"
"github.com/chenxin/hunjing/mcp/web-search/internal/providers"
)
func main() {
configPath := flag.String("config", "", "YAML 配置文件路径(不传则按项目惯例自动探测)")
port := flag.Int("port", 0, "HTTP SSE 模式监听端口(缺省时使用配置文件的 port)")
stdioMode := flag.Bool("stdio", false, "以 stdio 模式运行(通过 stdin/stdout 走 JSON-RPC,用于子进程集成)")
flag.Parse()
// 未指定 --config 时按项目惯例自动探测
if *configPath == "" {
for _, p := range []string{
"../../config/mcp/web-search.yaml", // 开发模式(mcp/web-search/ 下 go run)
"config/mcp/web-search.yaml", // 项目根目录下 go run mcp/... 或 make
"/etc/bowen/config.yaml", // Docker 容器内默认路径
} {
if _, err := os.Stat(p); err == nil {
*configPath = p
break
}
}
}
// 加载配置
cfg, err := config.Load(*configPath)
if err != nil {
fmt.Fprintf(os.Stderr, "配置加载失败: %v\n", err)
os.Exit(1)
}
// 设置日志
logCfg := logger.Config{
Level: slog.LevelInfo,
AddSource: false,
JSON: os.Getenv("ENV") == "production",
}
slog.SetDefault(logger.New(logCfg))
// 端口覆盖
if *port > 0 {
cfg.Server.Port = *port
}
// 初始化 Provider 注册中心
reg, err := providers.SetupFromConfig(cfg)
if err != nil {
slog.Error("provider 初始化失败", "error", err)
os.Exit(1)
}
// 初始化负载均衡器
lb := balancer.NewLoadBalancer(&cfg.Balancer)
// 为每个启用的 provider 及其密钥注册到负载均衡器
type providerEntry struct {
kind models.ProviderKind
keys []string
}
entries := []providerEntry{
{models.ProviderTavily, cfg.Providers.Tavily.APIKeys},
{models.ProviderBrave, cfg.Providers.Brave.APIKeys},
}
var providerKinds []models.ProviderKind
for _, e := range entries {
if len(e.keys) == 0 {
continue
}
prov, ok := reg.Get(e.kind)
if !ok {
continue
}
lb.AddProvider(e.kind, e.keys, 1, prov)
providerKinds = append(providerKinds, e.kind)
metrics.ActiveKeys.WithLabelValues(string(e.kind)).Set(float64(len(e.keys)))
slog.Info("provider 已注册",
"provider", e.kind,
"active_keys", len(e.keys),
)
}
// 初始化缓存
cacheMgr, err := cache.New(cfg.Cache)
if err != nil {
slog.Error("缓存初始化失败", "error", err)
os.Exit(1)
}
// 创建 handler
handler := mcp.NewHandler(lb, cacheMgr, providerKinds)
// 启动后台健康检查(使用第一个启用的 provider 的配置参数)
if len(providerKinds) > 0 && cfg.Providers.Tavily.HealthCheck.Interval > 0 {
checker := balancer.NewHealthChecker(lb, cfg.Providers.Tavily.HealthCheck)
checker.Start()
defer checker.Stop()
}
// 配置热重载(仅在指定了配置文件时启用)
if *configPath != "" {
onChange := func(old, new *config.Config) error {
handler.ReloadConfig(old, new)
return nil
}
reloader, err := config.NewReloader(*configPath, cfg, onChange)
if err != nil {
slog.Warn("配置热重载初始化失败(不影响启动)", "error", err)
} else {
defer reloader.Stop()
}
}
// 启动 Metrics 服务
if cfg.Metrics.Enabled {
go func() {
metricsMux := http.NewServeMux()
metricsMux.Handle(cfg.Metrics.Path, promhttp.Handler())
metricsMux.HandleFunc("/", handler.HandleMonitor)
metricsMux.HandleFunc("/health", handler.HandleHealth)
metricsMuxWithLog := mcp.LoggingMiddleware(metricsMux)
metricsAddr := fmt.Sprintf(":%d", cfg.Metrics.Port)
slog.Info("metrics 服务启动", "addr", metricsAddr)
if err := http.ListenAndServe(metricsAddr, metricsMuxWithLog); err != nil {
slog.Error("metrics 服务异常", "error", err)
}
}()
}
// 根据 CLI 标志选择传输模式:
// --stdio :stdin/stdout JSON-RPC(用于 Claude Desktop / Cursor 等 MCP Client 子进程集成)
// 默认 :Streamable HTTP 模式(用于灵台注册 / 远程 MCP Client 直连)
if *stdioMode {
handler.ServeStdio()
} else {
addr := fmt.Sprintf(":%d", cfg.Server.Port)
slog.Info("博闻 HTTP 服务启动", "addr", addr)
if err := handler.ServeHTTP(addr); err != nil {
slog.Error("博闻异常退出", "error", err)
os.Exit(1)
}
}
}