【分享】我在rrestjs架构基础上扩展的内容
发布于 12 年前 作者 a272121742 6308 次浏览 最后一次编辑是 8 年前

哈哈,一直都想写点什么,但是时间总是紧张的,自己的工作问题也是让人头疼。最近在恶补Java以及学习Android的东西,希望多学习多进步吧。

今天就来讲讲我在rrestjs架构上增加的自己的功能。

关于rrestjs,您可以查看github或者关注老吴@snoopy

首先我不得不说rrestjs是个好架构,这不代表express就不好,只是我从Java这门语言转过来就不太习惯express的变成风格,也许今后会去尝试使用express去写点什么,但是现在希望的是能先把工作找到,以及能多在nodejs上和大家有所研究分享。这不,今天就是给大家分享这样一个好框架,以及我在利用这个框架的便利附加的新功能。

首先我们来看一下在rrestjs下构建的项目文件特点吧!

chacex        //我们的项目名
    |- com 
        |- chacex
            |- action       //控制层,主要业务逻辑
            |- aspect       //注入层,旁路业务逻辑
            |- config       //配置文件
            |- enum         //枚举常量
            |- model        //数据模型
            |- modules      //自启动模块
            |- schema       //数据验证模型
            |- test         //测试区域
            |- util         //工具集
    |- view
        |- cache            //rrestjs缓存存放文件夹
        |- download         //下载存放文件夹
        |- static           //静态资源存放文件夹
            |- css 
            |- html
            |- image
            |- js
            |- template         //前端模板存放处
        |- template         //后台模板存放处
        |- upload           //上传资源存放处
    |- app.js   //入口文件,其他的package.json文件你们懂得,我懒惰了

这并不是rrestjs所规定的结构,但是是rrestjs允许的可配置的结构。例如,rrestjs默认生成的是controller文件夹作为控制层,但我个人比较偏好Javaaction目录。rrestjs默认是将viewcontrollermodules同级存放,但我更希望comview分离开发。

其实以上的目录结构都非常好理解,相信多看几遍记忆更深。但关键我讲的不是这个,rrestjs架构的好处就是他定义了自动路由引擎,举例一个简单的例子,URL上的地址http://www.chacex.com/user/find.html?id=001,这个地址在后台会被识别为查找控制层(controller)下的user.js文件下,并使用find方法,传入id=001这个参数,虽然功能很小,但在Java中已经很接近自动路由引擎的概念了。我将其进行了修改,使得他能按照我定义的路径进行自动查找,rrestjs当然支持这么做。我的查找方式就是URL上的http://www.chacex.com/admin/user!find.html?id=001,这个地址会被识别到控制层(action)下的admin文件夹下的user.js,然后使用find方法并传入id=001这个参数。

我的action下的目录结构如下:

action      //控制层,主要业务逻辑
    |- admin    //admin管理系统
        |- user.js
            --> function find(req,res){...}
            --> function create(req,res){...}
            ...
        |- message.js
            --> function get(req,res){...}
            --> function delete(req,res){...}
            ...
        ...
    |- cmc      //cmc管理系统
        |- user.js
        |- message.js
        ...
    |- oa       //oa管理系统
        |- user.js
        |- message.js
        ...
    |- index.js     //这里定义路由规则

对于action目录下,很重要的应当属于index.js文件,我们知道,nodejs加载机制中,如果require引用的是一个文件夹,那么默认引用这个文件夹下的index.js或者index.node文件。看过rrestjs架构的都知道,控制层是由入口文件app.js去引用的,而对于app.js,我个人认为不应该在这里配置路由规则,他只需要单纯的一句话require('./com/chacex/action')即可,而路由规则则由action的index.js带为负责。换句话说,我们可以将控制层当做是一个模块,该模块中的index.js则应该是他自己的唯一配置文件,其他文件则是具体的实现。

谈到这样的方式,后面的aspect文件夹也将是这样的一个目录,不过我们还是继续谈action目录。我的这种配置方式你不用改写rrestjs路由配置规则(他默认是2级且不读取目录至少1.x之前是这样,后来不知道改没改),只需要自己手动重写一个即可。由这里,我们可以大致知道rrestjs实现的基本流程是:由app.js启动web服务,并监听端口,一旦有请求URL,就将其丢入引用的action去实现,而action如何解析URL则由aciton目录下的index.js去实现,被分配的每一个请求都会找到对应的文件夹下的文件的方法去执行。

