Изучите библиотеку валидаторов для проверки параметров gin, достаточно прочитать эту статью

Go
Изучите библиотеку валидаторов для проверки параметров gin, достаточно прочитать эту статью

предисловие

哈喽,大家好,我是asong。这是我的第十篇原创文章。这周在公司做项目,在做API部分开发时,需要对请求参数的校验,防止用户的恶意请求。 Например, формат даты, возраст пользователя, пол и т. д. должны быть нормальными значениями и не могут быть установлены произвольно.最开始在做这一部分的时候,我采用老方法,自己编写参数检验方法,统一进行参数验证。后来在同事CR的时候,说GIN有更好的参数检验方法,gin框架使用GitHub.com/go-play собачье мясо…Проверка параметра, нам нужно только использовать при определении структурыbindingилиvalidateТег идентифицирует соответствующие правила проверки, и можно выполнить проверку параметров, что очень удобно. Я считаю, что есть много мелких партнеров, которые не знают эту функцию, поэтому сегодня я представлю эту часть.

自己翻译了一份gin官方中文文档。关注公众号[Golang梦工厂](扫描下方二维码),后台回复:gin,即可获取。

Быстрая установка

Прежде чем использовать его, нам нужно получитьvalidatorэта библиотека.

# 第一次安装使用如下命令
$ go get github.com/go-playground/validator/v10
# 项目中引入包
import "github.com/go-playground/validator/v10"

Простой пример

Установка по-прежнему очень проста.Позвольте мне сначала перейти к официальному примеру, чтобы увидеть, как он используется, а затем начать анализ.

package main

import (
	"fmt"
	"net/http"

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

type RegisterRequest struct {
	Username string `json:"username" binding:"required"`
	Nickname string `json:"nickname" binding:"required"`
	Email    string `json:"email" binding:"required,email"`
	Password string `json:"password" binding:"required"`
	Age      uint8  `json:"age" binding:"gte=1,lte=120"`
}

func main() {

	router := gin.Default()

	router.POST("register", Register)

	router.Run(":9999")
}

func Register(c *gin.Context) {
	var r RegisterRequest
	err := c.ShouldBindJSON(&r)
	if err != nil {
		fmt.Println("register failed")
		c.JSON(http.StatusOK, gin.H{"msg": err.Error()})
		return
	}
	//验证 存储操作省略.....
	fmt.Println("register success")
	c.JSON(http.StatusOK, "successful")
}

  • контрольная работа
curl --location --request POST 'http://localhost:9999/register' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username": "asong",
    "nickname": "golang梦工厂",
    "email": "7418.com",
    "password": "123",
    "age": 140
}'
  • вернуть результат
{
    "msg": "Key: 'RegisterRequest.Email' Error:Field validation for 'Email' failed on the 'email' tag\nKey: 'RegisterRequest.Age' Error:Field validation for 'Age' failed on the 'lte' tag"
}

Глядя на этот вывод, мы можем видетьvalidatorПроверка действительна, поле электронной почты не является действительным почтовым ящиком, а в возрасте поле превышает максимальный предел. Мы решаем эту проблему только путем добавления тегов на структуру. Это очень удобно? Давайте узнаем, как его использовать.

библиотека валидатора

Платформа gin использует библиотеку validator.v10 для проверки параметров, поэтому давайте сначала рассмотрим использование этой библиотеки.

Сначала установите эту библиотеку:

$ go get github.com/go-playground/validator/v10

Затем сначала напишите простой пример:

package main

import (
	"fmt"

	"github.com/go-playground/validator/v10"
)

type User struct {
	Username string `validate:"min=6,max=10"`
	Age      uint8  `validate:"gte=1,lte=10"`
	Sex      string `validate:"oneof=female male"`
}

func main() {
	validate := validator.New()

	user1 := User{Username: "asong", Age: 11, Sex: "null"}
	err := validate.Struct(user1)
	if err != nil {
		fmt.Println(err)
	}

	user2 := User{Username: "asong111", Age: 8, Sex: "male"}
	err = validate.Struct(user2)
	if err != nil {
		fmt.Println(err)
	}

}

