359 lines
10 KiB
Go
359 lines
10 KiB
Go
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":"库存不足"}
|
|||
|
|
}
|