依据 coord-final-decisions §3.8 W1-W8 裁决与 president-final-rulings §2.15/§2.16/§2.19 完整实现网关硬化: - W1/W2: 错误码 GW_ 前缀 + ActionState 信封响应体 - W3: 全量替换为 log/slog 结构化日志 - W4: /readyz 并行 ping 9 下游 + 软失败规则 - W5: 7 个业务 Prometheus 指标 + /metrics 端点 - W6: tracer 资源属性补全(name/version/env/host) - W7: DevMode=true && ENV=production panic 防护 - W8: 保持共享 downstream 熔断 P2 RS256 升级:接入 shared-go/jwks.Fetcher(TTL 5min)。 P2.7+P3-P5 路由扩展:student/parent/messages/dashboard。 文档同步:README/01/02/known-issues,arch.db 已更新。 质量校验:go vet + build + test 均通过。
45 lines
1.2 KiB
Go
45 lines
1.2 KiB
Go
package middleware
|
||
|
||
import (
|
||
"log/slog"
|
||
"net/http"
|
||
"runtime/debug"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
// Recovery 捕获 panic 并返回 500(ActionState 信封,W1/W2 裁决)。
|
||
//
|
||
// 响应体:{"success":false,"error":{"code":"GW_INTERNAL_ERROR","message":"..."},"request_id":"<uuid>"}。
|
||
// request_id 从 context 取(RequestID 中间件注入),若 Recovery 在 RequestID 之前触发
|
||
// 导致 context 无 request_id,则独立生成 uuid 保证可追溯。
|
||
func Recovery() gin.HandlerFunc {
|
||
return func(c *gin.Context) {
|
||
defer func() {
|
||
if r := recover(); r != nil {
|
||
stack := debug.Stack()
|
||
// 优先从 context 取 request_id(RequestID 中间件已注入)
|
||
requestID, exists := c.Get(requestIDContextKey)
|
||
if !exists {
|
||
requestID = uuid.New().String()
|
||
}
|
||
slog.Error("panic recovered",
|
||
"request_id", requestID,
|
||
"error", r,
|
||
"stack", string(stack),
|
||
)
|
||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
|
||
"success": false,
|
||
"error": gin.H{
|
||
"code": "GW_INTERNAL_ERROR",
|
||
"message": "internal server error",
|
||
},
|
||
"request_id": requestID,
|
||
})
|
||
}
|
||
}()
|
||
c.Next()
|
||
}
|
||
}
|