129 lines
3.9 KiB
Go
129 lines
3.9 KiB
Go
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...)
|
|||
|
|
}
|