优化方法的使用

This commit is contained in:
yun
2026-09-19 01:31:34 +08:00
parent 00eba5cc75
commit 407fc49e0c
20 changed files with 1710 additions and 376 deletions
Executable
+7
View File
@@ -0,0 +1,7 @@
# 本地开发用的 go.work(含本地 replace,提交会让使用方 go get 失败)
go.work
go.work.sum
# 临时/备份文件
*.bak
*.tmp
+358
View File
@@ -0,0 +1,358 @@
package responsex_test
// 本文件是可运行的示例(godoc Example),同时被 go test 真实执行并校验输出。
// 所有示例都关掉了响应日志,避免污染输出。
import (
"context"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"code.yun.ink/pkg/responsex"
"github.com/yuninks/errorx"
"github.com/yuninks/langx"
)
// ---------- 1. 最简成功响应(标准库 http) ----------
func ExampleSuccess() {
w := httptest.NewRecorder()
responsex.Success(context.Background(), w,
map[string]any{"id": 1, "name": "Yun"},
responsex.SetIgnoreLog(),
)
fmt.Println("HTTP", w.Code)
fmt.Println(w.Body.String())
// Output:
// HTTP 200
// {"code":200,"message":"请求成功","data":{"id":1,"name":"Yun"}}
}
// ---------- 2. data 为 nil 时字段被省略 ----------
func ExampleSuccess_nilData() {
w := httptest.NewRecorder()
responsex.Success(context.Background(), w, nil, responsex.SetIgnoreLog())
fmt.Println(w.Body.String())
// Output:
// {"code":200,"message":"请求成功"}
}
// ---------- 3. 失败响应 ----------
func ExampleError() {
w := httptest.NewRecorder()
responsex.Error(context.Background(), w,
errors.New("数据库连接超时"),
responsex.SetHttpStatusCode(http.StatusInternalServerError),
responsex.SetIgnoreLog(),
)
fmt.Println("HTTP", w.Code)
fmt.Println(w.Body.String())
// Output:
// HTTP 500
// {"code":400,"message":"数据库连接超时","data":""}
}
// ---------- 4. 业务错误码:用 errorx 定义,自动带 code 与多语言文案 ----------
// 实际项目中通常在单独的 errors 包里用 var 声明一次
var (
ExampleErrParamsInvalid = errorx.NewCode("example_params_invalid", 40001, "参数异常")
ExampleErrNotFound = errorx.NewCode("example_not_found", 40004, "资源不存在")
)
func ExampleError_businessCode() {
langx.RegisterTrans("zh", map[string]string{
"example_params_invalid": "参数异常",
})
langx.AppendTrans("en", map[string]string{
"example_params_invalid": "invalid params",
})
// 中文请求
w := httptest.NewRecorder()
responsex.Error(context.Background(), w,
ExampleErrParamsInvalid.New(context.Background()),
responsex.SetIgnoreLog(),
)
fmt.Println("zh:", w.Body.String())
// 英文请求:文案随 ctx 语言变化,业务码不变
ctxEn := langx.SetCtxLang(context.Background(), "en")
w = httptest.NewRecorder()
responsex.Error(ctxEn, w,
ExampleErrParamsInvalid.New(ctxEn),
responsex.SetIgnoreLog(),
)
fmt.Println("en:", w.Body.String())
// Output:
// zh: {"code":40001,"message":"参数异常","data":""}
// en: {"code":40001,"message":"invalid params","data":""}
}
// ---------- 5. 错误里带上下文数据(便于前端定位字段) ----------
func ExampleErrorWithData() {
w := httptest.NewRecorder()
responsex.ErrorWithData(context.Background(), w,
ExampleErrParamsInvalid.New(context.Background()),
map[string]any{"field": "email", "reason": "格式不正确"},
responsex.SetHttpStatusCode(http.StatusBadRequest),
responsex.SetIgnoreLog(),
)
fmt.Println("HTTP", w.Code)
fmt.Println(w.Body.String())
// Output:
// HTTP 400
// {"code":40001,"message":"参数异常","data":{"field":"email","reason":"格式不正确"}}
}
// ---------- 6. 直接回一句文案(不经过 errorx) ----------
func ExampleErrorStr() {
w := httptest.NewRecorder()
responsex.ErrorStr(context.Background(), w, "签名校验失败",
responsex.SetHttpStatusCode(http.StatusForbidden),
responsex.SetIgnoreLog(),
)
fmt.Println("HTTP", w.Code)
fmt.Println(w.Body.String())
// Output:
// HTTP 403
// {"code":400,"message":"签名校验失败"}
}
// ---------- 7. 带占位符的多语言文案 ----------
func ExampleFormatMessage() {
langx.AppendTrans("zh", map[string]string{
"example_welcome": "欢迎 #name#,你有 #count# 条新消息",
})
langx.AppendTrans("en", map[string]string{
"example_welcome": "Welcome #name#, you have #count# new messages",
})
w := httptest.NewRecorder()
responsex.FormatMessage(context.Background(), w, "example_welcome",
map[string]string{"name": "Yun", "count": "3"},
[]string{"a", "b", "c"},
responsex.SetIgnoreLog(),
)
fmt.Println(w.Body.String())
ctxEn := langx.SetCtxLang(context.Background(), "en")
w = httptest.NewRecorder()
responsex.FormatMessage(ctxEn, w, "example_welcome",
map[string]string{"name": "Yun", "count": "3"},
nil,
responsex.SetIgnoreLog(),
)
fmt.Println(w.Body.String())
// Output:
// {"code":200,"message":"欢迎 Yun,你有 3 条新消息","data":["a","b","c"]}
// {"code":200,"message":"Welcome Yun, you have 3 new messages"}
}
// ---------- 8. 分页响应 ----------
func ExampleSuccessWithPage() {
// 分页参数一般来自 Query,这里直接给值
page, size := int64(2), int64(10)
total := int64(35)
// 1) 换算数据库 offset/limit
offset, limit := responsex.GetOffset(int(page), int(size))
fmt.Printf("SQL: LIMIT %d OFFSET %d\n", limit, offset)
// 2) 查库(这里用假数据代替)
rows := []map[string]any{{"id": 11}, {"id": 12}}
// 3) 输出
pagination := responsex.GetPage(page, size, total)
w := httptest.NewRecorder()
responsex.SuccessWithPage(context.Background(), w, rows, &pagination, responsex.SetIgnoreLog())
fmt.Println(w.Body.String())
// Output:
// SQL: LIMIT 10 OFFSET 10
// {"code":200,"message":"请求成功","data":[{"id":11},{"id":12}],"pagination":{"page":2,"size":10,"total_page":4,"total_count":35}}
}
// ---------- 9. 自定义响应(自己控制 code/message/HTTP 状态码/响应头) ----------
func ExampleResponseCtx() {
w := httptest.NewRecorder()
responsex.ResponseCtx(context.Background(), w,
responsex.SetCode(20001),
responsex.SetMessage("订单已受理,处理中"),
responsex.SetData(map[string]any{"order_sn": "SN20240101"}),
responsex.SetHttpStatusCode(http.StatusAccepted),
responsex.SetHeaders(map[string]string{
"X-Order-Sn": "SN20240101",
"X-Rate-Limit": "100",
"Cache-Control": "no-store",
}),
responsex.SetIgnoreLog(),
)
fmt.Println("HTTP", w.Code)
fmt.Println("X-Order-Sn:", w.Header().Get("X-Order-Sn"))
fmt.Println(w.Body.String())
// Output:
// HTTP 202
// X-Order-Sn: SN20240101
// {"code":20001,"message":"订单已受理,处理中","data":{"order_sn":"SN20240101"}}
}
type myTraceKey struct{}
// ---------- 11. 自定义日志器:接入项目自有 logger ----------
// myLogger 实现 responsex.Logger
type myLogger struct{ prefix string }
func (l myLogger) Info(ctx context.Context, args ...interface{}) {
fmt.Println("INFO ", l.prefix, fmt.Sprint(args...))
}
func (l myLogger) Infof(ctx context.Context, format string, args ...interface{}) {
fmt.Println("INFO ", l.prefix, fmt.Sprintf(format, args...))
}
func (l myLogger) Error(ctx context.Context, args ...interface{}) {
fmt.Println("ERROR", l.prefix, fmt.Sprint(args...))
}
func (l myLogger) Errorf(ctx context.Context, format string, args ...interface{}) {
fmt.Println("ERROR", l.prefix, fmt.Sprintf(format, args...))
}
func ExampleSetLogger() {
w := httptest.NewRecorder()
responsex.Success(context.Background(), w, "ok",
responsex.SetLogger(myLogger{prefix: "[svc]"}),
)
// Output:
// INFO [svc] response:{"code":200,"message":"请求成功","data":"ok"}
}
// ---------- 12. 全局初始化:进程启动时执行一次 ----------
func ExampleInitOptions() {
// 一般放在 main() 或 wire 初始化里
responsex.InitOptions(
responsex.SetDefaultSuccessCode(0), // 业务成功码改为 0
responsex.SetDefaultErrorCode(-1), // 业务失败码改为 -1
responsex.SetDefaultSuccessMessage("操作成功"),
)
defer func() { // 恢复默认,避免影响其它用例/示例
responsex.InitOptions(
responsex.SetDefaultSuccessCode(200),
responsex.SetDefaultErrorCode(400),
responsex.SetDefaultSuccessMessage("请求成功"),
)
}()
w := httptest.NewRecorder()
responsex.Success(context.Background(), w, "ok", responsex.SetIgnoreLog())
fmt.Println(w.Body.String())
w = httptest.NewRecorder()
responsex.Error(context.Background(), w, errors.New("出错了"),
responsex.SetHttpStatusCode(http.StatusBadRequest), responsex.SetIgnoreLog())
fmt.Println(w.Body.String())
// Output:
// {"code":0,"message":"操作成功","data":"ok"}
// {"code":-1,"message":"出错了","data":""}
}
// ---------- 13. 成功文案的多语言 ----------
func ExampleSuccessMessageCtx() {
langx.AppendTrans("en", map[string]string{"responsex_success": "success"})
ctxZh := langx.SetCtxLang(context.Background(), "zh")
ctxEn := langx.SetCtxLang(context.Background(), "en")
fmt.Println("zh:", responsex.SuccessMessageCtx(ctxZh))
fmt.Println("en:", responsex.SuccessMessageCtx(ctxEn))
// Success 内部用同一套解析逻辑
w := httptest.NewRecorder()
responsex.Success(ctxEn, w, "ok", responsex.SetIgnoreLog())
fmt.Println(w.Body.String())
// Output:
// zh: 请求成功
// en: success
// {"code":200,"message":"success","data":"ok"}
}
// ---------- 14. 分页参数换算(含脏数据兜底) ----------
func ExampleGetOffset() {
for _, in := range [][2]int{{1, 10}, {3, 20}, {0, 0}, {-1, -5}} {
offset, limit := responsex.GetOffset(in[0], in[1])
fmt.Printf("page=%2d size=%3d -> offset=%3d limit=%3d\n", in[0], in[1], offset, limit)
}
// Output:
// page= 1 size= 10 -> offset= 0 limit= 10
// page= 3 size= 20 -> offset= 40 limit= 20
// page= 0 size= 0 -> offset= 0 limit= 10
// page=-1 size= -5 -> offset= 0 limit= 10
}
func ExampleGetPage() {
p := responsex.GetPage(3, 10, 35)
fmt.Println(p.Page, p.Size, p.TotalPage, p.TotalCount)
// 脏参数不会算出负页数
p = responsex.GetPage(1, -5, 100)
fmt.Println(p.Page, p.Size, p.TotalPage, p.TotalCount)
// Output:
// 3 10 4 35
// 1 -5 0 100
}
// ---------- 15. 做为一个组件接入自己的封装层 ----------
// respond 模拟业务项目里的统一响应入口
func respond(w http.ResponseWriter, v any, err error) {
ctx := context.Background()
if err != nil {
responsex.ErrorStr(ctx, w, err.Error(), responsex.SetIgnoreLog())
return
}
responsex.Success(ctx, w, v, responsex.SetIgnoreLog())
}
func Example_ownWrapper() {
w1 := httptest.NewRecorder()
respond(w1, map[string]string{"ok": "true"}, nil)
w2 := httptest.NewRecorder()
respond(w2, nil, errors.New("库存不足"))
fmt.Println(w1.Body.String())
fmt.Println(w2.Body.String())
// Output:
// {"code":200,"message":"请求成功","data":{"ok":"true"}}
// {"code":400,"message":"库存不足"}
}
+334
View File
@@ -0,0 +1,334 @@
package ginx_test
// 可运行示例:gin 接入 responsex 的完整用法。
// 每个示例都用 httptest 真实走一遍路由,输出即真实响应体。
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"os"
"testing"
"code.yun.ink/pkg/responsex"
ginx "code.yun.ink/pkg/responsex/gin"
"github.com/gin-gonic/gin"
"github.com/yuninks/errorx"
"github.com/yuninks/langx"
)
// ---------- 业务错误码:项目里通常在 errors 包声明一次 ----------
var (
// 第一种写法:text 直接给默认语言文案
ErrSignInvalid = errorx.NewCode("ex_sign_invalid", 41001, "签名校验失败")
// 第二种写法:文案由 langx 的语言表提供(推荐,便于集中维护多语言)
ErrStockNotEnough = errorx.NewCode("ex_stock_not_enough", 50001, "库存不足")
)
// TestMain 里注册一次语言表,等价于项目启动时执行 langx.RegisterDir / RegisterEmbed
func TestMain(m *testing.M) {
langx.RegisterTrans("zh", map[string]string{
"ex_stock_not_enough": "库存不足",
"ex_sign_invalid": "签名校验失败",
})
langx.RegisterTrans("en", map[string]string{
"ex_stock_not_enough": "out of stock",
"ex_sign_invalid": "invalid signature",
})
// 示例演示用:把默认 logger 换成静默实现,避免响应日志混入示例输出。
// 真实项目应接入自己的日志组件,例如:
// responsex.InitOptions(responsex.SetLogger(loggerx.New(...)))
responsex.InitOptions(responsex.SetLogger(silentLogger{}))
gin.SetMode(gin.TestMode)
os.Exit(m.Run())
}
// silentLogger 实现 responsex.Logger,什么都不做
type silentLogger struct{}
func (silentLogger) Info(ctx context.Context, args ...interface{}) {}
func (silentLogger) Infof(ctx context.Context, format string, args ...interface{}) {}
func (silentLogger) Error(ctx context.Context, args ...interface{}) {}
func (silentLogger) Errorf(ctx context.Context, format string, args ...interface{}) {
}
// ---------- 辅助 ----------
func newExampleEngine() *gin.Engine {
r := gin.New()
r.Use(ginx.Middleware())
return r
}
// do 发一次请求,返回响应记录器
func do(r *gin.Engine, method, path string, headers map[string]string) *httptest.ResponseRecorder {
req := httptest.NewRequest(method, path, nil)
for k, v := range headers {
req.Header.Set(k, v)
}
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
return w
}
// body 返回按 key 排序后的紧凑 JSON,便于示例输出稳定
func body(w *httptest.ResponseRecorder) string {
var m map[string]any
if err := json.Unmarshal(w.Body.Bytes(), &m); err != nil {
return w.Body.String()
}
return compact(m)
}
func compact(v any) string {
bs, err := json.Marshal(v)
if err != nil {
return ""
}
return string(bs)
}
// ---------- 1. 最小可用:挂中间件 + 成功响应 ----------
func Example() {
r := newExampleEngine()
r.GET("/ping", func(c *gin.Context) {
ginx.Success(c, gin.H{"pong": true})
})
w := do(r, http.MethodGet, "/ping", nil)
fmt.Println("HTTP", w.Code)
fmt.Println(w.Body.String())
// Output:
// HTTP 200
// {"code":200,"message":"请求成功","data":{"pong":true}}
}
// ---------- 2. Middleware:trace_id 桥接 ----------
func ExampleMiddleware() {
r := newExampleEngine()
r.GET("/order", func(c *gin.Context) {
// 业务侧照旧用 c.Set,不需要关心核心包怎么取值
c.Set("trace_id", "trace-9f8e7d")
ginx.Success(c, gin.H{"order_sn": "SN20240101"})
})
w := do(r, http.MethodGet, "/order", nil)
fmt.Println("c.Set :", w.Body.String())
// 没有 c.Set 时,回落到请求头 X-Request-Id
r2 := newExampleEngine()
r2.GET("/order", func(c *gin.Context) {
ginx.Success(c, "ok")
})
w = do(r2, http.MethodGet, "/order", map[string]string{"X-Request-Id": "req-123"})
fmt.Println("header :", w.Body.String())
// 两者都没有 → 不出现 trace_id 字段
w = do(r2, http.MethodGet, "/order", nil)
fmt.Println("none :", w.Body.String())
// Output:
// c.Set : {"code":200,"message":"请求成功","data":{"order_sn":"SN20240101"},"trace_id":"trace-9f8e7d"}
// header : {"code":200,"message":"请求成功","data":"ok","trace_id":"req-123"}
// none : {"code":200,"message":"请求成功","data":"ok"}
}
// ---------- 3. 参数绑定失败:直接回错误 ----------
func ExampleError() {
r := newExampleEngine()
r.GET("/user", func(c *gin.Context) {
ginx.Error(c, errors.New("EOF: 请求体为空"),
responsex.SetHttpStatusCode(http.StatusBadRequest),
)
})
w := do(r, http.MethodGet, "/user", nil)
fmt.Println("HTTP", w.Code)
fmt.Println(w.Body.String())
// Output:
// HTTP 400
// {"code":400,"message":"EOF: 请求体为空","data":""}
}
// ---------- 4. 业务错误码:code 与多语言文案自动带出 ----------
func ExampleError_businessCode() {
r := newExampleEngine()
r.POST("/order", func(c *gin.Context) {
// errorx 的 New(ctx) 会带上当前请求语言,Error 自动取 Code() 与译文
ginx.Error(c, ErrStockNotEnough.New(c.Request.Context()),
responsex.SetHttpStatusCode(http.StatusConflict))
})
w := do(r, http.MethodPost, "/order", nil)
fmt.Println("zh:", w.Body.String())
w = do(r, http.MethodPost, "/order", map[string]string{"Accept-Language": "en"})
fmt.Println("en:", w.Body.String())
// Output:
// zh: {"code":50001,"message":"库存不足","data":""}
// en: {"code":50001,"message":"out of stock","data":""}
}
// ---------- 5. 校验错误带上字段信息 ----------
func ExampleErrorWithData() {
r := newExampleEngine()
r.POST("/register", func(c *gin.Context) {
ginx.ErrorWithData(c, errors.New("参数校验失败"),
gin.H{
"fields": []string{"email", "phone"},
"detail": "邮箱格式不正确",
},
responsex.SetHttpStatusCode(http.StatusUnprocessableEntity),
)
})
w := do(r, http.MethodPost, "/register", nil)
fmt.Println("HTTP", w.Code)
fmt.Println(body(w))
// Output:
// HTTP 422
// {"code":400,"data":{"detail":"邮箱格式不正确","fields":["email","phone"]},"message":"参数校验失败"}
}
// ---------- 6. 列表分页 ----------
func ExampleSuccessWithPage() {
r := newExampleEngine()
r.GET("/users", func(c *gin.Context) {
// Query: ?page=2&size=10
var page, size int64 = 2, 10
total := int64(35)
// 1) 换算 SQL 的 offset/limit
offset, limit := responsex.GetOffset(int(page), int(size))
_, _ = offset, limit
// 2) 查库(此处给假数据)
rows := []gin.H{{"id": 11, "name": "Alice"}, {"id": 12, "name": "Bob"}}
// 3) 输出
ginx.SuccessWithPage(c, rows, responsex.GetPage(page, size, total))
})
w := do(r, http.MethodGet, "/users?page=2&size=10", nil)
fmt.Println(w.Body.String())
// Output:
// {"code":200,"message":"请求成功","data":[{"id":11,"name":"Alice"},{"id":12,"name":"Bob"}],"pagination":{"page":2,"size":10,"total_page":4,"total_count":35}}
}
// ---------- 7. 多语言文案 + 占位符 ----------
func ExampleFormatMessage() {
langx.AppendTrans("zh", map[string]string{
"ex_order_created": "订单 #sn# 创建成功,金额 #amount# 元",
})
langx.AppendTrans("en", map[string]string{
"ex_order_created": "Order #sn# created, amount #amount#",
})
r := newExampleEngine()
r.POST("/order", func(c *gin.Context) {
ginx.MsgMapData(c, "ex_order_created",
map[string]string{"sn": "SN20240101", "amount": "99.00"},
gin.H{"order_sn": "SN20240101"},
)
})
w := do(r, http.MethodPost, "/order", nil)
fmt.Println("zh:", w.Body.String())
w = do(r, http.MethodPost, "/order", map[string]string{"Accept-Language": "en"})
fmt.Println("en:", w.Body.String())
// Output:
// zh: {"code":200,"message":"订单 SN20240101 创建成功,金额 99.00 元","data":{"order_sn":"SN20240101"}}
// en: {"code":200,"message":"Order SN20240101 created, amount 99.00","data":{"order_sn":"SN20240101"}}
}
// ---------- 8. 单占位符快捷方法 ----------
func ExampleMsgKV() {
langx.AppendTrans("zh", map[string]string{
"ex_welcome": "欢迎回来,#name#",
})
r := newExampleEngine()
r.POST("/login", func(c *gin.Context) {
ginx.MsgKV(c, "ex_welcome", "name", "Yun")
})
w := do(r, http.MethodPost, "/login", nil)
fmt.Println(w.Body.String())
// Output:
// {"code":200,"message":"欢迎回来,Yun"}
}
// ---------- 9. 自定义响应:非标准 code / 自定义响应头 ----------
func ExampleCustom() {
r := newExampleEngine()
r.POST("/pay", func(c *gin.Context) {
ginx.Custom(c,
responsex.SetCode(20001),
responsex.SetMessage("支付受理中"),
responsex.SetData(gin.H{"pay_no": "PAY20240101"}),
responsex.SetHttpStatusCode(http.StatusAccepted),
responsex.SetHeader("X-Pay-No", "PAY20240101"),
)
})
w := do(r, http.MethodPost, "/pay", nil)
fmt.Println("HTTP", w.Code)
fmt.Println("X-Pay-No:", w.Header().Get("X-Pay-No"))
fmt.Println(w.Body.String())
// Output:
// HTTP 202
// X-Pay-No: PAY20240101
// {"code":20001,"message":"支付受理中","data":{"pay_no":"PAY20240101"}}
}
// ---------- 10. 完整骨架:全局初始化 + 中间件 + 业务错误 + 分页 ----------
func Example_completeServer() {
// 1) 启动时全局初始化(一般放 main / wire)
responsex.InitOptions(
responsex.SetDefaultSuccessCode(0),
responsex.SetDefaultErrorCode(-1),
)
defer responsex.InitOptions( // 示例里恢复,避免影响其它用例
responsex.SetDefaultSuccessCode(200),
responsex.SetDefaultErrorCode(400),
)
// 2) 路由
r := gin.New()
r.Use(ginx.Middleware()) // 关键:桥接 trace_id 与语言
r.GET("/goods", func(c *gin.Context) {
if c.Query("sign") == "" {
ginx.Error(c, ErrSignInvalid.New(c.Request.Context()))
return
}
ginx.Success(c, gin.H{"goods": []string{"apple", "pear"}})
})
w := do(r, http.MethodGet, "/goods", map[string]string{"X-Request-Id": "t-1"})
fmt.Println("fail:", w.Body.String())
w = do(r, http.MethodGet, "/goods?sign=abc", map[string]string{"X-Request-Id": "t-2"})
fmt.Println("ok: ", w.Body.String())
// Output:
// fail: {"code":41001,"message":"签名校验失败","data":"","trace_id":"t-1"}
// ok: {"code":0,"message":"请求成功","data":{"goods":["apple","pear"]},"trace_id":"t-2"}
}
-38
View File
@@ -1,38 +0,0 @@
package ginx
import (
"code.yun.ink/pkg/responsex"
"github.com/gin-gonic/gin"
)
func Error(ctx *gin.Context, err error, ops ...responsex.Option) {
responsex.Error(ctx, ctx.Writer, err, ops...)
}
func ErrorWithData(ctx *gin.Context, err error, data any, ops ...responsex.Option) {
responsex.ErrorWithData(ctx, ctx.Writer, err, data, ops...)
}
func ErrorStr(ctx *gin.Context, msg string, ops ...responsex.Option) {
responsex.ErrorStr(ctx, ctx.Writer, msg, ops...)
}
func Success(ctx *gin.Context, data any, ops ...responsex.Option) {
responsex.Success(ctx, ctx.Writer, data, ops...)
}
func SuccessWithPage(ctx *gin.Context, data any, page responsex.Pagination, ops ...responsex.Option) {
responsex.SuccessWithPage(ctx, ctx.Writer, data, &page, ops...)
}
func Custom(ctx *gin.Context, ops ...responsex.Option) {
responsex.ResponseCtx(ctx, ctx.Writer, ops...)
}
func MsgMapData(ctx *gin.Context, msgKey string, format map[string]string, data any, ops ...responsex.Option) {
responsex.FormatMessage(ctx, ctx.Writer, msgKey, format, data, ops...)
}
func MsgKV(ctx *gin.Context, msgKey string, k string, v string, ops ...responsex.Option) {
responsex.FormatMessage(ctx, ctx.Writer, msgKey, map[string]string{k: v}, nil, ops...)
}
Regular → Executable
+81 -3
View File
@@ -1,14 +1,16 @@
package ginx_test package ginx_test
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"code.yun.ink/pkg/responsex" "code.yun.ink/pkg/responsex"
respgin "code.yun.ink/pkg/responsex/ginx" respgin "code.yun.ink/pkg/responsex/gin"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/yuninks/langx"
) )
func init() { func init() {
@@ -35,6 +37,8 @@ func newGinCtx(w *httptest.ResponseRecorder) *gin.Context {
func TestSuccess(t *testing.T) { func TestSuccess(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
c := newGinCtx(w) c := newGinCtx(w)
// c.Set("trace_id","adada")
c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), "trace_id", "adada"))
respgin.Success(c, "hello world") respgin.Success(c, "hello world")
@@ -169,8 +173,8 @@ func TestErrorStr(t *testing.T) {
respgin.ErrorStr(c, "string error") respgin.ErrorStr(c, "string error")
body := decodeBody(t, w) body := decodeBody(t, w)
if body["message"] == "" { if body["message"] != "string error" {
t.Error("message should not be empty") t.Errorf("message = %v, want 'string error'", body["message"])
} }
} }
@@ -275,3 +279,77 @@ func TestCustom(t *testing.T) {
t.Errorf("code = %v, want 100", body["code"]) t.Errorf("code = %v, want 100", body["code"])
} }
} }
// ---------- Middleware:trace_id 桥接 ----------
func newEngine() *gin.Engine {
r := gin.New()
r.Use(respgin.Middleware())
return r
}
func TestMiddlewareBridgesTraceID(t *testing.T) {
r := newEngine()
r.GET("/t", func(c *gin.Context) {
c.Set("trace_id", "trace-from-gin")
respgin.Success(c, "ok")
})
w := httptest.NewRecorder()
r.ServeHTTP(w, httptest.NewRequest("GET", "/t", nil))
body := decodeBody(t, w)
if body["trace_id"] != "trace-from-gin" {
t.Errorf("trace_id = %v, want trace-from-gin (需要 Middleware 桥接)", body["trace_id"])
}
}
func TestMiddlewareTraceIDFromHeader(t *testing.T) {
r := newEngine()
r.GET("/t", func(c *gin.Context) {
respgin.Success(c, "ok")
})
req := httptest.NewRequest("GET", "/t", nil)
req.Header.Set("X-Request-Id", "req-header-id")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
body := decodeBody(t, w)
if body["trace_id"] != "req-header-id" {
t.Errorf("trace_id = %v, want req-header-id", body["trace_id"])
}
}
// ---------- Middleware:按 Accept-Language 翻译 ----------
func TestMiddlewareBridgesLanguage(t *testing.T) {
langx.AppendTrans("en", map[string]string{
"responsex_success": "success",
"not_found": "resource not found",
})
r := newEngine()
r.GET("/t", func(c *gin.Context) {
respgin.Success(c, "ok")
})
r.GET("/e", func(c *gin.Context) {
respgin.ErrorStr(c, "not_found")
})
req := httptest.NewRequest("GET", "/t", nil)
req.Header.Set("Accept-Language", "en")
w := httptest.NewRecorder()
r.ServeHTTP(w, req)
if body := decodeBody(t, w); body["message"] != "success" {
t.Errorf("message = %v, want success", body["message"])
}
req = httptest.NewRequest("GET", "/e", nil)
req.Header.Set("Accept-Language", "en")
w = httptest.NewRecorder()
r.ServeHTTP(w, req)
if body := decodeBody(t, w); body["message"] != "resource not found" {
t.Errorf("message = %v, want 'resource not found'", body["message"])
}
}
Executable
+128
View File
@@ -0,0 +1,128 @@
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...)
}
Executable
+61
View File
@@ -0,0 +1,61 @@
module code.yun.ink/pkg/responsex/gin
go 1.22.4
require (
code.yun.ink/pkg/responsex v1.0.1
github.com/gin-gonic/gin v1.9.1
github.com/yuninks/langx v0.0.7
)
require (
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/openzipkin/zipkin-go v0.4.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.0 // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
github.com/yuninks/errorx v1.0.0 // indirect
github.com/zeromicro/go-zero v1.6.4 // indirect
go.opentelemetry.io/otel v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/zipkin v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.19.0 // indirect
go.opentelemetry.io/otel/sdk v1.19.0 // indirect
go.opentelemetry.io/otel/trace v1.19.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.uber.org/automaxprocs v1.5.3 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.20.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/grpc v1.63.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Executable
+157
View File
@@ -0,0 +1,157 @@
code.yun.ink/pkg/responsex v1.0.1 h1:S0n0CSkJIKPyIOxyfWCSsiUond3FyvLKxCzvM35wABw=
code.yun.ink/pkg/responsex v1.0.1/go.mod h1:c36aY9efSBS9kmI6/toES3R82xVMW740e6g/H0GE4io=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA=
github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY=
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuninks/errorx v1.0.0 h1:/AS4dSEVPkfvs3hJFYgqYBFLKyVvxFV32XXbMXoHwvk=
github.com/yuninks/errorx v1.0.0/go.mod h1:N9e4ShfCTkLjHnGShAG0x9n2SYtCDKhKDMxRmyr05hU=
github.com/yuninks/langx v0.0.7 h1:QUt27h/FjnT7//759GoM/+p4ew+HBr10ZN/fkqeQfyA=
github.com/yuninks/langx v0.0.7/go.mod h1:nAylzjNIjCThhEQSJsIKP8Vhja0aJlcyig+NsNPZLSk=
github.com/zeromicro/go-zero v1.6.4 h1:GvZXxxwl1Lby/gIHxHwN/ZNmXl1WFJa1DvoVgqgttUs=
github.com/zeromicro/go-zero v1.6.4/go.mod h1:dQ39Zoz20/6x/SUhFXyEEg8lWjl+CO3dzg8Je2xG63Q=
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 h1:Nw7Dv4lwvGrI68+wULbcq7su9K2cebeCUrDjVrUJHxM=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0/go.mod h1:1MsF6Y7gTqosgoZvHlzcaaM8DIMNZgJh87ykokoNH7Y=
go.opentelemetry.io/otel/exporters/zipkin v1.19.0 h1:EGY0h5mGliP9o/nIkVuLI0vRiQqmsYOcbwCuotksO1o=
go.opentelemetry.io/otel/exporters/zipkin v1.19.0/go.mod h1:JQgTGJP11yi3o4GHzIWYodhPisxANdqxF1eHwDSnJrI=
go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8=
google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
-64
View File
@@ -3,70 +3,6 @@ module code.yun.ink/pkg/responsex
go 1.22.4 go 1.22.4
require ( require (
github.com/gin-gonic/gin v1.9.1
github.com/glebarez/sqlite v1.11.0
github.com/yuninks/errorx v1.0.0 github.com/yuninks/errorx v1.0.0
github.com/yuninks/langx v0.0.7 github.com/yuninks/langx v0.0.7
github.com/zeromicro/go-zero v1.6.4
gorm.io/gorm v1.30.0
)
require (
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-logr/logr v1.3.0 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/openzipkin/zipkin-go v0.4.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/spaolacci/murmur3 v1.1.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
go.opentelemetry.io/otel v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 // indirect
go.opentelemetry.io/otel/exporters/zipkin v1.19.0 // indirect
go.opentelemetry.io/otel/metric v1.19.0 // indirect
go.opentelemetry.io/otel/sdk v1.19.0 // indirect
go.opentelemetry.io/otel/trace v1.19.0 // indirect
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.uber.org/automaxprocs v1.5.3 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.20.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/grpc v1.63.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
) )
-178
View File
@@ -1,182 +1,4 @@
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.3.0 h1:2y3SDp0ZXuc6/cjLSZ+Q3ir+QB9T/iG5yYRXqsagWSY=
github.com/go-logr/logr v1.3.0/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0 h1:RtRsiaGvWxcwd8y3BiRZxsylPT8hLWZ5SPcfI+3IDNk=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.0/go.mod h1:TzP6duP4Py2pHLVPPQp42aoYI92+PCrVotyR5e8Vqlk=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/openzipkin/zipkin-go v0.4.2 h1:zjqfqHjUpPmB3c1GlCvvgsM1G4LkvqQbBDueDOCg/jA=
github.com/openzipkin/zipkin-go v0.4.2/go.mod h1:ZeVkFjuuBiSy13y8vpSDCjMi9GoI3hPpCJSBx/EYFhY=
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/yuninks/errorx v1.0.0 h1:/AS4dSEVPkfvs3hJFYgqYBFLKyVvxFV32XXbMXoHwvk= github.com/yuninks/errorx v1.0.0 h1:/AS4dSEVPkfvs3hJFYgqYBFLKyVvxFV32XXbMXoHwvk=
github.com/yuninks/errorx v1.0.0/go.mod h1:N9e4ShfCTkLjHnGShAG0x9n2SYtCDKhKDMxRmyr05hU= github.com/yuninks/errorx v1.0.0/go.mod h1:N9e4ShfCTkLjHnGShAG0x9n2SYtCDKhKDMxRmyr05hU=
github.com/yuninks/langx v0.0.7 h1:QUt27h/FjnT7//759GoM/+p4ew+HBr10ZN/fkqeQfyA= github.com/yuninks/langx v0.0.7 h1:QUt27h/FjnT7//759GoM/+p4ew+HBr10ZN/fkqeQfyA=
github.com/yuninks/langx v0.0.7/go.mod h1:nAylzjNIjCThhEQSJsIKP8Vhja0aJlcyig+NsNPZLSk= github.com/yuninks/langx v0.0.7/go.mod h1:nAylzjNIjCThhEQSJsIKP8Vhja0aJlcyig+NsNPZLSk=
github.com/zeromicro/go-zero v1.6.4 h1:GvZXxxwl1Lby/gIHxHwN/ZNmXl1WFJa1DvoVgqgttUs=
github.com/zeromicro/go-zero v1.6.4/go.mod h1:dQ39Zoz20/6x/SUhFXyEEg8lWjl+CO3dzg8Je2xG63Q=
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4=
go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0 h1:3d+S281UTjM+AbF31XSOYn1qXn3BgIdWl8HNEpx08Jk=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.19.0/go.mod h1:0+KuTDyKL4gjKCF75pHOX4wuzYDUZYfAQdSu43o+Z2I=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0 h1:Nw7Dv4lwvGrI68+wULbcq7su9K2cebeCUrDjVrUJHxM=
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.19.0/go.mod h1:1MsF6Y7gTqosgoZvHlzcaaM8DIMNZgJh87ykokoNH7Y=
go.opentelemetry.io/otel/exporters/zipkin v1.19.0 h1:EGY0h5mGliP9o/nIkVuLI0vRiQqmsYOcbwCuotksO1o=
go.opentelemetry.io/otel/exporters/zipkin v1.19.0/go.mod h1:JQgTGJP11yi3o4GHzIWYodhPisxANdqxF1eHwDSnJrI=
go.opentelemetry.io/otel/metric v1.19.0 h1:aTzpGtV0ar9wlV4Sna9sdJyII5jTVJEvKETPiOKwvpE=
go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8=
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0=
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0=
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de h1:cZGRis4/ot9uVm639a+rHCUaG0JJHEsdyzSQTMX+suY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:H4O17MA/PE9BsGx3w+a+W2VOLLD1Qf7oJneAoU6WktY=
google.golang.org/grpc v1.63.0 h1:WjKe+dnvABXyPJMD7KDNLxtoGk5tgk+YFWN6cBWjZE8=
google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
Executable
+24
View File
@@ -0,0 +1,24 @@
module code.yun.ink/pkg/responsex/gormx
go 1.22.4
require (
github.com/glebarez/sqlite v1.11.0
gorm.io/gorm v1.30.0
)
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.20.0 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
)
Executable
+34
View File
@@ -0,0 +1,34 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs=
gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
Regular → Executable
+6
View File
@@ -1,8 +1,14 @@
// Package gormx 提供 responsex 的 GORM 分页辅助。
//
// 按需加载:只有显式 import 本包的项目才依赖 gorm。
//
// import 路径:code.yun.ink/pkg/responsex/gormx
package gormx package gormx
import "gorm.io/gorm" import "gorm.io/gorm"
// 计算GORM DB的分页 // 计算GORM DB的分页
// page 与 size 同时小于等于 0 时,只限制最大条数(1000),不做偏移
func GetDbOffset(db *gorm.DB, page, size int64) *gorm.DB { func GetDbOffset(db *gorm.DB, page, size int64) *gorm.DB {
offset := int(0) offset := int(0)
limit := int(0) limit := int(0)
Regular → Executable
+38 -10
View File
@@ -1,6 +1,8 @@
package gormx_test package gormx_test
import ( import (
"fmt"
"strings"
"testing" "testing"
"code.yun.ink/pkg/responsex/gormx" "code.yun.ink/pkg/responsex/gormx"
@@ -18,19 +20,29 @@ func newDB(t *testing.T) *gorm.DB {
return db return db
} }
// newDryRunDB 创建 DryRun 模式的 DB,用于断言生成的 SQL,不真正查询
func newDryRunDB(t *testing.T) *gorm.DB {
t.Helper()
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{DryRun: true})
if err != nil {
t.Fatalf("open sqlite failed: %v", err)
}
return db
}
func TestGetDbOffset(t *testing.T) { func TestGetDbOffset(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
page, size int64 page, size int64
wantOffset int wantOffset int
wantLimit int wantLimit int
}{ }{
{"page1 size10", 1, 10, 0, 10}, {"page1 size10", 1, 10, 0, 10},
{"page2 size10", 2, 10, 10, 10}, {"page2 size10", 2, 10, 10, 10},
{"page3 size20", 3, 20, 40, 20}, {"page3 size20", 3, 20, 40, 20},
{"page0 size10 default page to 1", 0, 10, 0, 10}, {"page0 size10 default page to 1", 0, 10, 0, 10},
{"page1 size0 default size to 10", 1, 0, 0, 10}, {"page1 size0 default size to 10", 1, 0, 0, 10},
{"both negative or zero max 1000", 0, 0, 0, 1000}, {"both zero max 1000", 0, 0, 0, 1000},
{"negative page default to 1", -1, 10, 0, 10}, {"negative page default to 1", -1, 10, 0, 10},
{"negative size default to 10", 1, -1, 0, 10}, {"negative size default to 10", 1, -1, 0, 10},
{"both negative", -1, -1, 0, 1000}, {"both negative", -1, -1, 0, 1000},
@@ -39,14 +51,28 @@ func TestGetDbOffset(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
db := newDB(t) db := newDryRunDB(t)
result := gormx.GetDbOffset(db, tt.page, tt.size) var m testModel
stmt := gormx.GetDbOffset(db, tt.page, tt.size).Find(&m).Statement
stmt := result.Statement
// 验证 Limit 被正确设置
if stmt == nil { if stmt == nil {
t.Fatal("statement is nil") t.Fatal("statement is nil")
} }
got := stmt.SQL.String()
if got == "" {
t.Fatal("no SQL generated")
}
// 断言生成的 SQL 片段,避免依赖 Vars 顺序
wantLimit := fmt.Sprintf("LIMIT %d", tt.wantLimit)
if tt.wantOffset > 0 {
wantOffset := fmt.Sprintf("OFFSET %d", tt.wantOffset)
if !strings.Contains(got, wantOffset) {
t.Errorf("sql %q 缺少 %q", got, wantOffset)
}
}
if !strings.Contains(got, wantLimit) {
t.Errorf("sql %q 缺少 %q", got, wantLimit)
}
}) })
} }
} }
@@ -54,7 +80,9 @@ func TestGetDbOffset(t *testing.T) {
func TestGetDbOffset_Chaining(t *testing.T) { func TestGetDbOffset_Chaining(t *testing.T) {
db := newDB(t) db := newDB(t)
db.AutoMigrate(&testModel{}) if err := db.AutoMigrate(&testModel{}); err != nil {
t.Fatalf("migrate failed: %v", err)
}
// 插入测试数据 // 插入测试数据
for i := 1; i <= 50; i++ { for i := 1; i <= 50; i++ {
+43 -11
View File
@@ -6,11 +6,15 @@ import (
) )
type options struct { type options struct {
logger Logger logger Logger
ignoreLog bool ignoreLog bool
traceId string
defaultSuccessCode int traceFunc func(ctx context.Context) string
defaultErrorCode int
defaultSuccessCode int
defaultErrorCode int
defaultSuccessMessage string
successMsgSet bool
code int code int
data any data any
@@ -34,9 +38,20 @@ func InitOptions(ops ...Option) {
func defaultOptions() *options { func defaultOptions() *options {
return &options{ return &options{
logger: &defaultLogger{}, logger: &defaultLogger{},
defaultSuccessCode: 200,
defaultErrorCode: 400, traceFunc: func(ctx context.Context) string {
val := ctx.Value("trace_id")
v, ok := val.(string)
if ok {
return v
}
return ""
},
defaultSuccessCode: 200,
defaultErrorCode: 400,
defaultSuccessMessage: "请求成功",
code: 200, code: 200,
data: nil, // 默认为nil data: nil, // 默认为nil
@@ -77,6 +92,15 @@ func SetDefaultSuccessCode(code int) Option {
} }
} }
// 设置默认的成功文案(多语言场景见 SuccessMessageCtx)
// 传回内置默认文案 "请求成功" 即恢复为"未显式配置",交还给多语言解析
func SetDefaultSuccessMessage(message string) Option {
return func(o *options) {
o.defaultSuccessMessage = message
o.successMsgSet = message != defaultSuccessMessage
}
}
// 设置默认的失败code // 设置默认的失败code
func SetDefaultErrorCode(code int) Option { func SetDefaultErrorCode(code int) Option {
return func(o *options) { return func(o *options) {
@@ -98,10 +122,18 @@ func SetLogger(logger Logger) Option {
} }
} }
// 是否需要响应trace_id,&ctx里面的字段 func SetTraceFunc(f func(ctx context.Context) string) Option {
func SetTraceId(traceId string) Option {
return func(o *options) { return func(o *options) {
o.traceId = traceId o.traceFunc = f
}
}
// 设置trace_id的ctx key(默认已用标准key CtxTraceIDKey,一般无需调用)
func SetTraceKey(traceKey any) Option {
return func(o *options) {
o.traceFunc = func(ctx context.Context) string {
return ctx.Value(traceKey).(string)
}
} }
} }
+220 -5
View File
@@ -1,6 +1,221 @@
# 功能说明 # responsex
1. 这个是API响应封装组件 API 响应封装组件,**按需加载**:核心包不依赖任何 Web/ORM/追踪框架,
2. 支持多多语言的错误码和错误提示 用到哪个框架才 import 哪个适配包。
3. 优化了gin框架的响应体验
4. 支持gozero框架的traceid ## 功能
1. API 统一响应封装(code / message / data / pagination / trace_id)
2. 支持多语言的错误码与错误提示(基于 [langx](https://github.com/yuninks/langx))
3. gin 响应体验优化(`responsex/gin`)
4. go-zero trace_id 接入(`responsex/gozerotrace`,可选)
5. GORM 分页辅助(`responsex/gormx`,可选)
## 模块与按需加载
本仓库是**多模块**结构,每个模块独立 `go.mod`,依赖互不牵扯:
| 模块 | import 路径 | 依赖 | 适用场景 |
|---|---|---|---|
| 核心 | `code.yun.ink/pkg/responsex` | langx、errorx | 任意 HTTP 框架 |
| gin 适配 | `code.yun.ink/pkg/responsex/gin` | gin、核心 | gin 项目 |
| go-zero trace | `code.yun.ink/pkg/responsex/gozerotrace` | go-zero、核心 | 需要 go-zero trace_id |
| GORM 分页 | `code.yun.ink/pkg/responsex/gormx` | gorm | 需要分页偏移 |
实测依赖隔离效果(`go list -deps`,外部包数 / 是否引入 go-zero):
| 消费场景 | 模块图 | 外部包 | go-zero | gin | gorm |
|---|---|---|---|---|---|
| 只用核心包 | 4 | 4 | ✗ | ✗ | ✗ |
| 核心 + gin | 69 | 57 | ✗ | ✓ | ✗ |
| 核心 + gin + gorm | 82 | 69 | ✗ | ✓ | ✓ |
| 核心 + gin + gozerotrace | 174 | 210 | ✓ | ✓ | ✗ |
关键点:**不 import `gozerotrace` 就不会下载、不会链接 go-zero**
(含 OpenTelemetry / gRPC / zipkin / jaeger 等一整棵依赖树)。
## 快速开始
### 纯 gin(推荐)
```go
import (
"code.yun.ink/pkg/responsex"
ginx "code.yun.ink/pkg/responsex/gin"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.New()
r.Use(ginx.Middleware()) // 桥接 trace_id 与 Accept-Language
r.GET("/ping", func(c *gin.Context) {
c.Set("trace_id", "xxx") // 有就自动出现在响应里
ginx.Success(c, gin.H{"pong": true})
})
r.GET("/fail", func(c *gin.Context) {
ginx.ErrorStr(c, "参数异常,请检查参数")
})
r.Run()
}
```
`ginx.Middleware()` 做了两件事:
1. 把 gin 的 `c.Set("trace_id", ...)`(或 `X-Request-Id` 请求头)桥接到标准 context;
没有它,`trace_id` 不会出现在响应里(gin 的 `c.Value()` 读不到 `c.Set()` 的值)。
2. 把 `Accept-Language` 写入语言 context,核心包据此翻译文案。
### 接入 go-zero 的 trace_id
```go
import _ "code.yun.ink/pkg/responsex/gozerotrace" // 只需这一行
```
导入后 go-zero 的 OpenTelemetry trace_id 会被自动读取,业务代码无需改动。
(纯 gin 项目如果自己用 `c.Set("trace_id", ...)` 注入,可以不要这个包。)
### GORM 分页
```go
import "code.yun.ink/pkg/responsex/gormx"
var list []User
gormx.GetDbOffset(db, page, size).Find(&list)
respgin.SuccessWithPage(c, list, responsex.GetPage(page, size, total))
```
### 不使用 gin 的其它框架
核心包只依赖标准库的 `context` 与 `http.ResponseWriter`:
```go
responsex.Success(r.Context(), w, data)
responsex.Error(r.Context(), w, err)
```
## 多语言
1. 错误码/文案注册(`langx` / `errorx`):
```go
var ErrParamsInvalid = errorx.NewCode("params_invalid", 40001, "参数异常")
// 或按文件注册:code.json 为 map[string]int,其它 json 文件名为语言名
langx.RegisterDir("./langs")
```
2. 输出:
```go
responsex.Error(ctx, w, ErrParamsInvalid.New(ctx)) // 自动带业务码与译文
responsex.FormatMessage(ctx, w, "welcome", map[string]string{"name": "Yun"}, data)
ginx.MsgKV(c, "welcome", "name", "Yun")
```
3. 成功文案:默认 `请求成功`,可用两种方式覆盖:
```go
// 方式一:全局/单次指定
responsex.InitOptions(responsex.SetDefaultSuccessMessage("操作成功"))
// 方式二:注册专用 key "responsex_success",按请求语言输出
langx.AppendTrans("zh", map[string]string{"responsex_success": "请求成功"})
langx.AppendTrans("en", map[string]string{"responsex_success": "success"})
```
> 注意:`responsex_success` 是专用 key。不要用 `success`,该 key 已被 errorx 预注册。
## trace_id 解析顺序
1. 已注册的驱动(如 go-zero 适配包);
2. 标准 ctx key `responsex.CtxTraceIDKey`(值为 `"trace_id"`);
3. 取不到则响应中不出现 `trace_id` 字段。
框架适配包通过 `responsex.RegisterTraceDriver` 注册,业务代码一般无需调用。
需要自定义来源时:
```go
responsex.RegisterTraceDriver(func(ctx context.Context) string {
return myTracer.IDFrom(ctx)
})
```
## 主要 API
核心(`responsex`):
- 响应:`Success`、`SuccessWithPage`、`Error`、`ErrorWithData`、`ErrorStr`、`FormatMessage`、`ResponseCtx`
- 选项:`SetCode`、`SetMessage`、`SetData`、`SetPage`、`SetHttpStatusCode`、`SetHeader(s)`、`SetIgnoreLog`、`SetLogger`、`SetIgnoreTrace`、`SetDefaultSuccessCode`、`SetDefaultErrorCode`、`SetDefaultSuccessMessage`
- 分页:`GetOffset`、`GetPage`、`Pagination`
- trace:`SetTraceID`、`TraceID`、`RegisterTraceDriver`、`CtxTraceIDKey`、`SuccessMessageCtx`
gin(`ginx`):`Middleware`、`Success`、`SuccessWithPage`、`Error`、`ErrorWithData`、`ErrorStr`、`Custom`、`MsgMapData`、`MsgKV`
gormx:`GetDbOffset`
gozerotrace:空导入即注册
## 开发
本仓库是多模块,本地开发建议在根目录建 `go.work`(已 gitignore,勿提交):
```
go 1.22.4
use (
.
./gin
./gormx
./gozerotrace
)
```
运行测试:
```bash
go test ./... # 需要 go.work;否则分别进入各模块执行
cd gin && go test ./...
cd gormx && go test ./...
cd gozerotrace && go test ./...
```
校验依赖隔离(在核心模块根目录执行,应无任何框架包):
```bash
go list -deps code.yun.ink/pkg/responsex | grep -E 'gin-gonic|gorm.io|zeromicro|opentelemetry'
```
## 发布(tag 规则)
子模块必须带目录前缀的 tag,否则 proxy 不认:
| 模块 | tag | 说明 |
|---|---|---|
| 核心 | `v1.0.2` | 根目录 |
| gin | `gin/v1.0.2` | 子模块 |
| gormx | `gormx/v1.0.2` | 子模块 |
| gozerotrace | `gozerotrace/v1.0.2` | 子模块 |
**发布顺序**(重要):Go 无法 require 本地未发布的模块,所以必须
**先推子模块 tag,最后再改根 go.mod 并发布根版本**。
## v0.0.4 → v1.x 迁移
gin 适配层从根包移到了子模块,并改名为 `ginx`:
| 旧(v0.0.4,根包) | 新(v1.x) |
|---|---|
| `responsex.GinError` | `ginx.Error` |
| `responsex.GinErrorData` | `ginx.ErrorWithData` |
| `responsex.GinErrorStr` | `ginx.ErrorStr` |
| `responsex.GinSuccess` | `ginx.Success` |
| `responsex.GinSuccessWithPage` | `ginx.SuccessWithPage` |
| `responsex.GinOptions` | `ginx.Custom` |
| `responsex.GinMsgMapData` | `ginx.MsgMapData` |
| `responsex.GinMsgKV` | `ginx.MsgKV` |
新增的 `ops ...Option` 是变参,旧调用点语义不变;只需把 import 与函数名改掉,
并在路由上挂一次 `ginx.Middleware()`。
Regular → Executable
+78 -58
View File
@@ -6,12 +6,10 @@ package responsex
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"math"
"net/http" "net/http"
"github.com/yuninks/errorx" "github.com/yuninks/errorx"
"github.com/yuninks/langx" "github.com/yuninks/langx"
"github.com/zeromicro/go-zero/core/trace"
) )
type response struct { type response struct {
@@ -22,39 +20,39 @@ type response struct {
TraceId string `json:"trace_id,omitempty"` TraceId string `json:"trace_id,omitempty"`
} }
type Pagination struct { // 成功文案的多语言 key(独立 key,避免与 errorx 预注册的 "success" 冲突)
Page int64 `json:"page"` const successMsgKey = "responsex_success"
Size int64 `json:"size"`
TotalPage int64 `json:"total_page"` // 内置默认成功文案,也用作 defaultOptions 的初值
TotalCount int64 `json:"total_count"` const defaultSuccessMessage = "请求成功"
// SuccessMessageCtx 按 ctx 语言解析成功文案:
// 1. 显式配置过 SetDefaultSuccessMessage → 用配置值;
// 2. 注册过 key "responsex_success" → 用该语言的译文;
// 3. 都没有 → 内置默认文案。
//
// 多语言接入示例(由调用方决定何时注册):
//
// langx.AppendCode(map[string]int{"responsex_success": 200})
// langx.AppendTrans("zh", map[string]string{"responsex_success": "请求成功"})
// langx.AppendTrans("en", map[string]string{"responsex_success": "success"})
//
// Success 系列内部使用同一套解析逻辑,无需额外配置。
func SuccessMessageCtx(ctx context.Context) string {
return resolveSuccessMessage(ctx)
} }
// 计算DB分页偏移量 func resolveSuccessMessage(ctx context.Context) string {
func GetOffset(page, size int) (offset int, limit int) { if op != nil && op.successMsgSet {
if page <= 0 { return op.defaultSuccessMessage
page = 1
} }
offset = (page - 1) * size if msg := langx.GetMsgCtx(ctx, successMsgKey); msg != successMsgKey {
limit = size return msg
if limit == 0 {
// 默认查10条
limit = 10
} }
return if op != nil {
} return op.defaultSuccessMessage
// 计算响应分页
func GetPage(page, size, totalCount int64) Pagination {
pa := Pagination{}
totalPage := 0
if size != 0 {
totalPage = int(math.Ceil(float64(totalCount) / float64(size)))
} }
pa.Page = page return defaultSuccessMessage
pa.Size = size
pa.TotalPage = int64(totalPage)
pa.TotalCount = totalCount
return pa
} }
// Msg格式化响应 // Msg格式化响应
@@ -67,13 +65,13 @@ func FormatMessage(ctx context.Context, w http.ResponseWriter, msgKey string, fo
// 成功的响应 // 成功的响应
func Success(ctx context.Context, w http.ResponseWriter, data any, ops ...Option) { func Success(ctx context.Context, w http.ResponseWriter, data any, ops ...Option) {
baseOps := []Option{SetCode(op.defaultSuccessCode), SetMessage("请求成功"), SetData(data)} baseOps := []Option{SetCode(op.defaultSuccessCode), SetMessage(resolveSuccessMessage(ctx)), SetData(data)}
baseOps = append(baseOps, ops...) baseOps = append(baseOps, ops...)
ResponseCtx(ctx, w, baseOps...) ResponseCtx(ctx, w, baseOps...)
} }
func SuccessWithPage(ctx context.Context, w http.ResponseWriter, data any, page *Pagination, ops ...Option) { func SuccessWithPage(ctx context.Context, w http.ResponseWriter, data any, page *Pagination, ops ...Option) {
baseOps := []Option{SetCode(op.defaultSuccessCode), SetMessage("请求成功"), SetData(data), SetPage(page)} baseOps := []Option{SetCode(op.defaultSuccessCode), SetMessage(resolveSuccessMessage(ctx)), SetData(data), SetPage(page)}
baseOps = append(baseOps, ops...) baseOps = append(baseOps, ops...)
ResponseCtx(ctx, w, baseOps...) ResponseCtx(ctx, w, baseOps...)
} }
@@ -87,9 +85,7 @@ func ErrorWithData(ctx context.Context, w http.ResponseWriter, err error, data i
code := op.defaultErrorCode code := op.defaultErrorCode
msg := "请求失败" msg := "请求失败"
if err != nil { if err != nil {
val,ok := errorx.As(err) if val, ok := errorx.As(err); ok {
if ok {
if langx.GetDefaultCode() != val.Code() { if langx.GetDefaultCode() != val.Code() {
code = val.Code() code = val.Code()
} }
@@ -102,19 +98,21 @@ func ErrorWithData(ctx context.Context, w http.ResponseWriter, err error, data i
} }
func ErrorStr(ctx context.Context, w http.ResponseWriter, msg string, ops ...Option) { func ErrorStr(ctx context.Context, w http.ResponseWriter, msg string, ops ...Option) {
msg = langx.GetMsgCtx(ctx, msg) translated := langx.GetMsgCtx(ctx, msg)
code := langx.GetCode(msg) // msg 命中语言表时按 key 取其业务码;未命中则保持默认错误码
if code == langx.GetDefaultCode() { code := op.defaultErrorCode
code = op.defaultErrorCode if translated != msg {
if c := langx.GetCode(msg); c != langx.GetDefaultCode() {
code = c
}
} }
baseOps := []Option{SetCode(code), SetMessage(msg)} baseOps := []Option{SetCode(code), SetMessage(translated)}
baseOps = append(baseOps, ops...) baseOps = append(baseOps, ops...)
ResponseCtx(ctx, w, baseOps...) ResponseCtx(ctx, w, baseOps...)
} }
// 基础的响应 // 基础的响应
func ResponseCtx(ctx context.Context, w http.ResponseWriter, ops ...Option) { func ResponseCtx(ctx context.Context, w http.ResponseWriter, ops ...Option) {
def := defaultOptions() // 默认值 def := defaultOptions() // 默认值
for _, o := range ops { for _, o := range ops {
o(def) o(def)
@@ -126,27 +124,36 @@ func ResponseCtx(ctx context.Context, w http.ResponseWriter, ops ...Option) {
resp.Data = def.data resp.Data = def.data
resp.Pagination = def.pagination resp.Pagination = def.pagination
// 获取trace_id if def.traceFunc != nil {
if def.traceId != "" { resp.TraceId = def.traceFunc(ctx)
t := ctx.Value(def.traceId)
if t != nil {
val, ok := t.(string)
if ok {
resp.TraceId = val
}
} else {
// go-zero
resp.TraceId = trace.TraceIDFromContext(ctx)
}
} }
bs, _ := json.Marshal(resp) bs, err := json.Marshal(resp)
if err != nil {
// 序列化失败不能回空 body,否则客户端无法解析
logError(ctx, def, "response marshal failed err:%+v", err)
fallback := response{
Code: op.defaultErrorCode,
Message: "响应序列化失败",
}
if def.traceFunc != nil {
fallback.TraceId = def.traceFunc(ctx)
}
bs, _ = json.Marshal(fallback)
writeResponse(ctx, w, def, bs, http.StatusInternalServerError)
return
}
if !def.ignoreLog { if !def.ignoreLog {
// 记录日志 // 记录日志
def.logger.Info(ctx, "response:", string(bs)) logInfo(ctx, def, "response:", string(bs))
} }
writeResponse(ctx, w, def, bs, def.httpStatusCode)
}
// writeResponse 统一写响应,httpStatus 为 0 时按 200 处理
func writeResponse(ctx context.Context, w http.ResponseWriter, def *options, bs []byte, httpStatus int) {
// 写入自定义响应头 // 写入自定义响应头
for k, v := range def.headers { for k, v := range def.headers {
w.Header().Set(k, v) w.Header().Set(k, v)
@@ -154,8 +161,6 @@ func ResponseCtx(ctx context.Context, w http.ResponseWriter, ops ...Option) {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
// 确定HTTP状态码
httpStatus := def.httpStatusCode
if httpStatus == 0 { if httpStatus == 0 {
httpStatus = 200 httpStatus = 200
} }
@@ -163,6 +168,21 @@ func ResponseCtx(ctx context.Context, w http.ResponseWriter, ops ...Option) {
n, err := w.Write(bs) n, err := w.Write(bs)
if err != nil || n != len(bs) { if err != nil || n != len(bs) {
op.logger.Error(ctx, "write response failed err:%+v", err) logError(ctx, def, "write response failed err:%+v", err)
} }
} }
// logInfo / logError 使用本次响应的 logger(SetLogger 生效),并做空值保护
func logInfo(ctx context.Context, def *options, args ...interface{}) {
if def == nil || def.logger == nil {
return
}
def.logger.Info(ctx, args...)
}
func logError(ctx context.Context, def *options, format string, args ...interface{}) {
if def == nil || def.logger == nil {
return
}
def.logger.Errorf(ctx, format, args...)
}
+9 -9
View File
@@ -374,10 +374,10 @@ func TestFormatMessageWithOptions(t *testing.T) {
func TestGetOffset(t *testing.T) { func TestGetOffset(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
page, size int page, size int
wantOffset int wantOffset int
wantLimit int wantLimit int
}{ }{
{"page 1 size 10", 1, 10, 0, 10}, {"page 1 size 10", 1, 10, 0, 10},
{"page 2 size 10", 2, 10, 10, 10}, {"page 2 size 10", 2, 10, 10, 10},
@@ -404,11 +404,11 @@ func TestGetOffset(t *testing.T) {
func TestGetPage(t *testing.T) { func TestGetPage(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
page, size, total int64 page, size, total int64
wantPage, wantSize int64 wantPage, wantSize int64
wantTotalPage int64 wantTotalPage int64
wantTotalCount int64 wantTotalCount int64
}{ }{
{"normal", 1, 10, 100, 1, 10, 10, 100}, {"normal", 1, 10, 100, 1, 10, 10, 100},
{"partial last page", 3, 10, 25, 3, 10, 3, 25}, {"partial last page", 3, 10, 25, 3, 10, 3, 25},
Executable
+92
View File
@@ -0,0 +1,92 @@
package responsex_test
import (
"context"
"net/http/httptest"
"testing"
"code.yun.ink/pkg/responsex"
"github.com/yuninks/langx"
)
// ---------- 健壮性修复 ----------
func TestGetOffsetNegativeSize(t *testing.T) {
offset, limit := responsex.GetOffset(1, -5)
if limit != 10 || offset != 0 {
t.Errorf("GetOffset(1,-5) = (%d,%d), want (0,10)", offset, limit)
}
}
func TestGetPageNegativeSize(t *testing.T) {
p := responsex.GetPage(1, -5, 100)
if p.TotalPage != 0 {
t.Errorf("TotalPage = %d, want 0(负数 size 不应算出负页数)", p.TotalPage)
}
}
func TestResponseMarshalFailed(t *testing.T) {
w := httptest.NewRecorder()
// channel 无法序列化
responsex.Success(context.Background(), w, make(chan int), responsex.SetIgnoreLog())
if w.Code != 500 {
t.Errorf("http status = %d, want 500", w.Code)
}
body := decodeBody(t, w)
if body["code"] != float64(400) {
t.Errorf("code = %v, want 400", body["code"])
}
if body["message"] == "" {
t.Error("message 不应为空(不能回空 body)")
}
}
func TestNilLoggerDoesNotPanic(t *testing.T) {
w := httptest.NewRecorder()
// 序列化失败路径会调用 logger,此处不得 panic
responsex.Success(context.Background(), w, make(chan int), responsex.SetLogger(nil))
if w.Code != 500 {
t.Errorf("http status = %d, want 500", w.Code)
}
}
func TestSetDefaultSuccessMessage(t *testing.T) {
responsex.InitOptions(responsex.SetDefaultSuccessMessage("操作成功"))
defer responsex.InitOptions(responsex.SetDefaultSuccessMessage("请求成功"))
w := httptest.NewRecorder()
responsex.Success(context.Background(), w, "x", responsex.SetIgnoreLog())
if body := decodeBody(t, w); body["message"] != "操作成功" {
t.Errorf("message = %v, want 操作成功", body["message"])
}
}
// 多语言成功文案:注册专用 key 后,按 ctx 语言返回
func TestSuccessMessageFromLangx(t *testing.T) {
langx.AppendTrans("zh", map[string]string{"responsex_success": "请求成功"})
langx.AppendTrans("en", map[string]string{"responsex_success": "success"})
ctxEn := langx.SetCtxLang(context.Background(), "en")
ctxZh := langx.SetCtxLang(context.Background(), "zh")
if got := responsex.SuccessMessageCtx(ctxEn); got != "success" {
t.Errorf("SuccessMessageCtx(en) = %q, want success", got)
}
if got := responsex.SuccessMessageCtx(ctxZh); got != "请求成功" {
t.Errorf("SuccessMessageCtx(zh) = %q, want 请求成功", got)
}
// Success 走同一套解析逻辑
w := httptest.NewRecorder()
responsex.Success(ctxEn, w, "x", responsex.SetIgnoreLog())
if body := decodeBody(t, w); body["message"] != "success" {
t.Errorf("Success(en) message = %v, want success", body["message"])
}
}
+40
View File
@@ -0,0 +1,40 @@
package responsex
import "math"
type Pagination struct {
Page int64 `json:"page"`
Size int64 `json:"size"`
TotalPage int64 `json:"total_page"`
TotalCount int64 `json:"total_count"`
}
// 计算DB分页偏移量
// page/size 小于等于 0 时取默认值(page=1,size=10),保证 offset/limit 永远合法
func GetOffset(page, size int) (offset int, limit int) {
if page <= 0 {
page = 1
}
if size <= 0 {
// 默认查10条
size = 10
}
offset = (page - 1) * size
limit = size
return
}
// 计算响应分页
// size 小于等于 0 时不做除法,避免出现负数总页数
func GetPage(page, size, totalCount int64) Pagination {
pa := Pagination{}
var totalPage int64
if size > 0 {
totalPage = int64(math.Ceil(float64(totalCount) / float64(size)))
}
pa.Page = page
pa.Size = size
pa.TotalPage = totalPage
pa.TotalCount = totalCount
return pa
}