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