3.4 请求与校验
概述
本项目没有独立的"验证器文件"——请求校验由 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 逻辑层开发)。