Validator Thư viện Xác thực
Địa chỉ chính thức: go-playground/validator: 💯Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving (github.com)
Địa chỉ tài liệu: validator/README.md at master · go-playground/validator (github.com)
Ví dụ chính thức: validator/_examples at master · go-playground/validator (github.com)
Giới thiệu
go-playground/validator triển khai một trình xác thực giá trị dựa trên tag struct, nó có các tính năng độc đáo sau:
Có thể sử dụng tag xác thực và trình xác thực tùy chỉnh để xác thực cross-field và cross-struct
Slice, array, map, hoặc bất kỳ trường đa chiều nào đều có thể được xác thực
Có thể xác thực sâu key và value của map
Xác định cách xử lý trước khi xác thực thông qua kiểu cơ bản của nó
Có thể xử lý các kiểu trường tùy chỉnh
Hỗ trợ tag alias, cho phép ánh xạ nhiều trình xác thực vào một tag duy nhất để dễ dàng định nghĩa xác thực cho struct
Có thể trích xuất tên trường tùy chỉnh, ví dụ có thể trích xuất tên JSON khi xác thực để hiển thị trong thông báo lỗi
Thông báo lỗi đa ngôn ngữ tùy chỉnh
Thành phần xác thực tiêu chuẩn mặc định của framework
gin
Cài đặt
go get github.com/go-playground/validator/v10Import
import "github.com/go-playground/validator/v10"Tag
Validator có rất nhiều tag xác thực cơ bản, tất cả các hàm xác thực tương ứng với tag đều có thể được tìm thấy trong file baked_in.go, tag struct của validator là validate,
ví dụ
type User {
age int `validate:"gte=18"` // biểu thị lớn hơn hoặc bằng 18 tuổi
}Cũng có thể sửa tag mặc định thông qua method setTagName.
Field
| Tag | Mô tả |
|---|---|
eqcsfield | Trong một struct riêng biệt, xác thực giá trị trường hiện tại bằng với trường được chỉ định bởi giá trị param |
eqfield | Xác thực giá trị trường hiện tại bằng với trường được chỉ định bởi giá trị param |
fieldcontains | Xác thực giá trị trường hiện tại chứa trường được chỉ định bởi giá trị param |
fieldexcludes | Xác thực giá trị trường hiện tại không chứa trường được chỉ định bởi giá trị param |
gtcsfield | Trong một struct riêng biệt, xác thực giá trị trường hiện tại lớn hơn trường được chỉ định bởi giá trị param |
gtecsfield | Trong một struct riêng biệt, xác thực giá trị trường hiện tại lớn hơn hoặc bằng trường được chỉ định bởi giá trị param |
gtefield | Xác thực giá trị trường hiện tại lớn hơn hoặc bằng trường được chỉ định bởi giá trị param |
gtfield | Xác thực giá trị trường hiện tại lớn hơn trường được chỉ định bởi giá trị param |
ltcsfield | Trong một struct riêng biệt, xác thực giá trị trường hiện tại nhỏ hơn trường được chỉ định bởi giá trị param |
ltecsfield | Trong một struct riêng biệt, xác thực giá trị trường hiện tại nhỏ hơn hoặc bằng trường được chỉ định bởi giá trị param |
ltefield | Xác thực giá trị trường hiện tại nhỏ hơn hoặc bằng trường được chỉ định bởi giá trị param |
ltfield | Xác thực giá trị trường hiện tại nhỏ hơn trường được chỉ định bởi giá trị param |
necsfield | Xác thực giá trị trường hiện tại không bằng trường trong struct riêng biệt được chỉ định bởi giá trị param |
nefield | Xác thực giá trị trường hiện tại không bằng trường được chỉ định bởi giá trị param |
Network
| Tag | Mô tả |
|---|---|
cidr | Classless Inter-Domain Routing CIDR |
cidrv4 | Classless Inter-Domain Routing CIDRv4 |
cidrv6 | Classless Inter-Domain Routing CIDRv6 |
datauri | Data Uniform Resource Locator |
fqdn | Fully Qualified Domain Name (FQDN) |
hostname | Hostname RFC 952 |
hostname_port | Xác thực trường thường được sử dụng cho địa chỉ socket <dns>:<port> |
hostname_rfc1123 | Hostname RFC 952 |
ip | Internet Protocol Address IP |
ip4_addr | Internet Protocol Address IPv4 |
ip6_addr | Internet Protocol Address IPv6 |
ip_addr | Internet Protocol Address IP |
ipv4 | Internet Protocol Address IPv4 |
ipv6 | Internet Protocol Address IPv6 |
mac | Media Access Control Address, còn gọi là địa chỉ LAN |
tcp4_addr | Transmission Control Protocol Address TCP4 |
tcp6_addr | Transmission Control Protocol Address TCPv6 |
tcp_addr | Transmission Control Protocol Address TCP |
udp4_addr | User Datagram Protocol Address UDPv4 |
udp6_addr | User Datagram Protocol Address UDPv6 |
udp_addr | User Datagram Protocol Address UDP |
unix_addr | Unix Domain Socket Endpoint Address |
uri | Uniform Resource Identifier |
url | Uniform Resource Locator |
url_encoded | Uniform Resource Identifier Encoding |
urn_rfc2141 | RFC 2141 Uniform Resource Name |
String
| Tag | Mô tả |
|---|---|
alpha | Xác thực giá trị trường hiện tại có phải là chữ cái hợp lệ không |
alphanum | Xác thực giá trị trường hiện tại có phải là chữ số hợp lệ không |
alphanumunicode | Xác thực giá trị trường hiện tại có phải là chữ số unicode hợp lệ không |
alphaunicode | Xác thực giá trị trường hiện tại có phải là chữ cái unicode hợp lệ không |
ascii | Xác thực giá trị trường có phải là ký tự ASCII hợp lệ không |
boolean | Xác thực giá trị trường hiện tại có phải là giá trị boolean hợp lệ hoặc có thể chuyển đổi an toàn sang boolean không |
contains | Xác thực giá trị trường có chứa văn bản được chỉ định trong param không |
containsany | Xác thực giá trị trường có chứa bất kỳ ký tự nào được chỉ định trong param không |
containsrune | Xác thực giá trị trường có chứa rune được chỉ định trong param không |
endsnotwith | Xác thực giá trị trường không kết thúc bằng văn bản được chỉ định trong param |
endswith | Xác thực giá trị trường kết thúc bằng văn bản được chỉ định trong param |
excludes | Xác thực giá trị trường không chứa văn bản được chỉ định trong param |
excludesall | Xác thực giá trị trường không chứa bất kỳ ký tự nào được chỉ định trong param |
excludesrune | Xác thực giá trị trường không chứa rune được chỉ định trong param |
lowercase | Xác thực giá trị trường hiện tại có phải là string chữ thường không |
multibyte | Xác thực giá trị trường có ký tự multi-byte không |
number | Xác thực giá trị trường hiện tại có phải là số hợp lệ không |
numeric | Xác thực giá trị trường hiện tại có phải là số hợp lệ không |
printascii | Xác thực giá trị trường có phải là ký tự ASCII có thể in hợp lệ không |
startsnotwith | Xác thực giá trị trường không bắt đầu bằng văn bản được chỉ định trong param |
startswith | Xác thực giá trị trường bắt đầu bằng văn bản được chỉ định trong param |
uppercase | Xác thực giá trị trường hiện tại có phải là string chữ hoa không |
Format
| Tag | Mô tả |
|---|---|
base64 | Base64 String |
base64url | Base64URL String |
bic | Xác thực giá trị trường hiện tại có phải là mã BIC hợp lệ (mã SWIFT) theo định nghĩa ISO 9362 không |
bcp47_language_tag | Xác thực giá trị trường hiện tại có phải là tag ngôn ngữ theo tiêu chuẩn BCP47 không |
btc_addr | Xác thực giá trị trường có phải là địa chỉ BTC hợp lệ không |
btc_addr_bech32 | Xác thực giá trị trường có phải là địa chỉ bech32 BTC hợp lệ không |
credit_card | Xác thực giá trị trường hiện tại có phải là số thẻ tín dụng hợp lệ không |
datetime | Xác thực giá trị trường hiện tại có phải là string ngày giờ hợp lệ không |
e164 | Xác thực giá trị trường hiện tại có phải là số điện thoại định dạng e.164 hợp lệ không |
email | Xác thực giá trị trường hiện tại có phải là địa chỉ email hợp lệ không |
eth_addr | Xác thực giá trị trường có phải là địa chỉ Ethereum hợp lệ không |
hexadecimal | Xác thực giá trị trường hiện tại có phải là hexadecimal hợp lệ không |
hexcolor | Xác thực giá trị trường hiện tại có phải là màu hexadecimal hợp lệ không |
hsl | Xác thực giá trị trường hiện tại có phải là màu HSL hợp lệ không |
hsla | Xác thực giá trị trường hiện tại có phải là màu HSLA hợp lệ không |
html | Xác thực giá trị trường hiện tại có phải là HTML hợp lệ không |
html_encoded | Xác thực giá trị trường hiện tại có phải là HTML encoding hợp lệ không |
isbn | Xác thực giá trị trường có phải là ISBN v10 hoặc v13 hợp lệ (Số sách tiêu chuẩn quốc tế) không |
isbn10 | Xác thực giá trị trường có phải là ISBN v10 hợp lệ (Số sách tiêu chuẩn quốc tế) không |
isbn13 | Xác thực giá trị trường có phải là ISBN v13 hợp lệ (Số sách tiêu chuẩn quốc tế) không |
iso3166_1_alpha2 | Xác thực giá trị trường hiện tại có phải là mã quốc gia iso3166-1 alpha-2 hợp lệ không |
iso3166_1_alpha3 | Xác thực giá trị trường hiện tại có phải là mã quốc gia iso3166-1 alpha-3 hợp lệ không |
iso3166_1_alpha_numeric | Xác thực giá trị trường hiện tại có phải là mã quốc gia chữ số iso3166-1 hợp lệ không |
iso3166_2 | Xác thực giá trị trường hiện tại có phải là mã vùng quốc gia hợp lệ (ISO 3166-2) không |
iso4217 | Xác thực giá trị trường hiện tại có phải là mã tiền tệ hợp lệ (ISO 4217) không |
json | Xác thực giá trị trường hiện tại có phải là string json hợp lệ không |
jwt | Xác thực giá trị trường hiện tại có phải là string JWT hợp lệ không |
latitude | Xác thực giá trị trường có phải là tọa độ vĩ độ hợp lệ không |
longitude | Xác thực giá trị trường có phải là tọa độ kinh độ hợp lệ không |
postcode_iso3166_alpha2 | Xác thực theo giá trị mã quốc gia trong iso 3166 alpha 2 |
postcode_iso3166_alpha2_field | Xác thực qua trường, trường này biểu thị giá trị mã quốc gia trong iso 3166 alpha 2 |
rgb | Xác thực giá trị trường hiện tại có phải là màu RGB hợp lệ không |
rgba | Xác thực giá trị trường hiện tại có phải là màu RGBA hợp lệ không |
ssn | Xác thực giá trị trường có phải là SSN hợp lệ không |
timezone | Xác thực giá trị trường hiện tại có phải là string múi giờ hợp lệ không |
uuid | Xác thực giá trị trường có phải là UUID hợp lệ bất kỳ phiên bản nào không |
uuid3 | Xác thực giá trị trường có phải là UUID v3 hợp lệ không |
uuid3_rfc4122 | Xác thực giá trị trường có phải là UUID v3 RFC4122 hợp lệ không |
uuid4 | Xác thực giá trị trường có phải là UUID v4 hợp lệ không |
uuid4_rfc4122 | Xác thực giá trị trường có phải là UUID v4 RFC4122 hợp lệ không |
uuid5 | Xác thực giá trị trường có phải là UUID v5 hợp lệ không |
uuid5_rfc4122 | Xác thực giá trị trường có phải là UUID v5 RFC4122 hợp lệ không |
uuid_rfc4122 | Xác thực giá trị trường có phải là UUID RFC4122 hợp lệ bất kỳ phiên bản nào không |
md4 | Xác thực giá trị trường có phải là MD4 hợp lệ không |
md5 | Xác thực giá trị trường có phải là MD5 hợp lệ không |
sha256 | Xác thực giá trị trường có phải là SHA256 hợp lệ không |
sha384 | Xác thực giá trị trường có phải là SHA384 hợp lệ không |
sha512 | Xác thực giá trị trường có phải là SHA512 hợp lệ không |
ripemd128 | Xác thực giá trị trường có phải là RIPEMD128 hợp lệ không |
ripemd160 | Xác thực giá trị trường có phải là RIPEMD160 hợp lệ không |
tiger128 | Xác thực giá trị trường có phải là TIGER128 hợp lệ không |
tiger160 | Xác thực giá trị trường có phải là TIGER160 hợp lệ không |
tiger192 | Xác thực giá trị trường có phải là TIGER192 hợp lệ không |
semver | Xác thực giá trị trường hiện tại có phải là phiên bản semver hợp lệ theo định nghĩa Semantic Versioning 2.0.0 không |
ulid | Xác thực giá trị trường có phải là ULID hợp lệ không |
So sánh
| Tag | Mô tả |
|---|---|
eq | Bằng |
gt | Lớn hơn |
gte | Lớn hơn hoặc bằng |
lt | Nhỏ hơn |
lte | Nhỏ hơn hoặc bằng |
ne | Không bằng |
Khác
| Tag | Mô tả |
|---|---|
dir | Thư mục file |
file | Đường dẫn file |
isdefault | Xác thực giá trị trường hiện tại có phải là giá trị tĩnh mặc định không |
len | Độ dài trường |
max | Giá trị lớn nhất |
min | Giá trị nhỏ nhất |
oneof | Có phải là một trong các giá trị liệt kê không |
omitempty | Nếu trường không được đặt, bỏ qua trường đó |
required | Giá trị bắt buộc |
required_if | Chỉ khi tất cả các trường được chỉ định khác bằng với giá trị được chỉ định, trường xác thực phải tồn tại và không rỗng |
required_unless | Trừ khi tất cả các trường được chỉ định khác bằng với giá trị được chỉ định, trường xác thực phải tồn tại và không rỗng |
required_with | Khi bất kỳ trường được chỉ định nào tồn tại, trường xác thực phải tồn tại và không rỗng |
required_with_all | Khi tất cả các trường được chỉ định đều tồn tại, trường xác thực phải tồn tại và không rỗng |
required_without | Khi bất kỳ trường được chỉ định nào không tồn tại, trường xác thực phải tồn tại và không rỗng |
required_without_all | Khi tất cả các trường được chỉ định đều không tồn tại, trường xác thực phải tồn tại và không rỗng |
excluded_if | Chỉ khi tất cả các trường được chỉ định khác bằng với giá trị được chỉ định, trường có thể không tồn tại hoặc rỗng |
excluded_unless | Trừ khi tất cả các trường được chỉ định khác bằng với giá trị được chỉ định, trường có thể không tồn tại hoặc rỗng |
excluded_with | Khi bất kỳ trường được chỉ định nào tồn tại, trường có thể không tồn tại hoặc rỗng |
excluded_with_all | Khi tất cả các trường được chỉ định đều tồn tại, trường có thể không tồn tại hoặc rỗng |
excluded_without | Khi bất kỳ trường được chỉ định nào không tồn tại, trường có thể không tồn tại hoặc rỗng |
excluded_without_all | Khi tất cả các trường được chỉ định đều không tồn tại, trường có thể không tồn tại hoặc rỗng |
unique | Xác thực mỗi giá trị arr, map, slice có phải là duy nhất không |
Alias
| Tag | Mô tả |
|---|---|
iscolor | hexcolor|rgb|rgba|hsl|hsla |
country_code | iso3166_1_alpha2|iso3166_1_alpha3|iso3166_1_alpha_numeric |
Toán tử
| Tag | Mô tả | Hex |
|---|---|---|
, | Toán tử AND, sử dụng nhiều tag xác thực, phải thỏa mãn tất cả điều kiện, không được có khoảng trắng giữa các dấu phẩy | 0x2c |
| | Toán tử OR, sử dụng nhiều tag xác thực, nhưng chỉ cần thỏa mãn một trong số đó | 0x7c |
- | Bỏ qua xác thực cho trường này | 0x2d |
= | Ký hiệu khớp tham số | 0x3d |
TIP
Khi muốn khớp toán tử trong quá trình xác thực trường, cần sử dụng dạng hex十六进制 utf8 để thay thế, ví dụ
filed string `validate:"contains=0x2c"`Sử dụng
Dưới đây sẽ giới thiệu một số cách sử dụng cơ bản của Validator cùng với một số ví dụ code.
Singleton
var validate *validator.ValidateKhi sử dụng, official khuyến nghị trong toàn bộ vòng đời chương trình, chỉ nên có một instance validator, điều này sẽ có lợi cho việc cache một số dữ liệu.
Tạo Validator
Khi sử dụng riêng Validator mà không tích hợp với các framework khác, cần tạo validator thủ công.
validate = validator.New()Xác thực Struct
func (v *Validate) Struct(s interface{}) errorMethod Struct dùng để xác thực tất cả các trường public của một struct, mặc định sẽ tự động xác thực struct lồng nhau, khi truyền giá trị không hợp lệ hoặc giá trị là nil, sẽ trả về InvalidValidationError, nếu lỗi xác thực thất bại thì trả về ValidationErrors.
Ví dụ
package validate
import (
"fmt"
"github.com/go-playground/validator/v10"
"testing"
)
type User struct {
Name string `validate:"contains=jack"` // Tên chứa jack
Age int `validate:"gte=18"` // Lớn hơn hoặc bằng 18 tuổi
Address string `validate:"endswith=市"` // Kết thúc bằng 市
}
func TestStruct(t *testing.T) {
validate := validator.New()
user := User{
Name: "jacklove",
Age: 17,
Address: "滔博市",
}
err := validate.Struct(user)
for _, err := range err.(validator.ValidationErrors) {
fmt.Println(err.Namespace()) // Namespace
fmt.Println(err.Field())
fmt.Println(err.StructNamespace())
fmt.Println(err.StructField())
fmt.Println(err.Tag())
fmt.Println(err.ActualTag())
fmt.Println(err.Kind())
fmt.Println(err.Type())
fmt.Println(err.Value())
fmt.Println(err.Param())
fmt.Println()
}
fmt.Println(err)
}Output
User.Age
Age
User.Age
Age
gte
gte
int
int
17
18
Key: 'User.Age' Error:Field validation for 'Age' failed on the 'gte' tagXác thực Map
func (v *Validate) ValidateMap(data map[string]interface{}, rules map[string]interface{}) map[string]interface{}Xác thực key-value thông qua tag map.
Ví dụ
func TestMap(t *testing.T) {
user := map[string]interface{}{
"name": "jak",
"age": 17,
"address": "滔博市",
}
rules := map[string]interface{}{
"name": "contains=jacklove",
"age": "gte=18",
"address": "endswith=市",
}
validate := validator.New()
validateMap := validate.ValidateMap(user, rules)
fmt.Println(validateMap)
}Output
map[age:Key: '' Error:Field validation for '' failed on the 'gte' tag name:Key: '' Error:Field validation for '' failed on the 'contains' tag]Xác thực Slice
Xác thực string slice, trước dive là tag xác thực cho slice, sau dive là tag xác thực cho giá trị trong slice, slice lồng nhau cũng tương tự, có bao nhiêu chiều thì dùng bấy nhiêu dive
func TestSlice1(t *testing.T) {
list := []string{"jack", "mike", "lisa", "golang"}
err := validator.New().Var(list, "max=5,dive,contains=a,min=5") // Độ dài slice tối đa là 5, phần tử phải chứa ký tự a, và độ dài tối thiểu là 5
fmt.Println(err)
}Output
Key: '[0]' Error:Field validation for '[0]' failed on the 'min' tag
Key: '[1]' Error:Field validation for '[1]' failed on the 'contains' tag
Key: '[2]' Error:Field validation for '[2]' failed on the 'min' tagXác thực struct cho từng user trong slice
func TestSlice(t *testing.T) {
userList := make([]User, 0)
user := User{
Name: "jacklove",
Age: 17,
Address: "滔博市",
}
userList = append(userList, user)
err := validator.New().Var(userList, "dive") // "dive" nghĩa là xác thực sâu, khi phần tử là struct, sẽ tự động xác thực struct
fmt.Println(err)
}Output
Key: '[0].Age' Error:Field validation for 'Age' failed on the 'gte' tagXác thực Variable
Khá đơn giản và dễ hiểu, không giải thích nhiều
Ví dụ 1
func TestVar(t *testing.T) {
name := "jack"
err := validator.New().Var(name, "max=5,contains=a,min=1,endswith=l") // Độ dài tối đa là 5, độ dài tối thiểu là 1, chứa ký tự a, kết thúc bằng ký tự l
fmt.Println(err)
}Output
Key: '' Error:Field validation for '' failed on the 'endswith' tagVí dụ 2
func TestVar1(t *testing.T) {
age := 18
err := validator.New().Var(age, "gte=19")
fmt.Println(err)
}Output
Key: '' Error:Field validation for '' failed on the 'gte' tagTIP
Method Var có thể xác thực các kiểu bao gồm struct, variable, slice, map, cần kết hợp hợp lý với tag dive để sử dụng.
Xác thực Field
Tham số của xác thực field không còn là kiểu cơ bản, mà là tên trường của struct, có thể là trường của chính nó, cũng có thể là trường của struct lồng nhau.
type Password struct {
FirstPassword string `validate:"eqfield=SecondPassword"` // Xác thực hai lần nhập mật khẩu có bằng nhau không
SecondPassword string
}
type RegisterUser struct {
Username string `validate:"necsfield=Password.FirstPassword"` // Khi đăng ký để đảm bảo an toàn, cấm mật khẩu và tên người dùng giống nhau
Password Password
}
func TestCrossStructFieldValidate(t *testing.T) {
validate = validator.New()
// Thất bại
fmt.Println(validate.Struct(RegisterUser{
Username: "gopher",
Password: Password{
FirstPassword: "gopher",
SecondPassword: "gophers",
},
}))
// Thành công
fmt.Println(validate.Struct(RegisterUser{
Username: "gophers",
Password: Password{
FirstPassword: "gopher",
SecondPassword: "gopher",
},
}))
}Output
Key: 'RegisterUser.Username' Error:Field validation for 'Username' failed on the 'necsfield' tag
Key: 'RegisterUser.Password.FirstPassword' Error:Field validation for 'FirstPassword' failed on the 'eqfield' tag
<nil>WARNING
Khi sử dụng xác thực field, khi trường hoặc struct được truyền vào như tham số Tag không tồn tại, sẽ được coi là xác thực thất bại, ví dụ:
type Password struct {
FirstPassword string `validate:"eqfield=SeconddPaswod"` // SeconddPaswod != SecondPassword
SecondPassword string
}Đối với lỗi chính tả như thế này, rất khó phát hiện, và khi xác thực cũng chỉ hiển thị dưới dạng không vượt qua, cần rất chú ý.
Nâng cao
Tiếp theo sẽ giải thích một số kỹ thuật sử dụng nâng cao và nhiều thao tác tùy chỉnh hơn.
Alias Tùy chỉnh
Trong một số trường hợp, đối với một trường có rất nhiều tag xác thực, khi bạn muốn tái sử dụng cho trường khác, bạn có thể sao chép dán, nhưng đây không phải là cách tốt nhất, cách tốt hơn là cải thiện tính tái sử dụng thông qua việc đăng ký alias, hãy xem ví dụ sau:
var validate *validator.Validate
const PERSON_NAME_RULES = "max=10,min=1,contains=jack"
func TestAlias(t *testing.T) {
validate = validator.New()
// Đăng ký alias
validate.RegisterAlias("namerules", PERSON_NAME_RULES)
type person struct {
FirstName string `validate:"namerules"` // Sử dụng alias
LastName string `validate:"namerules"`
}
err := validate.Struct(person{
FirstName: "",
LastName: "",
})
fmt.Println(err)
}Output
Key: 'person.FirstName' Error:Field validation for 'FirstName' failed on the 'namerules' tag
Key: 'person.LastName' Error:Field validation for 'LastName' failed on the 'namerules' tagHàm Xác thực Tùy chỉnh
Mặc dù các tag xác thực đi kèm với component đã đủ đáp ứng cho hầu hết các trường hợp cơ bản, nhưng đôi khi đối với một số nhu cầu đặc biệt cần tự định nghĩa logic, Validator cung cấp cho chúng ta các API liên quan để tùy chỉnh hàm xác thực. Hãy xem ví dụ sau:
func TestCustomValidate(t *testing.T) {
validate = validator.New()
fmt.Println(validate.RegisterValidation("is666", is666))
type Example struct {
Name string `validate:"is666"`
}
fmt.Println(validate.Struct(Example{Name: "777"}))
fmt.Println(validate.Struct(Example{Name: "666"}))
}
func is666(fl validator.FieldLevel) bool {
return fl.Field().String() == "666"
}Tạo một hàm để kiểm tra xem giá trị trường có bằng "666" không, và tag tương ứng của nó là is666, output như sau
<nil>
Key: 'Example.Name' Error:Field validation for 'Name' failed on the 'is666' tagTIP
Nếu tag đã đăng ký đã tồn tại, nó sẽ bị ghi đè bởi cái mới, nói cách khác có thể "viết lại" logic kiểm tra tag mặc định.
Hàm Xác thực Kiểu Tùy chỉnh
Hàm xác thực kiểu là dành riêng cho một kiểu nào đó, thường được sử dụng cho một số kiểu không phải kiểu cơ bản, tương tự cũng có thể ghi đè kiểm tra mặc định của kiểu cơ bản, hãy xem ví dụ sau:
type Address struct {
name string
}
func TestCustomTypeValidate(t *testing.T) {
validate = validator.New()
validate.RegisterCustomTypeFunc(ValidateAddress, Address{}) // Đăng ký hàm xác thực kiểu và kiểu tương ứng
type Example struct {
Address Address `validate:"required"`
}
fmt.Println(validate.Struct(Example{Address: Address{name: ""}}))
fmt.Println(validate.Struct(Example{Address: Address{name: "cn"}}))
}
func ValidateAddress(value reflect.Value) interface{} {
if address, ok := value.Interface().(Address); ok {
// Xử lý lỗi
if address.name == "" {
return address.name
}
return value // Trả về trường nghĩa là xác thực đúng
}
return nil
}Output
Key: 'Example.Address' Error:Field validation for 'Address' failed on the 'required' tag
<nil>TIP
Đồng thời đăng ký nhiều kiểu vào một hàm cũng tương tự như vậy
Hàm Xác thực Struct Tùy chỉnh
Sự khác biệt của hàm xác thực struct là ở chỗ, tham số của các hàm khác là trường, còn tham số của hàm này là struct, hãy xem ví dụ sau:
type People struct {
FirstName string
LastName string
}
func TestCustomStructLevel(t *testing.T) {
validate = validator.New()
validate.RegisterStructValidation(PeopleValidate, People{}) // Giống như đăng ký kiểu, có thể truyền vào không chỉ một loại struct
err := validate.Struct(People{
FirstName: "",
LastName: "",
})
fmt.Println(err)
}
func PeopleValidate(sl validator.StructLevel) {
people := sl.Current().Interface().(People)
if people.FirstName == "" || people.LastName == "" {
sl.ReportError(people.FirstName, "FirstName", "FirstName", "", "")
sl.ReportError(people.FirstName, "LastName", "LastName", "", "")
}
}Output
Key: 'People.FirstName' Error:Field validation for 'FirstName' failed on the '' tag
Key: 'People.LastName' Error:Field validation for 'LastName' failed on the '' tagĐa ngôn ngữ
Component translator
go get github.com/go-playground/universal-translatorComponent region
go get github.com/go-playground/localesNgôn ngữ mặc định của validator là tiếng Anh, mà khi chúng ta phát triển dự án, có thể sẽ sử dụng nhiều hơn một ngôn ngữ, lúc này chúng ta cần sử dụng component quốc tế hóa đa ngôn ngữ, hãy xem ví dụ sau:
import (
"fmt"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zh_trans "github.com/go-playground/validator/v10/translations/zh"
"reflect"
"testing"
)
type User struct {
Name string `validate:"contains=jack"` // Tên chứa jack
Age int `validate:"gte=18"` // Lớn hơn hoặc bằng 18 tuổi
Address string `validate:"endswith=市"` // Kết thúc bằng 市
}
var (
uni *ut.UniversalTranslator
validate *validator.Validate
)
func TestTranslate(t *testing.T) {
zh := zh.New()
// Đầu tiên là dự phòng, sau đó là các ngôn ngữ được hỗ trợ, có thể có nhiều
uni = ut.New(zh, zh)
// Ngôn ngữ ở đây thường có thể lấy từ Accept-Language trong header của http request
trans, found := uni.GetTranslator(zh.Locale())
validate = validator.New()
if found {
zh_trans.RegisterDefaultTranslations(validate, trans) // Đăng ký translator mặc định
}
err := validate.Struct(User{
Name: "",
Age: 0,
Address: "",
})
fmt.Println(err.(validator.ValidationErrors).Translate(trans))
}Output
map[User.Address:Address phải kết thúc bằng văn bản '市' User.Age:Age phải lớn hơn hoặc bằng 18 User.Name:Name phải chứa văn bản 'jack']Cũng có thể dịch riêng từng lỗi
for _, fieldError := range err.(validator.ValidationErrors) {
fmt.Println(fieldError.Translate(trans))
}Output
Name phải chứa văn bản 'jack'
Age phải lớn hơn hoặc bằng 18
Address phải kết thúc bằng văn bản '市'Có thể thấy giá trị trả về là một map, có thể thấy thông tin lỗi cơ bản đã được dịch, nhưng chưa đủ để đưa vào sử dụng, chúng ta cần tiếp tục làm đẹp thông tin lỗi để dễ dàng对接 với khách hàng hoặc frontend hơn.
type User struct {
Name string `validate:"contains=jack" label:"姓名"` // Tên chứa jack
Age int `validate:"gte=18" label:"年龄"` // Lớn hơn hoặc bằng 18 tuổi
Address string `validate:"endswith=市" label:"地址"` // Kết thúc bằng 市
Sex string `validate:"required" label:"性别"`
}Đầu tiên tùy chỉnh tag label, giá trị của nó là tên tiếng Trung của trường, sau đó đăng ký một TagNameFunc thông qua validator, tác dụng của nó là khi lấy tên trường hoặc thay thế tên gốc. Chú thích trên method Filed() string trong file errors.go nói rằng: "Tên trường với label name được ưu tiên hơn tên trường thực tế", vì vậy khi xảy ra lỗi sau này, có thể sử dụng tên tiếng Trung tùy chỉnh để thay thế từ tiếng Anh. TagNameFunc như sau:
// Chúng ta thêm một tag tùy chỉnh, tag này dùng để đặt tên tiếng Trung cho trường struct, nó sẽ thay thế tên trường gốc
func CustomTagNameFunc(field reflect.StructField) string {
label := field.Tag.Get("label")
if len(label) == 0 {
return field.Name
}
return label
}Cuối cùng đăng ký
validate.RegisterTagNameFunc(CustomTagNameFunc)Thực thi lại output
Tên phải chứa văn bản 'jack'
Tuổi phải lớn hơn hoặc bằng 18
Địa chỉ phải kết thúc bằng văn bản '市'Sau đó vẫn chưa đủ, vẫn chưa đủ để làm thông tin lỗi trả về cho frontend, chúng ta cần định dạng thành json hoặc bất kỳ định dạng nào phù hợp cho việc truyền tải tin nhắn, bạn có thể nghĩ đến việc serialize map thành json, đây là một giải pháp, nhưng bạn có thể nhận được kết quả như sau:
{
"User.地址": "Địa chỉ phải kết thúc bằng văn bản '市'",
"User.姓名": "Tên phải chứa văn bản 'back'",
"User.年龄": "Tuổi phải lớn hơn hoặc bằng 18",
"User.性别": "Giới tính là trường bắt buộc"
}Bằng cách xử lý key của map, nhận được kết quả như sau:
{
"地址": "Địa chỉ phải kết thúc bằng văn bản '市'",
"姓名": "Tên phải chứa văn bản 'back'",
"年龄": "Tuổi phải lớn hơn hoặc bằng 18",
"性别": "Giới tính là trường bắt buộc"
}Tuy nhiên không khuyến nghị trả về thông tin như trên cho frontend, chúng ta có thể xử lý thành một string để làm thông tin trả về
Tên phải chứa văn bản 'back', Tuổi phải lớn hơn hoặc bằng 18, Địa chỉ phải kết thúc bằng văn bản '市', Giới tính là trường bắt buộc,Code đầy đủ
import (
"fmt"
"github.com/go-playground/locales/zh"
ut "github.com/go-playground/universal-translator"
"github.com/go-playground/validator/v10"
zh_trans "github.com/go-playground/validator/v10/translations/zh"
"reflect"
"strings"
"testing"
)
type User struct {
Name string `validate:"contains=back" label:"姓名"` // Tên chứa jack
Age int `validate:"gte=18" label:"年龄"` // Lớn hơn hoặc bằng 18 tuổi
Address string `validate:"endswith=市" label:"地址"` // Kết thúc bằng 市
Sex string `validate:"required" label:"性别"`
}
var (
uni *ut.UniversalTranslator
validate *validator.Validate
)
// Chúng ta thêm một tag tùy chỉnh, tag này dùng để đặt tên tiếng Trung cho trường struct, nó sẽ thay thế tên trường gốc
func CustomTagNameFunc(field reflect.StructField) string {
label := field.Tag.Get("label")
if len(label) == 0 {
return field.Name
}
return label
}
func TestTranslate(t *testing.T) {
zh := zh.New()
uni = ut.New(zh, zh)
// Ngôn ngữ ở đây thường có thể lấy từ Accept-Language trong header của http request
trans, found := uni.GetTranslator(zh.Locale())
validate = validator.New()
if found {
zh_trans.RegisterDefaultTranslations(validate, trans) // Đăng ký translator mặc định
}
validate.RegisterTagNameFunc(CustomTagNameFunc)
err := validate.Struct(User{
Name: "",
Age: 0,
Address: "",
})
translate := errInfoFormat(err.(validator.ValidationErrors), trans)
fmt.Println(translate)
}
func errInfoFormat(errors validator.ValidationErrors, trans ut.Translator) string {
builder := strings.Builder{}
for _, err := range errors {
builder.WriteString(err.Translate(trans))
builder.WriteString(", ")
}
return builder.String()
}Cuối cùng, nếu cảm thấy thông tin lỗi quá lạnh lùng, muốn nhân tính hóa hơn, có thể viết lại thông báo lỗi của tag chỉ định, điều này cần sử dụng method RegisterTranslation, đồng thời cũng cần sử dụng hai loại hàm, lần lượt là RegisterTranslationsFunc chịu trách nhiệm đăng ký template dịch cho tag tương ứng, còn TranslationFunc thì chịu trách nhiệm xử lý template để có được nội dung dịch cuối cùng. Ở đây lấy required làm ví dụ:
func requiredOverrideRegister(ut ut.Translator) error { // Hàm này có tác dụng đăng ký template dịch
return ut.Add("required", "{} là một trường bắt buộc phải điền", true) // {} là placeholder, true nghĩa là ghi đè template đã có
}
func requiredOverrideTranslation(ut ut.Translator, fe validator.FieldError) string { // Hàm này chịu trách nhiệm dịch nội dung
t, _ := ut.T("required", fe.Field()) // Tham số có thể có nhiều, tùy thuộc vào số lượng placeholder trong template của tag tương ứng đã đăng ký
return t
}Cuối cùng đăng ký
validate.RegisterTranslation("required", trans, requiredOverrideRegister, requiredOverrideTranslation)Kết quả
Tên phải chứa văn bản 'back', Tuổi phải lớn hơn hoặc bằng 18, Địa chỉ phải kết thúc bằng văn bản '市', Giới tính là một trường bắt buộc phải điền,File Ngôn ngữ
Trên thực tế, việc đăng ký từng cái bằng code rất phức tạp, universal-translator cung cấp cách thức thông qua việc viết file cấu hình JSON để dịch: universal-translator/examples/full-with-files at master · go-playground/universal-translator (github.com)
func TestFilei18n(t *testing.T) {
validate = validator.New()
zh := zh.New()
universalTranslator := ut.New(zh, zh)
translator, _ := universalTranslator.GetTranslator(zh.Locale())
zh_trans.RegisterDefaultTranslations(validate, translator)
er := universalTranslator.Import(ut.FormatJSON, "./zh.json") // Khuyến nghị import sau khi đăng ký, như vậy mới có thể ghi đè tag gốc
if er != nil {
log.Fatal(er)
}
type Gopher struct {
Language string `validate:"required"`
}
err := validate.Struct(Gopher{
"",
})
fmt.Println(err.(validator.ValidationErrors).Translate(translator))
}File JSON
[
{
"locale": "zh",
"key": "required",
"trans": "Đây là một trường rất quan trọng{0}, bạn phải điền nó",
"override": true
}
]Output
map[Gopher.Language:Đây là một trường rất quan trọngLanguage, bạn phải điền nó]TIP
universal-translator có rất nhiều cạm bẫy khi sử dụng, nếu muốn ghi đè tag gốc, type và rule đều có thể không điền, vì trong cấu hình gốc cũng không điền, tốt nhất nên giữ nhất quán. Điền type gì, sẽ thêm cấu hình vào map tương ứng, nếu là Cardinal hoặc type khác và rule được cấu hình one之类的, thì cần cấu hình tương ứng trong locale mới có thể sử dụng bình thường, nếu không sẽ báo lỗi.
