惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

WordPress大学
WordPress大学
Vercel News
Vercel News
博客园_首页
Y
Y Combinator Blog
美团技术团队
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
阮一峰的网络日志
阮一峰的网络日志
aimingoo的专栏
aimingoo的专栏
H
Hackread – Cybersecurity News, Data Breaches, AI and More
MyScale Blog
MyScale Blog
GbyAI
GbyAI
人人都是产品经理
人人都是产品经理
T
Tailwind CSS Blog
MongoDB | Blog
MongoDB | Blog
D
DataBreaches.Net
博客园 - Franky
Engineering at Meta
Engineering at Meta
量子位
The GitHub Blog
The GitHub Blog
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
N
Netflix TechBlog - Medium

Posts on WKLKEN THINKING

apisix 中的 lrucache apisix 中的服务发现机制 apisix 中的负载均衡 apisix etcd机制 聊聊框架 关于 k8s 的 zero downtime deployment 一些建议 apisix 遇到的一些问题 关于在除夕前一天换了一个洗衣机的故事 Django DRF 性能优化 DRF 的一些实践 Part1: Serializer DRF继承关系图 Better Code: 关于接口的灵活性 新的仓库: wklken/naming 缓存使用的一些经验 Better Code: 抽象: 可扩展性与可维护性的抉择 Better Code: 异常时, 该提示用户哪些信息? Better Code: 更好的异常日志打印 Go: some libs Go: go-redis/cache升级的坑 Go: logrus性能提升 远程办公的一点总结 Go: 开发过程中的一些bug 项目管理实践: 风险驱动开发 Go: 一种error wrap调用链处理方式 漫谈技术选型 Go: 基于 apitest 做handler层单元测试 Go: go-sql-driver interpolateparams参数优化 [分享]深度工作 你需要更多的思考时间 Django项目重构小结
Go: gin validation
2021-02-04 · via Posts on WKLKEN THINKING

这是在做 Golang 项目中的一些实践

传统的校验方式

需要在获取数据后, 写很多if判断语句, 无法复用且非常罗嗦

if a != "" {

}
if len(a) < 10 {

}

gin 使用了 go-playground/validator库, 使用tag声明的方式, 支持http请求request中的各类校验

几种常用参数校验

1. get 参数

gin: Only Bind Query String

curl -X GET “localhost:8085/testing?name=eason&address=xyz”

type Person struct {
	Name    string `form:"name"`
	Address string `form:"address"`
}

var person Person
if c.ShouldBindQuery(&person) == nil {
		log.Println("====== Only Bind By Query String ======")
		log.Println(person.Name)
		log.Println(person.Address)
}

2. 路径参数

gin: Bind Uri

curl -v localhost:8088/thinkerou/987fbc97-4bed-5078-9f07-9141ba07c9f3

package main

import "github.com/gin-gonic/gin"

type Person struct {
	ID string `uri:"id" binding:"required,uuid"`
	Name string `uri:"name" binding:"required"`
}

func main() {
	route := gin.Default()
	route.GET("/:name/:id", func(c *gin.Context) {
		var person Person
		if err := c.ShouldBindUri(&person); err != nil {
			c.JSON(400, gin.H{"msg": err})
			return
		}
		c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})
	})
	route.Run(":8088")
}

3. json body

type Login struct {
	User     string `json:"user" binding:"required"`
	Password string `json:"password" binding:"required"`
}

var json Login
if err := c.ShouldBindJSON(&json); err != nil {
    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    return
}

gin: Bind Header

curl -H “rate:300” -H “domain:music” 127.0.0.1:8080/

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

type testHeader struct {
	Rate   int    `header:"Rate"`
	Domain string `header:"Domain"`
}

func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		h := testHeader{}

		if err := c.ShouldBindHeader(&h); err != nil {
			c.JSON(200, err)
		}

		fmt.Printf("%#v\n", h)
		c.JSON(200, gin.H{"Rate": h.Rate, "Domain": h.Domain})
	})

	r.Run()
}

json body是一个对象数组

ShouldBindJSON并不会校验对象数组中的每个对象是否符合要求, 需要自行调用方法处理

