跳到主要内容
版本:2.7.x

Request 对象支持非常完美的请求校验能力,通过给结构体属性绑定 v 标签即可。由于底层校验功能通过 gvalid 模块实现, 更详细的校验规则和介绍请参考 数据校验-Struct校验 章节。

注意

以下示例代码中的请求参数转结构体方式适用于框架 v1 版本, v2 版本开始建议通过规范路由实现自动化的参数结构体转换和校验: 路由注册-规范路由

示例1,基本使用

我们将之前的示例做下调整,增加 v 校验标签。

package main

import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
)

// RegisterReq 注册请求数据结构
type RegisterReq struct {
Name string `p:"username" v:"required|length:4,30#请输入账号|账号长度为:{min}到:{max}位"`
Pass string `p:"password1" v:"required|length:6,30#请输入密码|密码长度不够"`
Pass2 string `p:"password2" v:"required|length:6,30|same:password1#请确认密码|密码长度不够|两次密码不一致"`
}

// RegisterRes 注册返回数据结构
type RegisterRes struct {
Code int `json:"code"`
Error string `json:"error"`
Data interface{} `json:"data"`
}

func main() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.ALL("/register", func(r *ghttp.Request) {
var req *RegisterReq
if err := r.Parse(&req); err != nil {
r.Response.WriteJsonExit(RegisterRes{
Code: 1,
Error: err.Error(),
})
}
// ...
r.Response.WriteJsonExit(RegisterRes{
Data: req,
})
})
})
s.SetPort(8199)
s.Run()
}

在该示例中,我们定义了两个结构体: RegisterReq 用于参数接收, RegisterRes 用于数据返回。由于该接口返回的是 JSON 数据结构,可以看到,只有返回的结构体中存在 json 标签,而接收的结构体中只有 p 标签。因为 RegisterReq 仅用于参数接收,无需设置返回的 json 标签。

提示

p 标签是可选的,默认情况下会通过 忽略特殊字符+不区分大小写 的规则进行属性名称匹配转换,默认匹配规则满足绝大部分业务场景。

为了演示测试效果,这里在正常的返回结果 Data 属性中将 RegisterReq 对象返回,由于该对象没有绑定 json 标签,因此返回的 JSON 字段将会为其属性名称。

执行后,我们通过 curl 工具来测试一下:

$ curl "http://127.0.0.1:8199/register?name=john&password1=123456&password2=123456"
{"code":0,"error":"","data":{"Name":"john","Pass":"123456","Pass2":"123456"}}

$ curl "http://127.0.0.1:8199/register?name=john&password1=123456&password2=12345"
{"code":1,"error":"密码长度不够","data":null}

$ curl "http://127.0.0.1:8199/register"
{"code":1,"error":"请输入账号","data":null}

示例2,校验错误处理

提示

最新版已经改成只返回第一个错误。

可以看到在以上示例中,当请求校验错误时,所有校验失败的错误都返回了,这样对于用户体验不是特别友好。当产生错误时,我们可以将校验错误转换为 gvalid.Error 接口对象,随后可以通过灵活的方法控制错误的返回。

package main

import (
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/gogf/gf/v2/util/gvalid"
)

type RegisterReq struct {
Name string `p:"username" v:"required|length:4,30#请输入账号|账号长度为:{min}到:{max}位"`
Pass string `p:"password1" v:"required|length:6,30#请输入密码|密码长度不够"`
Pass2 string `p:"password2" v:"required|length:6,30|same:password1#请确认密码|密码长度不够|两次密码不一致"`
}

type RegisterRes struct {
Code int `json:"code"`
Error string `json:"error"`
Data interface{} `json:"data"`
}

func main() {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.ALL("/register", func(r *ghttp.Request) {
var req *RegisterReq
if err := r.Parse(&req); err != nil {
// Validation error.
if v, ok := err.(gvalid.Error); ok {
r.Response.WriteJsonExit(RegisterRes{
Code: 1,
Error: v.FirstError().Error(),
})
}
// Other error.
r.Response.WriteJsonExit(RegisterRes{
Code: 1,
Error: err.Error(),
})
}
// ...
r.Response.WriteJsonExit(RegisterRes{
Data: req,
})
})
})
s.SetPort(8199)
s.Run()
}

可以看到,当错误产生后,我们可以通过 err.(gvalid.Error) 断言的方式判断错误是否为校验错误,如果是的话则返回第一条校验错误,而不是所有都返回。更详细的错误控制方法,请参考 数据校验-校验结果 章节。

提示

此外,我们这里也可以使用 gerror.Current 获取第一条报错信息,而不是使用断言判断。例如:

var req *RegisterReq
if err := r.Parse(&req); err != nil {
r.Response.WriteJsonExit(RegisterRes{
Code: 1,
Error: gerror.Current(err).Error(),
})
}

执行后,我们通过 curl 工具来测试一下:

$ curl "http://127.0.0.1:8199/register"
{"code":1,"error":"请输入账号","data":null}

$ curl "http://127.0.0.1:8199/register?name=john&password1=123456&password2=1234567"
{"code":1,"error":"两次密码不一致","data":null}