美文网首页
beego自动路由优化

beego自动路由优化

作者: hijiang | 来源:发表于2019-05-23 17:57 被阅读0次

最近使用go,刚好需要用go搭建一个web服务,由于之前使用过是php ci框架来编写,习惯了php ci的按url路径来映射到具体的控制器,然后调用控制器里面的方法的方式;
这种方式好处是不需要修改一个集中式的控制代码。

PHP CI的路由方式

例如:如果php ci框架代码结构如下图:


微信图片_20190523172356.png

其中controllers就是存放控制器,如果控制器中有目录,框架会根据目录层次自动搜索控制器加载:
如:有一个文件是:
application\controllers\cultural\media\v1\Item.php
而里面有一个方法:

public function item_manager_page() {
            session_start();
            if(!isset($_SESSION['user'])) {
                header("location:".HOST_URL."/offical/login_page?show_login=1");
                return;
            }
            $user = $_SESSION['user'];
            $data['user'] = $user;
            
            $app = $_SESSION['app'];
            $db_config['hostname'] = $app['db_hostname'];
            $db_config['username'] = $app['db_username'];
            $db_config['password'] = $app['db_password'];
            $db_config['database'] = 'mydatabase';
            $db_config['dbdriver'] = 'mysqli';
            $db_config['port'] = 3306;
            $db_config['database'] = $app['db_name'];
            $data['app'] = $app;
            
            if(!isset($_REQUEST['pi'])) {
                $_REQUEST['pi'] = 1;
            }
            
            if(!isset($_REQUEST['pc'])) {
                $_REQUEST['pc'] = 10;
            }
            
            $pi = $_REQUEST['pi'];
            $pc = $_REQUEST['pc'];
            $data['pc'] = $pc;
            $data['pi'] = $pi;
            $this->load->model($app['type'].'/'.$app['subtype'].'/'.$app['vername'].'/'.'items_model','',$db_config);
            $items = $this->items_model->page_query(null, $pi, $pc);
            $data['items'] = $items;
            $this->load->view($app['type'].'/'.$app['subtype'].'/'.$app['vername'].'/admin/item_manager_page',$data);
}

那么通过:
http://www.xxx.com/cultural/media/v1/item/item_manager_page就可以访问这个页面。

beego的方式

首先,beego需要在router.go中注册:
beego.Router("/cultural/media/v1/item/item_manager_page",&controllers.ItemController{},"post:ItemManagerPage")
如果页面比较多,那么每次都要修改这个文件,如果我们希望做一个大型的系统,比如一个模板类型的网站,那么我们需要对控制器按规则存放,如有:培训,电影,编程等,而每个主类下有存放了各种分类别,如果培训下面存放:成人培训,英语培训,课程辅导类。
那么希望controllers的组织形式就是
controllers/train/english
controllers/train/adult
controllers/movie/happy
controllers/movie/sad
等。
当然beego有自动映射的方式,但是不支持深路径,感觉还是不是很方便,所以我稍微改造了下,让beego可以像php ci一样,按照url路径自动从so加载controllers的代码。主要是通过go的plugin加载so,然后在so内部固定一个获取控制器对象的函数

func GetController() interface{}

然后通过go的reflect映射得到它的函数列表,并和url路径中的最后一个路径做匹配,注册到router中即可:
主要代码如下:

type ControllerRegister struct {
    routers      map[string]*Tree
    enablePolicy bool
    policies     map[string]*Tree
    enableFilter bool
    so_routers   map[string]interface{} //这里添加上我们的自定义map,存放从so获取到的对象
    filters      [FinishRouter + 1][]*FilterRouter
    pool         sync.Pool
}

// NewControllerRegister returns a new ControllerRegister.
func NewControllerRegister() *ControllerRegister {
    return &ControllerRegister{
        routers:  make(map[string]*Tree),
        policies: make(map[string]*Tree),
        pool: sync.Pool{
            New: func() interface{} {
                return beecontext.NewContext()
            },
        },
        so_routers:make(map[string]interface{}),//添加初始化代码
    }
}

修改下面这个函数里面的代码:

func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request)

下面是修改的代码,读者自己去router.go中查找即可,修改如下:(主要就是如果路由没发现,就到so的目录查找):