可以抽象出一个通用的函数

var ErrNotArray = errors.New("validate array fail, only support array")

func ToSlice(array interface{}) ([]interface{}, error) {
	v := reflect.ValueOf(array)
	if v.Kind() != reflect.Slice {
		return nil, ErrNotArray
	}
	l := v.Len()
	ret := make([]interface{}, l)
	for i := 0; i < l; i++ {
		ret[i] = v.Index(i).Interface()
	}
	return ret, nil
}


func ValidateArray(data interface{}) (bool, string) {
	array, err := ToSlice(data)
	if err != nil {
		return false, err.Error()
	}

	if len(array) == 0 {
		return false, "the array should contain at least 1 item"
	}
	for index, item := range array {
		if err := binding.Validator.ValidateStruct(item); err != nil {
			message := fmt.Sprintf("data in array[%d], %s", index, ValidationErrorMessage(err))
			return false, message
		}
	}
	return true, "valid"
}

使用时

var body []action
if err := c.ShouldBindJSON(&body); err != nil {
	// bad request
	return
}

if valid, message := common.ValidateArray(body); !valid {
    // bad request: message
    return
}

更友好的错误提示

当validation报错的时候, 我们期望得到一个更友好的提示信息, 便于使用者确认问题

reference:

  1. https://github.com/gin-gonic/gin/issues/430
  2. https://medium.com/@seb.nyberg/better-validation-errors-in-go-gin-88f983564a3d

以下是一种实现, 处理的常用的 validation 规则的错误展示

package util

import (
	"fmt"
	"io"

	"github.com/go-playground/validator/v10"
	log "github.com/sirupsen/logrus"
)

// 这里是通用的 FieldError 处理, 如果需要针对某些字段或struct做定制, 需要自行定义一个
type ValidationFieldError struct {
	Err validator.FieldError
}

func (v ValidationFieldError) String() string {
	e := v.Err

	switch e.Tag() {
	case "required":
		return fmt.Sprintf("%s is required", e.Field())
	case "max":
		return fmt.Sprintf("%s cannot be longer than %s", e.Field(), e.Param())
	case "min":
		return fmt.Sprintf("%s must be longer than %s", e.Field(), e.Param())
	case "email":
		return "Invalid email format"
	case "len":
		return fmt.Sprintf("%s must be %s characters long", e.Field(), e.Param())
	case "gt":
		return fmt.Sprintf("%s must greater than %s", e.Field(), e.Param())
	case "gte":
		return fmt.Sprintf("%s must greater or equals to %s", e.Field(), e.Param())
	case "lt":
		return fmt.Sprintf("%s must less than %s", e.Field(), e.Param())
	case "lte":
		return fmt.Sprintf("%s must less or equals to %s", e.Field(), e.Param())
	case "oneof":
		return fmt.Sprintf("%s must be one of '%s'", e.Field(), e.Param())
	}

	return fmt.Sprintf("%s is not valid, condition: %s", e.Field(), e.ActualTag())
}

func ValidationErrorMessage(err error) string {
	if err == io.EOF {
		return "EOF, json decode fail"
	}

	validationErrs, ok := err.(validator.ValidationErrors)
	if !ok {
		message := fmt.Sprintf("json decode or validate fail, err=%s", err)
		log.Info(message)
		return message
	}

	// currently, only return the first error
	for _, fieldErr := range validationErrs {
		return ValidationFieldError{fieldErr}.String()
	}

	return "validationErrs with no error message"
}

判断: 空值还是没有传递

有些场景, 如果一个json对象中, 某个字段可以为空, 但是非空的时候, 要执行某些特殊的逻辑

此时, 使用默认的validation是无法判断, 请求中没有这个字段, 还是传了空值;

// validate
var body action
err := c.ShouldBindBodyWith(&body, binding.JSON)
if err != nil {
	// bad request, ValidationErrorMessage(err)
	return
}
var data map[string]interface{}
err = c.ShouldBindBodyWith(&data, binding.JSON)
if err != nil {
	// bad request, ValidationErrorMessage(err)
	return
}

if _, ok := data["name"]; !ok {
	// do something
}