package main import ( "context" "os" "os/signal" "strconv" "syscall" "github.com/bytedance/sonic" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/compress" "github.com/gofiber/fiber/v2/middleware/requestid" "github.com/google/uuid" "github.com/redis/go-redis/v9" "go.uber.org/zap" "github.com/break/junhong_cmp_fiber/internal/handler" "github.com/break/junhong_cmp_fiber/internal/middleware" "github.com/break/junhong_cmp_fiber/pkg/config" "github.com/break/junhong_cmp_fiber/pkg/logger" "github.com/break/junhong_cmp_fiber/pkg/validator" ) func main() { // 加载配置 cfg, err := config.Load() if err != nil { panic("加载配置失败: " + err.Error()) } // 初始化日志 if err := logger.InitLoggers( cfg.Logging.Level, cfg.Logging.Development, logger.LogRotationConfig{ Filename: cfg.Logging.AppLog.Filename, MaxSize: cfg.Logging.AppLog.MaxSize, MaxBackups: cfg.Logging.AppLog.MaxBackups, MaxAge: cfg.Logging.AppLog.MaxAge, Compress: cfg.Logging.AppLog.Compress, }, logger.LogRotationConfig{ Filename: cfg.Logging.AccessLog.Filename, MaxSize: cfg.Logging.AccessLog.MaxSize, MaxBackups: cfg.Logging.AccessLog.MaxBackups, MaxAge: cfg.Logging.AccessLog.MaxAge, Compress: cfg.Logging.AccessLog.Compress, }, ); err != nil { panic("初始化日志失败: " + err.Error()) } defer func() { _ = logger.Sync() // 忽略 sync 错误(shutdown 时可能已经关闭) }() appLogger := logger.GetAppLogger() appLogger.Info("应用程序启动中...", zap.String("address", cfg.Server.Address), ) redisAddr := cfg.Redis.Address + ":" + strconv.Itoa(cfg.Redis.Port) // 连接 Redis redisClient := redis.NewClient(&redis.Options{ Addr: redisAddr, Password: cfg.Redis.Password, DB: cfg.Redis.DB, PoolSize: cfg.Redis.PoolSize, MinIdleConns: cfg.Redis.MinIdleConns, DialTimeout: cfg.Redis.DialTimeout, ReadTimeout: cfg.Redis.ReadTimeout, WriteTimeout: cfg.Redis.WriteTimeout, }) defer func() { if err := redisClient.Close(); err != nil { appLogger.Error("关闭 Redis 客户端失败", zap.Error(err)) } }() // 测试 Redis 连接 ctx := context.Background() if err := redisClient.Ping(ctx).Err(); err != nil { appLogger.Fatal("连接 Redis 失败", zap.Error(err)) } appLogger.Info("Redis 已连接", zap.String("address", redisAddr)) // 创建令牌验证器 tokenValidator := validator.NewTokenValidator(redisClient, appLogger) // 启动配置文件监听器(热重载) watchCtx, cancelWatch := context.WithCancel(context.Background()) defer cancelWatch() go config.Watch(watchCtx, appLogger) // 创建 Fiber 应用 app := fiber.New(fiber.Config{ AppName: "君鸿卡管系统 v1.0.0", StrictRouting: true, CaseSensitive: true, JSONEncoder: sonic.Marshal, JSONDecoder: sonic.Unmarshal, Prefork: cfg.Server.Prefork, ReadTimeout: cfg.Server.ReadTimeout, WriteTimeout: cfg.Server.WriteTimeout, }) // 中间件注册(顺序很重要) // 1. Recover - 必须第一个,捕获所有 panic app.Use(middleware.Recover(appLogger)) // 2. RequestID - 为每个请求生成唯一 ID app.Use(requestid.New(requestid.Config{ Generator: func() string { return uuid.NewString() }, })) // 3. Logger - 记录所有请求 app.Use(logger.Middleware()) // 4. Compress - 响应压缩 app.Use(compress.New(compress.Config{ Level: compress.LevelDefault, })) // 路由注册 // 公共端点(无需认证) app.Get("/health", handler.HealthCheck) // API v1 路由组 v1 := app.Group("/api/v1") // 受保护的端点(需要认证) if cfg.Middleware.EnableAuth { v1.Use(middleware.KeyAuth(tokenValidator, appLogger)) } // 可选:启用限流器 if cfg.Middleware.EnableRateLimiter { var rateLimitStorage fiber.Storage // 根据配置选择存储后端 if cfg.Middleware.RateLimiter.Storage == "redis" { rateLimitStorage = middleware.NewRedisStorage( cfg.Redis.Address, cfg.Redis.Password, cfg.Redis.DB, cfg.Redis.Port, ) appLogger.Info("限流器使用 Redis 存储", zap.String("redis_address", cfg.Redis.Address)) } else { rateLimitStorage = nil // 使用内存存储 appLogger.Info("限流器使用内存存储") } v1.Use(middleware.RateLimiter( cfg.Middleware.RateLimiter.Max, cfg.Middleware.RateLimiter.Expiration, rateLimitStorage, )) } // 注册受保护的路由 v1.Get("/users", handler.GetUsers) // 优雅关闭 quit := make(chan os.Signal, 1) signal.Notify(quit, os.Interrupt, syscall.SIGTERM) go func() { if err := app.Listen(cfg.Server.Address); err != nil { appLogger.Fatal("服务器启动失败", zap.Error(err)) } }() appLogger.Info("服务器已启动", zap.String("address", cfg.Server.Address)) // 等待关闭信号 <-quit appLogger.Info("正在关闭服务器...") // 取消配置监听器 cancelWatch() // 关闭 HTTP 服务器 if err := app.ShutdownWithTimeout(cfg.Server.ShutdownTimeout); err != nil { appLogger.Error("强制关闭服务器", zap.Error(err)) } appLogger.Info("服务器已停止") }