杨吉山 發表於 2025-11-2 09:51:06

Golang中web参数校验的实现

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">参数校验</a></li><li><a href="#_label1">基本用法</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_0">JSON</a></li><li><a href="#_lab2_1_1">URL</a></li></ul><li><a href="#_label2">常用的校验规则</a></li><ul class="second_class_ul"></ul><li><a href="#_label3">自定义错误信息</a></li><ul class="second_class_ul"></ul><li><a href="#_label4">自定义校验规则</a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>参数校验</h2>
<p>确保我们的应用接收到的数据是格式正确并且安全的</p>
<p class="maodian"><a name="_label1"></a></p><h2>基本用法</h2>
<p class="maodian"><a name="_lab2_1_0"></a></p><h3>JSON</h3>
<p>假设我们正在开发一个<strong>登录接口</strong>,希望用户必须提供用户名和密码。在 Gin 中,我们首先会定义一个 Go 的 <strong>struct</strong> 来描述这个数据结构。</p>
<div class="jb51code"><pre class="brush:go;">type Login struct {
        User   string `json:"user" binding:"required"`
        Password string `json:"password" binding:"required"`
}
</pre></div>
<ul><li>json:&ldquo;&hellip;&rdquo; 标签: 这个标签告诉 Gin,当它解析传入的 JSON 数据时,JSON 中的 user 字段应该映射到我们结构体中的 User 字段。</li><li>binding:&ldquo;required&rdquo; 标签: 这是最核心的部分!binding 就是用来做数据校验的。required 是一个校验规则,意思是&ldquo;这个字段是必填的,不能为空&rdquo;。</li></ul>
<div class="jb51code"><pre class="brush:go;">func loginHandler(c *gin.Context) {
        var login Login

        // 尝试将请求的 JSON body 绑定到 login 结构体上
        // 在绑定的同时,Gin 会根据 "binding" 标签进行校验
        err := c.ShouldBindJSON(&amp;login)
        if err != nil {
                // 如果 err 不是 nil,说明校验失败了!
                // 比如,某个 "required" 的字段没有被提供。
                // Gin 通常会自动返回一个 400 Bad Request 错误。
                c.JSON(400, gin.H{"error": err.Error()})
                return
        }

        // 如果 err 是 nil,说明所有校验都通过了!
        c.JSON(200, gin.H{"status": "you are logged in"})
}

</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025110209484150.png" /></p>
<p class="maodian"><a name="_lab2_1_1"></a></p><h3>URL</h3>
<p>除了处理 JSON,我们经常还需要处理来自 URL 查询(Query)和 HTML 表单(Form)的数据。<br /><strong>Gin 的设计非常统一。我们的 struct 定义方式几乎不变,只需要更换一个绑定方法就可以了。</strong></p>
<p><strong>如何校验 URL 查询参数,比如这样一个请求:GET /search?keyword=gin&amp;page=1</strong><br />我们希望 keyword 是必填的,并且 page 必须是一个大于 0 的数字。<br />首先,还是定义我们的 struct:</p>
<div class="jb51code"><pre class="brush:go;">type SearchQuery struct {
        Keyword string `form:"keyword" binding:"required"`
        Page    int    `form:"page"    binding:"required,gt=0"`
}
</pre></div>
<ul><li><strong>我们用了 form:&ldquo;&hellip;&rdquo; 标签</strong>,而不是 json:&ldquo;&hellip;&rdquo;。这个 form 标签既可以用于 URL 查询参数,也可以用于表单数据。</li><li>我在 Page 字段的 binding 里加了一个<strong>新规则 gt=0</strong>,它的意思是 &ldquo;<strong>greater than 0</strong>&rdquo;<strong>(必须大于0</strong>)。</li></ul>
<div class="jb51code"><pre class="brush:go;">func searchHandler(c *gin.Context) {
    var query SearchQuery

    // 从 URL query 中绑定并校验参数
    // 注意这里换成了 ShouldBindQuery
    err := c.ShouldBindQuery(&amp;query)

    if err != nil {
      c.JSON(400, gin.H{"error": err.Error()})
      return
    }

    // 校验通过!
    c.JSON(200, gin.H{
      "message": "search parameters are valid",
      "keyword": query.Keyword,
      "page":    query.Page,
    })
}
</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025110209484173.png" /></p>
<p class="maodian"><a name="_label2"></a></p><h2>常用的校验规则</h2>
<p>我们通过一个用户注册的例子来看看:</p>
<div class="jb51code"><pre class="brush:go;">type RegisterUser struct {
        // 用户名:必填,长度在 4 到 20 个字符之间
        Username string `json:"username" binding:"required,min=4,max=20"`

        // 邮箱:必填,且必须是合法的邮箱格式
        Email string `json:"email" binding:"required,email"`

        // 年龄:选填,但如果提供了,必须大于等于 18 岁,小于等于 100 岁
        Age int `json:"age" binding:"gte=18,lte=100"`

        // 用户类型:必填,并且值必须是 'user' 或 'admin' 中的一个
        UserType string `json:"user_type" binding:"required,oneof=user admin"`
}
</pre></div>
<ul><li>min=4,max=20:用于字符串、数组等,限制其最小和最大长度。多个规则用逗号 , 分隔。</li><li>email:检查字符串是否符合标准的 email 格式。</li><li>gte=18:意思是 &ldquo;Greater Than or Equal To&rdquo;,即大于或等于 18。</li><li>lte=100:意思是 &ldquo;Less Than or Equal To&rdquo;,即小于或等于 100。</li><li>oneof=user admin:表示这个字段的值必须是后面列出的值之一(用空格分隔)。</li></ul>
<div class="jb51code"><pre class="brush:go;">func registerUserHandler(c *gin.Context) {
        var register RegisterUser
        err := c.ShouldBindJSON(&amp;register)
        if err != nil {
                c.JSON(400, gin.H{"error": err.Error()})
                return
        }

        c.JSON(200, gin.H{
                "username": register.Username,
                "email":    register.Email,
        })
}
</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025110209484153.png" /></p>
<p class="maodian"><a name="_label3"></a></p><h2>自定义错误信息</h2>
<p>核心思路是:<strong>捕获到校验错误后,我们不再直接把它整个返回,而是逐一检查是哪个字段的哪条规则出错了,然后根据这些信息,手动&ldquo;翻译&rdquo;成我们想展示的话。</strong></p>
<div class="jb51code"><pre class="brush:go;">func registerUserHandler(c *gin.Context) {
        var register RegisterUser
        err := c.ShouldBindJSON(&amp;register)
        if err != nil {
                var verrs validator.ValidationErrors
                ok := errors.As(err, &amp;verrs)
                if !ok {
                        // 如果不是 ValidationErrors 类型的错误,直接返回原始错误
                        c.JSON(400, gin.H{"error": err.Error()})
                        return
                }
                // 创建一个 map 来存放我们“翻译”后的错误信息
                // key 是字段名,value 是错误信息
                // 比如:{"Username": "长度不能小于 4 个字符"}
                translatedErrors := make(mapstring)
                for _, fe := range verrs {
                        // fe.Field() 获取出错的字段名,比如 "Username"
                        translatedErrors = getErrorMsg(fe)
                }

                c.JSON(400, gin.H{"errors": translatedErrors})
                return
        }

        c.JSON(200, gin.H{
                "username": register.Username,
                "email":    register.Email,
        })
}

