Skip to content

middleware & 优雅关机

实现一个中间件 LoggerMiddleware: 拿个秒表,计算每个请求耗时多久(性能监控)。

1. 创建中间件文件

创建目录和文件:internal/middleware/common.go

Go

package middleware

import (
	"log"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
)

// LoggerMiddleware 记录请求耗时
func LoggerMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 1. 开始计时
		start := time.Now()
		path := c.Request.URL.Path
		method := c.Request.Method

		// 2. 让请求继续往前走,去执行下一个中间件或 Handler
		// ⚠️ 关键点:c.Next() 就像“放行”
		c.Next()

		// 3. 请求处理完了,回来计算耗时
		latency := time.Since(start)

		// 4. 打印日志
		// status := c.Writer.Status()
		log.Printf("[REQ] %s %s | %v | %d\n", method, path, latency, c.Writer.Status())
	}
}

// AuthMiddleware 简单的 Token 认证
func AuthMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 1. 获取请求头里的 token
		token := c.GetHeader("Authorization")

		// 2. 校验 token (这里简单写死,实际应该去查 Redis 或校验 JWT)
		if token != "secret_password_123" {
			// ❌ 认证失败
			c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized: invalid token"})

			// ⚠️ 关键点:Abort() 必须调用,否则请求虽然返回了 401,但还会继续往后走!
			c.Abort()
			return
		}

		// ✅ 认证通过,放行
		c.Next()
	}
}

🛑 架构师经验:c.Next()c.Abort()

  • c.Next(): 这是一个“回旋镖”。代码执行到这里,会挂起当前函数,先去执行后面的 Handler。等后面的做完了,再回来执行 c.Next() 下面的代码(常用于计算耗时)。
  • c.Abort(): 这里的坑最大!如果你检测到错误(如没权限),光 return 是不够的,必须调用 c.Abort(),明确告诉 Gin:“停止处理链,不要再调后面的 Handler 了”。

main.go 修改

go
        ....
	// --- 注册中间件 ---
	// 注册 Logger (所有请求都会经过它)
	r.Use(middleware.LoggerMiddleware())

	// 5. 注册路由
	.....

优雅关机

go
        // 6. 启动服务 (使用配置里的端口)
	//addr := ":" + conf.Server.Port
	//fmt.Printf("Starting server on %s...\n", addr)
	//r.Run(addr)

	// --- 启动服务器 (优雅关机版) ---

	// 定义 http.Server
	srv := &http.Server{
		Addr:    ":" + conf.Server.Port,
		Handler: r,
	}

	// 在 Goroutine 中启动服务器
	// 为什么?因为 srv.ListenAndServe 会阻塞,如果不放进 goroutine,后面的代码永远执行不到
	go func() {
		fmt.Printf("Starting server on %s...\n", conf.Server.Port)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("listen: %s\n", err)
		}
	}()

	// --- 监听关闭信号 ---
	// 创建一个通道,专门接收系统信号
	quit := make(chan os.Signal, 1)

	// kill (不带参数) 默认发送 syscall.SIGTERM
	// kill -2 (Ctrl+C) 发送 syscall.SIGINT
	// kill -9 是强制杀死,捕获不到的,别试这个
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)

	// 这里会阻塞,直到收到信号
	<-quit
	log.Println("Shutting down server...")

	// --- 执行优雅关机 ---
	// 创建一个 5 秒超时的 Context
	// 意思是:我给你 5 秒钟时间把手头的活儿干完,干不完我也要强行关机了
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := srv.Shutdown(ctx); err != nil {
		log.Fatal("Server forced to shutdown: ", err)
	}

	log.Println("Server exiting")