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":"库存不足"} }