为接口做参数校验 | Go 语言编程之旅
Opened this issue · 26 comments
为接口做参数校验 | Go 语言编程之旅
2.5 为接口做参数校验 接下来我们将正式进行编码,在进行对应的业务模块开发时,第一步要考虑到的问题的就是如何进行入参校验,我们需要将整个项目,甚至整个团队的组件给定下来,形成一个通用规范,在今天本章节将核心介绍这一块,并完成标签模块的接口的入参校验。
2.5.1 validator 介绍 在本项目中我们将使用开源项目
是我看错了吗 errorf方法还没有再logger文件中定义
您好,文章写的非常棒!有个小问题,CountTagRequest
的tag为什么是binding:"max=100"
而不是validate:"max=100"
? 我看validator package里面的example都是用的validate
作为tag的key?
一楼的同学 补充一下代码即可
func (l *Logger) Error(v ...interface{}) {
l.Output(LevelError, fmt.Sprint(v...))
}
func (l *Logger) Errorf(format string, v ...interface{}) {
l.Output(LevelError, fmt.Sprintf(format, v...))
}
买了实体书,也在线看,里面用到了很多第三方包,不先看一下第三方包的手册就一直看下去会很懵逼
买了实体书,也在线看,里面用到了很多第三方包,不先看一下第三方包的手册就一直看下去会很懵逼
@willnotlazy 确实,可以边看边查了解一下。因为这本书的定位是进阶,因此不会一个个的介绍第三方包。
是我看错了吗 errorf方法还没有再logger文件中定义
@yzawudi 文章里面提到了,因为受限于篇幅所以没有一个个代码展现出来。你只需要自己补充完善就好,因为代码都是雷同的。
一楼的同学 补充一下代码即可
func (l *Logger) Error(v ...interface{}) { l.Output(LevelError, fmt.Sprint(v...)) } func (l *Logger) Errorf(format string, v ...interface{}) { l.Output(LevelError, fmt.Sprintf(format, v...)) }
@Ca2OH4 感谢答疑!
您好,文章写的非常棒!有个小问题,
CountTagRequest
的tag为什么是binding:"max=100"
而不是validate:"max=100"
? 我看validator package里面的example都是用的validate
作为tag的key?
@jiapengliu613 因为 binding 是 gin validator 的属性,validate 的 validator 的缺省。
translations.go break?
大佬 这个是啥情况了go16版本保证换成go14就没事
imported from implicitly required module
大佬 这个是啥情况了
go16版本报错
换成go14就没事
imported from implicitly required module
你好请问一个问题,就是我是初学者,没有什么项目经验,我的疑问就是,这么大的项目主要的掌握的是 对于路由分发(router.go) 路由方法(article.go) 全局变量(global/setting.go)中的内容编写么? 然后才是根据具体的需求去编写具体的模块么? 这段话我可能描述不清楚,更广泛的问题就是从这个项目中应该掌握什么? 感谢回答, 一路跌跌撞撞 代码慢慢跑通到这一节,但是对于内容的庞大感到吃力了 不知道如何去掌握,已经支持了实体书 感谢
response := app.NewResponse(c)
valid, errs := app.BindAndValid(c, ¶m)
if !valid {
global.Logger.Errorf("app.BindAndValid errs: %v", errs)
response.ToErrorResponse(errcode.InvalidParams.WithDetails(errs.Errors()...))
return
}
response.ToResponse(gin.H{})
response 相关的代码在哪?
建议命名上做好区分度,太相近了..
func (v ValidErrors) Error() string {
return strings.Join(v.Errors(), ",")
}
func (v ValidErrors) Errors() []string {
var errs []string
for _, err := range v {
errs = append(errs, err.Error())
}
return errs
}
2.5.5中的 global.Logger.Errorf("app.BindAndValid errs: %v", errs) 语句
缺少了一个输入,应该修改为 global.Logger.Errorf(c, "app.BindAndValid errs: %v", errs)
很早之前在 validator v10.4.1 的时候发现了一个问题,如果 RegisterDefaultTranslations
放在中间件里调用,如果多个用户同时访问的时候,后端很容易就挂掉,出现 panic:concurrent map read and map write
并且无法 recovery,使用 ab 做压力测试可复现这个问题。
后来我改在 BindAndValid
函数里初始化 RegisterDefaultTranslations
,之后半年多都没有问题。今天突然又出现了 panic:concurrent map read and map write
,更新到 v10.10.1 仍然无法通过测试,在错误日志中找到问题出现在 https://github.com/go-playground/validator/blob/master/validator_instance.go#L301 这个位置,并发的时候可能会同时访问到这个 map,进而导致 panic。
通过查阅 gin/binding 的代码:https://github.com/gin-gonic/gin/blob/2bde107686759098e2d64273bc79d1a0216a4500/binding/binding.go#L70,gin 在 package 内实例化了 validator。所以,如果在每个请求内都调用如下代码:
v, ok := binding.Validator.Engine().(*val.Validate)
v
的地址始终都是相同的。
再看 validator 的代码:https://github.com/go-playground/validator/blob/42525d89abaf198b1e377addede1aef4b6183fc1/validator_instance.go#L301 如果两次传入的 tag
是不一样的,就会造成在 panic:concurrent map read and map write
因此在并发的时候执行 _ = zhTranslations.RegisterDefaultTranslations(v, trans)
就会出现 panic:concurrent map read and map write
查阅 validator 的 issue 发现在之前 v9 版本的时候就有类似的问题 go-playground/validator#286 (comment)
建议在 package 的 init 函数内做初始化,然后我现在是这样初始化翻译器的:
var trans ut.Translator
func init() {
uni := ut.New(zh.New())
trans, _ = uni.GetTranslator("zh")
v, ok := binding.Validator.Engine().(*val.Validate)
if ok {
_ = zhTranslations.RegisterDefaultTranslations(v, trans)
}
}
func bindAndValid(c *gin.Context, target interface{}) bool {
// ...
}
这样改了之后,再通过 ab 或 python 做并发测试就不会爆 panic 了
最后示例接口TagList绑定的时候为啥重复定义,不是已经定义好了TagListRequest嘛
直接var param TagListRequest
非常同意 @0xJacky 的意见,validator 不需要在中间件中重复注册翻译配置,只需要在项目启动的时候注册一次就行了,另BindAndValid
方法中
var errs ValidErrors
err := c.ShouldBind(v)
if err != nil {
v := c.Value("trans")
trans, _ := v.(ut.Translator)
verrs, ok := err.(val.ValidationErrors)
if !ok {
return false, errs
}
当verrs, ok := err.(val.ValidationErrors)
中ok
为false时,返回的errs还是个空的ValidErrors
,这时外部调用并不知道实际的err到底是啥。
上面大佬说的panic:concurrent map read and map write的问题,实际项目中踩坑了
global.Logger.Errorf("app.BindAndValid errs: %v", errs) 得改成global.Logger.Errorf(c, "app.BindAndValid errs: %v", errs),新版本好想要加入ctx了都
这里是internal/service吗还是internal/model呢
curl " http://127.0.0.1:8000/api/v1/tags\?state=6" -Method 'GET'
这样写才会报出示例的错误消息