Files
responsex/ginx/ginx.go
T
2026-09-19 01:31:34 +08:00

129 lines
3.9 KiB
Go
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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...)
}