package ginx // Package ginx 是 responsex 的 gin 适配层。 // // 依赖隔离:本包只依赖 gin、responsex 核心与 langx, // import 本包不会引入 go-zero / gorm。 // // go-zero 的 trace 提取(OpenTelemetry span 里的 trace_id)需要另外按需 import: // // import _ "code.yun.ink/pkg/responsex/gozerotrace" // // import 路径:code.yun.ink/pkg/responsex/gin import ( "context" "code.yun.ink/pkg/responsex" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/yuninks/langx" ) // traceIDCtxKey 是 gin 侧约定的 trace_id key(c.Set / c.Get 使用) const traceIDCtxKey = "trace_id" // Middleware 桥接 gin 与 responsex 核心: // // 1. 把 gin 的 trace_id 写进标准 context(核心包据此输出 trace_id); // 2. 按 Accept-Language 写入语言(核心包据此翻译文案)。 // // 为什么需要它:gin 的 c.Value() 读不到 c.Set() 存的值,而 responsex 核心只认标准 // context,没有中间件时 trace_id 不会出现在响应里。 // // r := gin.New() // r.Use(ginx.Middleware()) // // 业务侧只需像以前一样调用 c.Set("trace_id", id),响应会自动带上。 func Middleware(ops ...responsex.Option) gin.HandlerFunc { return func(c *gin.Context) { if c.Request == nil { c.Next() return } responsex.InitOptions(ops...) c.Request = c.Request.WithContext(bridgeCtx(c)) c.Next() } } // bridgeCtx 由 gin.Context 构造标准 context(trace_id + 语言) func bridgeCtx(c *gin.Context) context.Context { ctx := context.Background() if c.Request != nil { ctx = c.Request.Context() } responsex.SetTraceKey(traceIDCtxKey) // trace_id:优先 gin context,其次请求头 if v, ok := c.Get(traceIDCtxKey); ok { if s, ok := v.(string); ok && s != "" { uuid := uuid.New().String() ctx = context.WithValue(ctx, traceIDCtxKey, uuid) } } else if val, ok := c.Get("trace_id"); ok { ctx = context.WithValue(ctx, traceIDCtxKey, val) } else if s := c.GetHeader("X-Request-Id"); s != "" { ctx = context.WithValue(ctx, traceIDCtxKey, s) } else { ctx = context.WithValue(ctx, traceIDCtxKey, uuid.New().String()) } // Accept-Language:核心包按此语言取译文 if lang := c.GetHeader("Accept-Language"); lang != "" { ctx = langx.SetCtxLang(ctx, lang) } return ctx } // requestCtx 返回用于构造响应的 context。 // 在中间件建立的 ctx 基础上,于写响应前再同步一次 trace_id, // 这样 handler 内部才调用 c.Set("trace_id", ...) 也能生效。 func requestCtx(c *gin.Context) context.Context { if c.Request == nil { return context.Background() } ctx := c.Request.Context() if v, ok := c.Get(traceIDCtxKey); ok { if s, ok := v.(string); ok && s != "" { ctx = context.WithValue(ctx, traceIDCtxKey, s) } } return ctx } func Error(c *gin.Context, err error, ops ...responsex.Option) { responsex.Error(requestCtx(c), c.Writer, err, ops...) } func ErrorWithData(c *gin.Context, err error, data any, ops ...responsex.Option) { responsex.ErrorWithData(requestCtx(c), c.Writer, err, data, ops...) } func ErrorStr(c *gin.Context, msg string, ops ...responsex.Option) { responsex.ErrorStr(requestCtx(c), c.Writer, msg, ops...) } func Success(c *gin.Context, data any, ops ...responsex.Option) { responsex.Success(requestCtx(c), c.Writer, data, ops...) } func SuccessWithPage(c *gin.Context, data any, page responsex.Pagination, ops ...responsex.Option) { responsex.SuccessWithPage(requestCtx(c), c.Writer, data, &page, ops...) } func Custom(c *gin.Context, ops ...responsex.Option) { responsex.ResponseCtx(requestCtx(c), c.Writer, ops...) } func MsgMapData(c *gin.Context, msgKey string, format map[string]string, data any, ops ...responsex.Option) { responsex.FormatMessage(requestCtx(c), c.Writer, msgKey, format, data, ops...) } func MsgKV(c *gin.Context, msgKey string, k string, v string, ops ...responsex.Option) { responsex.FormatMessage(requestCtx(c), c.Writer, msgKey, map[string]string{k: v}, nil, ops...) }