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")
}
测试效果
正文开始
通过上面示例可以发现, 我们的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反射的一个实际应用