// User can define RunController and RunMethod in filter
    if context.Input.RunController != nil && context.Input.RunMethod != "" {
        findRouter = true
        runMethod = context.Input.RunMethod
        runRouter = context.Input.RunController
    } else {
        routerInfo, findRouter = p.FindRouter(context)
    }
        //下面是自己加的查找代码,主要就是查找so,反射函数,注册,然后注册成功后重新查找
    if !findRouter {//这里进一步判断是否可以命中我们的so文件
        paths := strings.Split(r.URL.Path, "/")
        // fmt.Println("could not find path=", r.URL.Path, ", paths=", len(paths))
        if len(paths) >= 3 {
            key := strings.Join(paths[0:len(paths)-1], "/")
            methodName := strings.ToLower(paths[len(paths)-1])
            c, ok := p.so_routers[key]
            if !ok {//如果不存在,那么看看本地路径是否有
                //这里后续改为支持配置so的目录
                soPath := "so_controllers" + strings.Join(paths[0:len(paths)-1], "/") + ".so"
                so, err := plugin.Open(soPath)
                if err == nil {//加载so成功
                    getFun, err := so.Lookup("GetController")//获取返回对象的挂载函数
                    if err == nil {//存在
                        c := getFun.(func() interface{})()//创建控制器
                        rt := reflect.TypeOf(c)
                        methodNum := rt.NumMethod()
                        p.so_routers[key] = c //登记
                        for i := 0; i < methodNum; i++ {
                            name := strings.ToLower(rt.Method(i).Name)//全部转小写
                            if name == methodName {//找到方法,判断方法是否被注册
                                // 这里还需要加一个判断是否在PublicFunc中注册,否则不安全
                                p.Add(r.URL.Path, c.(ControllerInterface), r.Method+":"+rt.Method(i).Name)
                                routerInfo, findRouter = p.FindRouter(context)
                                break;
                            }
                        }
                    }   
                }
            } else {//已经加载过so,还是没找到路由,那么就在so里面再找下
                rt := reflect.TypeOf(c)
                methodNum := rt.NumMethod()
                for i := 0; i < methodNum; i++ {
                    name := strings.ToLower(rt.Method(i).Name)//全部转小写
                    if name == methodName {//找到方法,那么就注册
                        p.Add(r.URL.Path, c.(ControllerInterface), r.Method+":"+rt.Method(i).Name)
                        routerInfo, findRouter = p.FindRouter(context)
                        break;
                    }
                }
            }
        }
    }

这里还有些东西没处理,就是对方法的一些过滤,避免内部controller方法被注册,有些形如:
DelSession
Delete
CheckXSRFCookie
是内部方法,不应该注册,但是因为没有判断,就会本注册进去。
打算通过controller提供一个方法或者field,判断是否可以注册函数到url。
接下来,在so_controllers目录下,可以创建自己的控制器代码switch.go:

package main

import (
    "context"
    "net"
    "strconv"
    "time"
    "encoding/json"
    "git.apache.org/thrift.git/lib/go/thrift"
    "github.com/astaxie/beego"
)

type SwitchController struct {
    beego.Controller
    PublicFun map[string]string //用来判断方法是否可以被route安全注册,还没实现
}
//这个方法必须都有,用来提供给外部创建本对象使用
func GetController() interface{} {
    return &SwitchController{}
}

func (this *SwitchController) createThriftClient() (*streamswitch.LiveTypeSwitchClient,*thrift.TFramedTransport) {
    tsock, err := thrift.NewTSocketTimeout(net.JoinHostPort(beego.AppConfig.String("thrifthost"), beego.AppConfig.String("thriftport")), time.Duration(100)*time.Millisecond)
    if err != nil {
        return nil, nil
    }
    
    trans := thrift.NewTFramedTransport(tsock)
    if err != nil {
        return nil, nil
    }
    
    protocolFactory := thrift.NewTBinaryProtocolFactoryDefault()
    iprot := protocolFactory.GetProtocol(trans)
    oprot := protocolFactory.GetProtocol(trans)
    client := streamswitch.NewLiveTypeThriftClient(thrift.NewTStandardClient(iprot, oprot))
    if err := trans.Open(); err != nil {
        return nil, nil
    }

    return client, trans
}