Мы определяем тег тега валидатора в структуре, используяvalidator.New()Создайте валидатор, который может задавать параметры, добавлять пользовательские ограничения, а затем вызывать его.Struct()методы для проверки того, что поля различных структурных объектов соответствуют определенным ограничениям.

В приведенном выше примере у нас есть три поля в структуре пользователя:

  • Имя: ограничено минимальным и максимальным значением, длина строки имени находится в пределах [6,10].
  • Возраст: Диапазон молодняка ограничен gte и lte, а размер возраста больше 1 и меньше 10.
  • Пол: значение ограничено oneof, которое может быть только указанным значением Oneof указывает пол как мужской 🚹 и женский 🚺 (не жесткое правило, могут быть и другие полы).

такuser1Будет сообщено об ошибке, сообщение об ошибке выглядит следующим образом:

Key: 'User.Name' Error:Field validation for 'Name' failed on the 'min' tag
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'lte' tag
Key: 'User.Sex' Error:Field validation for 'Sex' failed on the 'oneof' tag

Какие ограничения нарушило каждое поле, мы можем увидеть из сообщения об ошибке. Прочитав простой пример, давайте посмотрим, какие теги есть, мы можем это использовать. В этой статье представлены не все теги, пожалуйста, используйте их чаще.официальная документацияУчитесь самостоятельно.

