предисловие
哈喽,大家好,我是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个字符"
}
}
Посмотрите, китайский язык отображается напрямую. Разве это не здорово? Мы можем использовать это во время тестирования. Не рекомендуется для онлайн-проектов! ! !
Суммировать
Что ж, на этом статья заканчивается. Этот еще полон вкусностей. Изучите эти знания, повысьте эффективность нашей разработки и избавьтесь от ненужного кода. Мы по-прежнему не хотим пропустить доступные колеса.
Я Асонг, обычный программист, дайте мне стать сильнее вместе. Приветствую всеобщее внимание, увидимся в следующем выпуске~~~ Рекомендуемые прошлые статьи: