Files
go-skeleton/main.go
T

171 lines
4.4 KiB
Go

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("应用程序已优雅关闭")
}