bootstrap.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. package agentmcp
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "strings"
  8. "github.com/hibiken/asynq"
  9. "github.com/spf13/viper"
  10. "go.uber.org/zap"
  11. "go.uber.org/zap/zapcore"
  12. "fwh-toolbox-server/cache"
  13. "fwh-toolbox-server/config"
  14. "fwh-toolbox-server/database"
  15. "fwh-toolbox-server/logger"
  16. "fwh-toolbox-server/repositories"
  17. task_core "fwh-toolbox-server/task/core"
  18. "fwh-toolbox-server/task/queue/faultRecordQueue"
  19. "fwh-toolbox-server/task/queue/fwhQueue"
  20. "fwh-toolbox-server/utils/emspara"
  21. "fwh-toolbox-server/utils/minio"
  22. )
  23. // DefaultConfigFile fwh-agent-mcp 的独立配置文件(相对工作目录),与主服务的 config.env、旧 MCP 的 config.mcp.env 互不共用。
  24. // 示例见 deploy/config.agent-mcp.env.example。
  25. const DefaultConfigFile = "./config.agent-mcp.env"
  26. // Settings fwh-agent-mcp 自身的运行配置(AGENT_MCP_*)
  27. type Settings struct {
  28. Token string // AGENT_MCP_TOKEN:Bearer 令牌,必填
  29. SetTokens map[string]string // AGENT_MCP_TOKEN_<工具集大写>(如 AGENT_MCP_TOKEN_OPS):该工具集单独的令牌,可选,未配置时用 Token
  30. Listen string // AGENT_MCP_LISTEN:默认 127.0.0.1:8091
  31. MaxConcurrency int // AGENT_MCP_MAX_CONCURRENCY:默认 8
  32. LogPath string // AGENT_MCP_LOG_PATH:默认 /var/log/fwh-agent-mcp/agent-mcp.log
  33. }
  34. // LoadSettings 从 configFile 读取 AGENT_MCP_*,环境变量优先。
  35. func LoadSettings(configFile string) (Settings, error) {
  36. v := viper.New()
  37. v.SetDefault("AGENT_MCP_LISTEN", "127.0.0.1:8091")
  38. v.SetDefault("AGENT_MCP_MAX_CONCURRENCY", 8)
  39. v.SetDefault("AGENT_MCP_LOG_PATH", "/var/log/fwh-agent-mcp/agent-mcp.log")
  40. v.SetConfigFile(configFile)
  41. v.SetConfigType("env")
  42. if err := v.ReadInConfig(); err != nil {
  43. return Settings{}, fmt.Errorf("读取配置文件 %s 失败: %w", configFile, err)
  44. }
  45. v.AutomaticEnv()
  46. setTokens := map[string]string{}
  47. for _, set := range AllSets {
  48. if t := strings.TrimSpace(v.GetString(setTokenKey(set))); t != "" {
  49. setTokens[set] = t
  50. }
  51. }
  52. return Settings{
  53. Token: v.GetString("AGENT_MCP_TOKEN"),
  54. SetTokens: setTokens,
  55. Listen: v.GetString("AGENT_MCP_LISTEN"),
  56. MaxConcurrency: v.GetInt("AGENT_MCP_MAX_CONCURRENCY"),
  57. LogPath: v.GetString("AGENT_MCP_LOG_PATH"),
  58. }, nil
  59. }
  60. func setTokenKey(set string) string {
  61. return "AGENT_MCP_TOKEN_" + strings.ToUpper(set)
  62. }
  63. // TokenFor 返回工具集使用的令牌:AGENT_MCP_TOKEN_<工具集> 优先,否则 AGENT_MCP_TOKEN。
  64. func (s Settings) TokenFor(set string) string {
  65. if t := s.SetTokens[set]; t != "" {
  66. return t
  67. }
  68. return s.Token
  69. }
  70. // InitLogger 初始化日志:写入独立的日志文件;目录不可写时退回 stderr。
  71. // 同时设置 zap 全局 logger 与 logger 包变量(database 等模块直接引用 logger.Sugar)。
  72. func InitLogger(path string) *zap.Logger {
  73. outputs := []string{"stderr"}
  74. if path != "" {
  75. if err := os.MkdirAll(filepath.Dir(path), 0o755); err == nil {
  76. outputs = []string{path}
  77. } else {
  78. fmt.Fprintf(os.Stderr, "[fwh-agent-mcp] 创建日志目录失败,改为输出到 stderr: %v\n", err)
  79. }
  80. }
  81. cfg := zap.Config{
  82. Level: zap.NewAtomicLevelAt(zap.InfoLevel),
  83. Encoding: "json",
  84. EncoderConfig: zapcore.EncoderConfig{
  85. TimeKey: "ts",
  86. LevelKey: "level",
  87. NameKey: "logger",
  88. CallerKey: "caller",
  89. MessageKey: "msg",
  90. StacktraceKey: "stacktrace",
  91. LineEnding: zapcore.DefaultLineEnding,
  92. EncodeLevel: zapcore.LowercaseLevelEncoder,
  93. EncodeTime: zapcore.ISO8601TimeEncoder,
  94. EncodeDuration: zapcore.SecondsDurationEncoder,
  95. EncodeCaller: zapcore.ShortCallerEncoder,
  96. },
  97. OutputPaths: outputs,
  98. ErrorOutputPaths: outputs,
  99. }
  100. l, err := cfg.Build(zap.AddStacktrace(zap.ErrorLevel))
  101. if err != nil {
  102. fmt.Fprintf(os.Stderr, "[fwh-agent-mcp] logger 初始化失败: %v\n", err)
  103. l = zap.NewNop()
  104. }
  105. zap.ReplaceGlobals(l)
  106. logger.Log = l
  107. logger.Sugar = l.Sugar()
  108. logger.Std = zap.NewStdLog(l)
  109. return l
  110. }
  111. // InitInfra 从 configFile 加载配置并初始化工具用到的基础设施:数据库、Redis、MinIO、FWH 生产/测试 API、FWH ES、Plane、任务队列入队客户端。
  112. func InitInfra(configFile string) error {
  113. cfg, err := config.LoadConfig(configFile)
  114. if err != nil {
  115. return fmt.Errorf("加载配置失败: %w", err)
  116. }
  117. if err := database.Init(&cfg.App); err != nil {
  118. return fmt.Errorf("数据库初始化失败: %w", err)
  119. }
  120. cache.Init(cfg.App.RedisAddr)
  121. // ems_param_lookup 依赖的 EMS 参数字典(部署目录的 emspara.db -> Redis),与主服务相同
  122. emspara.Init(emspara.DefaultDBPath)
  123. if err := repositories.Init(context.Background()); err != nil {
  124. return fmt.Errorf("Repository 初始化失败: %w", err)
  125. }
  126. repositories.FwhHttpInit(&cfg.FwhApi)
  127. repositories.FwhTestHttpInit(&cfg.FwhTestApi)
  128. // MQTT 报文统计(iotcore-stat Elasticsearch):mqtt_traffic_*、mqtt_message_search
  129. repositories.EsHttpInit(&cfg.FwhEs)
  130. // 必须用 InitPlane(含 Feedback 专用实例 repositories.Plane),否则 feedback_create 会空指针
  131. repositories.InitPlane(&cfg.PlaneWebApi)
  132. // 只入队、不启动 worker:site_info_refresh、故障录波操作投递的任务由主服务执行(含 MQTT 收发)
  133. taskRedis := asynq.RedisClientOpt{Addr: cfg.App.RedisAddr, DB: task_core.RedisDB}
  134. fwhQueue.InitEnqueueClient(taskRedis)
  135. faultRecordQueue.InitEnqueueClient(taskRedis)
  136. // 故障录波文件(fault-record/ 前缀)的读取;使用 AI 只读账号即可
  137. minio.InitClient(&cfg.App)
  138. return nil
  139. }