下面该谈到aspect文件夹模块了。他的构造和action是一模一样的,如下:

aspect      //注入层,旁路业务逻辑
    |- admin    //admin管理系统
        |- user.js
            --> function find(req,res){...}
            --> function create(req,res){...}
            ...
        |- message.js
            --> function get(req,res){...}
            --> function delete(req,res){...}
            ...
        ...
    |- cmc      //cmc管理系统
        |- user.js
        |- message.js
        ...
    |- oa       //oa管理系统
        |- user.js
        |- message.js
        ...
    |- index.js     //这里定义注入规则

稍微不同的就是index.js文件夹里写的不再是路由规则了,而是注入规则。关于注入,各位可以去百度AOPIoCDI等,我只讲讲为什么我这个项目要用到注入。

action本身是处理业务逻辑的,一个请求对应肯定会找到某个文件夹下的某个文件的某个方法,该方法接受参数之后就开始处理,处理之后就要将处理结果以响应的方式返回给前端。看上去大多数都没觉得有问题,可是数据库的处理就是一个很大的问题。例如:数据验证、数据筛选、数据转换、存取数据等,这些如果都写在一个方法里面,试想这个方法该有多么的臃肿。而且其中数据验证、数据转换等我们都是可以按照业务逻辑和整体的设计架构进行封装以实现灵活和重用的。在使用nodejs进行前后端交互的时候,前段传送的数据虽然是json格式,但是值都是字符串。没有Java的OGNL的支持下,我们就只能自己去实现类似的表达式引擎,表达式引擎在Java中就是负责将数据转换为程序可以识别的代码或数据。既然要实现,就得封装,封装的东西在哪里,显然这是个很大的问题,如果你封装到了方法外但还是文件内,其他的文件无法复用,如果你抽离出来,其他的文件方法需要显示的调用,而我更希望数据的转换是他自动实现的。

为了解决这个问题,当然就采用了aspect文件模块。而如果将aspect模块注入到action中去,要归功于rrestjs本身提供的modules自启动模块。在rrestjs中,modules模块中存放的文件是可以在程序启动的时候跟着一起启动的,这个模块的名字你可以自己配置,哪些启动哪些不启动同样也是可以配置的。我所要做的就是让rrestjs在程序启动时,也启动aspect模块。aspect模块一旦启动,就会按照注入规则去匹配那些action中的方法。例如:aspect模块的admin模块下的user文件有个find方法,因此他会去找action模块下的admin模块下的user文件的find方法,如果没有也就不会执行注入,有的话,就会按照配置去注入。aspect下的index.js配置的是大规则,就是匹配aspectaction,二aspect里面的非index.js文件就是配置小规则,告诉程序是在什么时候对action进行注入。注入的方式有很多,我常用的就是前置注入和后置注入。前置注入就是在action的方法执行前拦截参数,后置注入就是在action方法执行完后拦截返回值。

前置注入为例子,就得说一下前置拦截的一个用例。大多数人都会采用mongodb作为配合nodejs开发或者学习的数据库。那么有多少人会知道,SQL数据库有注入,NoSQL其实也有注入,mongodb也是典型的有注入问题的。关于这问题,我在一个帖子中曾近回答过别人,但没有专门用帖子写出来,我这里也不做重点说明,只提示$符号在mongodb中是特殊符号,大家应该懂得。能注入,也就要处理,至少参数过来了,我就得想办法处理,aspect的前置注入就可以做这件事情。可以验证数据的安全性、匹配性,同时也可以对一些参数进行转换。经过这一层的过滤,基本可以保证进入action的数据是安全可靠的。当然你要拿更高端的技术跟我对抗我也没办法,毕竟我技术也有限,只能想到这么多提供给大家了,希望大家不要喷。

aspect的index.js还有一个作用,就是配置通用注入。例如分页,通常查询的时候都会使用list方法进行大数据查询,会传入一个分页参数json格式,带有一些分页信息是吧。其实分页的算法就那一个,真心不必要每一个文件的list方法都去处理一下,因此可以在index.js中写一个list注入方法,他会查找所有action中有无匹配list的方法,如果有则对其分页控件进行拦截。aspect甚至连方法名都可以按照正则进行匹配,我希望userlistadminlist都会被aspect的list注入,那么配置list注入到action的/list^/即可。关于这一点,拦截session*是非常有用的。

