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

359 lines
10 KiB
Go
Executable File
Raw Blame History

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