binding

binding 中间件为 Flame 实例提供请求数据绑定和验证服务,支持的数据格式包括 Form、Multipart Form、JSON 和 YAML。

你可以在 GitHubbinding - 图1在新窗口打开 上阅读该中间件的源码或通过 pkg.go.devbinding - 图2在新窗口打开 查看 API 文档。

下载安装

Go 语言的最低版本要求为 1.16

  1. go get github.com/flamego/binding

用法示例

提示

本小结仅展示 binding 中间件的相关用法,如需了解验证模块的用法请移步 validatorbinding - 图3在新窗口打开 的文档。

绑定对象自身会被注入到请求上下文中以供后续的处理器使用,并额外提供 binding.Errorsbinding - 图4在新窗口打开 用于错误传递。

警告

禁止传递绑定对象的指针以确保每个处理器都能够获得对象的全新副本,以及避免潜在的副作用而导致的意外错误。

Form

binding.Formbinding - 图5在新窗口打开 会将请求数据以 application/x-www-form-urlencoded 的编码格式将其解析到绑定对象上,binding.Optionsbinding - 图6在新窗口打开 可以被用于配置该函数的行为。

绑定对象内的字段需要使用结构体标签 form 来表示与请求数据之间的绑定关系:

  • main.go
  • templates/home.tmpl
  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/flamego/binding"
  6. "github.com/flamego/flamego"
  7. "github.com/flamego/template"
  8. "github.com/flamego/validator"
  9. )
  10. type User struct {
  11. FirstName string `form:"first_name" validate:"required"`
  12. LastName string `form:"last_name" validate:"required"`
  13. Age int `form:"age" validate:"gte=0,lte=130"`
  14. Email string `form:"email" validate:"required,email"`
  15. Hashtags []string `form:"hashtag"`
  16. }
  17. func main() {
  18. f := flamego.Classic()
  19. f.Use(template.Templater())
  20. f.Get("/", func(t template.Template) {
  21. t.HTML(http.StatusOK, "home")
  22. })
  23. f.Post("/", binding.Form(User{}), func(w http.ResponseWriter, form User, errs binding.Errors) {
  24. if len(errs) > 0 {
  25. var err error
  26. switch errs[0].Category {
  27. case binding.ErrorCategoryValidation:
  28. err = errs[0].Err.(validator.ValidationErrors)[0]
  29. default:
  30. err = errs[0].Err
  31. }
  32. w.WriteHeader(http.StatusBadRequest)
  33. _, _ = w.Write([]byte(fmt.Sprintf("Oops! Error occurred: %v", err)))
  34. return
  35. }
  36. w.WriteHeader(http.StatusOK)
  37. w.Write([]byte(fmt.Sprintf("User: %#+v", form)))
  38. })
  39. f.Run()
  40. }
  1. <form method="POST">
  2. <div>
  3. <label>First name:</label>
  4. <input type="text" name="first_name" value="John">
  5. </div>
  6. <div>
  7. <label>Last name:</label>
  8. <input type="text" name="last_name" value="Smith">
  9. </div>
  10. <div>
  11. <label>Age:</label>
  12. <input type="number" name="age" value="90">
  13. </div>
  14. <div>
  15. <label>Email:</label>
  16. <input type="email" name="email" value="john@example.com">
  17. </div>
  18. <div>
  19. <label>Hashtags:</label>
  20. <select name="hashtag" multiple>
  21. <option value="driver">Driver</option>
  22. <option value="developer">Developer</option>
  23. <option value="runner">Runner</option>
  24. </select>
  25. </div>
  26. <input type="submit" name="button" value="Submit">
  27. </form>

Multipart Form

binding.MultipartFormbinding - 图7在新窗口打开 会将请求数据以 multipart/form-data 的编码格式将其解析到绑定对象上,binding.Optionsbinding - 图8在新窗口打开 可以被用于配置该函数的行为。

