这里主要配置 请求日志中间件、跨域中间件、trace_id 中间件、安全头中间件
一般来说,这个中间件的信息 就是放在 middlewares/* 里面的*.go 进行操作
➜ 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 框架中的请求处理流程中。
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 下设置) |
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
动态设置允许哪些前端域名跨域访问后端接口,防止跨域请求被浏览器拦截。
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,用于日志追踪、请求链路追踪、错误排查等。
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
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
}