129 lines
5.7 KiB
Markdown
129 lines
5.7 KiB
Markdown
# 3.4 请求与校验
|
||
|
||
:::tip 概述
|
||
本项目没有独立的"验证器文件"——请求校验由 `api/v1` 请求结构体上的 gvalid 标签完成,路由/权限/日志元数据由内嵌 gmeta.Meta 声明。一个结构体即接口契约的全部。
|
||
:::
|
||
|
||
## 请求结构体解剖(真实代码,摘自 api/v1/example.go)
|
||
|
||
```go
|
||
// ============================== 案例演示模块(goframe_example) ==============================
|
||
|
||
// ExamplePageReq 案例演示分页请求
|
||
type ExamplePageReq struct {
|
||
gmeta.Meta `method:"get" path:"/api/example/page" summary:"案例演示分页" permission:"sys:example:page" log:"案例演示-查询分页记录" logType:"query"`
|
||
PageReq
|
||
Name string `json:"name" dc:"案例名称"` // 案例名称
|
||
Type int `json:"type" dc:"案例类型"` // 案例类型
|
||
Status int `json:"status" dc:"案例状态"` // 案例状态
|
||
Keyword string `json:"keyword" dc:"关键词"` // 关键词
|
||
}
|
||
|
||
// ExampleDetailReq 案例演示详情请求
|
||
type ExampleDetailReq struct {
|
||
gmeta.Meta `method:"get" path:"/api/example/detail/:id" summary:"案例演示详情" permission:"sys:example:detail" log:"案例演示-查询详情" logType:"query"`
|
||
Id int64 `json:"id" v:"required#缺少ID" dc:"记录ID"` // 记录ID
|
||
}
|
||
|
||
// ExampleAddReq 新增案例演示请求
|
||
type ExampleAddReq struct {
|
||
gmeta.Meta `method:"post" path:"/api/example/add" summary:"新增案例演示" permission:"sys:example:add" log:"案例演示-新增记录" logType:"add"`
|
||
Name string `json:"name" v:"required#请输入案例名称" dc:"案例名称"` // 案例名称
|
||
Avatar string `json:"avatar" dc:"案例图片"` // 案例图片
|
||
Type int `json:"type" dc:"案例类型"` // 案例类型
|
||
Status int `json:"status" dc:"案例状态"` // 案例状态
|
||
Sort int `json:"sort" dc:"案例排序"` // 案例排序
|
||
}
|
||
```
|
||
|
||
## 标签体系一览
|
||
|
||
| 标签 | 来源 | 作用 |
|
||
|------|------|------|
|
||
| `gmeta.Meta` | gf 框架 | 内嵌后由本项目 bind.go 扩展解析 |
|
||
| `method/path/summary` | gmeta | HTTP 方法、路由模板(`:id` 路由参数)、OpenAPI 摘要 |
|
||
| `permission` | gmeta | 权限节点 `sys:{module小驼峰}:{action}`;空 = 登录即可 |
|
||
| `log/logType` | gmeta | 操作日志标题与类型(query/add/update/delete/import/export…) |
|
||
| `public` | gmeta | `"true"` = 免登录(须与 middleware/public.go 白名单一致) |
|
||
| `json` | 标准 | 出入参字段名,**一律驼峰**(例外仅登录/令牌接口的下划线字段) |
|
||
| `v` | gvalid | 校验规则,如 `required#请输入案例名称`(# 后为自定义错误文案) |
|
||
| `dc` | goai | OpenAPI 文档中的字段说明 |
|
||
| `p` | ghttp | multipart 上传字段名(如 `p:"file"`) |
|
||
|
||
## gvalid 常用规则
|
||
|
||
```go
|
||
Id int64 `json:"id" v:"required#缺少ID"` // 必填
|
||
Age int `json:"age" v:"min:0#年龄不能为负"` // 数值范围
|
||
Ids []int64 `json:"ids" v:"required#缺少ID列表"` // 数组必填
|
||
Email string `json:"email" v:"email#邮箱格式不正确"` // 格式
|
||
Status int `json:"status" v:"in:1,2#状态值不合法"` // 枚举(如角色 dataScope 用 in:1,2,3,4)
|
||
```
|
||
|
||
校验失败由框架返回 code=400 信封,错误文案取 `#` 后缀;无需在业务代码里手写 if 判断。
|
||
|
||
## 路由参数承接
|
||
|
||
gmeta path 中的 `:id` 由请求结构体同名(驼峰 json)字段承接:
|
||
|
||
```text
|
||
路由模板 GET /api/example/detail/:id
|
||
└─▶ ExampleDetailReq.Id int64 `json:"id"` ← r.Parse 自动绑定
|
||
```
|
||
|
||
## 请求解析流程(Bind 包装层完成)
|
||
|
||
```text
|
||
1. r.Parse(req):路由参数 + query + body 一次性绑定(含 gvalid 校验)
|
||
2. 实现 BodyUnmarshaler 接口的请求对象走自定义解析逃生舱
|
||
(请求体顶层形态无法用结构体标签表达时使用,gvalid 需在实现内自完成)
|
||
3. 校验失败 → response.Fail(code=400) 短路返回,业务方法不执行
|
||
```
|
||
|
||
## 响应结构体(真实代码,摘自 api/v1/example.go)
|
||
|
||
```go
|
||
// ExamplePageRes 案例演示分页结果
|
||
// 统一壳:records/total/size/current/pages
|
||
type ExamplePageRes = PageData[ExampleItem]
|
||
|
||
// ExampleListRes 案例演示平铺列表结果
|
||
// data 直接为数组,树形结构由前端 buildTree 构建
|
||
type ExampleListRes = []ExampleItem
|
||
|
||
// ExampleIdRes 新增返回ID结构
|
||
type ExampleIdRes struct {
|
||
Id int64 `json:"id"` // 主键ID
|
||
}
|
||
|
||
// ExampleUpdateRes 修改案例演示结果
|
||
type ExampleUpdateRes struct{}
|
||
```
|
||
|
||
要点:
|
||
|
||
+ 分页结果统一用泛型壳 `PageData[T]`(`v1.NewPageData` 构造)
|
||
+ 新增返回统一 `{id}` 结构(模块自定义 `XxxIdRes`;公共场景用 `common.IdRes`)
|
||
+ 修改/删除等空结果返回空结构体 `struct{}`
|
||
+ 行结构体含 `{Field}Text string` 字段时由基座自动填字典显示名
|
||
|
||
## 行结构体示例(含字典文本字段)
|
||
|
||
```go
|
||
// ExampleItem 案例演示行数据
|
||
// 用于分页列表等场景的基础信息
|
||
type ExampleItem struct {
|
||
Id int `json:"id"` // 主键ID
|
||
Name string `json:"name"` // 案例名称
|
||
Avatar string `json:"avatar"` // 案例图片
|
||
Type int `json:"type"` // 案例类型
|
||
Status int `json:"status"` // 案例状态
|
||
Sort int `json:"sort"` // 案例排序
|
||
CreateUser string `json:"createUser"` // 创建人
|
||
CreateTime *gtime.Time `json:"createTime"` // 创建时间
|
||
StatusText string `json:"statusText"` // 状态显示名(dict_item 解析)
|
||
}
|
||
```
|
||
|
||
`StatusText` 与 `Status` 配对,配合 logic 里 `SerializeMaps = {"status": "example_status"}` 声明,读取时自动填充(见 3.5 逻辑层开发)。
|