topfans/backend/gateway/middleware/grpc_status_interceptor.go
2026-06-15 16:28:35 +08:00

67 lines
2.2 KiB
Go

package middleware
import (
"bytes"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
// responseRecorder 拦截下游 Writer,捕获 body 用于重写
// WriteHeader 走原始 writer(状态码由 gin's underlying writer 持有)
// Write/WriteString 走 buffer(body 暂存,post 阶段根据需要重写)
type responseRecorder struct {
gin.ResponseWriter
body *bytes.Buffer
}
func (r *responseRecorder) Write(b []byte) (int, error) {
return r.body.Write(b)
}
func (r *responseRecorder) WriteString(s string) (int, error) {
return r.body.WriteString(s)
}
// GRPCStatusInterceptor Dubbo 透传响应拦截器
//
// 用途: Dubbo-Triples 框架会自动把 gRPC code 转成 HTTP 状态码
// (例如 codes.Unauthenticated → HTTP 401)写到响应里,这是问题根源
// (把业务码泄漏到传输层)。本拦截器在 gateway 这一层剥掉这个自动转换:
//
// - 业务响应(原本要 4xx/5xx): 强制改写为 HTTP 200,业务码由 body 的 code 字段携带
// - transport 错误(AuthMiddleware 真鉴权失败 401、404 路由不存在、5xx 网关内部错):
// 保持原 HTTP 状态码,body 原样透传
//
// 实现关键: 用 c.IsAborted() 区分"中间件主动 abort"和"控制器正常返回"
// - AuthMiddleware 走 c.AbortWithStatusJSON(401, ...) → c.IsAborted() == true → 保留 401
// - 控制器调 c.JSON(403, ...) (Dubbo 自动转 gRPC code=7) → c.IsAborted() == false → 改 200
//
// 仅作用于 /api/* 路径,其他路径(health、swagger、segment/json 等)直接跳过
func GRPCStatusInterceptor() gin.HandlerFunc {
return func(c *gin.Context) {
// 跳过非 Dubbo 透传的请求
if !strings.HasPrefix(c.Request.URL.Path, "/api/") {
c.Next()
return
}
// 拦截响应体到 recorder(状态码走原 writer,body 走 buffer)
original := c.Writer
recorder := &responseRecorder{ResponseWriter: original, body: &bytes.Buffer{}}
c.Writer = recorder
c.Next()
// 已经被前置中间件 abort(如 AuthMiddleware 写 401),保持原状态码
if c.IsAborted() {
_, _ = recorder.body.WriteTo(original)
return
}
// 业务响应: 强制 HTTP 200
original.WriteHeader(http.StatusOK)
_, _ = recorder.body.WriteTo(original)
}
}