package main import ( "context" "fmt" "log" "net/http" "os" "os/signal" "skeleton/config" "skeleton/database" "skeleton/docs" "skeleton/middlewares" "skeleton/observability" "skeleton/routes" _ "skeleton/routes/rest" // 导入触发 init() 自动注册路由 "skeleton/utils" "syscall" "time" "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" "go.uber.org/zap" ) var buildVersion = "dev" // @title Skeleton API // @version 0.1.0 // @description Skeleton API documentation. // @BasePath /api // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization // @description 输入 Bearer {token} func main() { // 加载配置 cfg, err := config.Load() if err != nil { log.Fatal("加载配置失败:", err) } if buildVersion != "dev" { cfg.App.Version = buildVersion } // 初始化日志系统 if err := middlewares.InitLogger(&cfg.Logger); err != nil { log.Fatal("初始化日志系统失败:", err) } // 初始化JWT配置 middlewares.InitJWT(cfg.JWT.Secret, cfg.JWT.AccessExpireMinutes, cfg.JWT.RefreshExpireHours, cfg.JWT.Issuer) // 确保在程序退出时同步日志缓冲区 defer middlewares.Sync() shutdownTracing, err := observability.InitTracing(context.Background(), cfg.Observability) if err != nil { zap.L().Fatal("链路追踪初始化失败", zap.Error(err)) } defer func() { shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := shutdownTracing(shutdownCtx); err != nil { zap.L().Error("链路追踪关闭失败", zap.Error(err)) } }() // 使用结构化日志记录启动信息 zap.L().Info("应用程序启动", zap.String("version", cfg.App.Version), zap.String("environment", cfg.App.Environment), zap.Bool("debug", cfg.App.Debug), ) // 初始化时区 if err := utils.InitTimezone(cfg.App.Timezone); err != nil { zap.L().Warn("时区初始化警告", zap.Error(err)) } else { zap.L().Info("时区设置成功", zap.String("timezone", cfg.App.Timezone)) } // 初始化数据库 if err := database.Init(&cfg.Database, zap.L()); err != nil { zap.L().Fatal("数据库初始化失败", zap.Error(err)) } // 初始化Redis服务 if err := database.InitRedis(cfg, zap.L()); err != nil { zap.L().Fatal("Redis服务初始化失败", zap.Error(err)) } // 设置路由 r := routes.SetupRoutes(cfg) // 挂载 Swagger UI,文档由 `swag init` 根据注解生成。 docs.SwaggerInfo.Version = cfg.App.Version r.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) // 创建HTTP服务器 srv := &http.Server{ Addr: cfg.GetAddr(), Handler: r, } // 启动服务器 go func() { zap.L().Info("HTTP 服务器启动", zap.String("address", cfg.GetAddr()), zap.String("version", cfg.App.Version), zap.String("environment", cfg.App.Environment), zap.Bool("debug", cfg.App.Debug), ) fmt.Printf("服务器启动在: %s (版本: %s, 环境: %s)\n", cfg.GetAddr(), cfg.App.Version, cfg.App.Environment) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { zap.L().Fatal("启动服务器失败", zap.Error(err)) } }() // 等待关闭信号 quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit zap.L().Info("正在关闭服务器...") gracefulShutdown(srv) } // gracefulShutdown 执行优雅关闭 func gracefulShutdown(srv *http.Server) { // 设置关闭超时 ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() // 1. 关闭HTTP服务器 zap.L().Info("正在关闭HTTP服务器...") if err := srv.Shutdown(ctx); err != nil { zap.L().Error("HTTP服务器关闭失败", zap.Error(err)) // 强制关闭 if err := srv.Close(); err != nil { zap.L().Error("强制关闭HTTP服务器失败", zap.Error(err)) } } else { zap.L().Info("HTTP服务器关闭成功") } // 2. 关闭数据库连接 if err := database.Close(); err != nil { zap.L().Error("关闭数据库连接失败", zap.Error(err)) } else { zap.L().Info("数据库连接已关闭") } // 3. 关闭Redis连接 if redisClient := database.GetRedisClient(); redisClient != nil { if err := redisClient.Close(); err != nil { zap.L().Error("关闭Redis连接失败", zap.Error(err)) } else { zap.L().Info("Redis连接已关闭") } } // 4. 同步日志缓冲区 middlewares.Sync() zap.L().Info("应用程序已优雅关闭") }