绑定对象内的字段需要使用结构体标签 form 来表示与请求数据之间的绑定关系,用于存储上传文件的字段则必须声明为 *multipart.FileHeaderbinding - 图9在新窗口打开 类型:

  • main.go
  • templates/home.tmpl
  1. package main
  2. import (
  3. "fmt"
  4. "mime/multipart"
  5. "net/http"
  6. "github.com/flamego/binding"
  7. "github.com/flamego/flamego"
  8. "github.com/flamego/template"
  9. "github.com/flamego/validator"
  10. )
  11. type User struct {
  12. FirstName string `form:"first_name" validate:"required"`
  13. LastName string `form:"last_name" validate:"required"`
  14. Avatar *multipart.FileHeader `form:"avatar"`
  15. }
  16. func main() {
  17. f := flamego.Classic()
  18. f.Use(template.Templater())
  19. f.Get("/", func(t template.Template) {
  20. t.HTML(http.StatusOK, "home")
  21. })
  22. f.Post("/", binding.MultipartForm(User{}), func(w http.ResponseWriter, form User, errs binding.Errors) {
  23. if len(errs) > 0 {
  24. var err error
  25. switch errs[0].Category {
  26. case binding.ErrorCategoryValidation:
  27. err = errs[0].Err.(validator.ValidationErrors)[0]
  28. default:
  29. err = errs[0].Err
  30. }
  31. w.WriteHeader(http.StatusBadRequest)
  32. _, _ = w.Write([]byte(fmt.Sprintf("Oops! Error occurred: %v", err)))
  33. return
  34. }
  35. w.WriteHeader(http.StatusOK)
  36. w.Write([]byte(fmt.Sprintf("User: %#+v", form)))
  37. })
  38. f.Run()
  39. }
  1. <form enctype="multipart/form-data" method="POST">
  2. <div>
  3. <label>First name:</label>
  4. <input type="text" name="first_name" value="John">
  5. </div>
  6. <div>
  7. <label>Last name:</label>
  8. <input type="text" name="last_name" value="Smith">
  9. </div>
  10. <div>
  11. <label>Avatar:</label>
  12. <input type="file" name="avatar">
  13. </div>
  14. <input type="submit" name="button" value="Submit">
  15. </form>

JSON

binding.JSONbinding - 图10在新窗口打开 会将请求数据以 application/json 的编码格式将其解析到绑定对象上,binding.Optionsbinding - 图11在新窗口打开 可以被用于配置该函数的行为。

绑定对象内的字段需要使用结构体标签 json 来表示与请求数据之间的绑定关系:

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/flamego/binding"
  6. "github.com/flamego/flamego"
  7. "github.com/flamego/validator"
  8. )
  9. type Address struct {
  10. Street string `json:"street" validate:"required"`
  11. City string `json:"city" validate:"required"`
  12. Planet string `json:"planet" validate:"required"`
  13. Phone string `json:"phone" validate:"required"`
  14. }
  15. type User struct {
  16. FirstName string `json:"first_name" validate:"required"`
  17. LastName string `json:"last_name" validate:"required"`
  18. Age uint8 `json:"age" validate:"gte=0,lte=130"`
  19. Email string `json:"email" validate:"required,email"`
  20. Addresses []*Address `json:"addresses" validate:"required,dive,required"`
  21. }
  22. func main() {
  23. f := flamego.Classic()
  24. f.Post("/", binding.JSON(User{}), func(w http.ResponseWriter, form User, errs binding.Errors) {
  25. if len(errs) > 0 {
  26. var err error
  27. switch errs[0].Category {
  28. case binding.ErrorCategoryValidation:
  29. err = errs[0].Err.(validator.ValidationErrors)[0]
  30. default:
  31. err = errs[0].Err
  32. }
  33. w.WriteHeader(http.StatusBadRequest)
  34. _, _ = w.Write([]byte(fmt.Sprintf("Oops! Error occurred: %v", err)))
  35. return
  36. }
  37. w.WriteHeader(http.StatusOK)
  38. w.Write([]byte(fmt.Sprintf("User: %#+v", form)))
  39. })
  40. f.Run()
  41. }

YAML

binding.YAMLbinding - 图12在新窗口打开 会将请求数据以 application/yaml 的编码格式将其解析到绑定对象上,binding.Optionsbinding - 图13在新窗口打开 可以被用于配置该函数的行为。

绑定对象内的字段需要使用结构体标签 yaml 来表示与请求数据之间的绑定关系:

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/flamego/binding"
  6. "github.com/flamego/flamego"
  7. "github.com/flamego/validator"
  8. )
  9. type Address struct {
  10. Street string `yaml:"street" validate:"required"`
  11. City string `yaml:"city" validate:"required"`
  12. Planet string `yaml:"planet" validate:"required"`
  13. Phone string `yaml:"phone" validate:"required"`
  14. }
  15. type User struct {
  16. FirstName string `yaml:"first_name" validate:"required"`
  17. LastName string `yaml:"last_name" validate:"required"`
  18. Age uint8 `yaml:"age" validate:"gte=0,lte=130"`
  19. Email string `yaml:"email" validate:"required,email"`
  20. Addresses []*Address `yaml:"addresses" validate:"required,dive,required"`
  21. }
  22. func main() {
  23. f := flamego.Classic()
  24. f.Post("/", binding.YAML(User{}), func(w http.ResponseWriter, form User, errs binding.Errors) {
  25. if len(errs) > 0 {
  26. var err error
  27. switch errs[0].Category {
  28. case binding.ErrorCategoryValidation:
  29. err = errs[0].Err.(validator.ValidationErrors)[0]
  30. default:
  31. err = errs[0].Err
  32. }
  33. w.WriteHeader(http.StatusBadRequest)
  34. _, _ = w.Write([]byte(fmt.Sprintf("Oops! Error occurred: %v", err)))
  35. return
  36. }
  37. w.WriteHeader(http.StatusOK)
  38. w.Write([]byte(fmt.Sprintf("User: %#+v", form)))
  39. })
  40. f.Run()
  41. }

