分享一个 Golang 参数校验框架 jio

2018-12-02 14:29:07 +08:00
 faceair

在写 Golang 的 WebServer 时我就很难受,缺一个类似 Node.js 的 joi 的框架,有很多想写的规则写不出来。

在 Golang 社区中参数校验一般是用 https://github.com/go-playground/validator 或者 https://github.com/astaxie/beego/tree/develop/validation 这些类似的东西。

我在使用这类框架的时候遇到了两个问题:

  1. struct 的初始零值会干扰校验。比如 struct 上有个字段是 int,你在校验规则里写 required 的意思是期望客户端必须要传值上来,但是如果客户端传 0 上来校验就会失败,因为框架没法区分这个零值是客户端传上来的还是客户没写时反序列化默认赋的值。
  2. 拓展规则的语法难用。往往你要再很远的地方往 validator 对象上 register 一个 type,然后再在你的 struct 去用。如果这类规则是一次性的话,跟自己的代码逻辑离得的太远了可读性大大降低。

先给个 jio 的使用例子

package main

import (
    "errors"
    "io/ioutil"
    "net/http"

    "github.com/faceair/jio"
    "github.com/go-chi/chi"
)

func main() {
    r := chi.NewRouter()
    r.Route("/people", func(r chi.Router) {
        r.With(jio.ValidateBody(jio.Object().Keys(jio.K{
            "name": jio.String().Transform(func(ctx *jio.Context) {
                if ctx.Value != "faceair" {
                    ctx.Abort(errors.New("you are not faceair"))
                }
            }).Required(),
            "age":   jio.Number().Integer().Min(0).Max(100).Required(),
            "phone": jio.String().Regex(`^1[34578]\d{9}$`).Required(),
        }), jio.DefaultErrorHandler)).Post("/", func(w http.ResponseWriter, r *http.Request) {
            body, err := ioutil.ReadAll(r.Body)
            if err != nil {
                panic(err)
            }
            w.Header().Set("Content-Type", "application/json; charset=utf-8")
            w.WriteHeader( http.StatusOK)
            w.Write(body)
        })
    })
    http.ListenAndServe(":8080", r)
}

我是这么解决这些问题的。

  1. 既然 struct 缺少字段是否存在的信息,我们就不把 struct 的反序列化跟参数校验搞到一起。在反序列化之前对数据做校验,我选择把这个过程放到一个中间件里,让校验无法通过代码就无需再往下走。
  2. 支持在定义 schema 的时候直接拓展规则,上文中 name 这个字段我就直接拓展了一个规则。

同时 jio 还能给参数设置默认值、帮你做类型转换(string 转 array 转 int 等等)、能选择校验规则的顺序、能根据其他字段的数据选择不同的校验规则等等,我觉得 jio 灵活性比任何一个校验框架都要好(也包括 joi)。

更多的使用文档可以直接查看 https://github.com/faceair/jio/blob/master/README.zh.md 欢迎大家多多使用,可以给 star 鼓励,也可以给与一些反馈我好继续迭代改进。

6680 次点击
所在节点    分享创造
14 条回复
Cbdy
2018-12-02 15:37:15 +08:00
我写过一个 joi 风格的 Java 的,可以交流一下
https://github.com/cbdyzj/joi
但实际实践下来,参数校验最佳实践还是 jsr303
blless
2018-12-02 17:22:54 +08:00
为啥不用 tag 啊
bubuhere
2018-12-02 17:41:40 +08:00
已 Star
faceair
2018-12-02 17:42:30 +08:00
@blless #2 我在描述中不是说了吗?我觉得 tag 一点都不好用。
zn
2018-12-02 18:31:42 +08:00
这语法太啰嗦了,无法想象。
blless
2018-12-02 18:34:03 +08:00
想了想我自己也写过一个,不过还是用 tag,不过我没有 required 这个关键字,只有一个 default,没有 default 就是 required,序列化没有直接传入对象指针 encode,用 fastjson 直接判断是否存在字段…
所以 1 不是问题,2 如果你的框架很多地方要检验同一个扩展规则,也是要另外放一个地方写的。所以这些问题我觉得都不是 tag 问题,而且目前参数检验框架的问题
loading
2018-12-02 18:46:04 +08:00
为啥这个 go 代码看起来这么恶心!!
faceair
2018-12-02 19:03:43 +08:00
@blless #6 用 tag 可以解决 90% 的校验的问题吧。我可能是因为之前用过 joi 脑子里有些执念,另外还有一些骚操作想实现就自己重新造了一套。理性的说这些功能也不一定是人人都需要,比如:
1. 类型转换
字符串转布尔

字符串转数字

2. 根据其他字段选择校验规则

3. 业务层的逻辑校验用 Transform 也写进 jio 里,后面的业务逻辑里不用再关心数据对不对

🤔不过这些理论上有一部分也是可以自己写 parser 用 tag 实现,就是看到时候够不够像这么直观了。
只是一个轮子罢了,我自己可能有一些想法和思考,如果能碰上臭味相投的人就更好了 🤓
faceair
2018-12-02 19:06:48 +08:00
只是记得回复里不能用 markdown 贴代码,不过贴图上来以后这图片的大小处理也好恶心... 各位李姐万岁...
faceair
2018-12-02 19:13:29 +08:00
@loading #7
@zn #5

https://gist.github.com/faceair/58c60e768edb464ad550cc7f39c2950f

这么写会好受一点吗?剩下的要是觉得 schema 的定义也很啰嗦的话,那我这里也没有更好的办法了,设计就是这样的了...
reus
2018-12-02 21:50:18 +08:00
感觉太罗嗦了
Mitt
2018-12-04 01:19:11 +08:00
啰嗦但是有用,tag 是真的很难用,我一直想不通为什么要这么设计
layxy
2019-07-12 10:20:02 +08:00
go 上面没有一个好用的参数校验组件,都不能检查是 go 基本类型默认值还是调用者传的参数,有默认值的基本类型就没办法向调用者返回正确的错误信息,比如 A 调用 B 参数有一个布尔类型,该参数非必填,A 没有传该参数,B 接受反序列化结构体的时候该布尔类型会默认为 false,导致交互上的误导和错误,而且目前的校验框架是这样的,如果我传的参数和默认值一致也会认为我没传该参数,奇葩的逻辑和处理方式
faceair
2019-07-12 15:25:20 +08:00
@layxy jio 够使么?还是有别的场景用不了 jio ?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/513526

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX