package main import ( "context" "errors" "log/slog" "net/http" "os" "os/signal" "strconv" "syscall" "time" "goblog/config" "goblog/database" "goblog/models" "goblog/routers" "goblog/utils" "github.com/gin-gonic/gin" "gorm.io/gorm" ) func main() { // 加载配置 cfg := config.Load() // 设置 Gin 模式 gin.SetMode(cfg.Server.Mode) // 初始化日志系统(使用 Server.LogLevel) logger := newLogger(cfg.Server.LogLevel) slog.SetDefault(logger) slog.Info("启动博客系统", "app", cfg.App.Name, "theme", cfg.App.Theme, "db_driver", cfg.Database.Driver, "port", cfg.Server.Port, "mode", cfg.Server.Mode, "log_level", cfg.Server.LogLevel, ) // 初始化数据库 if err := database.Init(&cfg.Database); err != nil { slog.Error("数据库初始化失败", "error", err) os.Exit(1) } // defer database.Close() // 如果提供 // 从数据库加载主题(若不存在则使用配置值) theme, err := loadTheme(cfg.App.Theme) if err != nil { slog.Error("加载主题失败", "error", err) os.Exit(1) } // 创建路由引擎(使用自定义 slog 中间件) r := gin.New() r.Use(gin.Recovery()) r.Use(slogMiddleware(logger)) // 配置模板渲染器(现在 LoadTemplates 返回 error) htmlRenderer, err := utils.LoadTemplates(theme) if err != nil { slog.Error("加载模板失败", "error", err, "theme", theme) os.Exit(1) } utils.SetGlobalRenderer(htmlRenderer) r.HTMLRender = htmlRenderer // 配置所有路由 routers.SetupRoutes(r) // 启动服务器(优雅关闭) runServer(r, cfg.Server.Port) } // newLogger 根据配置字符串创建 slog.Logger func newLogger(level string) *slog.Logger { var lvl slog.Level if err := lvl.UnmarshalText([]byte(level)); err != nil { lvl = slog.LevelInfo // 默认 info } opts := &slog.HandlerOptions{Level: lvl} return slog.New(slog.NewTextHandler(os.Stdout, opts)) } // loadTheme 从数据库读取系统主题,若不存在则使用 fallback func loadTheme(fallback string) (string, error) { var option models.Option err := database.DB.Where("name = ? AND user_id = ?", "theme", 0).First(&option).Error if err == nil { slog.Info("从数据库加载主题", "theme", option.Value) return option.Value, nil } if errors.Is(err, gorm.ErrRecordNotFound) { slog.Info("使用配置文件中的主题", "theme", fallback) return fallback, nil } return "", err // 真实错误必须返回 } // slogMiddleware 自定义请求日志中间件 func slogMiddleware(logger *slog.Logger) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path c.Next() logger.Info("HTTP请求", "method", c.Request.Method, "path", path, "status", c.Writer.Status(), "duration", time.Since(start), "client_ip", c.ClientIP(), ) } } // runServer 启动 HTTP 服务并支持优雅关闭 func runServer(r *gin.Engine, port int) { addr := ":" + strconv.Itoa(port) srv := &http.Server{ Addr: addr, Handler: r, } go func() { slog.Info("服务器启动成功", "addr", addr) if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { slog.Error("服务器启动失败", "error", err) os.Exit(1) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit slog.Info("正在关闭服务器...") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { slog.Error("服务器强制关闭", "error", err) } else { slog.Info("服务器已优雅关闭") } }