阅读 485

手把手,带你从零封装Gin框架(六):初始化 Validator & 封装 Response & 实现第一个接口

前言

Gin 自带验证器返回的错误信息格式不太友好,本篇将进行调整,实现自定义错误信息,并规范接口返回的数据格式,分别为每种类型的错误定义错误码,前端可以根据对应的错误码实现后续不同的逻辑操作,篇末会使用自定义的 Validator 和 Response 实现第一个接口

自定义验证器错误信息

新建 app/common/request/validator.go 文件,编写:

package request import (    "github.com/go-playground/validator/v10" ) type Validator interface {    GetMessages() ValidatorMessages } type ValidatorMessages map[string]string // GetErrorMsg 获取错误信息 func GetErrorMsg(request interface{}, err error) string {    if _, isValidatorErrors := err.(validator.ValidationErrors); isValidatorErrors {       _, isValidator := request.(Validator)       for _, v := range err.(validator.ValidationErrors) {          // 若 request 结构体实现 Validator 接口即可实现自定义错误信息          if isValidator {             if message, exist := request.(Validator).GetMessages()[v.Field() + "." + v.Tag()]; exist {                return message             }          }          return v.Error()       }    }    return "Parameter error" } 复制代码

新建 app/common/request/user.go 文件,用来存放所有用户相关的请求结构体,并实现 Validator 接口

package request type Register struct {     Name string `form:"name" json:"name" binding:"required"`     Mobile string `form:"mobile" json:"mobile" binding:"required"`     Password string `form:"password" json:"password" binding:"required"` } // 自定义错误信息 func (register Register) GetMessages() ValidatorMessages {     return ValidatorMessages{         "Name.required": "用户名称不能为空",         "Mobile.required": "手机号码不能为空",         "Password.required": "用户密码不能为空",     } } 复制代码

routes/api.go 中编写测试代码

package routes import (     "github.com/gin-gonic/gin"     "jassue-gin/app/common/request"     "net/http"     "time" ) // SetApiGroupRoutes 定义 api 分组路由 func SetApiGroupRoutes(router *gin.RouterGroup) {     //...     router.POST("/user/register", func(c *gin.Context) {         var form request.Register         if err := c.ShouldBindJSON(&form); err != nil {            c.JSON(http.StatusOK, gin.H{                "error": request.GetErrorMsg(form, err),            })            return         }         c.JSON(http.StatusOK, gin.H{             "message": "success",         })     }) } 复制代码

启动服务器,使用 Postman 测试,如下图所示,自定义错误信息成功

image-20211018192056332.png

自定义验证器

有一些验证规则在 Gin 框架中是没有的,这个时候我们就需要自定义验证器

新建 utils/validator.go 文件,定义验证规则,后续有其他的验证规则将统一存放在这里

package utils import (     "github.com/go-playground/validator/v10"     "regexp" ) // ValidateMobile 校验手机号 func ValidateMobile(fl validator.FieldLevel) bool {     mobile := fl.Field().String()     ok, _ := regexp.MatchString(`^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$`, mobile)     if !ok {         return false     }     return true } 复制代码

新建 bootstrap/validator.go 文件,定制 Gin 框架 Validator 的属性