// 这个函数专门用来“翻译”错误信息
func getErrorMsg(fe validator.FieldError) string {
        // fe.Tag() 能获取到是哪个校验规则出错了,比如 "required", "min", "email"
        switch fe.Tag() {
        case "required":
                return "这是必填项"
        case "min":
                // fe.Param() 能获取到规则后面的参数,比如 "min=4" 中的 "4"
                return "长度不能小于 " + fe.Param() + " 个字符"
        case "max":
                return "长度不能超过 " + fe.Param() + " 个字符"
        case "email":
                return "请输入正确的邮箱地址"
        case "oneof":
                return "必须是 " + fe.Param() + " 中的一个"
        default:
                return "未知错误"
        }
}
</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025110209484115.png" /></p>
<p class="maodian"><a name="_label4"></a></p><h2>自定义校验规则</h2>
<p>当内置的规则(像 required, email, min, max 等)不够用时,我们就需要自定义规则。比如,我们想强制要求&ldquo;密码必须同时包含字母和数字&rdquo;。</p>
<p><strong>整个过程分为三步,我们一步一步来:</strong></p>
<ol><li>编写一个自定义校验函数<br />这个函数需要一个特定的格式。它接收一个 <strong>validator.FieldLevel</strong> 类型的参数,并返回一个 <strong>bool</strong> 值。返回 <strong>true 表示校验通过,false 表示失败。</strong></li></ol>
<div class="jb51code"><pre class="brush:go;">// isPasswordStrong 就是我们的自定义校验函数
func isPasswordStrong(fl validator.FieldLevel) bool {
    // fl.Field().String() 可以获取到字段的字符串值
    password := fl.Field().String()
   
    hasLetter := false
    hasDigit := false

    // 遍历密码字符串,检查是否同时包含字母和数字
    for _, char := range password {
      if unicode.IsLetter(char) {
            hasLetter = true
      }
      if unicode.IsDigit(char) {
            hasDigit = true
      }
    }
   
    return hasLetter &amp;&amp; hasDigit
}
</pre></div>
<ol start="2"><li><p>将自定义函数&ldquo;注册&rdquo;到 Gin 的校验器里<br />光写好函数还不行,我们得告诉 Gin 的校验器:&ldquo;嘿,我这里有一个新的校验规则,它的名字叫 strong_password,对应的处理函数是 isPasswordStrong。&rdquo;</p>
<p>这个注册过程通常在你的程序启动时(比如 main 函数里)完成。</p></li></ol>
<div class="jb51code"><pre class="brush:go;">// 从 Gin 的 binding 中获取底层的 validator 引擎
        if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
                // 注册我们的自定义校验函数
                // 第一个参数是这个规则在 struct tag 中使用的名字
                // 第二个参数是我们的函数
                v.RegisterValidation("strong_password", isPasswordStrong)
        }
</pre></div>
<ol start="3"><li>在 Struct Tag 中使用新规则<br />一旦注册成功,我们就可以像使用任何内置规则一样,在 struct tag 中使用 strong_password 了。</li></ol>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202511/2025110209484118.png" /></p>
頁: [1]
查看完整版本: Golang中web参数校验的实现