Go中间件:递归组装与反向迭代组装

在 Go 后端开发中,中间件(Middleware)是承载日志、鉴权、限流、监控、Tracing 等横切逻辑的核心方案。它的价值在于:把通用增强逻辑与核心业务解耦,让功能可插拔、可组合、可扩展。

本文围绕一个完整可运行的"订单创建"示例,讲清楚两种经典的中间件链路组装方式:

  • 递归组装(ComposeRecursive):更贴合理论推导,易讲清"从后往前包裹"
  • 反向迭代组装(ComposeReverse):工程里更常用,边界更少、无递归栈

并用输出日志直观验证:调用链遵循洋葱模型(请求逐层进入,响应逐层返回)。


一、中间件链路的核心思想:从后往前包裹

我们把"服务"抽象为一个接口,把"中间件"抽象为一个包装器:输入 next 服务,返回增强后的服务。

  • 核心服务:S
  • 中间件:A、B、C(传入顺序为 [A, B, C]

最终组装结构(外到内)是:

less 复制代码
A( B( C( S ) ) )

执行顺序(洋葱模型)是:

  • 请求进入:A 前置 → B 前置 → C 前置 → S(核心逻辑)
  • 响应返回:S → C 后置 → B 后置 → A 后置

这种结构的好处是:你新增/移除/调整中间件顺序,不需要改核心业务代码


二、示例目标:订单服务 + 三层中间件(日志/鉴权/限流)

我们用一个最贴近业务的例子演示:

  • 核心服务:创建订单
  • 日志中间件:记录请求进入与结束、耗时、错误
  • 鉴权中间件:token 校验,失败短路
  • 限流中间件:每秒最多 N 次,超限短路(并发安全)

并分别用两种 Compose 方式组装链路,验证效果一致。


三、完整可运行代码(复制即可运行)

保存为 main.go,执行:go run main.go

go 复制代码
package main

import (
	"context"
	"fmt"
	"sync"
	"time"
)

/*
	========================
	1) 抽象:服务 & 中间件
	========================
*/

type IOrderService interface {
	CreateOrder(ctx context.Context, req *CreateOrderReq) (*CreateOrderResp, error)
}

type CreateOrderReq struct {
	UserID  string
	Token   string
	GoodsID string
	Amount  int64
}

type CreateOrderResp struct {
	OrderID string
	Success bool
}

type IOrderMiddleware interface {
	Wrap(next IOrderService) IOrderService
}

/*
	========================
	2) 核心业务服务
	========================
*/

type OrderService struct{}

func (s *OrderService) CreateOrder(ctx context.Context, req *CreateOrderReq) (*CreateOrderResp, error) {
	orderID := fmt.Sprintf("ORDER_%d", time.Now().UnixNano()/1e3) // 微秒级
	fmt.Printf("[核心订单服务] user=%s 创建订单成功 orderID=%s\n", req.UserID, orderID)

	return &CreateOrderResp{
		OrderID: orderID,
		Success: true,
	}, nil
}

/*
	========================
	3) 中间件实现:日志/鉴权/限流
	========================
*/

// --- 3.1 日志中间件(前置+后置)

type LogMiddleware struct{}

func (m *LogMiddleware) Wrap(next IOrderService) IOrderService {
	return &logOrderService{next: next}
}

type logOrderService struct {
	next IOrderService
}

func (l *logOrderService) CreateOrder(ctx context.Context, req *CreateOrderReq) (*CreateOrderResp, error) {
	start := time.Now()
	fmt.Printf("[日志中间件] -> 请求进入 user=%s goods=%s at=%s\n",
		req.UserID, req.GoodsID, start.Format("2006-01-02 15:04:05.000"))

	resp, err := l.next.CreateOrder(ctx, req)

	cost := time.Since(start)
	orderID := ""
	if resp != nil {
		orderID = resp.OrderID
	}
	fmt.Printf("[日志中间件] <- 请求结束 orderID=%s cost=%v err=%v\n", orderID, cost, err)
	return resp, err
}

// --- 3.2 鉴权中间件(失败短路)

type AuthMiddleware struct {
	ValidToken string
}

func (m *AuthMiddleware) Wrap(next IOrderService) IOrderService {
	return &authOrderService{next: next, validToken: m.ValidToken}
}

type authOrderService struct {
	next       IOrderService
	validToken string
}

func (a *authOrderService) CreateOrder(ctx context.Context, req *CreateOrderReq) (*CreateOrderResp, error) {
	if req.Token != a.validToken {
		fmt.Printf("[鉴权中间件] 鉴权失败 user=%s token=%s\n", req.UserID, req.Token)
		return &CreateOrderResp{Success: false}, fmt.Errorf("token invalid")
	}
	fmt.Printf("[鉴权中间件] 鉴权通过 user=%s\n", req.UserID)
	return a.next.CreateOrder(ctx, req)
}

// --- 3.3 限流中间件(每秒最多 N 次;并发安全)

type RateLimitMiddleware struct {
	MaxPerSec int

	mu      sync.Mutex
	window  time.Time
	counter int
}

func (m *RateLimitMiddleware) Wrap(next IOrderService) IOrderService {
	return &rateLimitOrderService{next: next, limiter: m}
}

type rateLimitOrderService struct {
	next    IOrderService
	limiter *RateLimitMiddleware
}

func (r *rateLimitOrderService) CreateOrder(ctx context.Context, req *CreateOrderReq) (*CreateOrderResp, error) {
	now := time.Now()

	r.limiter.mu.Lock()
	defer r.limiter.mu.Unlock()

	// 以 1 秒为窗口
	if r.limiter.window.IsZero() || now.Sub(r.limiter.window) >= time.Second {
		r.limiter.window = now
		r.limiter.counter = 0
	}

	r.limiter.counter++
	if r.limiter.counter > r.limiter.MaxPerSec {
		fmt.Printf("[限流中间件] 请求超限 user=%s qps=%d limit=%d\n",
			req.UserID, r.limiter.counter, r.limiter.MaxPerSec)
		return &CreateOrderResp{Success: false}, fmt.Errorf("rate limit exceeded")
	}

	fmt.Printf("[限流中间件] 请求通过 user=%s qps=%d\n", req.UserID, r.limiter.counter)
	return r.next.CreateOrder(ctx, req)
}

/*
	========================
	4) Compose 两种写法
	========================
*/

// 4.1 递归写法:从后往前包裹
// middlewares[0] 最外层,middlewares[n-1] 最内层
func ComposeRecursive(core IOrderService, middlewares ...IOrderMiddleware) IOrderService {
	n := len(middlewares)
	if n == 0 {
		return core
	}
	// 先让"最后一个中间件"包裹 core,再递归组装剩余中间件
	last := middlewares[n-1]
	rest := middlewares[:n-1]
	return ComposeRecursive(last.Wrap(core), rest...)
}

// 4.2 反向迭代写法:从后往前包裹(工程上更常用)
// middlewares[0] 最外层,middlewares[n-1] 最内层
func ComposeReverse(core IOrderService, middlewares ...IOrderMiddleware) IOrderService {
	svc := core
	for i := len(middlewares) - 1; i >= 0; i-- {
		svc = middlewares[i].Wrap(svc)
	}
	return svc
}

/*
	========================
	5) main:分别测试两种 Compose 的效果一致
	========================
*/

func main() {
	core := &OrderService{}

	mws := []IOrderMiddleware{
		&LogMiddleware{},
		&AuthMiddleware{ValidToken: "VALID_TOKEN_2026"},
		&RateLimitMiddleware{MaxPerSec: 2},
	}

	fmt.Println("====================================================")
	fmt.Println("使用 ComposeRecursive(递归版)")
	fmt.Println("====================================================")
	svc1 := ComposeRecursive(core, mws...)
	runDemo(svc1)

	// 为了让两个 demo 互不影响(尤其限流窗口/计数),重新创建一套中间件实例
	mws2 := []IOrderMiddleware{
		&LogMiddleware{},
		&AuthMiddleware{ValidToken: "VALID_TOKEN_2026"},
		&RateLimitMiddleware{MaxPerSec: 2},
	}

	fmt.Println("\n====================================================")
	fmt.Println("使用 ComposeReverse(反向迭代版)")
	fmt.Println("====================================================")
	svc2 := ComposeReverse(core, mws2...)
	runDemo(svc2)
}

func runDemo(svc IOrderService) {
	fmt.Println("----- 合法请求 1 -----")
	req1 := &CreateOrderReq{UserID: "user_001", Token: "VALID_TOKEN_2026", GoodsID: "goods_1001", Amount: 99}
	resp1, err1 := svc.CreateOrder(context.Background(), req1)
	fmt.Printf("结果1 resp=%+v err=%v\n\n", resp1, err1)

	fmt.Println("----- 合法请求 2 -----")
	req2 := &CreateOrderReq{UserID: "user_001", Token: "VALID_TOKEN_2026", GoodsID: "goods_1002", Amount: 199}
	resp2, err2 := svc.CreateOrder(context.Background(), req2)
	fmt.Printf("结果2 resp=%+v err=%v\n\n", resp2, err2)

	fmt.Println("----- 超限请求 3(同一秒内第3次)-----")
	req3 := &CreateOrderReq{UserID: "user_001", Token: "VALID_TOKEN_2026", GoodsID: "goods_1003", Amount: 299}
	resp3, err3 := svc.CreateOrder(context.Background(), req3)
	fmt.Printf("结果3 resp=%+v err=%v\n\n", resp3, err3)

	time.Sleep(time.Second) // 让限流窗口重置,便于演示鉴权失败(不被限流挡住)

	fmt.Println("----- 非法请求 4(Token错误)-----")
	req4 := &CreateOrderReq{UserID: "user_002", Token: "INVALID_TOKEN_2026", GoodsID: "goods_1004", Amount: 399}
	resp4, err4 := svc.CreateOrder(context.Background(), req4)
	fmt.Printf("结果4 resp=%+v err=%v\n", resp4, err4)
}

四、两种 Compose 的组装过程详解

1)递归版 ComposeRecursive:更"像推导题"

