Files
go_typecho/main.go
T

152 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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))
r.Use(gin.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("服务器已优雅关闭")
}
}