在 Go 后端开发中,中间件(Middleware)是承载日志、鉴权、限流、监控、Tracing 等横切逻辑的核心方案。它的价值在于:把通用增强逻辑与核心业务解耦,让功能可插拔、可组合、可扩展。
本文围绕一个完整可运行的“订单创建”示例,讲清楚两种经典的中间件链路组装方式:
- 递归组装(ComposeRecursive):更贴合理论推导,易讲清“从后往前包裹”
- 反向迭代组装(ComposeReverse):工程里更常用,边界更少、无递归栈
并用输出日志直观验证:调用链遵循洋葱模型(请求逐层进入,响应逐层返回)。
一、中间件链路的核心思想:从后往前包裹
我们把“服务”抽象为一个接口,把“中间件”抽象为一个包装器:输入 next 服务,返回增强后的服务。
- 核心服务:
S - 中间件:
A、B、C(传入顺序为[A, B, C])
最终组装结构(外到内)是:
A( B( C( S ) ) )
执行顺序(洋葱模型)是:
- 请求进入:A 前置 → B 前置 → C 前置 → S(核心逻辑)
- 响应返回:S → C 后置 → B 后置 → A 后置
这种结构的好处是:你新增/移除/调整中间件顺序,不需要改核心业务代码。
二、示例目标:订单服务 + 三层中间件(日志/鉴权/限流)
我们用一个最贴近业务的例子演示:
- 核心服务:创建订单
- 日志中间件:记录请求进入与结束、耗时、错误
- 鉴权中间件:token 校验,失败短路
- 限流中间件:每秒最多 N 次,超限短路(并发安全)
并分别用两种 Compose 方式组装链路,验证效果一致。
三、完整可运行代码(复制即可运行)
保存为
main.go,执行:go run main.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 递归下去:
return ComposeRecursive(last.Wrap(core), rest...)
对 [Log, Auth, RateLimit] 和 Core 来说:
RateLimit(Core)Auth(RateLimit(Core))Log(Auth(RateLimit(Core)))
最终仍是 Log(Auth(RateLimit(Core)))。
2)反向迭代版 ComposeReverse:更“工程化”
关键点:从后往前遍历中间件数组,不断把 svc 重新赋值为“被包裹后的服务”。
svc := core
for i := len(middlewares)-1; i >= 0; i-- {
svc = middlewares[i].Wrap(svc)
}
return svc
结构完全一致,但实现更直观、边界更少,也不会产生递归调用栈。
五、调用链可视化:洋葱模型
中间件传入顺序:日志 -> 鉴权 -> 限流
组装后结构(外 → 内):
日志(
鉴权(
限流(
核心订单服务
)
)
)
一次成功请求的执行顺序:
- 日志:打印“请求进入”
- 鉴权:校验 token
- 限流:计数 + 判断阈值
- 核心服务:创建订单
- 日志:打印“请求结束”(包含耗时、错误)
当鉴权失败或限流超限时:会短路返回,不会进入更内层服务。