前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手把手,带你从零封装Gin框架(六):初始化 Validator & 封装 Response & 实现第一个接口

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

作者头像
用户10002156
发布2024-01-17 13:35:42
1K0
发布2024-01-17 13:35:42
举报
文章被收录于专栏:生活处处有BUG

前言

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

自定义验证器错误信息

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

代码语言:javascript
复制
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 接口

代码语言:javascript
复制
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 中编写测试代码

代码语言:javascript
复制
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",
        })
    })
}

自定义验证器

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

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

代码语言:javascript
复制
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 的属性

代码语言:javascript
复制
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 中调用

代码语言:javascript
复制
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 名称

代码语言:javascript
复制
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 测试,如下图所示,自定义验证器成功

自定义错误码

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

代码语言:javascript
复制
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 文件,编写:

代码语言:javascript
复制
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 文件,编写密码加密及验证密码的方法

代码语言:javascript
复制
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 文件,编写用户注册逻辑

代码语言:javascript
复制
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 注册逻辑

代码语言:javascript
复制
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 中,添加路由

代码语言:javascript
复制
package routes

import (
    "github.com/gin-gonic/gin"
    "jassue-gin/app/controllers/app"
)

// SetApiGroupRoutes 定义 api 分组路由
func SetApiGroupRoutes(router *gin.RouterGroup) {
    router.POST("/auth/register", app.Register)
}

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

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-01-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 生活处处有BUG 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 自定义验证器错误信息
  • 自定义验证器
  • 自定义错误码
  • 封装 Response
  • 实现用户注册接口
相关产品与服务
云服务器
云服务器(Cloud Virtual Machine,CVM)提供安全可靠的弹性计算服务。 您可以实时扩展或缩减计算资源,适应变化的业务需求,并只需按实际使用的资源计费。使用 CVM 可以极大降低您的软硬件采购成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档