Go 中间件开发
Go 包管理
Go Modules 基础
Go Modules 是 Go 官方从 1.11 版本引入、1.14 版本正式推荐的依赖管理方案。项目根目录下的 go.mod 文件定义了模块的路径和依赖信息。
初始化模块
go mod init github.com/user/my-project该命令创建 go.mod 文件,内容如下:
module github.com/user/my-project
go 1.22核心指令说明
| 指令 | 作用 |
|---|---|
module | 声明模块路径,通常是代码仓库地址 |
go | 声明 Go 版本,用于指定语言特性启用范围 |
require | 声明依赖模块及其版本 |
replace | 替换依赖模块的获取路径,常用于本地开发调试或解决冲突 |
exclude | 排除某个特定版本的依赖,阻止其被使用 |
retract | 标记已发布的版本为撤回状态,提示用户不要使用 |
require 指令
require (
github.com/gin-gonic/gin v1.9.1
github.com/redis/go-redis/v9 v9.3.0
)replace 指令
将远程依赖替换为本地路径或其他版本,用于调试或解决依赖冲突:
replace github.com/gin-gonic/gin => ../local/gin
// 或替换为其他版本
replace github.com/gin-gonic/gin => github.com/gin-gonic/gin v1.8.2exclude 指令
排除有缺陷的版本:
exclude github.com/gin-gonic/gin v1.9.0indirect 标记
go.mod 中版本号末尾标注 // indirect 表示该依赖非当前模块直接引用,而是被其他间接依赖引入。当直接依赖尚未将其自身依赖写入 go.mod 时,Go 工具链会自动补充 indirect 依赖。
incompatible 标记
go.mod 中版本号末尾标注 // incompatible 表示该模块未使用 go.mod 文件(或未遵循 Go Modules 规范),但 Go 工具链仍然尝试兼容处理。从 Go 1.21 开始,此标记已较少出现。
模块版本管理
语义化版本
Go Modules 严格遵循语义化版本规范 vMAJOR.MINOR.PATCH:
| 字段 | 含义 | 变更场景 |
|---|---|---|
| MAJOR | 主版本号 | 不兼容的 API 变更 |
| MINOR | 次版本号 | 向下兼容的功能新增 |
| PATCH | 修订号 | 向下兼容的问题修复 |
v2+ 路径约定
当主版本号升级到 v2 或更高时,Go Modules 要求在模块路径后附加版本后缀,以实现不同主版本的共存:
// v1 版本
module github.com/user/my-lib
// v2 版本
module github.com/user/my-lib/v2引用时也需要带上后缀:
import "github.com/user/my-lib/v2/pkg/component"主流版本号规则
- 稳定版本:
v1.0.0、v1.2.3 - 预发布版本:
v1.0.0-beta.1、v1.0.0-rc.1(带有预发布后缀的版本优先级低于对应正式版本) - 伪版本号:
v0.0.0-20231201000000-abcdef123456(基于某个 commit 自动生成,格式为vX.Y.Z-<时间戳>-<commit hash>)
tag 命名规范
Git tag 必须与模块版本号一致,Go 工具链通过 tag 定位特定版本:
git tag v1.0.0
git tag v2.0.0
git push origin --tags对于预发布版本:
git tag v1.1.0-alpha.1
git tag v1.1.0-rc.1incompatible 标记
对于未引入 go.mod 的旧项目,Go Modules 会尝试兼容处理。当主版本 >= v2 且模块路径不含 /v2 后缀时,引用该模块的 go.mod 中会出现 +incompatible 标记。新项目不应使用此类模块,建议升级依赖。
retract 指令撤回版本
当某个版本发布后发现有严重问题,可以通过 retract 指令标记撤回:
retract (
v1.0.5 // 包含安全漏洞
v1.0.4 // CI 构建失败
)撤回后,外部用户在运行 go get 或 go mod tidy 时不会自动升级到该版本,且 go list -m -versions 会将其标记为撤回状态。
依赖管理
go.sum 文件
go.sum 文件记录所有依赖模块的校验和(哈希值),用于防篡改验证。每条记录包含模块版本及其 go.mod 文件的哈希值:
github.com/gin-gonic/gin v1.9.1 h1:ABC...=
github.com/gin-gonic/gin v1.9.1/go.mod h1:XYZ...=go.sum 不应手工编辑,由 go mod tidy 自动维护。建议将其纳入版本控制。
go.mod 文件
go.mod 是模块的声明文件,记录了模块路径、Go 版本和所有直接依赖。与 go.sum 配合使用,go.mod 记录声明式的依赖版本,go.sum 提供完整性验证。
GOPROXY 配置
Go Modules 通过 GOPROXY 环境变量配置代理源,加速依赖下载:
# 默认配置
go env -w GOPROXY=https://proxy.golang.org,direct
# 国内常用代理
go env -w GOPROXY=https://goproxy.cn,direct
# 私有仓库跳过代理
go env -w GOPROXY=https://proxy.golang.org,direct
go env -w GOPRIVATE=github.com/mycompany/*GOPROXY 支持逗号分隔的多个地址,Go 会依次尝试。direct 为特殊关键字,表示直接从源码仓库获取。
GOPATH vs Go Modules 对比
| 特性 | GOPATH 模式 | Go Modules 模式 |
|---|---|---|
| 项目位置 | 必须在 $GOPATH/src 下 | 任意目录 |
| 版本管理 | 所有依赖使用同一版本(latest) | 支持语义化版本和依赖图 |
| 依赖锁定 | 无 | go.sum 锁定校验和 |
| 依赖目录 | $GOPATH/pkg/mod | $GOPATH/pkg/mod(缓存) |
| 构建一致性 | 不保证 | go.sum + go.mod 保证 |
| 多项目依赖 | 共用同一版本,冲突频繁 | 每个项目独立版本管理 |
| go get 行为 | 下载到 $GOPATH/src | 更新 go.mod |
Go Modules 具体说明
- 模块缓存位于
$GOPATH/pkg/mod/cache,可通过go clean -modcache清理。 go mod tidy自动添加缺失的依赖、移除未使用的依赖。go mod download预先下载所有依赖到本地缓存。go mod vendor生成 vendor 目录。go mod verify验证本地缓存中的依赖是否与 go.sum 匹配。
vendor 目录
vendor 目录将项目的所有依赖代码直接复制到当前项目内:
go mod vendor生成 vendor/ 目录和 vendor/modules.txt 清单文件。
何时使用 vendor:
| 场景 | 说明 |
|---|---|
| 离线构建 | 内网环境无法访问外部依赖仓库 |
| 构建确定性 | 确保每次构建使用完全相同的源码(包括被删除或修改的远端版本) |
| CI/CD 加速 | 避免每次构建时重复下载依赖 |
| 代码审计 | 需要审查所有依赖源码的合规性 |
使用 vendor 后,通过 -mod=vendor 参数强制启用:
go build -mod=vendor ./...注意:vendor 目录不应与 go.sum 冲突,两者是互补关系——vendor 提供源码,go.sum 提供完整性验证。
Go HTTP 中间件
标准库 net/http 中间件模式
Handler 接口
Go 标准库 net/http 定义了核心的 HTTP 处理器接口:
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}任何实现了 ServeHTTP(ResponseWriter, *Request) 方法的类型都满足 Handler 接口。
HandlerFunc 适配器
http.HandlerFunc 是一个函数类型适配器,将普通函数转换为 Handler:
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}这使得普通函数可以直接作为 Handler 使用:
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, World!"))
}
http.Handle("/hello", http.HandlerFunc(helloHandler))中间件函数签名
Go HTTP 中间件的标准函数签名是 func(http.Handler) http.Handler,接收一个 Handler 并返回一个新的 Handler,在内部对请求和响应进行包装处理:
type Middleware func(http.Handler) http.Handler链式调用
通过嵌套调用实现中间件链:
func Chain(handler http.Handler, middlewares ...Middleware) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}使用示例:
mux := http.NewServeMux()
mux.HandleFunc("/api", apiHandler)
wrappedHandler := Chain(mux, LoggerMiddleware, AuthMiddleware, CORSMiddleware)
http.ListenAndServe(":8080", wrappedHandler)常用中间件实现
日志中间件(Logging)
记录每个请求的方法、路径、状态码和耗时:
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 包装 ResponseWriter 以捕获状态码
lrw := &loggingResponseWriter{ResponseWriter: w, statusCode: http.StatusOK}
next.ServeHTTP(lrw, r)
duration := time.Since(start)
log.Printf("[%s] %s %s %d %v",
r.Method, r.URL.Path, r.RemoteAddr, lrw.statusCode, duration)
})
}
type loggingResponseWriter struct {
http.ResponseWriter
statusCode int
}
func (lrw *loggingResponseWriter) WriteHeader(code int) {
lrw.statusCode = code
lrw.ResponseWriter.WriteHeader(code)
}鉴权中间件(Auth)
验证请求中的认证令牌:
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" || !isValidToken(token) {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// 将用户信息注入 context
ctx := context.WithValue(r.Context(), "user", extractUser(token))
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func isValidToken(token string) bool {
// 验证 token 逻辑
return len(token) > 10
}
func extractUser(token string) string {
// 从 token 解析用户信息
return "user-123"
}限流中间件(Rate Limiting)
基于令牌桶算法的限流实现:
func RateLimitMiddleware(next http.Handler) http.Handler {
limiter := rate.NewLimiter(100, 200) // 每秒 100 个请求,突发 200 个
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}CORS 中间件
处理跨域请求:
func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
w.Header().Set("Access-Control-Max-Age", "86400")
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}Recovery 中间件
捕获 panic 并返回 500 错误,防止服务崩溃:
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
buf := make([]byte, 4096)
n := runtime.Stack(buf, false)
log.Printf("panic recovered: %v\n%s", err, buf[:n])
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}RequestID 中间件
为每个请求注入唯一追踪 ID:
type contextKey string
const RequestIDKey contextKey = "request_id"
func RequestIDMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestID := r.Header.Get("X-Request-ID")
if requestID == "" {
requestID = uuid.New().String()
}
w.Header().Set("X-Request-ID", requestID)
ctx := context.WithValue(r.Context(), RequestIDKey, requestID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}完整示例:中间件链组合
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/users", usersHandler)
mux.HandleFunc("/api/health", healthHandler)
// 构建中间件链
handler := Chain(
mux,
RecoveryMiddleware,
RequestIDMiddleware,
LoggingMiddleware,
CORSMiddleware,
RateLimitMiddleware,
)
server := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
}
log.Fatal(server.ListenAndServe())
}第三方路由
Gin
Gin 是 Go 生态中流行的 HTTP 框架,以高性能著称,基于 httprouter 实现路由匹配。
import "github.com/gin-gonic/gin"
func main() {
r := gin.New() // 创建无默认中间件的路由
r.Use(gin.Logger()) // 添加日志中间件
r.Use(gin.Recovery()) // 添加恢复中间件
r.GET("/users/:id", func(c *gin.Context) {
id := c.Param("id")
c.JSON(http.StatusOK, gin.H{"user_id": id})
})
r.Run(":8080")
}Gorilla Mux
Gorilla Mux 是 Go 标准库 net/http 的路由增强器,提供了路径参数、子路由、中间件支持等功能。
import "github.com/gorilla/mux"
func main() {
r := mux.NewRouter()
r.Use(loggingMiddleware)
r.Use(authMiddleware)
r.HandleFunc("/users/{id}", getUserHandler).Methods("GET")
r.HandleFunc("/users", createUserHandler).Methods("POST")
srv := &http.Server{Handler: r, Addr: ":8080"}
log.Fatal(srv.ListenAndServe())
}Mux 中间件签名与标准库一致:func(http.Handler) http.Handler。
Echo
Echo 是高性能的轻量级 HTTP 框架,支持中间件链、路由分组和丰富的内置中间件。
import "github.com/labstack/echo/v4"
func main() {
e := echo.New()
e.Use(echo.MiddlewareFunc(middleware.Logger()))
e.Use(echo.MiddlewareFunc(middleware.Recover()))
e.GET("/users/:id", func(c echo.Context) error {
id := c.Param("id")
return c.JSON(http.StatusOK, map[string]string{"user_id": id})
})
e.Logger.Fatal(e.Start(":8080"))
}Echo 中间件签名:func(next echo.HandlerFunc) echo.HandlerFunc。
Fiber
Fiber 受 Express.js 启发,使用 fasthttp 引擎,性能极高。
import "github.com/gofiber/fiber/v2"
func main() {
app := fiber.New()
app.Use(middleware.Logger())
app.Use(middleware.Recover())
app.Get("/users/:id", func(c *fiber.Ctx) error {
return c.JSON(fiber.Map{"user_id": c.Params("id")})
})
app.Listen(":8080")
}Fiber 中间件签名:func(c *fiber.Ctx) error。
框架对比
| 框架 | 路由引擎 | 中间件签名 | 性能 | 社区生态 | 学习曲线 |
|---|---|---|---|---|---|
| net/http + mux | 标准库 | func(http.Handler) http.Handler | 中等 | 标准 | 低 |
| Gin | httprouter | gin.HandlerFunc | 高 | 丰富 | 低 |
| Gorilla Mux | 自定义 | func(http.Handler) http.Handler | 中等 | 丰富 | 低 |
| Echo | 自定义 | func(echo.HandlerFunc) echo.HandlerFunc | 高 | 丰富 | 低 |
| Fiber | fasthttp | func(*fiber.Ctx) error | 极高 | 中等 | 中 |
Gin 中间件完整示例:Logger + Recovery + Auth + CORS 链式组合
package main
import (
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
)
// 自定义 Logger 中间件
func CustomLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
duration := time.Since(start)
log.Printf("[%s] %s %s %d %v",
c.Request.Method,
c.Request.URL.Path,
c.ClientIP(),
c.Writer.Status(),
duration,
)
}
}
// Auth 中间件
func AuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
token := c.GetHeader("Authorization")
if token == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"error": "missing authorization token",
})
return
}
// 验证通过后设置用户信息
c.Set("user_id", "user-123")
c.Next()
}
}
// CORS 中间件
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(http.StatusNoContent)
return
}
c.Next()
}
}
func main() {
r := gin.New() // 使用 gin.New() 避免自动添加 Logger 和 Recovery
// 全局中间件:按顺序执行
r.Use(CustomLogger())
r.Use(CORSMiddleware())
r.Use(gin.Recovery())
// 公开路由组
r.GET("/health", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"status": "ok"})
})
// 需要认证的路由组
authorized := r.Group("/api")
authorized.Use(AuthMiddleware())
{
authorized.GET("/users/:id", func(c *gin.Context) {
userID := c.Param("id")
c.JSON(http.StatusOK, gin.H{
"user_id": userID,
"message": "user details",
})
})
authorized.POST("/users", func(c *gin.Context) {
var user struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
}
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"user": user})
})
}
r.Run(":8080")
}gRPC 中间件
gRPC 拦截器分类
gRPC 拦截器(Interceptor)是 gRPC 的中间件机制,分为两大类共四种:
| 类型 | 方向 | 适用场景 |
|---|---|---|
| Unary Client Interceptor | 客户端单向 | 超时控制、重试、熔断、tracing |
| Unary Server Interceptor | 服务端单向 | 参数验证、日志、鉴权、panic 恢复 |
| Stream Client Interceptor | 客户端流式 | 流式请求的监控、tracing |
| Stream Server Interceptor | 服务端流式 | 流式消息的审计、限流 |
Unary 拦截器函数签名
// Server 端
type UnaryServerInterceptor func(
ctx context.Context,
req interface{},
info *UnaryServerInfo,
handler UnaryHandler,
) (resp interface{}, err error)
// Client 端
type UnaryClientInterceptor func(
ctx context.Context,
method string,
req, reply interface{},
cc *ClientConn,
invoker UnaryInvoker,
opts ...CallOption,
) errorStream 拦截器函数签名
// Server 端
type StreamServerInterceptor func(
srv interface{},
ss ServerStream,
info *StreamServerInfo,
handler StreamHandler,
) error
// Client 端
type StreamClientInterceptor func(
ctx context.Context,
desc *StreamDesc,
cc *ClientConn,
method string,
streamer Streamer,
opts ...CallOption,
) (ClientStream, error)Server 端拦截器
context 传递
在拦截器之间通过 context 传递元数据:
func ContextMiddleware() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// 从传入的 metadata 中提取信息
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
md = metadata.New(nil)
}
// 注入追踪 ID
traceID := uuid.New().String()
ctx = metadata.NewIncomingContext(ctx, metadata.Pairs(
"trace_id", traceID,
))
// 将值存入 context
ctx = context.WithValue(ctx, "trace_id", traceID)
return handler(ctx, req)
}
}参数验证拦截器
func ValidationInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// 检查请求是否实现了 Validator 接口
if v, ok := req.(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return nil, status.Errorf(codes.InvalidArgument,
"validation failed: %v", err)
}
}
return handler(ctx, req)
}
}日志拦截器
func LoggingInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
start := time.Now()
log.Printf("[gRPC] calling method: %s", info.FullMethod)
resp, err := handler(ctx, req)
duration := time.Since(start)
if err != nil {
log.Printf("[gRPC] method: %s failed: %v, duration: %v",
info.FullMethod, err, duration)
} else {
log.Printf("[gRPC] method: %s succeeded, duration: %v",
info.FullMethod, duration)
}
return resp, err
}
}鉴权拦截器
func AuthInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// 从 metadata 中获取 token
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Errorf(codes.Unauthenticated, "missing metadata")
}
tokens := md.Get("authorization")
if len(tokens) == 0 {
return nil, status.Errorf(codes.Unauthenticated, "missing token")
}
// 验证 token(示例简化处理)
token := tokens[0]
if !validateToken(token) {
return nil, status.Errorf(codes.Unauthenticated, "invalid token")
}
// 将用户信息注入 context
ctx = context.WithValue(ctx, "user", extractUser(token))
return handler(ctx, req)
}
}
func validateToken(token string) bool {
return len(token) > 10
}
func extractUser(token string) string {
return "user-123"
}panic 恢复拦截器
func RecoveryInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
defer func() {
if r := recover(); r != nil {
buf := make([]byte, 4096)
n := runtime.Stack(buf, false)
log.Printf("gRPC panic recovered: %v\n%s", r, buf[:n])
err = status.Errorf(codes.Internal, "internal server error")
}
}()
return handler(ctx, req)
}
}Server 端注册拦截器
func main() {
s := grpc.NewServer(
grpc.ChainUnaryInterceptor(
RecoveryInterceptor(),
LoggingInterceptor(),
AuthInterceptor(),
ValidationInterceptor(),
),
grpc.ChainStreamInterceptor(
streamRecoveryInterceptor(),
streamLoggingInterceptor(),
),
)
pb.RegisterUserServiceServer(s, &userServer{})
lis, _ := net.Listen("tcp", ":50051")
s.Serve(lis)
}Client 端拦截器
超时控制拦截器
func TimeoutInterceptor(timeout time.Duration) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string,
req, reply interface{}, cc *grpc.ClientConn,
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
return invoker(ctx, method, req, reply, cc, opts...)
}
}重试拦截器
func RetryInterceptor(maxRetries int) grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string,
req, reply interface{}, cc *grpc.ClientConn,
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
var err error
for i := 0; i <= maxRetries; i++ {
if i > 0 {
// 指数退避
backoff := time.Duration(i*i) * 100 * time.Millisecond
time.Sleep(backoff)
}
err = invoker(ctx, method, req, reply, cc, opts...)
if err == nil {
return nil
}
// 只重试可恢复的错误
if st, ok := status.FromError(err); ok {
switch st.Code() {
case codes.Unavailable, codes.DeadlineExceeded:
continue
default:
return err
}
}
}
return err
}
}熔断拦截器
type CircuitBreakerInterceptor struct {
halfMaxRequests int
failureCount int32
successCount int32
state int32 // 0: closed, 1: half-open, 2: open
lastFailureTime time.Time
timeout time.Duration
mu sync.Mutex
}
func (cb *CircuitBreakerInterceptor) UnaryClientInterceptor() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string,
req, reply interface{}, cc *grpc.ClientConn,
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
if !cb.allowRequest() {
return status.Errorf(codes.Unavailable, "circuit breaker is open")
}
err := invoker(ctx, method, req, reply, cc, opts...)
cb.recordResult(err == nil)
return err
}
}
func (cb *CircuitBreakerInterceptor) allowRequest() bool {
cb.mu.Lock()
defer cb.mu.Unlock()
state := atomic.LoadInt32(&cb.state)
switch state {
case 0: // closed
return true
case 2: // open
if time.Since(cb.lastFailureTime) > cb.timeout {
atomic.StoreInt32(&cb.state, 1) // half-open
return true
}
return false
case 1: // half-open
return atomic.LoadInt32(&cb.successCount) < int32(cb.halfMaxRequests)
}
return true
}
func (cb *CircuitBreakerInterceptor) recordResult(success bool) {
cb.mu.Lock()
defer cb.mu.Unlock()
if success {
atomic.AddInt32(&cb.successCount, 1)
if atomic.LoadInt32(&cb.state) == 1 {
atomic.StoreInt32(&cb.state, 0) // reset to closed
atomic.StoreInt32(&cb.failureCount, 0)
atomic.StoreInt32(&cb.successCount, 0)
}
} else {
atomic.AddInt32(&cb.failureCount, 1)
cb.lastFailureTime = time.Now()
if atomic.LoadInt32(&cb.failureCount) >= 5 {
atomic.StoreInt32(&cb.state, 2) // open
}
}
}Tracing 拦截器
func TracingInterceptor() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string,
req, reply interface{}, cc *grpc.ClientConn,
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// 从 context 中提取或创建 span
span := trace.NewSpan(method)
defer span.End()
// 将 trace ID 注入 metadata
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = metadata.New(nil)
}
md.Set("x-trace-id", span.TraceID())
ctx = metadata.NewOutgoingContext(ctx, md)
return invoker(ctx, method, req, reply, cc, opts...)
}
}指标采集拦截器
func MetricsInterceptor() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string,
req, reply interface{}, cc *grpc.ClientConn,
invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
start := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...)
duration := time.Since(start)
// 记录指标(示例使用 Prometheus 风格)
metrics.Record(method, duration, err)
return err
}
}Client 端注册拦截器
func main() {
conn, err := grpc.Dial(
"localhost:50051",
grpc.WithInsecure(),
grpc.WithChainUnaryInterceptor(
TimeoutInterceptor(5*time.Second),
RetryInterceptor(3),
TracingInterceptor(),
MetricsInterceptor(),
),
)
if err != nil {
log.Fatal(err)
}
defer conn.Close()
client := pb.NewUserServiceClient(conn)
resp, err := client.GetUser(context.Background(), &pb.GetUserRequest{Id: "123"})
}go-grpc-middleware 链式使用
go-grpc-middleware 是社区广泛使用的 gRPC 中间件库,提供了丰富的内置拦截器和链式注册能力。
安装
go get github.com/grpc-ecosystem/go-grpc-middleware/v2Server 端链式使用
import (
"github.com/grpc-ecosystem/go-grpc-middleware/v2"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery"
)
func main() {
// 使用 grpc_middleware 的 ChainUnaryServer/ChainStreamServer
s := grpc.NewServer(
grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
recovery.UnaryServerInterceptor(),
logging.UnaryServerInterceptor(logging.DefaultServerLogger()),
auth.UnaryServerInterceptor(authFunc),
customValidationInterceptor(),
)),
grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
recovery.StreamServerInterceptor(),
logging.StreamServerInterceptor(logging.DefaultServerLogger()),
)),
)
pb.RegisterUserServiceServer(s, &userServer{})
lis, _ := net.Listen("tcp", ":50051")
s.Serve(lis)
}自定义拦截器注册到链中
// 自定义拦截器工厂函数
func customValidationInterceptor() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
if v, ok := req.(interface{ Validate() error }); ok {
if err := v.Validate(); err != nil {
return nil, status.Errorf(codes.InvalidArgument, err.Error())
}
}
return handler(ctx, req)
}
}Client 端链式使用
import (
"github.com/grpc-ecosystem/go-grpc-middleware/v2"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/timeout"
)
func main() {
conn, err := grpc.Dial(
"localhost:50051",
grpc.WithInsecure(),
grpc.WithUnaryInterceptor(grpc_middleware.ChainUnaryClient(
timeout.UnaryClientInterceptor(5 * time.Second),
retry.UnaryClientInterceptor(
retry.WithMax(3),
retry.WithBackoff(retry.BackoffExponential(100*time.Millisecond)),
),
)),
)
}Go 开源库实践
常用中间件开源库对比
路由框架对比
| 框架 | 路由性能 | 中间件机制 | 社区活跃度 | 内置中间件 | 适用场景 |
|---|---|---|---|---|---|
| Gin | 高 | gin.HandlerFunc 链式 | 极高 | Logger、Recovery、CORS 等 | RESTful API、微服务 |
| Echo | 高 | echo.MiddlewareFunc 链式 | 高 | Logger、Recover、CORS、CSRF 等 | RESTful API、性能敏感场景 |
| Fiber | 极高 | func(*fiber.Ctx) error 链式 | 高 | Logger、Recover、CORS、Compress 等 | 高并发场景 |
| Chi | 中 | func(http.Handler) http.Handler | 中 | 无内置,组合第三方 | 兼容 net/http、轻量项目 |
| net/http | 中 | func(http.Handler) http.Handler | 标准库 | 无 | 简单服务、标准库优先 |
中间件专用库对比
| 库 | 类型 | 特点 |
|---|---|---|
| Negroni | 中间件管理器 | 显式中间件栈,negroni.Handler 接口,支持 ServeHTTP |
| Alice | 中间件链工具 | 仅提供链式组合,alice.Chain 构造中间件列表 |
| Chi | 全功能路由 | 内置中间件包 chi/middleware,包含 RequestID、Logger、Recover、Timeout 等 |
Negroni 使用示例
import "github.com/urfave/negroni"
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello"))
})
n := negroni.New()
n.Use(negroni.NewLogger())
n.Use(negroni.NewRecovery())
n.UseHandler(mux)
http.ListenAndServe(":3000", n)
}Alice 使用示例
import "github.com/justinas/alice"
func main() {
chain := alice.New(
LoggingMiddleware,
AuthMiddleware,
CORSMiddleware,
).Then(handler)
http.ListenAndServe(":8080", chain)
}Chi 使用示例
import "github.com/go-chi/chi/v5"
func main() {
r := chi.NewRouter()
// 使用 chi 内置中间件
r.Use(chiMiddleware.RequestID)
r.Use(chiMiddleware.Logger)
r.Use(chiMiddleware.Recoverer)
r.Use(chiMiddleware.Timeout(10 * time.Second))
r.Get("/users/{id}", getUserHandler)
r.Post("/users", createUserHandler)
http.ListenAndServe(":8080", r)
}context 传递
Go 的 context.Context 是中间件链中传递请求级数据的标准方式。
context.WithValue
用于在中间件之间传递键值对数据:
type contextKey string
const (
UserIDKey contextKey = "user_id"
TraceIDKey contextKey = "trace_id"
RequestIDKey contextKey = "request_id"
)
func AuthMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 解析 token 后设置用户 ID
userID := extractUserID(r)
ctx := context.WithValue(r.Context(), UserIDKey, userID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// 在后续处理中获取
func getUserFromContext(ctx context.Context) (string, bool) {
userID, ok := ctx.Value(UserIDKey).(string)
return userID, ok
}context.WithDeadline / WithCancel
用于超时控制和级联取消:
func TimeoutMiddleware(timeout time.Duration) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), timeout)
defer cancel()
r = r.WithContext(ctx)
done := make(chan bool)
go func() {
next.ServeHTTP(w, r)
done <- true
}()
select {
case <-done:
return
case <-ctx.Done():
if ctx.Err() == context.DeadlineExceeded {
http.Error(w, "Request Timeout", http.StatusGatewayTimeout)
}
}
})
}
}TraceID 跨中间件传递
TraceID 的生成和传递应覆盖整个请求生命周期:
const TraceIDHeader = "X-Trace-ID"
func TraceIDMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
traceID := r.Header.Get(TraceIDHeader)
if traceID == "" {
traceID = generateTraceID()
}
// 向下游传递
w.Header().Set(TraceIDHeader, traceID)
ctx := context.WithValue(r.Context(), TraceIDKey, traceID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func generateTraceID() string {
return fmt.Sprintf("%016x", rand.Uint64())
}中间件链路追踪
使用 OpenTelemetry 实现跨中间件的链路追踪:
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/trace"
)
func OpenTelemetryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
tracer := otel.Tracer("http-middleware")
ctx, span := tracer.Start(r.Context(), r.Method+" "+r.URL.Path)
defer span.End()
// 在 span 中记录请求属性
span.SetAttributes(
attribute.String("http.method", r.Method),
attribute.String("http.url", r.URL.String()),
attribute.String("http.host", r.Host),
)
next.ServeHTTP(w, r.WithContext(ctx))
})
}错误处理中间件
统一错误响应
定义统一的错误响应格式:
// APIError 统一的 API 错误结构
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
Details interface{} `json:"details,omitempty"`
}
func (e *APIError) Error() string {
return e.Message
}
// 预定义错误码
var (
ErrBadRequest = &APIError{Code: 40000, Message: "bad request"}
ErrUnauthorized = &APIError{Code: 40100, Message: "unauthorized"}
ErrForbidden = &APIError{Code: 40300, Message: "forbidden"}
ErrNotFound = &APIError{Code: 40400, Message: "not found"}
ErrInternal = &APIError{Code: 50000, Message: "internal server error"}
)错误处理中间件
func ErrorHandlingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 包装 ResponseWriter 以捕获后续处理中的错误
erw := &errorResponseWriter{
ResponseWriter: w,
wroteHeader: false,
}
next.ServeHTTP(erw, r)
// 如果业务处理中有未捕获的错误,在此统一处理
})
}
type errorResponseWriter struct {
http.ResponseWriter
wroteHeader bool
}
// 在业务代码中返回统一错误
func writeError(w http.ResponseWriter, apiErr *APIError) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest) // 应根据错误码映射
json.NewEncoder(w).Encode(apiErr)
}错误码与 HTTP 状态码映射
func HTTPStatusFromCode(code int) int {
switch {
case code >= 50000:
return http.StatusInternalServerError
case code == 40100:
return http.StatusUnauthorized
case code == 40300:
return http.StatusForbidden
case code == 40400:
return http.StatusNotFound
case code >= 40000:
return http.StatusBadRequest
default:
return http.StatusInternalServerError
}
}
func ErrorHandlingMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
// 检查是否有错误
if len(c.Errors) > 0 {
err := c.Errors.Last().Err
// 错误类型断言
if apiErr, ok := err.(*APIError); ok {
c.JSON(HTTPStatusFromCode(apiErr.Code), apiErr)
return
}
// 未知错误统一返回 500
c.JSON(http.StatusInternalServerError, &APIError{
Code: 50000,
Message: "internal server error",
})
}
}
}错误类型断言
func classifyError(err error) *APIError {
if err == nil {
return nil
}
// 断言为预定义错误类型
switch e := err.(type) {
case *APIError:
return e
case *json.SyntaxError:
return &APIError{Code: 40001, Message: "invalid JSON format", Details: e.Error()}
case *json.UnmarshalTypeError:
return &APIError{Code: 40002, Message: "type mismatch", Details: e.Error()}
default:
// 使用 errors.Is 检查包装错误
if errors.Is(err, context.DeadlineExceeded) {
return &APIError{Code: 40800, Message: "request timeout"}
}
return ErrInternal
}
}Panic Recovery + Stack Trace
func RecoveryWithStackTrace() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if rec := recover(); rec != nil {
// 获取堆栈信息
buf := make([]byte, 64<<10) // 64KB
n := runtime.Stack(buf, false)
stack := string(buf[:n])
// 记录日志
log.Printf("panic recovered: %v\nstack:\n%s", rec, stack)
// 返回统一错误
c.AbortWithStatusJSON(http.StatusInternalServerError, &APIError{
Code: 50000,
Message: "internal server error",
})
}
}()
c.Next()
}
}在标准库 net/http 中实现:
func RecoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
buf := make([]byte, 64<<10)
n := runtime.Stack(buf, false)
log.Printf("panic recovered: %v\nstack:\n%s", rec, buf[:n])
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
json.NewEncoder(w).Encode(APIError{
Code: 50000,
Message: "internal server error",
})
}
}()
next.ServeHTTP(w, r)
})
}自定义 Go 库发布
Go 库项目结构
标准目录规范
my-go-lib/
├── cmd/ # 可执行入口(多个 main 包)
│ └── my-lib/
│ └── main.go
├── pkg/ # 对外暴露的公共包
│ ├── config/
│ │ └── config.go
│ └── middleware/
│ └── middleware.go
├── internal/ # 内部包,外部无法导入
│ ├── handler/
│ │ └── handler.go
│ └── store/
│ └── store.go
├── examples/ # 使用示例
│ └── basic/
│ └── main.go
├── test/ # 集成测试数据和辅助工具
├── go.mod
├── go.sum
├── Makefile
└── README.mdcmd 目录
存放可执行文件的入口。每个子目录对应一个独立的 main 包:
cmd/
├── server/
│ └── main.go # go install 后生成 server 可执行文件
└── client/
└── main.go # go install 后生成 client 可执行文件pkg 目录
存放对外暴露的公共代码。pkg/ 下的包可以被外部项目直接导入使用。
internal 目录
Go 语言特殊机制:internal 包只能被其父目录下的代码导入。
my-go-lib/
└── internal/
└── store/ # 仅能被 my-go-lib 及子目录导入
└── store.go
# 外部项目导入会编译失败
import "github.com/user/my-go-lib/internal/store" // 编译错误internal 包的限制规则
internal包的限制基于目录层级,而非模块路径。- 如果代码在
my-go-lib/下,它可以导入my-go-lib/internal/store。 - 外部模块无法导入
my-go-lib/internal/store。 internal可以嵌套在任意层级,每个internal都对其父目录范围生效。
API 设计
导出类型少而精
导出的类型应当遵循最小可用原则,只暴露必要的类型和函数:
// 好的设计:只暴露接口,隐藏实现细节
package ratelimit
// Limiter 是限流器接口,只暴露行为
type Limiter interface {
Allow() bool
}
// NewTokenBucket 返回接口类型,隐藏结构体
func NewTokenBucket(rate int, burst int) Limiter {
return &tokenBucket{
rate: rate,
burst: burst,
}
}
// tokenBucket 不导出,外部无法直接访问
type tokenBucket struct {
rate int
burst int
// ...
}
func (tb *tokenBucket) Allow() bool {
// 实现细节对外部隐藏
return true
}接口小而稳定
接口应保持最小化,遵循单一职责原则:
// 小接口示例
type Logger interface {
Log(ctx context.Context, msg string, fields ...Field)
}
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
type Cache interface {
Get(ctx context.Context, key string) (interface{}, error)
Set(ctx context.Context, key string, value interface{}, ttl time.Duration) error
}Options 模式
通过配置结构体集中管理可选参数:
type Options struct {
Timeout time.Duration
MaxRetries int
Logger Logger
Cache Cache
}
// DefaultOptions 提供默认值
var DefaultOptions = Options{
Timeout: 30 * time.Second,
MaxRetries: 3,
Logger: defaultLogger,
}
func NewClient(endpoint string, opts Options) *Client {
if opts.Timeout == 0 {
opts.Timeout = DefaultOptions.Timeout
}
if opts.MaxRetries == 0 {
opts.MaxRetries = DefaultOptions.MaxRetries
}
// ...
}Functional Options 模式
通过函数选项实现灵活的配置:
type Client struct {
endpoint string
timeout time.Duration
maxRetry int
logger Logger
}
type Option func(*Client)
func WithTimeout(timeout time.Duration) Option {
return func(c *Client) {
c.timeout = timeout
}
}
func WithMaxRetry(n int) Option {
return func(c *Client) {
c.maxRetry = n
}
}
func WithLogger(logger Logger) Option {
return func(c *Client) {
c.logger = logger
}
}
func NewClient(endpoint string, opts ...Option) *Client {
c := &Client{
endpoint: endpoint,
timeout: 30 * time.Second, // 默认值
maxRetry: 3,
logger: defaultLogger,
}
for _, opt := range opts {
opt(c)
}
return c
}
// 使用
client := NewClient("https://api.example.com",
WithTimeout(10*time.Second),
WithMaxRetry(5),
WithLogger(customLogger),
)Builder 模式
通过 Builder 模式构建复杂对象:
type Server struct {
host string
port int
tlsEnabled bool
certFile string
keyFile string
middlewares []func(http.Handler) http.Handler
}
type ServerBuilder struct {
server *Server
}
func NewServerBuilder() *ServerBuilder {
return &ServerBuilder{server: &Server{
host: "0.0.0.0",
port: 8080,
}}
}
func (b *ServerBuilder) WithHost(host string) *ServerBuilder {
b.server.host = host
return b
}
func (b *ServerBuilder) WithPort(port int) *ServerBuilder {
b.server.port = port
return b
}
func (b *ServerBuilder) WithTLS(certFile, keyFile string) *ServerBuilder {
b.server.tlsEnabled = true
b.server.certFile = certFile
b.server.keyFile = keyFile
return b
}
func (b *ServerBuilder) WithMiddleware(mw func(http.Handler) http.Handler) *ServerBuilder {
b.server.middlewares = append(b.server.middlewares, mw)
return b
}
func (b *ServerBuilder) Build() (*Server, error) {
if b.server.port < 0 || b.server.port > 65535 {
return nil, fmt.Errorf("invalid port: %d", b.server.port)
}
return b.server, nil
}Chain 调用
链式调用在中间件场景中广泛应用:
// Middleware 链式构建器
type MiddlewareChain struct {
middlewares []func(http.Handler) http.Handler
}
func NewChain() *MiddlewareChain {
return &MiddlewareChain{}
}
func (mc *MiddlewareChain) Use(mw func(http.Handler) http.Handler) *MiddlewareChain {
mc.middlewares = append(mc.middlewares, mw)
return mc
}
func (mc *MiddlewareChain) Then(handler http.Handler) http.Handler {
for i := len(mc.middlewares) - 1; i >= 0; i-- {
handler = mc.middlewares[i](handler)
}
return handler
}
// 使用
chain := NewChain().
Use(LoggingMiddleware).
Use(AuthMiddleware).
Use(CORSMiddleware)
finalHandler := chain.Then(myHandler)文档与测试
GoDoc 注释规范
Go 使用特殊的注释格式自动生成文档,遵循以下规范:
// Package ratelimit 提供了基于令牌桶算法的限流实现。
//
// 基本用法:
//
// limiter := ratelimit.NewTokenBucket(100, 200)
// if limiter.Allow() {
// // 处理请求
// }
package ratelimit
// Limiter 是限流器的核心接口。
// 所有限流实现都必须满足此接口。
type Limiter interface {
// Allow 检查当前是否允许通过一个请求。
// 返回 true 表示允许,false 表示限流。
Allow() bool
// Wait 阻塞等待直到允许通过。
// ctx 用于控制等待的超时和取消。
Wait(ctx context.Context) error
}
// NewTokenBucket 创建一个基于令牌桶算法的限流器。
//
// rate 参数指定每秒生成的令牌数,burst 参数指定桶的最大容量。
//
// 示例:
//
// limiter := NewTokenBucket(10, 20)
// for i := 0; i < 100; i++ {
// if limiter.Allow() {
// fmt.Println("allowed")
// }
// }
func NewTokenBucket(rate int, burst int) Limiter {
return &tokenBucket{
rate: rate,
burst: burst,
}
}godoc 格式要点
| 规则 | 示例 |
|---|---|
包注释以 Package <name> 开头 | // Package ratelimit 提供限流功能 |
| 导出类型紧跟上注释,无需空行 | // Limiter 是限流器接口 |
| 函数注释描述行为而非实现 | // NewTokenBucket 创建令牌桶限流器 |
| 注释中的代码示例缩进 | 每行以一个 tab 前缀 |
| Bug 标记 | // BUG(user): 已知问题描述 |
| Deprecated 标记 | // Deprecated: 请使用 NewFunction 替代 |
goldmark DOM 树验证
goldmark 是 Go 的 Markdown 解析器,可验证文档生成的 DOM 结构:
package main
import (
"bytes"
"fmt"
"strings"
"github.com/yuin/goldmark"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/text"
)
func validateMarkdown(content string) error {
md := goldmark.New()
reader := text.NewReader([]byte(content))
doc := md.Parser().Parse(reader)
// 遍历 DOM 树验证结构
err := ast.Walk(doc, func(n ast.Node, entering bool) (ast.WalkingStatus, error) {
if !entering {
return ast.WalkContinue, nil
}
switch n.Kind() {
case ast.KindHeading:
heading := n.(*ast.Heading)
if heading.Level < 1 || heading.Level > 6 {
return ast.WalkStop, fmt.Errorf("invalid heading level: %d", heading.Level)
}
case ast.KindFencedCodeBlock:
cb := n.(*ast.FencedCodeBlock)
lang := string(cb.Language(reader.Source()))
if lang == "" {
return ast.WalkStop, fmt.Errorf("code block missing language identifier")
}
}
return ast.WalkContinue, nil
})
return err
}Example 测试
Example 函数直接作为文档展示,同时作为可执行测试:
// examples_test.go
package ratelimit_test
import (
"fmt"
"time"
"github.com/user/my-go-lib/ratelimit"
)
// Example 函数名称格式:Example_<type>_<method>_<suffix>
// 包级别示例
func Example() {
limiter := ratelimit.NewTokenBucket(10, 20)
fmt.Println(limiter.Allow())
// Output: true
}
// 函数示例
func ExampleNewTokenBucket() {
limiter := ratelimit.NewTokenBucket(100, 200)
allowed := limiter.Allow()
fmt.Printf("allowed: %v", allowed)
// Output: allowed: true
}
// 类型方法示例
func ExampleLimiter_Wait() {
limiter := ratelimit.NewTokenBucket(1, 1)
ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
defer cancel()
err := limiter.Wait(ctx)
fmt.Printf("err: %v", err)
// Output: err: <nil>
}
// 有前缀的混沌示例
func ExampleLimiter_waitTimeout() {
limiter := ratelimit.NewTokenBucket(1, 1)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond)
defer cancel()
_ = limiter.Wait(ctx) // 消耗令牌
err := limiter.Wait(ctx) // 超时
fmt.Printf("err: %v", err)
// Output: err: context deadline exceeded
}Table Driven Test
表驱动测试是 Go 测试的标准模式:
// ratelimit_test.go
package ratelimit
import (
"testing"
"time"
)
func TestTokenBucket_Allow(t *testing.T) {
tests := []struct {
name string
rate int
burst int
calls int
want []bool
}{
{
name: "burst capacity respected",
rate: 10,
burst: 5,
calls: 7,
want: []bool{true, true, true, true, true, false, false},
},
{
name: "single request always allowed",
rate: 1,
burst: 1,
calls: 1,
want: []bool{true},
},
{
name: "zero rate blocks all",
rate: 0,
burst: 0,
calls: 3,
want: []bool{false, false, false},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
limiter := NewTokenBucket(tt.rate, tt.burst)
for i, expected := range tt.want {
got := limiter.Allow()
if got != expected {
t.Errorf("call %d: Allow() = %v, want %v", i, got, expected)
}
}
})
}
}子测试
使用 t.Run 组织嵌套的子测试:
func TestLimiter(t *testing.T) {
t.Run("basic", func(t *testing.T) {
limiter := NewTokenBucket(100, 200)
if !limiter.Allow() {
t.Error("expected Allow() to return true")
}
})
t.Run("rate limiting", func(t *testing.T) {
limiter := NewTokenBucket(1, 1)
// 消耗唯一令牌
if !limiter.Allow() {
t.Error("first call should be allowed")
}
// 第二次应被限流
if limiter.Allow() {
t.Error("second call should be rate limited")
}
})
t.Run("refill after interval", func(t *testing.T) {
limiter := NewTokenBucket(100, 1)
limiter.Allow() // 消耗
time.Sleep(time.Second / 100)
if !limiter.Allow() {
t.Error("after refill interval, should allow")
}
})
}集成测试
// integration_test.go
//go:build integration
package integration
import (
"context"
"net/http"
"testing"
"time"
)
func TestServerWithMiddleware(t *testing.T) {
// 启动测试服务器
handler := buildHandlerWithMiddleware()
server := &http.Server{Addr: ":0", Handler: handler}
go server.ListenAndServe()
defer server.Shutdown(context.Background())
// 发送测试请求
client := &http.Client{Timeout: 5 * time.Second}
resp, err := client.Get("http://localhost:8080/api/health")
if err != nil {
t.Fatalf("request failed: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("expected 200, got %d", resp.StatusCode)
}
}基准测试
基准测试用于评估中间件性能开销:
func BenchmarkMiddlewareChain(b *testing.B) {
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})
chain := NewChain().
Use(LoggingMiddleware).
Use(AuthMiddleware).
Use(CORSMiddleware).
Then(handler)
req := httptest.NewRequest("GET", "/test", nil)
req.Header.Set("Authorization", "Bearer test-token")
b.ResetTimer()
for i := 0; i < b.N; i++ {
w := httptest.NewRecorder()
chain.ServeHTTP(w, req)
}
}
func BenchmarkGinMiddleware(b *testing.B) {
gin.SetMode(gin.TestMode)
r := gin.New()
r.Use(gin.Logger(), gin.Recovery())
r.GET("/test", func(c *gin.Context) {
c.Status(http.StatusOK)
})
b.ResetTimer()
for i := 0; i < b.N; i++ {
w := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/test", nil)
r.ServeHTTP(w, req)
}
}执行测试
# 运行所有单元测试
go test ./...
# 运行带有详细输出的测试
go test -v ./...
# 运行指定测试
go test -run TestTokenBucket_Allow -v
# 运行集成测试(需要 integration build tag)
go test -tags=integration ./...
# 运行基准测试
go test -bench=. -benchmem ./...
# 生成测试覆盖率报告
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.html