forked from go/golangs_learn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
6.7 KiB
160 lines
6.7 KiB
4 years ago
|
# Gin(四):表单提交校验和模型绑定
|
||
|
|
||
|
上一个章节中已经开始逐渐搭建了一个 web 页面,现在我们开始逐步完善页面上的功能,首先要完成的是登录和注册功能。
|
||
|
|
||
|
# 接受表单数据
|
||
|
|
||
|
注册页面的 HTML 元素不在详细写出,具体页面代码可以直接参考[Github](https://github.com/youngxhui/GinHello/tree/gin_form/templates) 上代码。
|
||
|
|
||
|
页面完成后布局:
|
||
|
|
||
|
![页面效果](./img/4.1.jpeg)
|
||
|
|
||
|
注册页面有三个输入框,分别为 `email` ,`password` 和 `password again` 。
|
||
|
|
||
|
完善后端 Gin 代码。我们在 `initRouter` 中 `userGroup` 中编写新的接口。
|
||
|
|
||
|
```
|
||
|
userRouter.POST("/register", handler.UserRegister)
|
||
|
```
|
||
|
|
||
|
编写完新的接口就要开始编写 `Handler`。
|
||
|
|
||
|
```
|
||
|
func UserRegister(context *gin.Context) {
|
||
|
email := context.PostForm("email")
|
||
|
password := context.DefaultPostForm("password", "Wa123456")
|
||
|
passwordAgain := context.DefaultPostForm("password-again", "Wa123456")
|
||
|
println("email", email, "password", password, "password again", passwordAgain)
|
||
|
}
|
||
|
```
|
||
|
|
||
|
`UserRegister` 方法中采用新的方式来接受 Post 请求提交的表单参数,`PostForm` 和 `DefaultPostForm` 。`PostForm` 直接接受参数,而 `DefaultPostForm` 可以设置一个默认值,如果前端没有进行传值,那么我们可以设置默认值,如上面的代码,如果前端没有将密码传输过来我们可以设置一个默认密码。
|
||
|
|
||
|
当我们运行并且输入的时候,在控制台上可以清楚的看到我们在表单上的输入。
|
||
|
|
||
|
当我们项目功能完善的时候,就可以完善我们的单元测试。
|
||
|
|
||
|
此时的单元测试交之前有点复杂。
|
||
|
|
||
|
首先我们要构造一个结构,该结构是为了帮助我们将我们要提交的信息存放到表单中,同时要指定请求头信息。
|
||
|
|
||
|
```go
|
||
|
func TestUserPostForm(t *testing.T) {
|
||
|
value := url.Values{}
|
||
|
value.Add("email", "youngxhui@gmail.com")
|
||
|
value.Add("password", "1234")
|
||
|
value.Add("password-again", "1234")
|
||
|
w := httptest.NewRecorder()
|
||
|
req, _ := http.NewRequest(http.MethodPost, "/user/register", bytes.NewBufferString(value.Encode()))
|
||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded; param=value")
|
||
|
router.ServeHTTP(w, req)
|
||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||
|
}
|
||
|
```
|
||
|
|
||
|
单元测试编写完成后可以运行单元测试,发现控制台答应了我们在测试中写的数据。
|
||
|
|
||
|
# 模型绑定
|
||
|
|
||
|
上例中我们的表单仅仅传输了三个参数,如果后期项目出现了十多个参数,每次写一遍都很花费时间,也很消耗经历。下面就对该方法进行改善
|
||
|
|
||
|
Gin 中提供了 模型绑定,将我们的表单数据与我们的模型进行一样绑定。Gin会将数据统一封装到模型中,方便我们日后使用。
|
||
|
|
||
|
首先定义我们的模型,新建 `model` 文件夹,建立 `userModel.go`
|
||
|
|
||
|
```go
|
||
|
package model
|
||
|
|
||
|
type UserModel struct {
|
||
|
Email string `form:"email"`
|
||
|
Password string `form:"password"`
|
||
|
PasswordAgain string `form:"password-again"`
|
||
|
}
|
||
|
```
|
||
|
|
||
|
通过 `form:"email"` 来对表单中的 `email` 输入数据进行绑定。然后需要修改一下 `Handler` 方法。
|
||
|
|
||
|
```go
|
||
|
func UserRegister(context *gin.Context) {
|
||
|
var user model.UserModel
|
||
|
if err := context.ShouldBind(&user); err != nil {
|
||
|
println("err ->", err.Error())
|
||
|
return
|
||
|
}
|
||
|
println("email", user.Email, "password", user.Password, "password again", user.PasswordAgain)
|
||
|
}
|
||
|
```
|
||
|
|
||
|
此时我们的模型绑定已经写好,运行 `TestUserPostForm` 测试用例,测试用例可以完美的通过。说明我们的模型绑定方法是正确的。同时模型绑定还是从 `json`, `xml` ,`yml` 等格式数据的绑定,日后会有介绍和说明。当然也可以通过浏览器中的注册表单进行提交。
|
||
|
|
||
|
# 数据校验
|
||
|
|
||
|
做后端开发的人都明白一个道理:永远不要相信前端传过来的数据。所有的数据在进过后端时,务必要进行数据的校验。
|
||
|
|
||
|
在模型中可用 `binding` 来对数据进行校验。Gin 对于数据校验使用的是 `validator.v8` 库,该库提供多种校验方法。通过 `binding:""` 方式来进行对数据的校验。
|
||
|
|
||
|
我们将 `UserModel` 进行修改,添加一些规则,邮箱验证和密码校验,要求第二次重复密码要和第一次密码一致。更多的校验规则可以看[官方文档](https://godoc.org/gopkg.in/go-playground/validator.v9)
|
||
|
|
||
|
```go
|
||
|
type UserModel struct {
|
||
|
Email string `form:"email" binding:"email"`
|
||
|
Password string `form:"password"`
|
||
|
PasswordAgain string `form:"password-again" binding:"eqfield=Password"`
|
||
|
}
|
||
|
```
|
||
|
|
||
|
我们重新写一个测试用例用来测试邮箱和密码校验是否有效。
|
||
|
|
||
|
```go
|
||
|
func TestUserPostFormEmailErrorAndPasswordError(t *testing.T) {
|
||
|
value := url.Values{}
|
||
|
value.Add("email", "youngxhui")
|
||
|
value.Add("password", "1234")
|
||
|
value.Add("password-again", "qwer")
|
||
|
w := httptest.NewRecorder()
|
||
|
req, _ := http.NewRequest(http.MethodPost, "/user/register", bytes.NewBufferString(value.Encode()))
|
||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded; param=value")
|
||
|
router.ServeHTTP(w, req)
|
||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||
|
}
|
||
|
```
|
||
|
|
||
|
运行测试,发现测试虽然通过了,但是会有两行 `error` 信息
|
||
|
|
||
|
```
|
||
|
err -> Key: 'UserModel.Email' Error:Field validation for 'Email' failed on the 'email' tag
|
||
|
Key: 'UserModel.PasswordAgain' Error:Field validation for 'PasswordAgain' failed on the 'eqfield' tag
|
||
|
|
||
|
```
|
||
|
|
||
|
该信息说明了我们的 `Email` 和 `PasswordAgain` 信息校验没有通过。
|
||
|
|
||
|
# 使用Log和重定向
|
||
|
|
||
|
测试通过是因为无论我们代码如何都会返回 200 状态码,这是不符合http 状态码的规范的,所以我们要对http状态码进行规范化。同时我们之前的代码中一直使用 `Printf` 来打印日志信息,也是不规范的,因为 `Printf` 打印的日志信息相对局限,所以应该选用 `Log` 进行日志打印。
|
||
|
|
||
|
```go
|
||
|
func UserRegister(context *gin.Context) {
|
||
|
var user model.UserModel
|
||
|
if err := context.ShouldBind(&user); err != nil {
|
||
|
log.Println("err ->", err.Error())
|
||
|
context.String(http.StatusBadRequest, "输入的数据不合法")
|
||
|
} else {
|
||
|
log.Println("email", user.Email, "password", user.Password, "password again", user.PasswordAgain)
|
||
|
context.Redirect(http.StatusMovedPermanently, "/")
|
||
|
}
|
||
|
}
|
||
|
```
|
||
|
|
||
|
首先我们将原来只用 `Println` 打印的数据都改成了 `log` 去打印数据。
|
||
|
|
||
|
同时将原来的状态码都进行了更改,不同的状态码代表不同的请求响应结果。
|
||
|
|
||
|
最后在请求成功的时候我们对路由进行了重定向,将页面转跳到首页。
|
||
|
|
||
|
同时我们也要将测试用例里的返回状态码进行修改。
|
||
|
|
||
|
# 总结
|
||
|
|
||
|
本节将表单提交,模型绑定和数据校验有了一个相对细致的介绍,代码中也通过不同的测试用例来检查代码是否正确。
|