实现gin框架增强(ginplus)

337 阅读2分钟

gin框架增强, 实现路由自动注入

写在前面, 开篇之前, 我们先来看效果

安装

go get -u github.com/aide-cloud/gin-plus

使用

package main

import (
    "log"

    ginplush "github.com/aide-cloud/gin-plus"

    "github.com/gin-gonic/gin"
)

type People struct {
}

func (p *People) GetInfo() gin.HandlerFunc {
    return func(ctx *gin.Context) {
       ctx.String(200, "GetInfo")
    }
}

func (p *People) Middlewares() []gin.HandlerFunc {
    return []gin.HandlerFunc{
       func(context *gin.Context) {
          log.Println("middleware1")
       },
       func(context *gin.Context) {
          log.Println("middleware2")
       },
    }
}

func main() {
    r := gin.Default()
    ginInstance := ginplush.New(r, ginplush.WithControllers(&People{}))
    ginInstance.Run(":8080")
}

测试效果

image.png

正文开始

通过上面示例可以发现, 我们的People注册到ginplus, ginplus内部通过反射获取Controller名称和http方法, 然后把这些路由注册的到gin实例里面.

核心实现

  • 结构定义
type (
    GinEngine struct {
       *gin.Engine
       Controllers []Controller
    }

    Controller interface {
       Middlewares() []gin.HandlerFunc
    }

    Route struct {
       Path       string
       HttpMethod string
       Handles    []gin.HandlerFunc
    }

    Option func(*GinEngine)
)
  • 获取controller和method信息
func genRoute(controller any) []*Route {
    t := reflect.TypeOf(controller)
    var routes []*Route

    tmp := t
    for tmp.Kind() == reflect.Ptr {
       tmp = t.Elem()
    }

    if tmp.Kind() != reflect.Struct {
       panic(fmt.Errorf("controller is %s, not struct or pointer to struct", tmp.Kind().String()))
    }

    var middlewares []gin.HandlerFunc
    // Controller中的Middlewares方法返回的是gin.HandlerFunc类型的切片, 中间件
    for i := 0; i < t.NumMethod(); i++ {
       if t.Method(i).Name == "Middlewares" {
          middlewares = t.Method(i).Func.Call([]reflect.Value{reflect.ValueOf(controller)})[0].Interface().([]gin.HandlerFunc)
       }
    }

    for i := 0; i < t.NumMethod(); i++ {
       // 解析方法名称, 生成路由, 例如: GetInfoAction -> get /info  PostPeopleAction -> post /people
       // 通过反射获取方法的返回值类型
       if isHandlerFunc(t.Method(i).Type) {
          route := parseRoute(t.Method(i).Name)
          if route == nil {
             continue
          }
          route.Path = path.Join("/", routeToCamel(tmp.Name()), route.Path)
          route.Handles = append(route.Handles, t.Method(i).Func.Call([]reflect.Value{reflect.ValueOf(controller)})[0].Interface().(gin.HandlerFunc))
          route.Handles = append(append([]gin.HandlerFunc{}, middlewares...), route.Handles...)
          routes = append(routes, route)
       }

       // Controller中的Middlewares方法返回的是gin.HandlerFunc类型的切片, 中间件
       if t.Method(i).Name == "Middlewares" {

       }
    }

    return routes
}
  • 构建Route
// New returns a GinEngine instance.
func New(r *gin.Engine, opts ...Option) *GinEngine {
    instance := &GinEngine{Engine: r}
    for _, opt := range opts {
       opt(instance)
    }

    routes := make([]*Route, 0)
    for _, c := range instance.Controllers {
       routes = append(routes, genRoute(c)...)
    }

    for _, route := range routes {
       instance.Handle(route.HttpMethod, route.Path, route.Handles...)
    }

    return instance
}

这是该库核心实现, 通过反射获取controller名称作为一级路由, 方法(gin.HandlerFunc)名称http-method前缀作为请求方式, 后面部分(字符串转小驼峰)作为路由二级, 组装好这些参数后, 把这些路由通过Handle方法注册到gin实例中就可以了.

备注: gin Handle方法接受三个参数, 分别是请求方式, 路由, 请求触发的回调函数(如果是多个, 前面的为中间件, 最后一个才是Action)

通过完成Handle所需参数的构建, 然后一次性注入到gin实例中, 这样我们就完成了gin 框架的增强版

最后

实现很简单, 感兴趣的同学可以看看实现的源码. 其实还可以在这基础增加更多的功能, 例如basePath、路由嵌套等等, 这些后面再慢慢补充, 目前完成v0.0.1版本供大家参考, 算是对go反射的一个实际应用