优化方法的使用
This commit is contained in:
Executable
+7
@@ -0,0 +1,7 @@
|
|||||||
|
# 本地开发用的 go.work(含本地 replace,提交会让使用方 go get 失败)
|
||||||
|
go.work
|
||||||
|
go.work.sum
|
||||||
|
|
||||||
|
# 临时/备份文件
|
||||||
|
*.bak
|
||||||
|
*.tmp
|
||||||
Executable
+358
@@ -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":"库存不足"}
|
||||||
|
}
|
||||||
Executable
+334
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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=
|
||||||
@@ -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
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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"])
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user