Skip to content

API 请求体

把 govalid 接入 net/http(或任何 framework)的处理器中。校验在 JSON 解码之后、业务逻辑之前运行。

配合 net/http

go
package main

import (
    "encoding/json"
    "errors"
    "net/http"

    "github.com/wuhan005/govalid"
    "golang.org/x/text/language"
)

type CreatePost struct {
    Title    string   `valid:"required;minlen:3;maxlen:200" label:"标题"`
    Body     string   `valid:"required;minlen:10"           label:"正文"`
    Tags     []string `valid:"maxlen:5"                     label:"标签"`
    Author   string   `valid:"required;username"            label:"作者"`
    Category string   `valid:"list:tech,life,news"          label:"分类"`
}

func (p *CreatePost) Validate() error {
    if len(p.Body) > 100 && len(p.Tags) == 0 {
        return errors.New("长文章必须至少包含一个标签")
    }
    return nil
}

func createPost(w http.ResponseWriter, r *http.Request) {
    var body CreatePost
    if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    locale := negotiate(r)
    if errs, ok := govalid.Check(&body, locale); !ok {
        respondValidationErrors(w, errs)
        return
    }

    // ……业务逻辑……
    w.WriteHeader(http.StatusCreated)
}

func negotiate(r *http.Request) language.Tag {
    matcher := language.NewMatcher([]language.Tag{
        language.Chinese, language.English,
    })
    accept, _, _ := language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))
    tag, _, _ := matcher.Match(accept...)
    return tag
}

func respondValidationErrors(w http.ResponseWriter, errs []*govalid.ErrContext) {
    type item struct {
        Field   string `json:"field"`
        Label   string `json:"label"`
        Message string `json:"message"`
    }
    out := make([]item, 0, len(errs))
    for _, e := range errs {
        out = append(out, item{
            Field:   e.FieldName,
            Label:   e.FieldLabel,
            Message: e.Error(),
        })
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusUnprocessableEntity)
    _ = json.NewEncoder(w).Encode(map[string]any{"errors": out})
}

示例响应

json
{
  "errors": [
    { "field": "Title",  "label": "标题",  "message": "标题长度应大于3" },
    { "field": "Author", "label": "作者", "message": "作者的第一个字符必须为字母" }
  ]
}

模式要点

  • 在解码之后才调用 Check。校验标签解决不了 JSON 格式错误。
  • 把协商好的 language.Tag 传给 Check,让响应使用调用方的 locale。
  • 校验失败时优先用 HTTP 422 Unprocessable Entity,而不是 400 Bad Request——前端通常需要区分"我发的就是垃圾"和"你的数据 需要修一下"。

基于 MIT 协议发布。