Gin 与中间件
2025/7/27大约 5 分钟componentginmiddleware
概述
Gin 是一个 Go 语言编写的 Web 框架,它提供了轻量级的 API,并可快速开发服务端应用。
下面介绍了 Gin 框架开发服务端应用及中间件的使用示例。
配置
sponge 创建的 Web 服务默认使用 Gin,在 configs 目录下的 YAML 配置文件中,可通过 http 字段配置 http 服务参数:
http:
  port: 8080       # 服务监听端口号
  timeout: 0       # 请求超时时间(秒),设置为 0 表示禁用超时控制内置 gin 中间件
sponge 创建的服务已预集成以下常用中间件(实现代码位于 internal/routers/routers.go)。可根据需要选择使用(部分拦截器的配置可通过 configs 目录下的 YAML 配置文件进行调整)。
| 类别 | 功能列表 | 
|---|---|
| 基础功能 | 日志、Request ID、超时、Recovery | 
| 安全认证 | JWT 鉴权、CORS 跨域支持 | 
| 流量控制 | 自适应限流、熔断降级 | 
| 可观测性 | 链路追踪、Metrics 采集 | 
日志中间件
点击查看示例代码
import (
    "github.com/gin-gonic/gin"
    "github.com/go-dev-frame/sponge/pkg/gin/middleware"
)
func NewRouter() *gin.Engine {
    r := gin.Default()
    // ......
    // Print input parameters and return results
    // Case 1: default
    {
        r.Use(middleware.Logging()
    }
    // Case 2: custom
    {
        r.Use(middleware.Logging(
            middleware.WithLog(logger.Get()))
            middleware.WithMaxLen(400),
            middleware.WithRequestIDFromHeader(),
            //middleware.WithRequestIDFromContext(),
            //middleware.WithIgnoreRoutes("/hello"),
        ))
    }
    /*******************************************
    TIP: You can use middleware.SimpleLog instead of
           middleware.Logging, it only prints return results
    *******************************************/
    // ......
    return r
}CORS 跨域中间件
点击查看示例代码
import (
    "github.com/gin-gonic/gin"
    "github.com/go-dev-frame/sponge/pkg/gin/middleware"
)
func NewRouter() *gin.Engine {
    r := gin.Default()
    // ......
    r.Use(middleware.Cors())
    // ......
    return r
}JWT 鉴权中间件
有两种用法示例可供使用:
- 示例一:本示例采用了高度抽象的设计,使用起来更加简单方便。
点击查看示例代码
package main
import (
    "time"
    "github.com/gin-gonic/gin"
    "github.com/go-dev-frame/sponge/pkg/gin/middleware/auth"
    "github.com/go-dev-frame/sponge/pkg/gin/response"
)
func main() {
    r := gin.Default()
    // initialize jwt first
    auth.InitAuth([]byte("your-sign-key"), time.Hour*24) // default signing method is HS256
    // auth.InitAuth([]byte("your-sign-key"), time.Minute*24, WithInitAuthSigningMethod(HS512), WithInitAuthIssuer("foobar.com"))
    r.POST("/auth/login", Login)
    g := r.Group("/api/v1")
    g.Use(auth.Auth())
    //g.Use(auth.Auth(auth.WithExtraVerify(extraVerifyFn))) // add extra verify function
    g.GET("/user/:id", GetByID)
    //g.PUT("/user/:id", Create)
    //g.DELETE("/user/:id", DeleteByID)
    r.Run(":8080")
}
func Login(c *gin.Context) {
    // ......
    // Case 1: only uid for token
    {
        token, err := auth.GenerateToken("100")
    }
    // Case 2: uid and custom fields for token
    {
        uid := "100"
        fields := map[string]interface{}{
            "name":   "bob",
            "age":    10,
            "is_vip": true,
        }
        token, err := auth.GenerateToken(uid, auth.WithGenerateTokenFields(fields))
    }
    response.Success(c, token)
}
func GetByID(c *gin.Context) {
    uid := c.Param("id")
    // if necessary, claims can be got from gin context
    claims, ok := auth.GetClaims(c)
    //uid := claims.UID
    //name, _ := claims.GetString("name")
    //age, _ := claims.GetInt("age")
    //isVip, _ := claims.GetBool("is_vip")
    response.Success(c, gin.H{"id": uid})
}
func extraVerifyFn(claims *auth.Claims, c *gin.Context) error {
    // check if token is about to expire (less than 10 minutes remaining)
    if time.Now().Unix()-claims.ExpiresAt.Unix() < int64(time.Minute*10) {
        token, err := auth.RefreshToken(claims)
        if err != nil {
            return err
        }
        c.Header("X-Renewed-Token", token)
    }
    // judge whether the user is disabled, query whether jwt id exists from the blacklist
    //if CheckBlackList(uid, claims.ID) {
    //    return errors.New("user is disabled")
    //}
    return nil
}- 示例二:灵活性更强,适合自定义实现的场景。
点击查看示例代码
package main
import (
    "time"
    "github.com/gin-gonic/gin"
    "github.com/go-dev-frame/sponge/pkg/gin/middleware"
    "github.com/go-dev-frame/sponge/pkg/gin/response"
    "github.com/go-dev-frame/sponge/pkg/jwt"
)
func main() {
    r := gin.Default()
    g := r.Group("/api/v1")
    // Case 1: default jwt options, signKey, signMethod(HS256), expiry time(24 hour)
    {
        r.POST("/auth/login", LoginDefault)
        g.Use(middleware.Auth())
        //g.Use(middleware.Auth(middleware.WithExtraVerify(extraVerifyFn))) // add extra verify function
    }
    // Case 2: custom jwt options, signKey, signMethod(HS512), expiry time(48 hour), fields, claims
    {
        r.POST("/auth/login", LoginCustom)
        signKey := []byte("your-sign-key")
        g.Use(middleware.Auth(middleware.WithSignKey(signKey)))
        //g.Use(middleware.Auth(middleware.WithSignKey(signKey), middleware.WithExtraVerify(extraVerifyFn))) // add extra verify function
    }
    g.GET("/user/:id", GetByID)
    //g.PUT("/user/:id", Create)
    //g.DELETE("/user/:id", DeleteByID)
    r.Run(":8080")
}
func customGenerateToken(uid string, fields map[string]interface{}) (string, error) {
    _, token, err := jwt.GenerateToken(
        uid,
        jwt.WithGenerateTokenSignKey([]byte("custom-sign-key")),
        jwt.WithGenerateTokenSignMethod(jwt.HS512),
        jwt.WithGenerateTokenFields(fields),
        jwt.WithGenerateTokenClaims([]jwt.RegisteredClaimsOption{
            jwt.WithExpires(time.Hour * 48),
            //jwt.WithIssuedAt(now),
            // jwt.WithSubject("123"),
            // jwt.WithIssuer("https://middleware.example.com"),
            // jwt.WithAudience("https://api.example.com"),
            // jwt.WithNotBefore(now),
            // jwt.WithJwtID("abc1234xxx"),
        }...),
    )
    return token, err
}
func LoginDefault(c *gin.Context) {
    // ......
    _, token, err := jwt.GenerateToken("100")
    response.Success(c, token)
}
func LoginCustom(c *gin.Context) {
    // ......
    uid := "100"
    fields := map[string]interface{}{
        "name":   "bob",
        "age":    10,
        "is_vip": true,
    }
    token, err := customGenerateToken(uid, fields)
    response.Success(c, token)
}
func GetByID(c *gin.Context) {
    uid := c.Param("id")
    // if necessary, claims can be got from gin context.
    claims, ok := middleware.GetClaims(c)
    //uid := claims.UID
    //name, _ := claims.GetString("name")
    //age, _ := claims.GetInt("age")
    //isVip, _ := claims.GetBool("is_vip")
    response.Success(c, gin.H{"id": uid})
}
func extraVerifyFn(claims *jwt.Claims, c *gin.Context) error {
    // check if token is about to expire (less than 10 minutes remaining)
    if time.Now().Unix()-claims.ExpiresAt.Unix() < int64(time.Minute*10) {
        token, err := claims.NewToken(time.Hour*24, jwt.HS512, []byte("your-sign-key")) // same signature as jwt.GenerateToken
        if err != nil {
            return err
        }
        c.Header("X-Renewed-Token", token)
    }
    // judge whether the user is disabled, query whether jwt id exists from the blacklist
    //if CheckBlackList(uid, claims.ID) {
    //    return errors.New("user is disabled")
    //}
    return nil
}限流中间件
自适应限流是一种流量控制技术,通过限制客户端的请求速率,防止服务器过载,提高系统的稳定性。
点击查看示例代码
import (
    "github.com/gin-gonic/gin"
    "github.com/go-dev-frame/sponge/pkg/gin/middleware"
)
func NewRouter() *gin.Engine {
    r := gin.Default()
    // ......
    // Case 1: 默认参数
    r.Use(middleware.RateLimit())
    // Case 2: 自定义参数
    r.Use(middleware.RateLimit(
        middleware.WithWindow(time.Second*10),
        middleware.WithBucket(1000),
        middleware.WithCPUThreshold(100),
        // middleware.WithCPUQuota(0),
    ))
    // ......
    return r
}熔断中间件
熔断机制是一种容错机制,当系统遇到某些异常情况时,会自动切断流量,保护系统的可用性。支持熔断后降级处理,降低系统的损失。
点击查看示例代码
import (
    "github.com/gin-gonic/gin"
    "github.com/go-dev-frame/sponge/pkg/gin/middleware"
)
func NewRouter() *gin.Engine {
    r := gin.Default()
    // ......
    r.Use(middleware.CircuitBreaker(
        //middleware.WithBreakerOption(
            //circuitbreaker.WithSuccess(75),           // default 60
            //circuitbreaker.WithRequest(100),          // default 100
            //circuitbreaker.WithBucket(20),            // default 10
            //circuitbreaker.WithWindow(time.Second*3), // default 3s
        //),
        //middleware.WithDegradeHandler(handler), // add custom degrade handler
        //middleware.WithValidCode(http.StatusRequestTimeout), // add error code 408 for circuit breaker
    ))
    // ......
    return r
}链路追踪中间件
点击查看示例代码
import (
    "github.com/gin-gonic/gin"
    "github.com/go-dev-frame/sponge/pkg/gin/middleware"
    "github.com/go-dev-frame/sponge/pkg/tracer"
)
func InitTrace(serviceName string) {
    exporter, err := tracer.NewJaegerAgentExporter("192.168.3.37", "6831")
    if err != nil {
        panic(err)
    }
    resource := tracer.NewResource(
        tracer.WithServiceName(serviceName),
        tracer.WithEnvironment("dev"),
        tracer.WithServiceVersion("demo"),
    )
    tracer.Init(exporter, resource) // collect all by default
}
func NewRouter() *gin.Engine {
    r := gin.Default()
    // ......
    r.Use(middleware.Tracing("your-service-name"))
    // ......
    return r
}
// if necessary, you can create a span in the program
func CreateSpanDemo(serviceName string, spanName string, ctx context.Context) {
    _, span := otel.Tracer(serviceName).Start(
        ctx, spanName,
        trace.WithAttributes(attribute.String(spanName, time.Now().String())),
    )
    defer span.End()
    // ......
}Metrics 中间件
点击查看示例代码
import (
    "github.com/gin-gonic/gin"
    "github.com/go-dev-frame/sponge/pkg/gin/middleware"
    "github.com/go-dev-frame/sponge/pkg/gin/middleware/metrics"
)
func NewRouter() *gin.Engine {
    r := gin.Default()
    // ......
    r.Use(metrics.Metrics(r,
        //metrics.WithMetricsPath("/demo/metrics"), // default is /metrics
        metrics.WithIgnoreStatusCodes(http.StatusNotFound), // ignore status codes
        //metrics.WithIgnoreRequestMethods(http.MethodHead),  // ignore request methods
        //metrics.WithIgnoreRequestPaths("/ping", "/health"), // ignore request paths
    ))
    // ......
    return rRequest ID 中间件
点击查看示例代码
import (
    "github.com/gin-gonic/gin"
    "github.com/go-dev-frame/sponge/pkg/gin/middleware"
)
func NewRouter() *gin.Engine {
    r := gin.Default()
    // ......
    // Case 1: default request id
    {
        r.Use(middleware.RequestID())
    }
    // Case 2: custom request id key
    {
        //r.User(middleware.RequestID(
        //    middleware.WithContextRequestIDKey("your ctx request id key"), // default is request_id
        //    middleware.WithHeaderRequestIDKey("your header request id key"), // default is X-Request-Id
        //))
        // If you change the ContextRequestIDKey, you have to set the same key name if you want to print the request id in the mysql logs as well.
        // example:
        //     db, err := mysql.Init(dsn,mysql.WithLogRequestIDKey("your ctx request id key"))  // print request_id
    }
    // ......
    return r
}超时中间件
点击查看示例代码
import (
    "github.com/gin-gonic/gin"
    "github.com/go-dev-frame/sponge/pkg/gin/middleware"
)
func NewRouter() *gin.Engine {
    r := gin.Default()
    // ......
    // Case 1: global set timeout
    {
        r.Use(middleware.Timeout(time.Second*5))
    }
    // Case 2: set timeout for specifyed router
    {
        r.GET("/userExample/:id", middleware.Timeout(time.Second*3), GetByID)
    }
    // Note: If timeout is set both globally and in the router, the minimum timeout prevails
    // ......
    return r
}
func GetByID(c *gin.Context) {
    // do something
}