Строковые ограничения

  • excludesall: не содержит символов UNICODE в параметре, например.excludesall=ab;

  • excludesrune: не содержит символа руны, представленного параметром,excludesrune=asong;

  • startswith: с префиксом подстроки параметра, например.startswith=hi;

  • endswith: с суффиксом подстроки параметра, например.endswith=bye.

  • contains=: содержит подстроку параметра, например.contains=email;

  • containsany: содержит любые символы UNICODE в параметре, например.containsany=ab;

  • containsrune: содержит символ руны, представленный параметром, например, `containsrune=asong;

  • excludes: не содержит подстрок параметров, например.excludes=email;

ограничения области

Существует три типа полей для ограничений диапазона:

  • Для числовых значений мы можем ограничить его значение
  • Для срезов, массивов и карт мы можем ограничить их длину.
  • Для строк мы можем ограничить их длину

Введение в общие теги:

  • ne: не равно значению параметра, например.ne=5;
  • gt: больше, чем значение параметра, например.gt=5;
  • gte: больше или равно значению параметра, напримерgte=50;
  • lt: меньше значения параметра, например.lt=50;
  • lte: меньше или равно значению параметра, напримерlte=50;
  • oneof: Может быть только одно из перечисленных значений.Эти значения должны быть числами или строками, разделенными пробелами.Если в строке есть пробелы,заключите строку в одинарные кавычки,напримерoneof=male female.
  • eq: равно значению параметра, обратите внимание наlenразные. Для струн,eqограничивает значение самой строки, аlenДлина строки ограничения. Напримерeq=10;
  • len: равно значению параметра, например.len=10;
  • max: меньше или равно значению параметра, напримерmax=10;
  • min: больше или равно значению параметра, напримерmin=10

Ограничения полей

  • eqfield: определите ограничения равенства между полями, чтобы ограничить поля в одной структуре. Например:eqfield=Password
  • eqcsfield: Ограничение поля в единой структуре равным другому полю (относительному), которое можно использовать при подтверждении пароля, например:eqfiel=ConfirmPassword
  • nefield: используется для ограничения совпадения двух полей и может использоваться для подтверждения совпадения двух цветов, например:nefield=Color1
  • necsfield: Ограничение, являются ли два поля одинаковыми (относительными).

Общие ограничения

  • unique: укажите уникальные ограничения, разные типы обрабатываются по-разному:

    • Для карты уникальное ограничение не имеет повторяющихся значений
    • unique не имеет повторяющихся значений для массивов и срезов
    • Для фрагментов, тип элемента которых является структурой, уникальное ограничение, заключающееся в том, что поле объекта структуры не повторяется, используйтеunique=fieldУкажите имя поля
  • email:использоватьemailДля ограничения поле должно быть в виде почты, пишите напрямуюeamilВот и не надо ничего указывать.

  • omitempty: поле не задано, оно игнорируется

  • -: пропустить это поле без проверки;

  • |: При использовании нескольких ограничений необходимо удовлетворить только одно из них, например.rgb|rgba;

  • required: поле должно быть установлено и не может быть значением по умолчанию;

Что ж, давайте познакомимся с этими часто используемыми ограничениями. Дополнительные ограничения можно найти в документации, чтобы изучить их самостоятельно. Очень скоро вы найдете примеры для изучения.

Проверка параметров в джине

Изучив валидатор, мы также знаем, как использовать валидацию параметров в джине. Ни одно из этих ограничений не изменилось вvalidator, мы помещаем ограничения непосредственно в структуруvalidateтег, по той же причине, в джине нам нужно только поставить ограничение вbindingтег на нем. Разве это не просто.

Но иногда не все проверки параметров могут удовлетворить наши потребности, поэтому мы можем определить свои собственные ограничения. Пользовательские ограничения поддерживают проверку пользовательской структуры, проверку настраиваемых полей и т. д. Вот введение в пользовательскую проверку структуры.

проверка пользовательской структуры

Когда дело доходит до некоторых сложных правил проверки, эти существующие правила проверки не могут удовлетворить наши потребности. Например, теперь есть требование, чтобы время создания и время обновления в пользовательской информации БД было больше определенного времени, предполагая, что оно исходит из внешнего интерфейса (конечно, это невозможно, ха-ха). Теперь давайте напишем простой пример, чтобы узнать, как проверить этот параметр.

package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/validator/v10"
)

type Info struct {
	CreateTime time.Time `form:"create_time" binding:"required,timing" time_format:"2006-01-02"`
	UpdateTime time.Time `form:"update_time" binding:"required,timing" time_format:"2006-01-02"`
}

// 自定义验证规则断言
func timing(fl validator.FieldLevel) bool {
	if date, ok := fl.Field().Interface().(time.Time); ok {
		today := time.Now()
		if today.After(date) {
			return false
		}
	}
	return true
}

func main() {
	route := gin.Default()
	// 注册验证
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		err := v.RegisterValidation("timing", timing)
		if err != nil {
			fmt.Println("success")
		}
	}

	route.GET("/time", getTime)
	route.Run(":8080")
}

func getTime(c *gin.Context) {
	var b Info
	// 数据模型绑定查询字符串验证
	if err := c.ShouldBindWith(&b, binding.Query); err == nil {
		c.JSON(http.StatusOK, gin.H{"message": "time are valid!"})
	} else {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
	}
}

Теперь, когда это написано, я проверю это:

$ curl "localhost:8080/time?create_time=2020-10-11&update_time=2020-10-11"
# 结果
{"message":"time are valid!"}%
$ curl "localhost:8080/time?create_time=1997-10-11&update_time=1997-10-11"
# 结果
{"error":"Key: 'Info.CreateTime' Error:Field validation for 'CreateTime' failed on the 'timing' tag\nKey: 'Info.UpdateTime' Error:Field validation for 'UpdateTime' failed on the 'timing' tag"}%

Здесь мы видим, что хотя проверка параметров прошла успешно, но ошибка, возвращаемая здесь, слишком полная, вернуть столь подробную информацию во фронтенд во время разработки проекта невозможно, поэтому нам нужно ее преобразовать:

func getTime(c *gin.Context) {
	var b Info
	// 数据模型绑定查询字符串验证
	if err := c.ShouldBindWith(&b, binding.Query); err == nil {
		c.JSON(http.StatusOK, gin.H{"message": "time are valid!"})
	} else {
		_, ok := err.(validator.ValidationErrors)
		if !ok {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}
		c.JSON(http.StatusOK, gin.H{"code": 1000, "msg": "param is error"})
	}
}

Здесь вы можете вернуть фиксированную ошибку при возникновении ошибки. Здесь вы также можете использовать метод для его инкапсуляции, обработки ошибки и ее возврата, и другие методы использования ждут вас.

пасхальные яйца

Когда мы возвращаем ошибки, мы все на английском языке.Когда ошибки очень длинные, для английских отморозков, таких как я, мы должны использовать программы-переводчики. Так что было бы неплохо, если бы возвращаемая ошибка была прямо на китайском языке.validatorСама библиотека поддерживает интернационализацию, а автоматический перевод сообщений об ошибках проверки можно реализовать с помощью соответствующего языкового пакета. Напишите код ниже, чтобы продемонстрировать это.

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/gin-gonic/gin/binding"
	"github.com/go-playground/locales/en"
	"github.com/go-playground/locales/zh"
	ut "github.com/go-playground/universal-translator"
	"github.com/go-playground/validator/v10"
	enTranslations "github.com/go-playground/validator/v10/translations/en"
	chTranslations "github.com/go-playground/validator/v10/translations/zh"
)

var trans ut.Translator

// loca 通常取决于 http 请求头的 'Accept-Language'
func transInit(local string) (err error) {
	if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
		zhT := zh.New() //chinese
		enT := en.New() //english
		uni := ut.New(enT, zhT, enT)

		var o bool
		trans, o = uni.GetTranslator(local)
		if !o {
			return fmt.Errorf("uni.GetTranslator(%s) failed", local)
		}
		//register translate
		// 注册翻译器
		switch local {
		case "en":
			err = enTranslations.RegisterDefaultTranslations(v, trans)
		case "zh":
			err = chTranslations.RegisterDefaultTranslations(v, trans)
		default:
			err = enTranslations.RegisterDefaultTranslations(v, trans)
		}
		return
	}
	return
}

type loginRequest struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required,max=16,min=6"`
}

