package main import ( "context" "fwh-toolbox-server/cache" "fwh-toolbox-server/config" "fwh-toolbox-server/controllers" "fwh-toolbox-server/database" "fwh-toolbox-server/repositories" "fwh-toolbox-server/repositories/api/fwh_pro" "fwh-toolbox-server/repositories/api/fwh_test" "fwh-toolbox-server/task" "fwh-toolbox-server/task/queue/faultRecordQueue" "fwh-toolbox-server/utils/casdoor" "fwh-toolbox-server/utils/minio" "log" "net" "net/http" "os" "os/signal" // "fwh-toolbox-server/database" _ "fwh-toolbox-server/docs" // swag 自动生成文档 "fwh-toolbox-server/logger" "fwh-toolbox-server/routes" "fwh-toolbox-server/scheduler" "fwh-toolbox-server/networkEvents" "github.com/gofiber/fiber/v2" "github.com/gofiber/swagger" "github.com/hibiken/asynq" "github.com/hibiken/asynqmon" "go.uber.org/zap" "golang.org/x/sys/unix" ) func main() { var cfg *config.Config var err error cfg, err = config.LoadConfig() if err != nil { panic(err) } err = logger.Init(cfg.App.AppEnv, cfg.App.LogLevel) if err != nil { panic(err) } defer logger.Log.Sync() // 退出前同步zap缓冲区 // 初始化数据库 err = database.Init(&cfg.App) if err != nil { zap.L().Error("database init failed.", zap.String("err", err.Error())) panic(err) } // 初始化缓存 cache.Init(cfg.App.RedisAddr) // 初始化Minio minio.InitClient(&cfg.App) // 初始化 Repository migration/index;必须在 scheduler/worker 之前完成。 if err = repositories.Init(context.Background()); err != nil { zap.L().Error("repository init failed", zap.String("err", err.Error())) panic(err) } // 初始化FwhApi repositories.FwhHttpInit(&cfg.FwhApi) // 初始化FwhTestApi(测试环境) repositories.FwhTestHttpInit(&cfg.FwhTestApi) // 初始化Fwh ES(iotcore-stat 报文统计) repositories.EsHttpInit(&cfg.FwhEs) // 初始化PlaneApi repositories.PlaneHttpInit(&cfg.PlaneApi) // 初始化PlaneWebApi // 初始化新架构 Plane(PlaneClient + PlaneService) repositories.InitPlane(&cfg.PlaneWebApi) // 初始化计划任务 scheduler.Init() // 初始化任务 task.InitTask(task.TaskRedisOpt{ Addr: cfg.App.RedisAddr, DB: 12, }, &cfg.App) // 初始化Casdoor casdoor.InitAuthConfig() // 初始化FWH MQTT客户端(正式服、测试服各一个实例,单个环境失败不影响服务启动) if err := repositories.FwhMqttInit(repositories.FwhMqttEnvProd, &cfg.FwhMqtt); err != nil { zap.L().Error("FWH MQTT init failed.", zap.String("env", "prod"), zap.String("err", err.Error())) } if err := repositories.FwhMqttInit(repositories.FwhMqttEnvTest, &cfg.FwhTestMqtt); err != nil { zap.L().Error("FWH MQTT init failed.", zap.String("env", "test"), zap.String("err", err.Error())) } // 恢复服务重启前未完成的故障录波操作(依赖任务系统和 MQTT 已初始化) faultRecordQueue.RecoverActive(context.Background()) app := fiber.New() app.Use(func(c *fiber.Ctx) error { logger.Log.Info("请求", zap.String("path", c.Path())) return c.Next() }) // 注册 Swagger 路由 app.Get("/swagger/*", swagger.HandlerDefault) routes.Setup(app) // TOTP 页面使用独立端口,不占用主 APP 的路由。 var totpApp *fiber.App if cfg.FwhApi.FwhTotpSecret != "" && cfg.App.TotpViewPassword != "" { if cfg.App.TotpViewPort == cfg.App.AppPort { panic("FWH_TOTP_VIEW_PORT must differ from APP_PORT") } totpApp = fiber.New() routes.TotpViewSetup(totpApp, cfg.FwhApi.FwhTotpSecret, cfg.App.TotpViewPassword) go func() { addr := net.JoinHostPort(cfg.App.TotpViewHost, cfg.App.TotpViewPort) if err := totpApp.Listen(addr); err != nil { zap.L().Error("TOTP view port listening failed", zap.String("addr", addr), zap.Error(err)) panic(err) } }() } // 初始化 AI Chat (ACP Client) controllers.InitAIChat(".", cfg.Kiro.RunAsUser, cfg.Kiro.Home, cfg.Kiro.DebugLog) // 初始化新 AI 助手(AgentServer) controllers.InitAgent(cfg.Agent) // 启动第一个监听 go func() { if err := networkEvents.InitEvio(); err != nil { zap.L().Error("networkEvents init failed.", zap.String("err", err.Error())) panic(err) } }() // 启动Asynqmon界面 go func() { h := asynqmon.New(asynqmon.Options{ RootPath: "/asynqmon", // RootPath specifies the root for asynqmon app RedisConnOpt: asynq.RedisClientOpt{Addr: cfg.App.RedisAddr, DB: 12}, }) // Note: We need the tailing slash when using net/http.ServeMux. http.Handle("/", h) listenAddr := "0.0.0.0:" + cfg.App.AsynqmonPort // Go to http://localhost:8080/asynqmon to see asynqmon homepage. err = http.ListenAndServe(listenAddr, nil) if err != nil { zap.L().Error("Asynqmon port listening failed.", zap.String("err", err.Error())) panic(err) } log.Println("asynqmon shutdown finished") zap.L().Info("Asynqmon started successfully.", zap.String("addr", listenAddr)) }() // 提前登录FWH err = fwh_pro.Login() if err != nil { zap.L().Error("FWH login failed.", zap.String("err", err.Error())) // panic(err) } // 提前登录FWH测试环境 err = fwh_test.Login() if err != nil { zap.L().Error("FWH test login failed.", zap.String("err", err.Error())) // panic(err) } // TEST // err = task.CreateTaskSimCardCollect() // if err != nil { // zap.L().Error("CreateTaskSimCardCollect failed.", zap.String("err", err.Error()), zap.String("time(UTC)", time.Now().UTC().Format(time.DateTime))) // } go func() { sigs := make(chan os.Signal, 1) signal.Notify(sigs, unix.SIGTERM, unix.SIGINT) <-sigs log.Println("fiber shutdown") if totpApp != nil { _ = totpApp.Shutdown() } app.Shutdown() }() err = app.Listen("localhost:" + cfg.App.AppPort) if err != nil { zap.L().Error("Port listening failed.", zap.String("err", err.Error())) panic(err) } }