diff --git a/go.mod b/go.mod index 5277137..80612d1 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,11 @@ require ( ) require ( + github.com/go-playground/form/v4 v4.2.0 + github.com/go-playground/locales v0.14.0 + github.com/go-playground/mold/v4 v4.2.0 + github.com/go-playground/universal-translator v0.18.0 + github.com/go-playground/validator/v10 v10.9.0 github.com/spf13/viper v1.10.1 gorm.io/driver/postgres v1.2.3 ) @@ -29,11 +34,13 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.3 // indirect github.com/klauspost/compress v1.13.6 // indirect - github.com/kr/pretty v0.2.0 // indirect + github.com/leodido/go-urn v1.2.1 // indirect github.com/magiconair/properties v1.8.5 // indirect github.com/mitchellh/mapstructure v1.4.3 // indirect github.com/pelletier/go-toml v1.9.4 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/segmentio/go-camelcase v0.0.0-20160726192923-7085f1e3c734 // indirect + github.com/segmentio/go-snakecase v1.2.0 // indirect github.com/spf13/afero v1.6.0 // indirect github.com/spf13/cast v1.4.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect @@ -47,7 +54,6 @@ require ( golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20211210111614-af8b64212486 // indirect golang.org/x/text v0.3.7 // indirect - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/ini.v1 v1.66.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 640c95d..d842fbf 100644 --- a/go.sum +++ b/go.sum @@ -93,6 +93,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -122,6 +123,18 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/form/v4 v4.2.0 h1:N1wh+Goz61e6w66vo8vJkQt+uwZSoLz50kZPJWR8eic= +github.com/go-playground/form/v4 v4.2.0/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIhMHjgvJiGo6K7U= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/mold/v4 v4.2.0 h1:y/lYVFY2YgyF8TfQSy9SjmRzRVWEDztJin0feXT5tJk= +github.com/go-playground/mold/v4 v4.2.0/go.mod h1:ZQbmiTbTsS+corctxBmsAE1mE88TsubHHJIENbk8vlc= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A= +github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -307,12 +320,17 @@ github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxv github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -356,6 +374,7 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -379,6 +398,9 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= @@ -386,6 +408,10 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sagikazarmark/crypt v0.4.0/go.mod h1:ALv2SRj7GxYV4HO9elxH9nS6M9gW+xDNxqmyJ6RfDFM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/segmentio/go-camelcase v0.0.0-20160726192923-7085f1e3c734 h1:Cpx2WLIv6fuPvaJAHNhYOgYzk/8RcJXu/8+mOrxf2KM= +github.com/segmentio/go-camelcase v0.0.0-20160726192923-7085f1e3c734/go.mod h1:hqVOMAwu+ekffC3Tvq5N1ljnXRrFKcaSjbCmQ8JgYaI= +github.com/segmentio/go-snakecase v1.2.0 h1:4cTmEjPGi03WmyAHWBjX53viTpBkn/z+4DO++fqYvpw= +github.com/segmentio/go-snakecase v1.2.0/go.mod h1:jk1miR5MS7Na32PZUykG89Arm+1BUSYhuGR6b7+hJto= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -877,8 +903,9 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= diff --git a/internal/pkg/form/conform.go b/internal/pkg/form/conform.go new file mode 100644 index 0000000..440a871 --- /dev/null +++ b/internal/pkg/form/conform.go @@ -0,0 +1,22 @@ +package form + +import ( + "context" + + "github.com/go-playground/mold/v4" + "github.com/go-playground/mold/v4/modifiers" +) + +var conform = NewConform() + +func NewConform() *mold.Transformer { + return modifiers.New() +} + +func ConformStruct(v interface{}) error { + return ConformStructWithContext(context.TODO(), v) +} + +func ConformStructWithContext(ctx context.Context, v interface{}) error { + return conform.Struct(ctx, v) +} diff --git a/internal/pkg/form/encoding.go b/internal/pkg/form/encoding.go new file mode 100644 index 0000000..47579bd --- /dev/null +++ b/internal/pkg/form/encoding.go @@ -0,0 +1,28 @@ +package form + +import ( + "net/url" + + "github.com/go-playground/form/v4" +) + +var ( + decoder = NewDecoder() + encoder = NewEncoder() +) + +func NewDecoder() *form.Decoder { + return form.NewDecoder() +} + +func Decode(values url.Values, v interface{}) error { + return decoder.Decode(v, values) +} + +func NewEncoder() *form.Encoder { + return form.NewEncoder() +} + +func Encode(v interface{}) (values url.Values, err error) { + return encoder.Encode(v) +} diff --git a/internal/pkg/form/form.go b/internal/pkg/form/form.go new file mode 100644 index 0000000..a494d2b --- /dev/null +++ b/internal/pkg/form/form.go @@ -0,0 +1,29 @@ +package form + +import "net/url" + +func SafeQuery(values url.Values, v interface{}) error { + if err := Decode(values, v); err != nil { + return err + } + + if err := ConformStruct(v); err != nil { + return err + } + + if err := ValidateStruct(v); err != nil { + return err + } + return nil +} + +func SafeStruct(v interface{}) error { + if err := ConformStruct(v); err != nil { + return err + } + + if err := ValidateStruct(v); err != nil { + return err + } + return nil +} diff --git a/internal/pkg/form/scrub.go b/internal/pkg/form/scrub.go new file mode 100644 index 0000000..f28cc66 --- /dev/null +++ b/internal/pkg/form/scrub.go @@ -0,0 +1,24 @@ +package form + +import ( + "context" + + "github.com/go-playground/mold/v4" + "github.com/go-playground/mold/v4/scrubbers" +) + +var ( + scrub = NewScrubbers() +) + +func NewScrubbers() *mold.Transformer { + return scrubbers.New() +} + +func ScrubStruct(v interface{}) error { + return ScrubStructWithContext(context.TODO(), v) +} + +func ScrubStructWithContext(ctx context.Context, v interface{}) error { + return scrub.Struct(ctx, v) +} diff --git a/internal/pkg/form/validate.go b/internal/pkg/form/validate.go new file mode 100644 index 0000000..84dceb4 --- /dev/null +++ b/internal/pkg/form/validate.go @@ -0,0 +1,121 @@ +package form + +import ( + "fmt" + "reflect" + "strings" + + "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" + + zhtrans "github.com/go-playground/validator/v10/translations/zh" + // entrans "github.com/go-playground/validator/v10/translations/en" +) + +var ( + translator = NewValidateTranslator() + validate = NewValidate() +) + +type FieldError struct { + Field string `json:"field"` + Error string `json:"error"` + Value string `json:"value"` + Raw validator.FieldError `json:"-"` +} + +func (err FieldError) String() string { + return fmt.Sprintf("%s:%s", err.Field, err.Error) +} + +type FieldsError []*FieldError + +func (errs FieldsError) Error() string { + var msg []string + for _, err := range errs { + msg = append(msg, err.String()) + } + return strings.Join(msg, ";") +} + +func init() { + // en := en.New() //英文翻译器 + // zh := zh.New() //中文翻译器 + + // // 第一个参数是必填,如果没有其他的语言设置,就用这第一个 + // // 后面的参数是支持多语言环境( + // // uni := ut.New(en, en) 也是可以的 + // // uni := ut.New(en, zh, tw) + // uni := ut.New(en, zh) + + // translator, ok := uni.GetTranslator("zh") //获取需要的语言 + // if !ok { + // panic("translator not found") + // } + + // validate := validator.New() + // validate.RegisterTagNameFunc(func(field reflect.StructField) string { + // label := field.Tag.Get("label") + // if label == "" { + // return field.Name + // } + // return label + // }) + + zhtrans.RegisterDefaultTranslations(validate, translator) +} + +func NewValidateTranslator() ut.Translator { + en := en.New() //英文翻译器 + zh := zh.New() //中文翻译器 + + // 第一个参数是必填,如果没有其他的语言设置,就用这第一个 + // 后面的参数是支持多语言环境( + // uni := ut.New(en, en) 也是可以的 + // uni := ut.New(en, zh, tw) + uni := ut.New(en, zh) + + var ok bool + trans, ok := uni.GetTranslator("zh") //获取需要的语言 + if !ok { + panic("form validation translator not found") + } + return trans +} + +func NewValidate() *validator.Validate { + v := validator.New() + v.RegisterTagNameFunc(func(field reflect.StructField) string { + label := field.Tag.Get("label") + if label == "" { + return field.Name + } + return label + }) + return v +} + +func ValidateStruct(v interface{}) FieldsError { + if err := validate.Struct(v); err != nil { + validationErrs, ok := err.(validator.ValidationErrors) + if ok { + var fieldsErr FieldsError + for _, ve := range validationErrs { + fieldsErr = append(fieldsErr, NewFieldError(ve, translator)) + } + return fieldsErr + } + } + return nil +} + +func NewFieldError(err validator.FieldError, unt ut.Translator) *FieldError { + return &FieldError{ + Field: err.StructField(), + Error: err.Translate(unt), + Raw: err, + Value: fmt.Sprintf("%v", err.Value()), + } +}