222 lines
6.8 KiB
Markdown
222 lines
6.8 KiB
Markdown
# responsex
|
||
|
||
API 响应封装组件,**按需加载**:核心包不依赖任何 Web/ORM/追踪框架,
|
||
用到哪个框架才 import 哪个适配包。
|
||
|
||
## 功能
|
||
|
||
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()`。
|