Golang中c.Bind()方法的意外行为解析
Golang中c.Bind()方法的意外行为解析 我想将数据绑定到的结构体是:
type Form_Eingabe struct {
Eingabe1 float64 `json:"eingabe1" form:"eingabe1" binding:"required,numeric,gt=0,max=1000000000"`
Eingabe2 float64 `json:"eingabe2" form:"eingabe2" binding:"required,numeric,gt=0,max=99"`
}
基本上一切正常,但我想检查我的应用程序,以防某些人可能发送错误信息。具体来说,就是发送一个**string** 而不是 float。
我的处理流程如下:
- c.Bind()
- 使用验证器进行验证
正如我所说,如果你发送浮点数值,一切正常,但如果你发送一个字符串,一切都会出错。
以下是一些示例:
示例 1 (正常工作)
输入:
- eingabe1 = 1000
- eingabe2 = 12.2
输出:
- eingabe1 = 1000
- eingabe2 = 12.2
示例 2 (完全中断)
输入:
- eingabe1 = “some string”
- eingabe2 = 12.2
输出:
- eingabe1 = 0
- eingabe2 = 0
示例 3 (部分中断)
输入:
- eingabe1 = 1000
- eingabe2 = “some string”
输出:
- eingabe1 = 1000
- eingabe2 = 0
根据这些数据(我经过调试和分析),c.Bind() 会在第一个错误发生后停止处理任何新数据。所以,如果我有 5 个值,错误发生在第一个,那么所有数据都是 0;如果错误发生在第三个,那么从第三个开始的所有数据都是 0,但之前的所有数据都会被设置。即使第 4 个和第 5 个值再次有效,它们也不会被设置!
错误值被设置为 0 我没有问题,但我有问题的是:
- 从错误发生点开始的所有值(无论是否有效)也都会被设置为 0。
- 没有为此输入设置任何错误,而只是触发了一个单一的一般性错误。
我的绑定和验证部分如下所示:
[...]
if err := c.Bind(&form); err != nil {
var ve validator.ValidationErrors
if errors.As(err, &ve) {
out = make([]Fehler_entry, len(ve))
for i, fe := range ve {
out[i] = Fehler_entry{fe.Field(), getErrorMsg(fe)}
F = append(F, out[i])
}
fmt.Println(F)
p = Messages{Debug: D, Fehler: F}
c.HTML(200, "main.html", p)
return
}
[...]
}
[...]
所以,如果有人填写了一个字符串,我只会得到这条消息:
strconv.ParseFloat: parsing "some string": invalid syntax,但没有任何信息表明是哪个字段触发了这个错误。
如果所有绑定都正常,只是验证失败,我会得到像这样的良好错误信息:
Key: 'Form_Eingabe.Eingabe1' Error:Field validation for 'Eingabe1' failed on the 'max' tag
这清楚地告诉我哪个字段以及为什么出错。
我添加了一个验证器 “numeric”,所以 int 和 float 都被接受,但由于验证发生在绑定之后,所以这个验证器永远不会运行。 有谁知道我如何修复这个问题,以便当一个字段绑定失败时,我能确切知道是哪一个,并且如何让它不中断,而是跳过这个字段并设置一个错误,最好与我从验证器本身得到的错误非常相似。
我知道这要求很多,但我已经花了整整两天时间尝试修复,却不知道该如何做。
更多关于Golang中c.Bind()方法的意外行为解析的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang中c.Bind()方法的意外行为解析的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
c.Bind() 在遇到类型转换错误时会停止处理后续字段,这是设计行为。要解决这个问题,需要使用 c.ShouldBind() 配合自定义绑定器。以下是具体实现:
import (
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10"
"strconv"
)
// 自定义绑定器
type CustomBinder struct {
binding.Binding
}
func (cb CustomBinder) Bind(c *gin.Context, obj interface{}) error {
// 先尝试标准绑定
if err := c.ShouldBind(obj); err != nil {
// 如果是类型转换错误,继续处理其他字段
if _, ok := err.(*strconv.NumError); ok {
// 手动处理表单数据
return cb.bindFormData(c, obj)
}
return err
}
return nil
}
func (cb CustomBinder) bindFormData(c *gin.Context, obj interface{}) error {
// 使用反射手动处理每个字段
v := reflect.ValueOf(obj).Elem()
t := v.Type()
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
fieldType := t.Field(i)
// 获取表单标签
formTag := fieldType.Tag.Get("form")
if formTag == "" {
continue
}
// 获取表单值
formValue := c.PostForm(formTag)
if formValue == "" {
continue
}
// 根据字段类型转换
switch field.Kind() {
case reflect.Float64:
if val, err := strconv.ParseFloat(formValue, 64); err == nil {
field.SetFloat(val)
} else {
// 记录错误但不中断
field.SetFloat(0)
}
// 添加其他类型处理...
}
}
return nil
}
// 使用自定义绑定器
func main() {
r := gin.Default()
// 注册自定义绑定器
binding.Validator = &CustomBinder{}
r.POST("/test", func(c *gin.Context) {
var form Form_Eingabe
// 使用自定义绑定
if err := c.ShouldBindWith(&form, &CustomBinder{}); err != nil {
// 处理错误
c.JSON(400, gin.H{"error": err.Error()})
return
}
// 手动验证
if err := validateForm(form); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
c.JSON(200, form)
})
r.Run()
}
// 手动验证函数
func validateForm(form Form_Eingabe) error {
validate := validator.New()
// 注册自定义验证器
validate.RegisterValidation("numeric", func(fl validator.FieldLevel) bool {
// 检查是否为数字
_, err := strconv.ParseFloat(fmt.Sprintf("%v", fl.Field()), 64)
return err == nil
})
return validate.Struct(form)
}
另一种更简洁的方法是使用中间件预处理数据:
func PreprocessFormData() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.Method == "POST" {
// 复制原始表单数据
c.Request.ParseForm()
formData := make(map[string]interface{})
for key, values := range c.Request.PostForm {
if len(values) > 0 {
// 尝试转换为float64
if val, err := strconv.ParseFloat(values[0], 64); err == nil {
formData[key] = val
} else {
// 记录转换错误
formData[key] = 0
// 存储错误信息
c.Set(key+"_error", "invalid number format")
}
}
}
// 将处理后的数据存储到上下文中
c.Set("processed_form", formData)
}
c.Next()
}
}
// 在路由中使用
r.Use(PreprocessFormData())
r.POST("/submit", func(c *gin.Context) {
var form Form_Eingabe
// 从上下文获取处理后的数据
if processed, exists := c.Get("processed_form"); exists {
formData := processed.(map[string]interface{})
// 手动绑定到结构体
if val, ok := formData["eingabe1"]; ok {
form.Eingabe1 = val.(float64)
}
if val, ok := formData["eingabe2"]; ok {
form.Eingabe2 = val.(float64)
}
// 检查错误
if err1, exists := c.Get("eingabe1_error"); exists {
// 处理字段特定错误
fmt.Println("Eingabe1 error:", err1)
}
}
// 继续验证
validate := validator.New()
if err := validate.Struct(form); err != nil {
// 处理验证错误
}
c.JSON(200, form)
})
对于验证错误信息的收集,可以这样实现:
type FieldError struct {
Field string `json:"field"`
Message string `json:"message"`
}
func CollectValidationErrors(err error) []FieldError {
var errors []FieldError
if validationErrors, ok := err.(validator.ValidationErrors); ok {
for _, fe := range validationErrors {
errors = append(errors, FieldError{
Field: fe.Field(),
Message: getErrorMessage(fe),
})
}
} else if numErr, ok := err.(*strconv.NumError); ok {
// 处理数字转换错误
errors = append(errors, FieldError{
Field: "unknown", // 需要从上下文中获取字段名
Message: fmt.Sprintf("Invalid number format: %s", numErr.Num),
})
}
return errors
}
这些方法可以解决字段绑定中断问题和错误信息不明确的问题。

