feat: run.sh四模式(dev/docker/build/binary) + CLI flags + Redka内嵌Redis + Agent复制修复 + 服务器编辑IP字段 + 安装Agent按钮

This commit is contained in:
attacker committed 2026-06-18 03:36:04 +00:00
1 parent d0b94d39c0
commit 2354959811
14 files changed
+1355 -575

No files matched your search

+189 -13
View File
@@ -2,6 +2,8 @@ package main
import (
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
@@ -12,18 +14,50 @@ import (
"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 注入)
// 版本信息(通过编译时 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文档
@@ -45,32 +79,67 @@ var (
// @description Type "Bearer" followed by a space and JWT token.
func main() {
// 加载配置
cfg := config.Load()
// 解析 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" {
if cfg.Mode == "production" || cfg.Mode == "release" {
gin.SetMode(gin.ReleaseMode)
}
// 初始化数据库
db, err := database.New(cfg.Database)
if err != nil {
log.Fatal("failed to connect database", zap.Error(err))
log.Fatal("数据库连接失败", zap.Error(err))
}
// 自动迁移
if err := database.AutoMigrate(db); err != nil {
log.Fatal("failed to migrate database", zap.Error(err))
log.Fatal("数据库迁移失败", zap.Error(err))
}
// 初始化种子数据
seedData(db)
// 初始化缓存(Redka 内嵌 / Redis 外部)// Logan.Li
cache, err := ezredis.New(ezredis.Config{
Driver: "redka", // 默认内嵌 Redka,零依赖
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", "redka"))
defer cache.Close()
}
// 创建Gin实例
r := gin.New()
@@ -85,11 +154,13 @@ func main() {
"version": Version,
"build_time": BuildTime,
"git_commit": GitCommit,
"mode": cfg.Mode,
"db_driver": cfg.Database.Driver,
})
})
// 注册路由
routes.Register(r, db, log, cfg)
routes.Register(r, db, log, cfg, cache)
// 创建HTTP服务器
srv := &http.Server{
@@ -99,28 +170,133 @@ func main() {
// 优雅关闭
go func() {
log.Info("服务已启动", zap.String("addr", srv.Addr))
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal("failed to start server", zap.Error(err))
log.Fatal("服务启动失败", zap.Error(err))
}
}()
log.Info("server started", zap.String("port", cfg.Port))
// 等待中断信号
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Info("shutting down server...")
log.Info("正在关闭服务...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("server forced to shutdown", zap.Error(err))
log.Fatal("强制关闭服务", zap.Error(err))
}
log.Info("server exited")
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.password", "")
viper.SetDefault("redis.db", 0)
viper.SetDefault("jwt.secret", "aiops-jwt-secret-change-in-production")
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 跨域中间件