竹简文档
gRPC 最佳实践

中间件模式

服务级中间件分发机制、App 认证示例与自定义中间件

中间件模式

bamboo-base-go 提供了灵活的服务级中间件机制,允许为不同的 gRPC 服务绑定不同的中间件链。

服务级中间件分发

工作原理

全局拦截器 xGrpcIUnary.Middleware() 作为分发器,根据请求的 FullMethod 解析服务名,查找并执行对应的中间件链。

请求进入 → Middleware 拦截器 → 解析服务名 → 查找中间件链 → 执行中间件 → Handler

注册 API

import xGrpcMiddle "github.com/bamboo-services/bamboo-base-go/plugins/grpc/middleware"

// 注册一元中间件
func UseUnary(desc grpc.ServiceDesc, middlewares ...UnaryMiddlewareFunc)

// 注册流式中间件
func UseStream(desc grpc.ServiceDesc, middlewares ...StreamMiddlewareFunc)

洋葱模型

中间件链遵循洋葱模型执行顺序:

注册顺序 [A, B, C] 执行顺序:
A-enter → B-enter → C-enter → handler → C-exit → B-exit → A-exit

App 认证中间件示例

以下是一个完整的 App 认证中间件实现,验证调用方的应用身份:

internal/grpc/middleware/app_verify.go
package middleware

import (
    "context"

    xError "github.com/bamboo-services/bamboo-base-go/common/error"
    xLog "github.com/bamboo-services/bamboo-base-go/common/log"
    xSnowflake "github.com/bamboo-services/bamboo-base-go/common/snowflake"
    xGrpcConst "github.com/bamboo-services/bamboo-base-go/plugins/grpc/constant"
    xGrpcUtil "github.com/bamboo-services/bamboo-base-go/plugins/grpc/utility"
    "github.com/your-org/beacon-bucket/internal/logic"
    "google.golang.org/grpc"
)

// UnaryAppVerify 创建用于验证应用身份的 gRPC 一元服务端拦截器
//
// 它从传入请求的 gRPC 元数据中提取应用 ID 和密钥,
// 并调用 Logic 层进行身份认证。认证失败将直接返回错误响应。
//
// 参数:
//   - mainCtx: 用于初始化内部 AppLogic 和 Logger 的上下文
//
// 返回值:
//   - 返回一个 grpc.UnaryServerInterceptor,用于拦截请求执行认证逻辑
func UnaryAppVerify(mainCtx context.Context) grpc.UnaryServerInterceptor {
    log := xLog.WithName(xLog.NamedMIDE, "UnaryAppVerify")
    appLogic := logic.NewAppLogic(mainCtx)

    return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
        log.Info(ctx, "验证应用身份")

        // 从 metadata 提取认证信息
        appIDStr, xErr := xGrpcUtil.ExtractMetadata(ctx, xGrpcConst.MetadataAppAccessID)
        if xErr != nil {
            return nil, xErr
        }

        secretKey, xErr := xGrpcUtil.ExtractMetadata(ctx, xGrpcConst.MetadataAppSecretKey)
        if xErr != nil {
            return nil, xErr
        }

        // 校验必填性
        if appIDStr == "" || secretKey == "" {
            log.Warn(ctx, "缺少应用认证信息")
            return nil, xError.NewError(ctx, xError.Unauthorized, "缺少应用认证信息", false)
        }

        // 解析雪花 ID
        appID, err := xSnowflake.ParseSnowflakeID(appIDStr)
        if err != nil {
            log.Warn(ctx, "应用 ID 格式无效")
            return nil, xError.NewError(ctx, xError.ValidationError, "应用 ID 格式无效", false, err)
        }

        // 调用 Logic 层认证
        _, xErr = appLogic.Authenticate(ctx, appID, secretKey)
        if xErr != nil {
            log.Warn(ctx, "应用认证失败")
            return nil, xErr
        }

        log.Debug(ctx, "应用认证成功")

        // 将 AppID 存入 context,供后续逻辑使用
        ctx = context.WithValue(ctx, "app_id", appID)

        return handler(ctx, req)
    }
}

流式中间件版本

对于流式 RPC,需要实现流式拦截器:

internal/grpc/middleware/app_verify.go
// StreamAppVerify 用于 gRPC 流式请求的应用认证拦截器
func StreamAppVerify(mainCtx context.Context) grpc.StreamServerInterceptor {
    log := xLog.WithName(xLog.NamedMIDE, "StreamAppVerify")
    appLogic := logic.NewAppLogic(mainCtx)

    return func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
        ctx := ss.Context()

        // 从 metadata 提取认证信息
        appIDStr, xErr := xGrpcUtil.ExtractMetadata(ctx, xGrpcConst.MetadataAppAccessID)
        if xErr != nil {
            return xErr
        }

        secretKey, xErr := xGrpcUtil.ExtractMetadata(ctx, xGrpcConst.MetadataAppSecretKey)
        if xErr != nil {
            return xErr
        }

        if appIDStr == "" || secretKey == "" {
            log.Warn(ctx, "缺少应用认证信息")
            return xError.NewError(ctx, xError.Unauthorized, "缺少应用认证信息", false)
        }

        appID, err := xSnowflake.ParseSnowflakeID(appIDStr)
        if err != nil {
            return xError.NewError(ctx, xError.ValidationError, "应用 ID 格式无效", false, err)
        }

        _, xErr = appLogic.Authenticate(ctx, appID, secretKey)
        if xErr != nil {
            return xErr
        }

        return handler(srv, ss)
    }
}

