- response.go: errorx.As/val.Code() 换成 errors.As(errorx.ErrorInterface) 取 GetCode(),文案改用 Msg(ctx) 按请求语言渲染(errorx v1.2 起错误值只保存语言, 渲染时再传 ctx)。 - FormatMessage: langx v0.1 起未知 key 的默认码从 200 变成 -1,命中默认码时 回落到 SetDefaultSuccessCode,保持升级前 code:200 的行为。 - go.mod: github.com/yuninks/errorx v1.2.0 / github.com/yuninks/langx v0.1.0。
359 lines
10 KiB
Go
Executable File
359 lines
10 KiB
Go
Executable File
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.New("example_params_invalid", 40001, "参数异常")
|
||
ExampleErrNotFound = errorx.New("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.Err(),
|
||
responsex.SetIgnoreLog(),
|
||
)
|
||
fmt.Println("zh:", w.Body.String())
|
||
|
||
// 英文请求:文案随 ctx 语言变化,业务码不变
|
||
ctxEn := langx.SetCtxLang(context.Background(), "en")
|
||
w = httptest.NewRecorder()
|
||
responsex.Error(ctxEn, w,
|
||
ExampleErrParamsInvalid.Err(),
|
||
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.Err(),
|
||
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":"库存不足"}
|
||
}
|