func (this *SwitchController) SetGlobalType() {
    var param struct {LiveType int32}
    data := this.Ctx.Input.RequestBody
    //json数据
    err := json.Unmarshal(data, &param)
    if err != nil {
        this.Data["json"] = &struct{
                                Code int32
                                Msg string
                            }{Code:-1, Msg:"error param"}
        this.ServeJSON()
        return
    }
    live_type := param.LiveType
    //create thrift client
    client, trans := this.createThriftClient()
    if client == nil || trans == nil {
        this.Data["json"] = &struct{
                                Code int32
                                Msg string
                            }{Code:-2, Msg:"create thrift client failed"}
        this.ServeJSON()
        return
    }
    defer trans.Close()
    //request thrift
    ret, err := client.SetGlobalType(context.Background(), live_type)
    if err != nil {
        beego.Error(err.Error())
        this.Data["json"] = &struct{
                                Code int32
                                Msg string
                            }{Code:-5, Msg:"SetGlobalLiveType failed"}
        this.ServeJSON()
        return
    } else if ret != 0 {
        this.Data["json"] = &struct{
                                Code int32
                                Msg string
                            }{Code:-6, Msg:"SetGlobalLiveType failed, errcode=" + strconv.Itoa(int(ret))}
        this.ServeJSON()
        return
    }

    this.Data["json"] = &struct {Code int32}{0}
    this.ServeJSON()
    return
}

func (c *SwitchController) Index() {
    c.Data["hosturl"] = beego.AppConfig.String("hosturl")
    c.Data["indexurl"] = beego.AppConfig.String("indexurl")
    c.Data["apiurl"] = beego.AppConfig.String("apiurl")
    c.Data["menu"] = "设置"
    c.TplName = "index.html"
}

这个例子被我删了一些代码,你知道的,主要就是实现一个SwitchController,它有页面的接口Index,并且再Index中设置了一个变量menu(用来在加载模板后,模板中相应按钮做高亮,这就不说了);有一个cgi接口SetGlobalType
然后我们在so_controllers中编译:go build -buildmode=plugin switch.go,就会生成switch.so了
访问方式就是:
打开页面:http://www.xxxx.com/switch/index
调用cgi:http://www.xxxx.com/switch/setglobaltype
当然这种方式可能存在一些问题,编译出的so文件,可能会占用内存,做管理端应该还没问题,如果是其他的,需要再测试测试看看效果。
后续工作:
1、增加json配置文件,支持启动的时候就加载so注册,同时支持查找路由中动态加。
2、这种方式不足之处在在于对公共资源的访问不太友好,比如要做sql的连接池,每个so都隔离的,就没法做;解决方法:定义的接口函数传入一个公共模块对象类似:

type SwitchController struct {
    beego.Controller
    PublicFun map[string]string //用来判断方法是否可以被route安全注册,还没实现
    Common interface{}            //这里的类型还需要考虑下
}

//这个方法必须都有,用来提供给外部创建本对象使用
func GetController(common interface{}) interface{} {
    return &SwitchController{Common:common}
}

然后在成员函数中就可以使用这些公共的模块,如sql连接池,redis连接池,配置等。
初学go,有不足之处欢迎指正

相关文章

  • beego自动路由优化

    最近使用go,刚好需要用go搭建一个web服务,由于之前使用过是php ci框架来编写,习惯了php ci的按ur...

  • beego 路由详解

    beego路由设置 beego存在三种方式的路由:固定路由、正则路由、自动路由。下面就详细说一下如何使用这三种路由...

  • beego自动路由

    自动匹配 用户首先需要把需要路由的控制器注册到自动路由中: 那么 beego 就会通过反射获取该结构体中所有的实现...

  • beego之路由

    beego路由用法 路由文件 router.go 入口文件main.go中引用routers包 beego四种路由...

  • Beego自动化文档(最新版)

    之前写过一篇使用Beego自动化api文档的文章:Beego自动化文档,随着Beego的更新,1.7.0之后Bee...

  • beego的正则路由

    注解路由 从 beego 1.3 版本开始支持了注解路由 在router.go中注册路由 在controller中...

  • 2.2 Beego中路由的快速体验

    2.2 Beego中路由的快速体验 2.2.1路由的简单设置 路由的作用:根据不同的请求指定不同的控制器 路由函数...

  • beego路由配置

    原文 web框架中,路由是重要的一环,对于beego的路由配置如何?让我们从入口文件先分析起来吧: 我们看到 ma...

  • beego注解路由

    在router.go里面,用include注册路由 在方法上加注解 请求的url http://127.0.0.1...

  • beego传统路由

    beego.Router("/api/getbalance", &controllers.MainControll...

网友评论

      本文标题:beego自动路由优化

      本文链接:https://www.haomeiwen.com/subject/ovpbzqtx.html