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) } }