package bootstrap import (     "github.com/gin-gonic/gin/binding"     "github.com/go-playground/validator/v10"     "jassue-gin/utils"     "reflect"     "strings" ) func InitializeValidator() {     if v, ok := binding.Validator.Engine().(*validator.Validate); ok {         // 注册自定义验证器         _ = v.RegisterValidation("mobile", utils.ValidateMobile)         // 注册自定义 json tag 函数         v.RegisterTagNameFunc(func(fld reflect.StructField) string {             name := strings.SplitN(fld.Tag.Get("json"), ",", 2)[0]             if name == "-" {                 return ""             }             return name         })     } } 复制代码

main.go 中调用

package main import (     "jassue-gin/bootstrap"     "jassue-gin/global" ) func main() {     // ...          // 初始化验证器     bootstrap.InitializeValidator()     // 启动服务器     bootstrap.RunServer() } 复制代码

app/common/request/user.go 文件,增加 Resister 请求结构体中 Mobile 属性的验证 tag

注:由于在 InitializeValidator()  方法中,使用 RegisterTagNameFunc() 注册了自定义 json tag, 所以在 GetMessages() 中自定义错误信息 key 值时,需使用 json tag 名称

package request type Register struct {     Name string `form:"name" json:"name" binding:"required"`     Mobile string `form:"mobile" json:"mobile" binding:"required,mobile"`     Password string `form:"password" json:"password" binding:"required"` } func (register Register) GetMessages() ValidatorMessages {     return ValidatorMessages{         "name.required": "用户名称不能为空",         "mobile.required": "手机号码不能为空",         "mobile.mobile": "手机号码格式不正确",         "password.required": "用户密码不能为空",     } } 复制代码

重启服务器,使用 PostMan 测试,如下图所示,自定义验证器成功

image-20211018194531068.png

自定义错误码

新建 global/error.go 文件,将项目中可能存在的错误都统一存放到这里,为每一种类型错误都定义一个错误码,便于在开发过程快速定位错误,前端也可以根据不同错误码实现不同逻辑的页面交互

package global type CustomError struct {     ErrorCode int     ErrorMsg string } type CustomErrors struct {     BusinessError CustomError     ValidateError CustomError } var Errors = CustomErrors{     BusinessError: CustomError{40000, "业务错误"},     ValidateError: CustomError{42200, "请求参数错误"}, } 复制代码

封装 Response

新建 app/common/response/response.go 文件,编写:

package response import (     "github.com/gin-gonic/gin"     "jassue-gin/global"     "net/http" ) // 响应结构体 type Response struct {     ErrorCode int `json:"error_code"` // 自定义错误码     Data interface{} `json:"data"` // 数据     Message string `json:"message"` // 信息 } // Success 响应成功 ErrorCode 为 0 表示成功 func Success(c *gin.Context, data interface{}) {     c.JSON(http.StatusOK, Response{         0,         data,         "ok",     }) } // Fail 响应失败 ErrorCode 不为 0 表示失败 func Fail(c *gin.Context, errorCode int, msg string) {     c.JSON(http.StatusOK, Response{         errorCode,         nil,         msg,     }) } // FailByError 失败响应 返回自定义错误的错误码、错误信息 func FailByError(c *gin.Context, error global.CustomError) {     Fail(c, error.ErrorCode, error.ErrorMsg) } // ValidateFail 请求参数验证失败 func ValidateFail(c *gin.Context, msg string)  {     Fail(c, global.Errors.ValidateError.ErrorCode, msg) } // BusinessFail 业务逻辑失败 func BusinessFail(c *gin.Context, msg string) {     Fail(c, global.Errors.BusinessError.ErrorCode, msg) } 复制代码

实现用户注册接口

新建 utils/bcrypt.go 文件,编写密码加密及验证密码的方法

package utils import (     "golang.org/x/crypto/bcrypt"     "log" ) func BcryptMake(pwd []byte) string {     hash, err := bcrypt.GenerateFromPassword(pwd, bcrypt.MinCost)     if err != nil {         log.Println(err)     }     return string(hash) } func BcryptMakeCheck(pwd []byte, hashedPwd string) bool {     byteHash := []byte(hashedPwd)     err := bcrypt.CompareHashAndPassword(byteHash, pwd)     if err != nil {         return false     }     return true } 复制代码

新建 app/services/user.go 文件,编写用户注册逻辑

package services import (     "errors"     "jassue-gin/app/common/request"     "jassue-gin/app/models"     "jassue-gin/global"     "jassue-gin/utils" ) type userService struct { } var UserService = new(userService) // Register 注册 func (userService *userService) Register(params request.Register) (err error, user models.User) {     var result = global.App.DB.Where("mobile = ?", params.Mobile).Select("id").First(&models.User{})     if result.RowsAffected != 0 {         err = errors.New("手机号已存在")         return     }     user = models.User{Name: params.Name, Mobile: params.Mobile, Password: utils.BcryptMake([]byte(params.Password))}     err = global.App.DB.Create(&user).Error     return } 复制代码

新建 app/controllers/app/user.go 文件,校验入参,调用 UserService 注册逻辑

package app import (     "github.com/gin-gonic/gin"     "jassue-gin/app/common/request"     "jassue-gin/app/common/response"     "jassue-gin/app/services" ) // Register 用户注册 func Register(c *gin.Context) {     var form request.Register     if err := c.ShouldBindJSON(&form); err != nil {         response.ValidateFail(c, request.GetErrorMsg(form, err))         return     }     if err, user := services.UserService.Register(form); err != nil {        response.BusinessFail(c, err.Error())     } else {        response.Success(c, user)     } } 复制代码

routes/api.go 中,添加路由

package routes import (     "github.com/gin-gonic/gin"     "jassue-gin/app/controllers/app" ) // SetApiGroupRoutes 定义 api 分组路由 func SetApiGroupRoutes(router *gin.RouterGroup) {     router.POST("/user/register", app.Register) } 复制代码

使用 Postman 调用接口 http://localhost:8888/api/user/register ,如下图所示,接口返回成功

image-20211022184652754.png

查看数据库 users 表,数据已成功写入

image-20211022184849385.png


作者:jassue
链接:https://juejin.cn/post/7021842747673804808


文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