Define reusable response bodies in .api with typed code mappings, per-route overrides and matching OpenAPI schemas. Add application and callback encoders, early response policy selection, and isolated remote-consumer release verification. Release v0.8.0-preview after full race, vet, build and local MySQL workflow acceptance.
141 lines
7.9 KiB
Markdown
141 lines
7.9 KiB
Markdown
# 公共响应体与独立接口协议
|
||
|
||
这些功能从 `v0.8.0-preview` 提供。CLI 与项目框架依赖请固定到同一版本;源码开发可显式使用 `--framework-dir`。`v0.7.0-preview.1` 不包含响应模板声明。
|
||
|
||
go-omni 的响应编码由应用或接口配置。没有配置时保留旧版默认值;业务可以定义自己的字段、嵌套结构、错误码类型、提示和空值行为。`response.Response` 仅保留为兼容 DTO,运行时与生成器不再通过它固定响应结构。
|
||
|
||
## 在 .api 中定义一次、到处引用
|
||
|
||
```go
|
||
response CommonResponse (
|
||
body: {
|
||
"code": "${code}",
|
||
"msg": "${message}",
|
||
"data": "${data}"
|
||
}
|
||
codeType: int32
|
||
successCode: 200
|
||
errorCode: 9999
|
||
successMessage: "操作成功"
|
||
dataMode: always
|
||
codes: {"USER_NOT_FOUND":404001}
|
||
messages: {"USER_NOT_FOUND":"用户不存在"}
|
||
)
|
||
|
||
@server (
|
||
group: user
|
||
response: CommonResponse
|
||
)
|
||
service user-api {
|
||
@handler getUser
|
||
get /users/:userId (GetUserReq) returns (UserResp)
|
||
|
||
@response raw
|
||
@handler rawUser
|
||
get /rawUsers/:userId (GetUserReq) returns (UserResp)
|
||
}
|
||
```
|
||
|
||
请求/业务返回类型照常在 `type` 中定义。上例 `UserResp` 是业务数据,由模板中的 `${data}` 放入公共体。请求失败时,同一模板输出映射后的业务错误。错误本身的 HTTP 状态保持不变,业务 `code` 与 HTTP 状态相互独立。
|
||
|
||
- 公共声明可以放进 `common.api`,其他契约通过 `import "common.api"` 复用。
|
||
- `@server (response: CommonResponse)` 指定整个 service 块的公共体;不同块可以引用同一个模板。
|
||
- `@response PartnerResponse` 为单接口替换模板,成功和错误一起生效。
|
||
- `@response raw` 让成功 JSON 直接返回业务对象,错误沿用组级模板;组本身为 raw 时,错误使用默认模板。
|
||
- 模板名使用 UpperCamelCase;未知模板、未知配置、错误的占位符和冲突设置在生成前报错。
|
||
|
||
**生成接口以 `.api` 为响应契约来源。** 生成器把选定模板编译进 `internal/handler/responseGen.go`,在全局鉴权、限流等应用中间件之前选定,然后再执行路由中间件和业务。OpenAPI 与运行时共用配置和映射规则。即使 `.api` 没有声明模板,生成接口也明确使用默认契约,不会被应用的 YAML 设置静默改变。
|
||
|
||
业务只需返回业务数据或业务错误,无需每个 Handler 手工组装公共体。生成器维护模板、路由及文档文件,重复生成不覆盖手写 Service、Repository、middleware 和配置。
|
||
|
||
## 自定义结构与占位符
|
||
|
||
公共体没有规定必须出现的字段,也可以把成功和失败分开:
|
||
|
||
```go
|
||
response PartnerResponse (
|
||
body: {
|
||
"accepted": "${ok}",
|
||
"payload": "${data}",
|
||
"meta": {"trace":"${requestId}","httpStatus":"${status}"},
|
||
"version": 1
|
||
}
|
||
errorBody: {
|
||
"accepted": "${ok}",
|
||
"error": {"reason":"${message}","id":"${code}"}
|
||
}
|
||
)
|
||
```
|
||
|
||
`body` 是 JSON 对象,可嵌套对象、数组和常量;`errorBody` 可选,需要同时定义 `body`。空对象 `{}` 也是有效模板。
|
||
|
||
| 整值占位符 | 输出值 |
|
||
| --- | --- |
|
||
| `${data}` | 当前接口的业务返回值,保留对象/数组/数值等原类型 |
|
||
| `${code}` | 映射后的 string / int32 / int64 业务错误码 |
|
||
| `${message}` | 公开提示,经 message 配置映射 |
|
||
| `${requestId}` | 请求标识;为空时省略所在对象字段 |
|
||
| `${ok}` | 成功为 true;有错误或 HTTP 状态 ≥400 时为 false |
|
||
| `${status}` | HTTP 状态整数 |
|
||
|
||
占位符只替换整个值,不执行表达式或字符串拼接。字面量 `${data}` 写成 `$${data}`。数据为 nil 时,`dataMode: omitNil`(默认)省略所在对象字段;`always` 输出 null。数组中的占位符保留位置。模板最多嵌套 24 层。
|
||
|
||
`codeType` 使用与业务字段一致的类型名,默认 `string`:
|
||
|
||
| 类型 | 含义 | OpenAPI |
|
||
| --- | --- | --- |
|
||
| `string` | 字符串业务码,例如 `"OK"`、`"USER_NOT_FOUND"` | string |
|
||
| `int32` | 32 位有符号整数,−2147483648 到 2147483647 | integer / int32 |
|
||
| `int64` | 64 位有符号整数,−9223372036854775808 到 9223372036854775807 | integer / int64 |
|
||
|
||
整数类型必须提供 `successCode` 和 `errorCode`,支持 0 和负数;默认码和 `codes` 映射都会校验范围,越界或小数会报错。`int64` 的配置读取和生成代码保留完整整数精度,并按 JSON 数值输出;消费端也需要支持对应的整数精度。需要文本形式时可用 `string` 并配置字符串码。
|
||
|
||
旧写法 `integer` 保留兼容,沿用 ±(2^53−1) 的数值范围;新配置优先写 `int32` 或 `int64`。
|
||
|
||
`codes` 按逻辑错误码覆盖默认码;`messages` 优先于 `successMessage` / `errorMessage`。未配置的公开提示沿用原错误消息;内部 cause 不会被默认编码器序列化。
|
||
|
||
简单的平铺响应也可使用 `codeField` / `messageField` / `dataField` / `requestIdField` 快捷配置,`"-"` 禁用字段。空名称沿用旧默认值。这些字段名快捷配置不能和 `body` 混用,以免两个定义冲突。
|
||
|
||
## 手写路由、配置文件与三方回调
|
||
|
||
手写路由的应用默认配置可以放在 JSON/YAML 的 `response` 字段,也可用 Go 选项:
|
||
|
||
```go
|
||
app := omni.New(omni.WithResponse(response.Config{
|
||
Body: map[string]any{"code":"${code}", "msg":"${message}", "data":"${data}"},
|
||
CodeType: "int32", SuccessCode: 200, ErrorCode: 9999, DataMode: "always",
|
||
}))
|
||
```
|
||
|
||
应用之间的配置独立,模板在创建时验证并复制;没有全局可变的响应格式。应用配置快照也会复制模板和映射。响应配置属于静态配置,变更后需重启。
|
||
|
||
单次显式输出可使用 `c.JSON(status, value)`、`c.Text(status, text)`、`c.XML(status, value)`、`c.Bytes(status, contentType, bytes)` 或 `c.NoContent()`,都绕过公共体。已经提交的响应不会再追加错误 JSON。
|
||
|
||
三方回调可能要求成功与失败都返回 HTTP 200,并分别输出 `success` / `fail`。通过接口编码器统一处理成功、错误和 panic:
|
||
|
||
```go
|
||
ack := response.EncoderFunc(func(r response.Result) (response.Payload, error) {
|
||
if r.Err != nil || r.Status >= 400 {
|
||
return response.Text(200, "fail"), nil
|
||
}
|
||
return response.Text(200, "success"), nil
|
||
})
|
||
app.HandleWithResponse("POST", "/hooks/payment", ack, paymentHandler)
|
||
```
|
||
|
||
编码器也可以设置 HTTP 状态、Header、XML 或任意字节。`omni.WithResponseEncoder` 设置应用默认编码器;`httpx.WithResponse` 是普通路由中间件,按中间件顺序生效。需要覆盖全局中间件提前拒绝时,应使用 `HandleWithResponse`。自定义 Registrar 可以实现 `httpx.ResponseRegistrar` 提供相同的早期挂载能力;未实现时辅助函数退化为普通路由中间件。
|
||
|
||
`.api` 当前声明的是 JSON 协议,`@response raw` 也是原始 JSON,不代表任意文本/XML协议。自定义文本/XML回调使用上述 Go 入口,并由应用维护相应文档;生成器不会猜测自定义 Go 编码器的输出。如果在命名中间件里替换编码器,业务也必须同步维护文档。
|
||
|
||
框架内置路由的 404/405 使用应用默认格式;它们不属于某个 `.api` 分组。TLS 握手、畸形 HTTP、连接级过载等发生在进入应用前,仍按网络层协议处理。自定义编码器失败、panic 或返回非法状态/204 body 时,最终返回空 500,避免递归编码或泄露另一种响应体。
|
||
|
||
## 运行示例与验证
|
||
|
||
```sh
|
||
go run ./cmd/go-omni api validate --api examples/contracts/responses.api
|
||
go run ./cmd/go-omni api generate --api examples/contracts/responses.api \
|
||
--module example.com/responseDemo --framework-dir . --dir ../responseDemo
|
||
```
|
||
|
||
示例业务方法起初为 501,完成 Service 后就会套用声明的模板。参考 [完整 .api](examples/contracts/responses.api)。验收测试会在临时目录生成真实项目,写入业务方法,执行已生成路由,检查数字错误码、原始 JSON、鉴权错误、路由覆盖与重复生成保护。
|