绑定中间件到服务

中间件在 Handler 构造函数中绑定:

internal/grpc/handler/upload.go
func NewUploadHandler(ctx context.Context, server grpc.ServiceRegistrar) *UploadHandler {
    handler := &UploadHandler{
        // ...
    }

    // 1. 注册 gRPC 服务
    gen.RegisterNormalUploadServiceServer(server, handler)

    // 2. 绑定服务级中间件(只对此服务生效)
    xGrpcMiddle.UseUnary(
        gen.NormalUploadService_ServiceDesc,
        middleware.UnaryAppVerify(ctx),
    )

    return handler
}

多中间件链

可以绑定多个中间件,按注册顺序执行:

xGrpcMiddle.UseUnary(
    gen.YourService_ServiceDesc,
    middleware.UnaryRateLimit(ctx),      // 1. 限流
    middleware.UnaryAppVerify(ctx),       // 2. App 认证
    middleware.UnaryAccessLog(ctx),       // 3. 访问日志
)

自定义中间件模板

基础模板

internal/grpc/middleware/custom.go
package middleware

import (
    "context"

    xLog "github.com/bamboo-services/bamboo-base-go/common/log"
    "google.golang.org/grpc"
)

// UnaryCustom 自定义一元中间件
func UnaryCustom(mainCtx context.Context) grpc.UnaryServerInterceptor {
    log := xLog.WithName(xLog.NamedMIDE, "UnaryCustom")

    return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
        // === 前置处理 ===
        log.Debug(ctx, "中间件前置处理", "method", info.FullMethod)

        // === 调用下一个处理器 ===
        resp, err := handler(ctx, req)

        // === 后置处理 ===
        if err != nil {
            log.Warn(ctx, "请求处理失败", "error", err)
        }

        return resp, err
    }
}

请求/响应拦截

internal/grpc/middleware/access_log.go
func UnaryAccessLog(mainCtx context.Context) grpc.UnaryServerInterceptor {
    log := xLog.WithName(xLog.NamedMIDE, "AccessLog")

    return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
        start := time.Now()

        // 记录请求
        log.Info(ctx, "gRPC 请求开始",
            "method", info.FullMethod,
            "request_type", reflect.TypeOf(req).String(),
        )

        // 调用处理器
        resp, err := handler(ctx, req)

        // 记录响应
        duration := time.Since(start)
        if err != nil {
            log.Warn(ctx, "gRPC 请求失败",
                "method", info.FullMethod,
                "duration", duration,
                "error", err,
            )
        } else {
            log.Info(ctx, "gRPC 请求成功",
                "method", info.FullMethod,
                "duration", duration,
            )
        }

        return resp, err
    }
}

限流中间件

internal/grpc/middleware/rate_limit.go
func UnaryRateLimit(mainCtx context.Context, limit int) grpc.UnaryServerInterceptor {
    limiter := rate.NewLimiter(rate.Limit(limit), limit*2)
    log := xLog.WithName(xLog.NamedMIDE, "RateLimit")

    return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
        if !limiter.Allow() {
            log.Warn(ctx, "请求被限流", "method", info.FullMethod)
            return nil, xError.NewError(ctx, xError.TooManyRequests, "请求过于频繁,请稍后重试", false)
        }

        return handler(ctx, req)
    }
}

全局拦截器配置

main.go 中配置全局拦截器链:

main.go
grpcTask := xGrpcRunner.New(
    xGrpcRunner.WithLogger(xLog.WithName(xLog.NamedGRPC)),
    xGrpcRunner.WithRegisterService(registerGrpcService),
    xGrpcRunner.WithUnaryInterceptors(
        xGrpcIUnary.InitContext(reg.Init.Ctx),  // 1. 注入上下文
        xGrpcIUnary.Recover(),                   // 2. Panic 捕获
        xGrpcIUnary.Middleware(),                // 3. 服务级中间件分发
        xGrpcIUnary.ResponseBuilder(),           // 4. 响应构建(最后)
    ),
)

拦截器链顺序

顺序拦截器作用
1Trace请求追踪 ID(Runner 默认)
2InitContext注入主上下文到请求上下文
3RecoverPanic 恢复,防止服务崩溃
4Middleware服务级中间件分发
5服务中间件App 认证、限流等
6ResponseBuilder错误转换、响应注入
7Handler业务处理

最佳实践

  1. 中间件粒度:每个中间件只做一件事
  2. 日志规范:使用命名日志器,便于过滤
  3. 错误处理:返回 *xError.Error,由 ResponseBuilder 统一处理
  4. Context 传递:通过 context 传递中间件提取的信息

下一步

On this page