package main import ( "context" "flag" "fmt" "net/http" "os" "os/signal" "syscall" "time" "ezops/internal/api/routes" "ezops/internal/config" "ezops/pkg/database" "ezops/pkg/logger" ezredis "ezops/pkg/redis" "github.com/gin-gonic/gin" "github.com/spf13/viper" "go.uber.org/zap" ) // 版本信息(通过编译时 ldflags 注入)// Logan.Li var ( Version = "dev" BuildTime = "unknown" GitCommit = "unknown" ) // CLI 参数定义 // Logan.Li var ( flagPort = flag.String("port", "", "服务端口 (默认: 8080)") flagConfig = flag.String("config", "", "配置文件路径 (默认: config.yaml)") flagMode = flag.String("mode", "", "运行模式: development, production, release") flagLogLevel = flag.String("log-level", "", "日志级别: debug, info, warn, error") // 数据库参数 flagDBDriver = flag.String("db-driver", "", "数据库驱动: sqlite, mysql, postgres (默认: sqlite)") flagDBHost = flag.String("db-host", "", "数据库主机") flagDBPort = flag.Int("db-port", 0, "数据库端口") flagDBUser = flag.String("db-user", "", "数据库用户名") flagDBPassword = flag.String("db-password", "", "数据库密码") flagDBName = flag.String("db-name", "", "数据库名 / SQLite 文件路径") flagDBCharset = flag.String("db-charset", "", "数据库字符集") // Redis 参数 flagRedisHost = flag.String("redis-host", "", "Redis 主机") flagRedisPort = flag.Int("redis-port", 0, "Redis 端口") flagRedisPassword = flag.String("redis-password", "", "Redis 密码") flagRedisDB = flag.Int("redis-db", 0, "Redis 数据库编号") // JWT 参数 flagJWTSecret = flag.String("jwt-secret", "", "JWT 密钥") flagJWTExpire = flag.Int("jwt-expire", 0, "JWT 过期时间 (小时)") // 显示版本 flagVersion = flag.Bool("version", false, "显示版本信息") ) // @title AIOps运维平台 API // @version 1.0 // @description AIOps智能运维平台后端API文档 // @termsOfService http://swagger.io/terms/ // @contact.name API Support // @contact.url http://www.swagger.io/support // @contact.email support@swagger.io // @license.name Apache 2.0 // @license.url http://www.apache.org/licenses/LICENSE-2.0.html // @host localhost:8080 // @BasePath /api/v1 // @securityDefinitions.apikey Bearer // @in header // @name Authorization // @description Type "Bearer" followed by a space and JWT token. func main() { // 解析 CLI 参数 // Logan.Li flag.Parse() // 显示版本信息 // Logan.Li if *flagVersion { fmt.Printf("AIOps 智能运维平台\n") fmt.Printf(" 版本: %s\n", Version) fmt.Printf(" 构建: %s\n", BuildTime) fmt.Printf(" 提交: %s\n", GitCommit) os.Exit(0) } // 加载配置 // Logan.Li cfg := loadConfig() // 初始化日志 log := logger.New(cfg.LogLevel) defer log.Sync() log.Info("AIOps 启动中", zap.String("version", Version), zap.String("mode", cfg.Mode), zap.String("port", cfg.Port), ) // 设置Gin模式 if cfg.Mode == "production" || cfg.Mode == "release" { gin.SetMode(gin.ReleaseMode) } // 初始化数据库 db, err := database.New(cfg.Database) if err != nil { log.Fatal("数据库连接失败", zap.Error(err)) } // 自动迁移 if err := database.AutoMigrate(db); err != nil { log.Fatal("数据库迁移失败", zap.Error(err)) } // 初始化种子数据 seedData(db) // 初始化缓存(Redka 内嵌 / Redis 外部)// Logan.Li cache, err := ezredis.New(ezredis.Config{ Driver: cfg.Redis.Driver, // 从配置读取 driver Host: cfg.Redis.Host, Port: cfg.Redis.Port, Password: cfg.Redis.Password, DB: cfg.Redis.DB, DataDir: "data/redka", }) if err != nil { log.Warn("缓存初始化失败,继续启动", zap.Error(err)) cache = nil } else { log.Info("缓存已就绪", zap.String("driver", cfg.Redis.Driver)) defer cache.Close() } // 创建Gin实例 r := gin.New() // 中间件 r.Use(gin.Recovery()) r.Use(logger.GinLogger(log)) r.Use(CORS()) // 版本信息 API r.GET("/version", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "version": Version, "build_time": BuildTime, "git_commit": GitCommit, "mode": cfg.Mode, "db_driver": cfg.Database.Driver, }) }) // 注册路由 routes.Register(r, db, log, cfg, cache) // 前端静态文件服务(go:embed 模式,二进制部署用) // Logan.Li setupFrontend(r) // 创建HTTP服务器 srv := &http.Server{ Addr: ":" + cfg.Port, Handler: r, } // 优雅关闭 go func() { log.Info("服务已启动", zap.String("addr", srv.Addr)) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatal("服务启动失败", zap.Error(err)) } }() // 等待中断信号 quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Info("正在关闭服务...") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal("强制关闭服务", zap.Error(err)) } log.Info("服务已退出") } // loadConfig 加载配置(CLI flags 优先于 config.yaml)// Logan.Li func loadConfig() *config.Config { viper.SetConfigName("config") viper.SetConfigType("yaml") viper.AddConfigPath("./configs") viper.AddConfigPath(".") // 如果指定了配置文件路径,使用它 // Logan.Li if *flagConfig != "" { viper.SetConfigFile(*flagConfig) } // 默认值(独立二进制模式:SQLite + 无 Redis)// Logan.Li viper.SetDefault("mode", "release") viper.SetDefault("port", "8080") viper.SetDefault("log_level", "info") viper.SetDefault("database.driver", "sqlite") viper.SetDefault("database.host", "localhost") viper.SetDefault("database.port", 3306) viper.SetDefault("database.user", "") viper.SetDefault("database.password", "") viper.SetDefault("database.dbname", "data/aiops.db") viper.SetDefault("database.charset", "utf8mb4") viper.SetDefault("database.max_conn", 100) viper.SetDefault("database.max_idle", 10) viper.SetDefault("redis.host", "localhost") viper.SetDefault("redis.port", 6379) viper.SetDefault("redis.driver", "redka") viper.SetDefault("redis.password", "") viper.SetDefault("redis.db", 0) viper.SetDefault("jwt.secret", "") // 生产环境必须通过配置或 JWT_SECRET 环境变量设置 viper.SetDefault("jwt.expire_hour", 24) viper.SetDefault("websocket.read_buffer_size", 1024) viper.SetDefault("websocket.write_buffer_size", 1024) // 读取配置文件(不存在也不报错)// Logan.Li if err := viper.ReadInConfig(); err != nil { // 配置文件不存在是正常的(独立二进制模式) if _, ok := err.(viper.ConfigFileNotFoundError); !ok { fmt.Fprintf(os.Stderr, "警告: 读取配置文件失败: %v,使用默认配置\n", err) } } // 环境变量覆盖 // Logan.Li viper.AutomaticEnv() // CLI flags 覆盖(最高优先级)// Logan.Li applyFlags() // 解析配置 // Logan.Li var cfg config.Config if err := viper.Unmarshal(&cfg); err != nil { fmt.Fprintf(os.Stderr, "配置解析失败: %v\n", err) os.Exit(1) } return &cfg } // applyFlags 将 CLI 参数映射到 viper(覆盖 config.yaml 和默认值)// Logan.Li func applyFlags() { // 检查是否有任何 flag 被显式设置 // Logan.Li flag.Visit(func(f *flag.Flag) { switch f.Name { case "port": viper.Set("port", *flagPort) case "mode": viper.Set("mode", *flagMode) case "log-level": viper.Set("log_level", *flagLogLevel) case "db-driver": viper.Set("database.driver", *flagDBDriver) case "db-host": viper.Set("database.host", *flagDBHost) case "db-port": viper.Set("database.port", *flagDBPort) case "db-user": viper.Set("database.user", *flagDBUser) case "db-password": viper.Set("database.password", *flagDBPassword) case "db-name": viper.Set("database.dbname", *flagDBName) case "db-charset": viper.Set("database.charset", *flagDBCharset) case "redis-host": viper.Set("redis.host", *flagRedisHost) case "redis-port": viper.Set("redis.port", *flagRedisPort) case "redis-password": viper.Set("redis.password", *flagRedisPassword) case "redis-db": viper.Set("redis.db", *flagRedisDB) case "jwt-secret": viper.Set("jwt.secret", *flagJWTSecret) case "jwt-expire": viper.Set("jwt.expire_hour", *flagJWTExpire) } }) } // CORS 跨域中间件 func CORS() gin.HandlerFunc { return func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS") c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") c.Writer.Header().Set("Access-Control-Expose-Headers", "Content-Length") c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() } }