func main() {
	if err := transInit("zh"); err != nil {
		fmt.Printf("init trans failed, err:%v\n", err)
		return
	}
	router := gin.Default()

	router.POST("/user/login", login)

	err := router.Run(":8888")
	if err != nil {
		log.Println("failed")
	}
}

func login(c *gin.Context) {
	var req loginRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		// 获取validator.ValidationErrors类型的errors
		errs, ok := err.(validator.ValidationErrors)
		if !ok {
			// 非validator.ValidationErrors类型错误直接返回
			c.JSON(http.StatusOK, gin.H{
				"msg": err.Error(),
			})
			return
		}
		// validator.ValidationErrors类型错误则进行翻译
		c.JSON(http.StatusOK, gin.H{
			"msg": errs.Translate(trans),
		})
		return
	}
	//login 操作省略
	c.JSON(http.StatusOK, gin.H{
		"code": 0,
		"msg":  "success",
	})
}

Здесь я ограничиваю длину пароля в параметре запроса, давайте проверим.

curl --location --request POST 'http://localhost:8888/user/login' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username": "asong",
    "password": "11122222222222222222"
}'
# 返回
{
    "msg": {
        "loginRequest.Password": "Password长度不能超过16个字符"
    }
}

Посмотрите, китайский язык отображается напрямую. Разве это не здорово? Мы можем использовать это во время тестирования. Не рекомендуется для онлайн-проектов! ! !

Суммировать

Что ж, на этом статья заканчивается. Этот еще полон вкусностей. Изучите эти знания, повысьте эффективность нашей разработки и избавьтесь от ненужного кода. Мы по-прежнему не хотим пропустить доступные колеса.

Я Асонг, обычный программист, дайте мне стать сильнее вместе. Приветствую всеобщее внимание, увидимся в следующем выпуске~~~ 公众号图片Рекомендуемые прошлые статьи: