V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
serenader
V2EX  ›  Node.js

关于 Express 动态路由的问题

  •  
  •   serenader · 2014-07-17 00:40:25 +08:00 · 5056 次点击
    这是一个创建于 3790 天前的主题,其中的信息可能已经有所发展或是发生改变。
    楼主最近在用 Express 4.2.0 写一个项目,项目中有一个这样的需求:

    用户可以自定义后台控制面板的地址。比如说,初始化的后台地址是 http://example.com/admin 。用户在后台中可以修改成 http://example.com/newadmin 。修改提交完成之后自动跳转到新的后台地址。访问原来的地址会提示无法访问。

    我自己的思路是这样的,先在项目中新建一个 config.js 文件,把初始化数据存进去,初始化的后台地址为 /admin 。然后用户登录后台控制面板之后,可以提交新的后台地址,把这个新的后台地址存在数据库中。

    在项目的路由控制中,我是这样写的:

    var setting = require('../setting');
    var config = require('../config');
    setting.getSetting(function (err, s) {
    //判断数据库中是否有 admin_path 这个值,如果有的话将 app.locals.adminPath 赋值为其值,否则
    //赋值为 config 文件中的初始值。
    app.locals.adminPath = (s && s.admin_path) ? s.admin_path : config.admin_path;
    app.use(app.locals.adminPath, Router);
    });


    我这个思路从表面上看没什么问题,但是实际上行不通。实际的效果是,除非重新启动该项目,否则存储在数据库中的路径发挥不了作用。

    我稍微分析了一下原因,发现是因为当运行该 Express 项目时,app.use(app.locals.adminPath, Router) 这个命令只会运行一次。而不是一次请求运行一次。所以就算修改 app.locals.adminPath 的值也没用了。

    我试着在用户提交新的后台地址之后,把新的后台地址赋值给 app.locals.adminPath ,然后重新执行一次 app.use(app.locals.adminPath, Router) ,以为会覆盖之前的路径处理,但是结果不然。重新绑定路由已经没效果了。

    现在我的方案是使用 supervisor 运行该项目,然后当用户成功存储后台地址到数据库的时候强制结束该项目进程。然后 supervisor 自动将其重启。这是一种比较笨的方法。

    想问问各位大神,有没有更好的解决办法?
    21 条回复    2014-07-18 14:59:10 +08:00
    paloalto
        1
    paloalto  
       2014-07-17 01:31:36 +08:00
    是个可供学习的好案例,看楼下有没有好的解决办法。
    rankjie
        2
    rankjie  
       2014-07-17 02:19:22 +08:00 via iPhone   ❤️ 1
    简单点嘛,后面的 admin、newadmin 都拿来当URL参数处理不就好了

    app.get "/:admin_path", (req, res)->

    存个字典找下每个 admin_path 对应的用户,或者直接存数据库,从数据库里找不就可以了吗………

    然后在 /:admin_path 下面的路径,加个中间件把对应的用户放到 res.admin_user 就可以了

    比如

    app.get "/:admin_path/test", fetchUser, (req ,res)->


    不知道我对lz的意思理解的对不对
    liteneo
        3
    liteneo  
       2014-07-17 09:40:25 +08:00   ❤️ 1
    我觉得简单的做法就是301跳转,永久重定向到新的admin_path,这样所有的/admin路由都可以统一处理,根据当前用户设置的admin_path然后永久重定向过去,二楼每次都需要轮询代价有点大吧。。。
    serenader
        4
    serenader  
    OP
       2014-07-17 09:52:45 +08:00
    @rankjie 感谢回复。是的,你没有理解错我的意思。

    其实你的这个思路我之前也有想到过。在 StackOverflow 上面我也找到了类似这个问题的一个答案,其思路也是你这样的。

    但是其实在这个地址后面我还有很多路由请求要处理,比如说

    app.get('/admin/post')
    app.post('/admin/post')
    app.get('/admin/setting')
    app.post('/admin/setting')

    等等。如果采用你这种方法的话,那么,经过 /admin/ 这个路径的请求都要查询一次数据库,这对网站的性能有很大影响吧?

    另外不知道你有没有注意到,其实我是用

    app.use(app.locals.adminPath, Router)

    这样的方法处理路由请求的。这样的话,程序运行的时候只会在刚开始运行的时候查询一次数据库。另外这样也比较方便,比如我想要处理 get /admin/post 的话,我只需在 Router 中绑定这样的处理:

    Router.get('/post', function (req, res, next){});

    这样在 Router 中就完全不用理会这个 /admin/ 这个路径了。

    说了那么多,其实就是想表达,你说的这种方法会大量查询数据库,我个人觉得不是一个好办法。另外就是这种方法没办法用 app.use() 这个方法处理路由处理。


    不过现在想想,其实重启一次程序也不是一件什么坏事。毕竟在生产环境中一般都会使用 supervisor 或者 forever 去让它永久运行的。所以如果说没有更好的办法的话,我也只能用最初的这种方法了。
    serenader
        5
    serenader  
    OP
       2014-07-17 09:54:37 +08:00
    @liteneo 能否详细说说过程?
    jarlyyn
        6
    jarlyyn  
       2014-07-17 09:58:15 +08:00   ❤️ 1
    感觉应该前后台分离,然后后台修改后重启。
    jarlyyn
        7
    jarlyyn  
       2014-07-17 09:59:15 +08:00
    @serenader
    性能和数据库的问题,完全可以靠缓存解决吧?
    serenader
        8
    serenader  
    OP
       2014-07-17 10:03:21 +08:00
    @jarlyyn 前后台分离,什么意思呢?不太理解。

    其实我是不想每次都查询一次数据库。缓存没使用过,所以没这个打算呢。
    rekey
        9
    rekey  
       2014-07-17 10:05:03 +08:00
    更改了后台地址以后重启下 server,动态生成相关的 router,这个应该不难吧?
    jarlyyn
        10
    jarlyyn  
       2014-07-17 10:05:44 +08:00
    @serenader 作为两个进程来跑。
    写web这种重数据查询的,不考虑缓存,个人无法理解。
    个人认为,对于一般的web应用来说,最大的核心问题就是怎么做好缓存,或准备好以后怎么做缓存。
    serenader
        11
    serenader  
    OP
       2014-07-17 10:07:12 +08:00
    @rekey 现在我的方案就是更改之后就重启 server 。但是总觉得应该有个更优雅的方式,不用重启,达到这个目的。
    rekey
        12
    rekey  
       2014-07-17 10:09:39 +08:00
    @serenader 那你就绕不过缓存了。
    serenader
        13
    serenader  
    OP
       2014-07-17 10:09:57 +08:00
    @jarlyyn 谢谢回复。前后台用两个进程来跑 这个思路倒是没想过。可以试试。

    不考虑缓存是因为现阶段还没有达到需要用到缓存的这个阶段。以后应该会使用到的。
    rekey
        14
    rekey  
       2014-07-17 10:12:03 +08:00   ❤️ 1
    @serenader app.locals.adminPath 你使用这个变量,就已经是缓存了。
    serenader
        15
    serenader  
    OP
       2014-07-17 10:14:02 +08:00
    @rekey 嗯。看来得花时间研究一下怎么做缓存了。
    rekey
        16
    rekey  
       2014-07-17 10:17:22 +08:00
    @serenader 简单来说,你就是不想放弃 express router 的便利性。又想能够动态的处理 url,其实二楼的方案真的写的很清楚了。

    app.use() 其实已经可以直接处理掉一个 request 了。所以你认为 url 命中了以后可以直接调用相关的 router 处理。类似

    app.use(function(req, res, next){
    if(req.url){
    require('./router/admin')(req,res,next);
    }
    });
    serenader
        17
    serenader  
    OP
       2014-07-17 10:24:46 +08:00
    @rekey 感谢回复!之前没想到可以用你说的这种方法。待会试试。谢谢。
    gamexg
        18
    gamexg  
       2014-07-17 11:12:50 +08:00
    没用过 Express ,但是应该支持中间件。路由上固定为admin为管理地址。在中间件检测地址,如果是用户指定的管理地址则在中间件把地址修改为admin,之后路由还是一样处理。

    不知道 Express 有url生成吗?有的话还得处理url生成部分。
    rankjie
        19
    rankjie  
       2014-07-17 12:10:34 +08:00 via iPhone
    @liteneo
    这么简单的kv结构要轮循?


    @serenader
    你要是觉得 数据库效率低、Redis等级的缓存系统还没必要,那你就存个json作缓存呗,第一次运行的时候把全部用户和他们的admin_path都读出来,然后以admin_path为key,用户信息为value,这样应该解决你的问题了
    你想的这种方案居然要重启服务进程,这不是你阶段不阶段的问题,而是你整个架构都有问题,我觉得是有点邪门歪道,造了个三角形轮子
    lijinma
        20
    lijinma  
       2014-07-18 14:35:57 +08:00
    @serenader

    没看懂二楼 @rankjie 提供的解决方案有什么问题,我觉得能解决你的问题。

    你回答说“ 如果采用你这种方法的话,那么,经过 /admin/ 这个路径的请求都要查询一次数据库,这对网站的性能有很大影响吧?”

    能解释下,为什么这里是查数据库吗?
    favormm
        21
    favormm  
       2014-07-18 14:59:10 +08:00
    我也不明白,有人详细介绍一下吗?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1085 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 23:33 · PVG 07:33 · LAX 15:33 · JFK 18:33
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.