description
body反序列化与校验

elton中body-parser中间件只将数据读取为字节,并没有做反序列化以及参数的校验。使用json来反序列化时,只能简单的对参数类型做校验,下面介绍如何使用validatorgovalidator增强参数校验,可以按自己喜好选择合格的校验库。

下面的例子是用户登录功能,参数为账号与密码,两个参数的限制如下:

  • 账号:只允许为数字与字母,而且长度不能超过20位
  • 密码:只允许为数字与字母,而且长度不能小于6位,不能超过20位

validator

  1. package main
  2. import (
  3. "encoding/json"
  4. "reflect"
  5. "regexp"
  6. "github.com/go-playground/validator/v10"
  7. "github.com/vicanso/elton"
  8. "github.com/vicanso/elton/middleware"
  9. )
  10. var (
  11. validate = validator.New()
  12. )
  13. func toString(value reflect.Value) (string, bool) {
  14. if value.Kind() != reflect.String {
  15. return "", false
  16. }
  17. return value.String(), true
  18. }
  19. func init() {
  20. rxAlphanumeric := regexp.MustCompile("^[a-zA-Z0-9]+$")
  21. // 添加自定义参数校验,如果返回false则表示参数不符合
  22. validate.RegisterAlias("xAccount", "alphanum,max=20")
  23. _ = validate.RegisterValidation("xPassword", func(fl validator.FieldLevel) bool {
  24. value, ok := toString(fl.Field())
  25. if !ok {
  26. return false
  27. }
  28. if value == "" {
  29. return false
  30. }
  31. // 如果不是字母与数字
  32. if !rxAlphanumeric.MatchString(value) {
  33. return false
  34. }
  35. // 密码<=20而且>=6
  36. return len(value) <= 20 && len(value) >= 6
  37. })
  38. }
  39. type (
  40. loginParams struct {
  41. Account string `json:"account,omitempty" validate:"xAccount"`
  42. Password string `json:"password,omitempty" validate:"xPassword"`
  43. }
  44. )
  45. func doValidate(s interface{}, data interface{}) (err error) {
  46. // 如果有数据则做反序列化
  47. if data != nil {
  48. switch data := data.(type) {
  49. case []byte:
  50. err = json.Unmarshal(data, s)
  51. if err != nil {
  52. return
  53. }
  54. default:
  55. // 如果数据不是字节,则先序列化(有可能是map)
  56. buf, err := json.Marshal(data)
  57. if err != nil {
  58. return err
  59. }
  60. err = json.Unmarshal(buf, s)
  61. if err != nil {
  62. return err
  63. }
  64. }
  65. }
  66. err = validate.Struct(s)
  67. return
  68. }
  69. func main() {
  70. e := elton.New()
  71. e.Use(middleware.NewError(middleware.ErrorConfig{
  72. ResponseType: "json",
  73. }))
  74. e.Use(middleware.NewDefaultBodyParser())
  75. e.Use(middleware.NewDefaultResponder())
  76. e.POST("/users/login", func(c *elton.Context) (err error) {
  77. params := &loginParams{}
  78. err = doValidate(params, c.RequestBody)
  79. if err != nil {
  80. return
  81. }
  82. c.Body = params
  83. return
  84. })
  85. err := e.ListenAndServe(":3000")
  86. if err != nil {
  87. panic(err)
  88. }
  89. }

govalidator

  1. package main
  2. import (
  3. "encoding/json"
  4. "github.com/asaskevich/govalidator"
  5. "github.com/vicanso/elton"
  6. "github.com/vicanso/elton/middleware"
  7. )
  8. var (
  9. customTypeTagMap = govalidator.CustomTypeTagMap
  10. )
  11. func init() {
  12. // 添加自定义参数校验,如果返回false则表示参数不符合
  13. customTypeTagMap.Set("xAccount", func(i interface{}, _ interface{}) bool {
  14. v, ok := i.(string)
  15. if !ok || v == "" {
  16. return false
  17. }
  18. // 如果不是字母与数字
  19. if !govalidator.IsAlphanumeric(v) {
  20. return false
  21. }
  22. // 账号长度不能大于20
  23. if len(v) > 20 {
  24. return false
  25. }
  26. return true
  27. })
  28. customTypeTagMap.Set("xPassword", func(i interface{}, _ interface{}) bool {
  29. v, ok := i.(string)
  30. if !ok || v == "" {
  31. return false
  32. }
  33. // 如果不是字母与数字
  34. if !govalidator.IsAlphanumeric(v) {
  35. return false
  36. }
  37. // 密码长度不能大于20小于6
  38. if len(v) > 20 || len(v) < 6 {
  39. return false
  40. }
  41. return true
  42. })
  43. }
  44. type (
  45. loginParams struct {
  46. Account string `json:"account,omitempty" valid:"xAccount~账号只允许数字与字母且不能超过20位"`
  47. Password string `json:"password,omitempty" valid:"xPassword~密码只允许数字与字母且不能少于6位超过20位"`
  48. }
  49. )
  50. func doValidate(s interface{}, data interface{}) (err error) {
  51. // 如果有数据则做反序列化
  52. if data != nil {
  53. switch data := data.(type) {
  54. case []byte:
  55. err = json.Unmarshal(data, s)
  56. if err != nil {
  57. return
  58. }
  59. default:
  60. // 如果数据不是字节,则先序列化(有可能是map)
  61. buf, err := json.Marshal(data)
  62. if err != nil {
  63. return err
  64. }
  65. err = json.Unmarshal(buf, s)
  66. if err != nil {
  67. return err
  68. }
  69. }
  70. }
  71. _, err = govalidator.ValidateStruct(s)
  72. return
  73. }
  74. func main() {
  75. e := elton.New()
  76. e.Use(middleware.NewError(middleware.ErrorConfig{
  77. ResponseType: "json",
  78. }))
  79. e.Use(middleware.NewDefaultBodyParser())
  80. e.Use(middleware.NewDefaultResponder())
  81. e.POST("/users/login", func(c *elton.Context) (err error) {
  82. params := &loginParams{}
  83. err = doValidate(params, c.RequestBody)
  84. if err != nil {
  85. return
  86. }
  87. c.Body = params
  88. return
  89. })
  90. err := e.ListenAndServe(":3000")
  91. if err != nil {
  92. panic(err)
  93. }
  94. }

调用示例

  1. curl -XPOST -H 'Content-Type:application/json' -d '{"account":"treexie", "password": "123"}' 'http://127.0.0.1:3000/users/login'

从上面的代码中可以看到,可以自通过定义校验标签进行值校验(一般都是长度,大小,符合性的校验),而且大部分的校验都可复用常规校验函数,实现简单便捷。建议在实际项目中,针对每个不同的参数都自定义校验,尽可能保证参数的合法性。