分离之后,action的代码就变得更加的干净,只有基本业务的处理,而数据的处理,不管是进来前还是出来后,都统统交给了aspect去处理。当然,基本业务处理的过程多了,也会出现代码臃肿,此时就要去优化业务,例如封装业务层、数据访问层等。我的项目中是将数据访问层和连接池封装在Dao中,他位于util文件夹下。

在以上的结构目录中,有一个schema不在rrestjs架构本身中定义,是我个人开发的JSV(JsonValidation)模块中需要的。因为数据验证、数据转换、数据筛选针对的数据都是JSON数据,因此我开发了一个JSV模块,schema存放的都是验证规则。该模块停滞状态,他更相当于Java中的表达式引擎,需要进一步研究之后再做改善,等改善好了我在发帖。

其实rrestjs中还有很多功能我都没用到,也许是需求还没到那里来吧。我仅仅只是在开发量巨大的情况下,在原有的rrestjs基础上扩展了一些我个人觉得很方便的分离方式。回顾之前讲的,无非就是:1.扩展了自动路由匹配引擎的规则,2.分离了action,由aspect处理action数据的处理。

好吧,以上就这么多,欢迎各位有做rrestjs的和我一起交流讨论。同时感谢老吴@snoppy能做出这么让我爽快的框架。

最后,我很喜欢nodejs,也很喜欢java,目前创业失败,找工作中,各位大大们求机会~

7 回复

顶。虽然没用过rrestjs。。

我使用的路由规则均在app.js中,定义如下:

jsGen.api = {};
jsGen.api.index = require('./api/index.js');
jsGen.api.user = require('./api/user.js');
jsGen.api.article = require('./api/article.js');
// 更多。。。

if (req.path[0] === 'api' && jsGen.api[req.path[1]]) {
    // /api/index/XXX,/api/user/XXX等请求均由此进入
    jsGen.api[req.path[1]][req.method.toUpperCase()](req, res, dm);
} else {
    //错误请求
}

下面是article.js中的路由:

function getFn(req, res, dm) {
    switch (req.path[2]) {
        case undefined:
        case 'index':
        case 'latest':
            return getLatest(req, res, dm);
        case 'hots':
            return getHots(req, res, dm);
        case 'update':
            return getUpdate(req, res, dm);
        case 'comment':
            return getHotComments(req, res, dm);
        default:
            return getArticle(req, res, dm);
    }
};

function postFn(req, res, dm) {
    switch (req.path[2]) {
        case undefined:
        case 'index':
            return addArticle(req, res, dm);
        case 'comment':
            return getComments(req, res, dm);
        default:
            return setArticle(req, res, dm);
    }
};

function deleteFn(req, res, dm) {
    return deleteArticle(req, res, dm);
};

module.exports = {
    GET: getFn,
    POST: postFn,
    DELETE: deleteFn
};

@zensh 那我把我的也贴出来以供分享吧!

以下是app.js

var rrest = require('rrestjs');
var http = require('http');
var action = require('./com/chacex/action');
http.createServer(function (req, res){
	action(req,res);
}).listen(rrest.config.listenPort);

以下是action下的index.js

module.exports = function(req, res) {
  try {
    var paths = req.url.substring(1).trim().split(/\/|!|\.|\?/);
    paths[2] = paths[2] || 'index';
    paths[1] = paths[1] || 'index';
    paths[0] = paths[0] || 'cmc';
    require('./' + paths[0] + '/' + paths[1])[paths[2]](req, res);
  } catch (err) {
    if (err instanceof AjaxError) {
      res.sendjson({
        err : err.errors
      });
    } else {
      res.statusCoe = 404;
      res.end('查无此页');
    }
    if (root.debug) {
      console.log(err.stack);
    }
  }
};

lz 工作找的怎么样了?那个chancexstudio到底是干啥的?

以前就是想做个uxd交互设计接单子,后来搞不了了就散了! 目前工作搞定,继续nodejs研究中,happy!感谢大家的关注

@a272121742 你好,关于你这个扩展部份的代码可以共享一份出来吗?请发邮件至tcrct@163.com,谢谢!

回到顶部