本地化验证错误消息

如果你的 Web 应用支持多语言,那么必然也希望能够提供本地化的错误消息给你的用户。

下面提供了一个可以在浏览器中把玩的示例来帮助你实现独居一格的本地化错误消息:

  • Directory
  • main.go
  • templates/home.tmpl
  • locale_en-US.ini
  • locale_zh-CN.ini
  1. $ tree .
  2. .
  3. ├── locales
  4. ├── locale_en-US.ini
  5. └── locale_zh-CN.ini
  6. ├── templates
  7. └── home.tmpl
  8. ├── go.mod
  9. ├── go.sum
  10. └── main.go
  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "github.com/flamego/binding"
  7. "github.com/flamego/flamego"
  8. "github.com/flamego/i18n"
  9. "github.com/flamego/template"
  10. "github.com/flamego/validator"
  11. )
  12. type User struct {
  13. FirstName string `form:"first_name" validate:"required"`
  14. LastName string `form:"last_name" validate:"required"`
  15. Age int `form:"age" validate:"gte=0,lte=130"`
  16. Email string `form:"email" validate:"required,email"`
  17. }
  18. func main() {
  19. f := flamego.Classic()
  20. f.Use(template.Templater())
  21. f.Use(i18n.I18n(
  22. i18n.Options{
  23. Languages: []i18n.Language{
  24. {Name: "en-US", Description: "English"},
  25. {Name: "zh-CN", Description: "简体中文"},
  26. },
  27. },
  28. ))
  29. f.Get("/", func(t template.Template) {
  30. t.HTML(http.StatusOK, "home")
  31. })
  32. f.Post("/", binding.Form(User{}), func(w http.ResponseWriter, form User, errs binding.Errors, l i18n.Locale) {
  33. if len(errs) > 0 {
  34. var err error
  35. switch errs[0].Category {
  36. case binding.ErrorCategoryValidation:
  37. verr := errs[0].Err.(validator.ValidationErrors)[0]
  38. name := l.Translate("field::" + verr.Namespace())
  39. param := verr.Param()
  40. var reason string
  41. if param == "" {
  42. reason = l.Translate("validation::" + verr.Tag())
  43. } else {
  44. reason = l.Translate("validation::"+verr.Tag(), verr.Param())
  45. }
  46. err = errors.New(name + reason)
  47. default:
  48. err = errs[0].Err
  49. }
  50. w.WriteHeader(http.StatusBadRequest)
  51. _, _ = w.Write([]byte(fmt.Sprintf("Oops! Error occurred: %v", err)))
  52. return
  53. }
  54. w.WriteHeader(http.StatusOK)
  55. w.Write([]byte(fmt.Sprintf("User: %#+v", form)))
  56. })
  57. f.Run()
  58. }
  1. <form method="POST">
  2. <div>
  3. <label>First name:</label>
  4. <input type="text" name="first_name" value="John">
  5. </div>
  6. <div>
  7. <label>Last name:</label>
  8. <input type="text" name="last_name" value="Smith">
  9. </div>
  10. <div>
  11. <label>Age:</label>
  12. <input type="number" name="age" value="90">
  13. </div>
  14. <div>
  15. <label>Email:</label>
  16. <input type="email" name="email" value="john@example.com">
  17. </div>
  18. <div>
  19. <label>Language:</label>
  20. <a href="?lang=en-US">English</a>,
  21. <a href="?lang=zh-CN">简体中文</a>
  22. </div>
  23. <input type="submit" name="button" value="Submit">
  24. </form>
  1. [field]
  2. User.FirstName = First name
  3. User.LastName = Last name
  4. User.Age = Age
  5. User.Email = Email
  6. [validation]
  7. required = ` cannot be empty`
  8. gte = ` must be greater than or equal to %s`
  9. lte = ` must be less than or equal to %s`
  10. email = ` must be an email address`
  1. [field]
  2. User.FirstName = 名字
  3. User.LastName = 姓氏
  4. User.Age = 年龄
  5. User.Email = 邮箱
  6. [validation]
  7. required = 不能为空
  8. gte = 必须大于或等于 %s
  9. lte = 必须小于或等于 %s
  10. email = 必须是一个电子邮箱地址