关键点:每次取最后一个中间件 last,先包裹当前 core,然后把结果当成新的 core 递归下去:

go 复制代码
return ComposeRecursive(last.Wrap(core), rest...)

[Log, Auth, RateLimit]Core 来说:

  1. RateLimit(Core)
  2. Auth(RateLimit(Core))
  3. Log(Auth(RateLimit(Core)))

最终仍是 Log(Auth(RateLimit(Core)))

2)反向迭代版 ComposeReverse:更"工程化"

关键点:从后往前遍历中间件数组,不断把 svc 重新赋值为"被包裹后的服务"。

go 复制代码
svc := core
for i := len(middlewares)-1; i >= 0; i-- {
    svc = middlewares[i].Wrap(svc)
}
return svc

结构完全一致,但实现更直观、边界更少,也不会产生递归调用栈。


五、调用链可视化:洋葱模型

中间件传入顺序:日志 -> 鉴权 -> 限流

组装后结构(外 → 内):

markdown 复制代码
日志(
  鉴权(
    限流(
      核心订单服务
    )
  )
)

一次成功请求的执行顺序:

  1. 日志:打印"请求进入"
  2. 鉴权:校验 token
  3. 限流:计数 + 判断阈值
  4. 核心服务:创建订单
  5. 日志:打印"请求结束"(包含耗时、错误)

当鉴权失败或限流超限时:会短路返回,不会进入更内层服务。

相关推荐
熙胤8 分钟前
Spring Boot 3.x 引入springdoc-openapi (内置Swagger UI、webmvc-api)
spring boot·后端·ui
tumeng071116 分钟前
springboot项目架构
spring boot·后端·架构
LES000LIE19 分钟前
Spring Cloud
后端·spring·spring cloud
mldlds40 分钟前
Spring Boot应用关闭分析
java·spring boot·后端
zjjsctcdl42 分钟前
Spring Boot与MyBatis
spring boot·后端·mybatis
tuyanfei1 小时前
Spring 简介
java·后端·spring
代码探秘者1 小时前
【大模型应用】2.RAG详细流程
java·开发语言·人工智能·后端·python
神奇小汤圆1 小时前
网易一面:KAFKA写入数据时是先写Leader还是先写Follower?
后端
baizhigangqw1 小时前
Spring Boot spring.factories文件详细说明
spring boot·后端·spring