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

推荐订阅源

C
CXSECURITY Database RSS Feed - CXSecurity.com
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Y
Y Combinator Blog
U
Unit 42
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
MongoDB | Blog
MongoDB | Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Hugging Face - Blog
Hugging Face - Blog
B
Blog
T
Tailwind CSS Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
H
Help Net Security
D
Docker
NISL@THU
NISL@THU
P
Privacy International News Feed
L
Lohrmann on Cybersecurity
Scott Helme
Scott Helme
P
Palo Alto Networks Blog
S
Securelist
WordPress大学
WordPress大学
B
Blog RSS Feed
Blog — PlanetScale
Blog — PlanetScale
有赞技术团队
有赞技术团队
L
LINUX DO - 热门话题
人人都是产品经理
人人都是产品经理
博客园 - 聂微东
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Security Latest
Security Latest
D
DataBreaches.Net
T
The Exploit Database - CXSecurity.com
Recorded Future
Recorded Future
Stack Overflow Blog
Stack Overflow Blog
T
The Blog of Author Tim Ferriss
The Hacker News
The Hacker News
I
Intezer
T
Threat Research - Cisco Blogs
Cyberwarzone
Cyberwarzone
C
Cyber Attacks, Cyber Crime and Cyber Security
P
Privacy & Cybersecurity Law Blog
Cisco Talos Blog
Cisco Talos Blog
L
LangChain Blog
Jina AI
Jina AI
量子位
V
V2EX
阮一峰的网络日志
阮一峰的网络日志
Last Week in AI
Last Week in AI
AWS News Blog
AWS News Blog
Apple Machine Learning Research
Apple Machine Learning Research

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项目重构小结 工作七年小结: 学习,生活及其他 [分享]bash日常: bash-utils 极客时间推广海报 2017总结: 予时光以意义 k8s APIServer源码: api注册详细细节 k8s APIServer源码: api注册主体流程 k8s APIServer源码: 服务启动 k8s APIServer源码: go-restful框架 重构 - 读书笔记(Python示例) 写给新人的沟通建议 vim 杂谈 - 关于快速编辑 vim 杂谈 - 关于移动 读书笔记-重构: 章11 处理概括关系 读书笔记-重构: 章10 简化函数调用 读书笔记-重构: 章9 简化表达式 读书笔记-重构: 章8 重新组织数据 读书笔记-重构: 章7 在对象之间搬移特性 读书笔记-重构: 章6 重新组织函数 Python 代码规范小结 [分享]关于vim ElasticSearch集群部署文档 Logstash+ElasticSearch处理mysql慢查询日志 [分享]关于代码调试DE那些事 Logstash+ElasticSearch+Kibana- 实现相对通用的数据收集分析 ELK维护的一些点(二) [分享]Python源码剖析-数据结构 一些Centos Python生产环境的部署命令 摘录<<6个月学会任何一种外语>> ELK 维护的一些点 也许是一个新的开始 一些vim的个性化配置 读书笔记-调试九法 这段时间的一些想法 Python 源码阅读 - 垃圾回收机制 我为什么要写博客 APUE笔记-第一章 UNIX基础知识 Python源码阅读-闭包的实现 Python源码阅读-内存管理机制(二) Python源码阅读-内存管理机制(一) Python-基础-数据结构小结 '活动'设计的一些trick 一些简单的Python测试题 我的tmux配置及说明【k-tmux】 Review and Restart 工作四周年小结 vim插件: surround & repeat[成对符号编辑] vim插件: gundo[时光机] vim插件: expand-region[区域选中] vim插件: quickrun[快速执行] vim插件: trailing-whitespace[行尾空格处理] vim插件: closetag[成对标签补全] vim插件: ctrlp[文件搜索] vim插件: airline[状态栏增强] vim插件: theme[主题] vim插件: tagbar[大纲式导航] vim插件: nerdcommenter[快速注释] vim插件: rainbow_parentheses[括号高亮] vim插件: syntastic[语法检查] vim插件: delimitmate[符号自动补全] vim插件: matchit[成对标签跳转] vim插件: easy-align[快速对齐] vim插件: multiple-cursors[多光标操作] vim插件: vim-signature[快速标记跳转] vim插件: easymotion[快速跳转] vim插件: vundle[管理插件] Elasticsearch几个问题的解决 分享一份 Vim 简介PPT k-vim 更新9.0版本 关于知识管理工具的思考 Logstash+ElasticSearch+Kibana处理nginx访问日志
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
}