项目中gin封装
目录
背景
项目中使用gin框架,一般的注册路由和函数都这样写
func main() {
engine := gin.New()
engine.GET("/test",testHandler)
engine.Run("localhost:8080")
}
type TestReq struct {
Name string
}
func testHandler(ctx *gin.Context) {
// 解析参数
name:=ctx.Query("name")
testReq:=TestReq{Name: name}
// 检查参数
checkTestReq(testReq)
...
// 获取session
sessionId:=ctx.GetHeader("Session-Id")
...
}
在这里我们只写一些核心伪代码。 我们经常要再hanler里去解析参数,校验参数,获取登录者的session,这样才能校验登录拿到userId等操作。
每一个hanler里面基本都要写一套这样的东西,好烦! 当然我们可以把这一块封装一下,把解析参数、检查参数、获取session封装个函数,然后在各个handler调用一下不就好了吗? 可以,但是每次都调用一下也够的。
如果我们每次都只处理这样的函数就好了:
func MyTestHandler(ctx *gin.Context,userId int,req *TestReq) {
// req是已经过校验合法的
// 安心处理自己的逻辑
}
修改注册路由
我们首先要修改路由注册原始的方法。
func main() {
engine := gin.New()
engine.GET("/test",GenHandlerFunc(MyTestHandler))
engine.Run("localhost:8080")
}
func GenHandlerFunc(myHandlerFunc interface{}) gin.HandlerFunc {
return func(context *gin.Context) {
// 代码1处。调用myHandlerFunc
}
}
func MyTestHandler(ctx *gin.Context,userId int,req *TestReq) {
// req是已经过校验合法的
// 安心处理自己的逻辑
}
MyTestHandler函数是用着方便了,可是GenHandlerFunc可麻烦了。代码1处调用myHandlerFunc谈何容易。
以后有越来越来多的MyTestHandler,每个接口都有自己的req类型,怎么能动态填充千变万化的req参数呢?
封装
首先为了校验req参数是否合法,我们定义一个参数,每个req结构体都要实现该方法
type ValidateAble interface {
Valid() error
}
type TestReq struct {
Name string `form:"name"`
}
func (req *TestReq) Valid() error {
if req.Name==""{
return errors.New("name is empty")
}
return nil
}
func GenHandlerFunc(myHandlerFunc interface{}) gin.HandlerFunc {
return func(context *gin.Context) {
// 反射函数
controllerFuncType := reflect.TypeOf(myHandlerFunc)
if controllerFuncType.Kind() != reflect.Func {
panic( "controllerFunc not a func")
}
// 调用函数所需要的参数
args := make([]reflect.Value, 0)
args = append(args, reflect.ValueOf(context))
// 为了方便我们假设userId是10,开发中根据自己业务去取
args = append(args, reflect.ValueOf(10))
for i := 0; i < controllerFuncType.NumIn(); i++ {
inParam := controllerFuncType.In(i)
// 判断req是否实现了ValidateAble接口
if !inParam.Implements(reflect.TypeOf((*ValidateAble)(nil)).Elem()) {
continue
}
inParamVale := reflect.New(inParam.Elem())
req, ok := inParamVale.Interface().(ValidateAble)
if !ok {
panic(context.Request.URL.Path + " req convert requestutil.ValidateAble err")
}
err:=context.ShouldBind(req)
fmt.Println(req)
if err != nil {
fmt.Println(err)
}
err = req.Valid()
if err != nil {
context.JSON(404,gin.H{
"message":"参数错误",
})
return
}
args = append(args, reflect.ValueOf(req))
}
reflect.ValueOf(myHandlerFunc).Call(args)
}
}
其实核心代码是inParamVale := reflect.New(inParam.Elem())
我们看下reflect.New()方法
// New returns a Value representing a pointer to a new zero value
// for the specified type. That is, the returned Value's Type is PtrTo(typ).
func New(typ Type) Value {
if typ == nil {
panic("reflect: New(nil)")
}
t := typ.(*rtype)
ptr := unsafe_New(t)
fl := flag(Ptr)
return Value{t.ptrTo(), ptr, fl}
}
我们只要看注释就行了,写的很明白, 返回一个零值的结构体的指针。虽然我们不知道到底需要哪个类型的req,没关系。