项目文件夹

文件
2026-07-14 10:24:10 +08:00

88 行
3.1 KiB
Go

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
package middleware
import (
"fmt"
"net/http"
"runtime/debug"
"github.com/EthanCodeCraft/xlgo-core/logger"
"github.com/EthanCodeCraft/xlgo-core/response"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
)
// Recover panic恢复中间件,捕获panic并返回统一错误响应
func Recover() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// 获取请求ID
requestID := GetRequestID(c)
// 记录错误日志
logger.Error("panic 已恢复",
zap.String("request_id", requestID),
zap.String("error", fmt.Sprintf("%v", err)),
zap.String("path", c.Request.URL.Path),
zap.String("method", c.Request.Method),
zap.String("stack", string(debug.Stack())),
)
// 返回错误响应。
// 必须用 response.Custom 显式写 HTTP 500FailWithCode 经 writeResp→httpStatusFor,
// 在默认 ModeBusiness 下对 CodeServerError 返回 200,c.JSON(200,...) 会先 flush
// 锁定状态,随后 AbortWithStatus(500) 因 w.Written()==true 沦为 no-op,
// 导致客户端收到 HTTP 200 + body code:500,网关/APM 按 status 看不到 panic。
// Custom 不受 Mode 影响,直接写 500 且保留 RequestID。
//
// M-B 修复:若 panic 发生在响应已开始写出之后(流式/部分写),c.Writer.Written()==true,
// gin 拒绝再次写响应,response.Custom 沦为 no-op,客户端会收到截断响应而非 500。
// 此时不再尝试写 500(无效),仅 Abort + 记录,避免无意义的二次写入。
if !c.Writer.Written() {
response.Custom(c, http.StatusInternalServerError, response.CodeServerError, "服务器内部错误", nil)
} else {
logger.Warn("panic 发生在响应已开始写入后,无法写入 500,客户端将收到截断响应",
zap.String("request_id", requestID),
zap.String("path", c.Request.URL.Path),
zap.Int("bytes_written", c.Writer.Size()),
)
}
c.Abort()
}
}()
c.Next()
}
}
// RecoverWithDetail panic恢复中间件(详细版本,返回更多信息)
// 注意: 生产环境不应使用,会暴露敏感信息
func RecoverWithDetail() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
requestID := GetRequestID(c)
logger.Error("panic 已恢复",
zap.String("request_id", requestID),
zap.String("error", fmt.Sprintf("%v", err)),
zap.String("path", c.Request.URL.Path),
zap.String("method", c.Request.Method),
zap.String("stack", string(debug.Stack())),
)
// 开发环境返回详细错误。同样用 Custom 显式写 500(见 Recover 注释)。
// M-B 修复:响应已写出时不重复写 500(同 Recover)。
if !c.Writer.Written() {
response.Custom(c, http.StatusInternalServerError, response.CodeServerError, fmt.Sprintf("服务器内部错误: %v", err), nil)
} else {
logger.Warn("panic 发生在响应已开始写入后,无法写入 500,客户端将收到截断响应",
zap.String("request_id", requestID),
zap.String("path", c.Request.URL.Path),
)
}
c.Abort()
}
}()
c.Next()
}
}