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")