gin 常见中间件配置

这里主要配置 请求日志中间件、跨域中间件、trace_id 中间件、安全头中间件

一般来说,这个中间件的信息 就是放在 middlewares/* 里面的*.go 进行操作

jsx 复制代码
➜  middlewares git:(main) tree 
.
├── cors.go
├── logging.go
├── request_id.go
└── security.go

1 directory, 4 files
➜  middlewares git:(main) 

安全头中间件

middlewares/security.go

增强 Web 安全性的中间件,用于 Gin 框架中的请求处理流程中。

jsx 复制代码
package middlewares

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

func SecurityMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 设置安全头 (在处理请求之前)
		c.Header("X-Frame-Options", "DENY") //	•	防止网页被嵌入到 <iframe> 中,防止点击劫持(Clickjacking)
		c.Header("Content-Security-Policy", "frame-ancestors 'none'") //更强的防 iframe 策略,不允许任何来源嵌入本页面
		c.Header("Referrer-Policy", "no-referrer")//不发送 Referer 请求头,防止隐私泄露
		c.Header("Cross-Origin-Opener-Policy", "same-origin")
		c.Header("X-Content-Type-Options", "nosniff")
		c.Header("X-XSS-Protection", "1; mode=block") //禁止浏览器 MIME 类型嗅探,防止脚本注入

		// HTTPS 才设置 HSTS
		if c.Request.TLS != nil || c.GetHeader("X-Forwarded-Proto") == "https" {
			c.Header("Strict-Transport-Security", "max-age=63072000; includeSubDomains; preload")
		}

		// 处理请求
		c.Next() //执行下一个中间件或 handler:
	}
}

>>>>>>返回一个符合 Gin 规范的中间件函数 gin.HandlerFunc,用于设置多个安全相关的响应头。
>>>>>>func(c *gin.Context) 类型的函数(即 gin.HandlerFunc)
功能 实现方式
防 iframe 嵌套劫持 X-Frame-Options, Content-Security-Policy
禁止 Referer 泄露 Referrer-Policy
强制同源隔离 Cross-Origin-Opener-Policy
防止 MIME 嗅探 X-Content-Type-Options
启用浏览器 XSS 过滤器 X-XSS-Protection
启用 HTTPS 严格传输策略 Strict-Transport-Security(仅在 HTTPS 下设置)
jsx 复制代码
func 函数名(参数列表) 返回类型 {
    // 函数体
    return 返回值
}
func add(a int, b int) int {
    return a + b
}
函数也是一种值,就像字符串、整数一样,是可以 return 的!
func gen() func() string {
    return func() string {
        return "hello"
    }
}

跨域中间件

middlewares/cors.go

动态设置允许哪些前端域名跨域访问后端接口,防止跨域请求被浏览器拦截。

jsx 复制代码
package middlewares

import (
	"gin-api-template/utils"
	"time"

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

func CORSMiddleware() gin.HandlerFunc {
	allowedOrigins := []string{} //动态设置允许跨域的域名列表

	if utils.AppConfig != nil && len(utils.AppConfig.CORSAllowedOrigins) > 0 {
		allowedOrigins = utils.AppConfig.CORSAllowedOrigins
	} else if utils.IsDevelopment() {
		allowedOrigins = []string{
			"http://localhost:3000",
			"http://localhost:8000",
			"http://127.0.0.1:3000",
		}
	}

	config := cors.Config{
		AllowOrigins:     allowedOrigins,
		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowHeaders:     []string{"Origin", "Content-Type", "Authorization", "X-Request-ID"},
		ExposeHeaders:    []string{"X-Request-ID"},
		AllowCredentials: true,
		MaxAge:           12 * time.Hour,
	}

	return cors.New(config)
}
字段 含义
AllowOrigins 允许哪些域名跨域访问
AllowMethods 允许的请求方法(默认 OPTIONS 也必须允许)
AllowHeaders 前端允许携带哪些 Header 发起请求
ExposeHeaders 浏览器允许访问响应头中的哪些字段(如 X-Request-ID)
AllowCredentials 是否允许携带 Cookie
MaxAge 预检请求的缓存时长(12 小时内不会再次发 OPTIONS)

trace_id 中间件

middlewares/request_id.go

✅ 为每一个请求自动分配或传递一个唯一的 Request ID,用于日志追踪、请求链路追踪、错误排查等。

jsx 复制代码
package middlewares

import (
	"context"

	"gin-api-template/utils"

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

const RequestIDKey = "X-Request-ID"

// RequestIDMiddleware 请求ID中间件
func RequestIDMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		// 先检查请求头中是否已有 Request ID
		requestID := c.GetHeader(RequestIDKey)

		// 如果没有,则生成新的 UUID
		if requestID == "" {
			requestID = uuid.New().String()
		}

		// 将 Request ID 存储到 Context 中
		c.Set(RequestIDKey, requestID)

		// 创建带 Request ID 的 context
		ctx := context.WithValue(context.Background(), utils.RequestIDKey, requestID)

		// 设置到全局 context (当前 goroutine)
		utils.SetRequestContext(ctx)

		// 设置响应头
		c.Header(RequestIDKey, requestID)

		// 继续处理请求
		c.Next()

		// 请求结束后清理 context
		utils.ClearRequestContext()
	}
}
  • 日志加上 RequestID,便于搜索同一个请求的全链路日志
  • 链路追踪(如 Zipkin、Jaeger)
  • 接口调试时,前端可将 X-Request-ID 提交给后端排查问题

请求响应日志中间件

middlewares/logging.go

复制代码
package middlewares

import (
	"bytes"
	"fmt"
	"io"
	"time"

	"gin-api-template/utils"

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

// LoggingMiddleware API请求响应日志中间件
func LoggingMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		startTime := time.Now()

		// 读取请求体
		var requestBody []byte
		if c.Request.Body != nil {
			requestBody, _ = io.ReadAll(c.Request.Body)
			// 重新设置请求体,因为读取后会被消耗
			c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
		}

		// 创建自定义的响应写入器来捕获响应
		blw := &bodyLogWriter{body: bytes.NewBufferString(""), ResponseWriter: c.Writer}
		c.Writer = blw

		// 记录请求信息 - 自动包含 Request ID
		utils.LogInfo(fmt.Sprintf("Request: %s %s | IP: %s | User-Agent: %s | Body: %s",
			c.Request.Method,
			c.Request.URL.Path,
			c.ClientIP(),
			c.Request.UserAgent(),
			string(requestBody)))

		// 处理请求
		c.Next()

		// 计算处理时间
		duration := time.Since(startTime)

		// 记录响应信息 - 自动包含 Request ID
		utils.LogInfo(fmt.Sprintf("Response: %s %s | Status: %d | Duration: %v | Response: %s",
			c.Request.Method,
			c.Request.URL.Path,
			c.Writer.Status(),
			duration,
			blw.body.String()))
	}
}

// bodyLogWriter 自定义响应写入器
type bodyLogWriter struct {
	gin.ResponseWriter
	body *bytes.Buffer
}

func (w bodyLogWriter) Write(b []byte) (int, error) {
	w.body.Write(b)
	return w.ResponseWriter.Write(b)
}

注册中间件

routers/main.go

jsx 复制代码
package router

import (
	"gin-api-template/middlewares"

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

func SetupRouter() *gin.Engine {
	// 使用 gin.New() 而不是 gin.Default(),避免默认的日志中间件
	r := gin.New()

	// 添加 Recovery 中间件(防止 panic 导致服务崩溃)
	r.Use(gin.Recovery())
	// 使用安全头中间件 (应该在最前面)
	r.Use(middlewares.SecurityMiddleware())
	// 使用 CORS 中间件
	r.Use(middlewares.CORSMiddleware())

	// 使用 Request ID 中间件
	r.Use(middlewares.RequestIDMiddleware())

	// 使用我们自定义的日志中间件
	r.Use(middlewares.LoggingMiddleware())

	// 注册各个模块的路由
	setupHealthRoutes(r)
	// setupUserRoutes(r)
	// setupAuthRoutes(r)
	// setupProductRoutes(r)
	// 在这里添加更多路由模块...

	return r
}
相关推荐
阿昌喜欢吃黄桃16 天前
RocketMq事务消息原理
java·中间件·消息队列·rocketmq·mq
半夜修仙17 天前
延迟队列的介绍及常见问题
java·数据库·中间件·rabbitmq
手握风云-17 天前
一条消息的旅程:RabbitMQ 学习与实践(一)
中间件·rabbitmq
RH23121118 天前
2026.6.8Linux
java·数据库·中间件
理人综艺好会19 天前
双Token机制在实际项目中的应用与实践
中间件·token
番茄去哪了19 天前
神领物流面试题(一)
java·大数据·中间件
念何架构之路19 天前
消息中间件
中间件
都说名字长不会被发现19 天前
Spring Boot Starter 中间件账号密码加密方案设计与实现
java·spring boot·后端·中间件
瀚高PG实验室20 天前
java中间件无法连接数据库
java·数据库·中间件·瀚高数据库
之歆20 天前
Day11_Express 深入解析:从中间件到项目实战
中间件·express