Gin 与中间件
大约 5 分钟componentginmiddleware
基础配置
在 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 r
Request 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
}