个人NODEJS REST 轻量级框架 源码已共享
发布于 5 年前 作者 solqkwgkwg 7001 次浏览 最后一次编辑是 3 年前

作者 : solq

blog : http://www.cnblogs.com/solq/p/3574640.html

整个框架只有一个核心文件,两个配置文件,够轻了吧

1.只需要添加 ws Controller 文件就行,启动自动注入 Controller

2.path 路径参数,请求参数自动注入

3.请求参数格式转换,可扩展转换类型

4.容器变量字段自动注入

5.容器初始化执行

反正是就仿spring 那套 个人觉得已秒杀一切了哈哈

源码下载 : http://files.cnblogs.com/solq/solq_nodejsapp.rar

使用方式 =============================================================

1.controller 自动注入请求参数示例:

‘/testparam’:{ auth : [],//权限 methods : [‘GET’], controller : function(param_p2,param_p1,body_xxx){ console.log(“testparam==”,arguments); } } 以param_为标识自动注入

如 url 请求

http://xxxxx:port/testparam?p1=111&p2=2222

拦截器会自动将 p1,p2注入 param_p1,param_p2

body_ 标识会注入 body_xxx={p1:111,p2:2222}

2.path 参数注入变量

‘/testpathParam/{p1}/{p2}’:{ auth : [],//权限 methods : [‘GET’], controller : function(path_int_p2,path_p1,req, res){ console.log(“testpathParam”,arguments); } }

如 url 请求

http://xxxxx:port/testpathParam/11111/22222

path_(var)标识 (var) 会自动替换路径上的 {var}

req,res 会自动注入的

3.容器字段自动注入 : auto_ 标识

module.exports = { f1 :‘this f1’, auto_xxx:null, auto_ip:null, postConstruct : function(){ console.log(‘postConstruct’,this.auto_xxx,this.auto_ip.id); }, preDestroy : function(){ console.log(‘preDestroy’); } }; 每个容器都有自己的ID 属性,如没有会以文件名为ID

auto_(id)

当完成所有扫描容器时,再遍历查找所有容器,根据 auto_ 标识拦载 注入,然后初始化容器 postConstruct

下面是实现代码

var _injection = function(filePath,container){ var id = container.id; if(id == null){ id =_path.basename(filePath).replace(’.js’,’’); container.id = id; } container.filePath = filePath ; AppContext[id]!=null && _error(" injection key is has : " ,id); AppContext[id] = container; }

var _auto_injection_field = function(){ for(var id in AppContext){ var container = AppContext[id];

    for(var field in container){
        if(field.indexOf('auto_')>-1){
        
            var checkFn = container[field];
            if(typeof checkFn == 'function'){
                continue;
            }
            var injectionKey = field.replace('auto_','');
            
            if(AppContext[injectionKey]==null){
                _error('injection field not find id : ',field, id );
            }else{
                container[field] = AppContext[injectionKey]; 
                debug("injection field : ",injectionKey,id )
            } 
        } 
    } 
}

}

var _postConstruct = function(){ for(var id in AppContext){ var container = AppContext[id]; container.postConstruct!=null && container.postConstruct(); } }

先晒晒代码 ==================================

config.js

1 /** 2 * @author solq 3 * @deprecated blog: cnblogs.com/solq 4 * */ 5 var appConfig={ 6 /db config/ 7 dbHost : ‘127.0.0.1’, 8 dbPort : 9977 , 9 dbUser : ‘’, 10 dbPasssWord : ‘’, 11 12 /debug config/ 13 LOGDEBUG : 1, 14 LOGINFO : 2, 15 LOGERROR : 4, 16 LOGLEVEL : (1 | 4), 17 18 /web config/ 19 webPort : 5555, 20 21 /auto scan config/ 22 scanResource : { 23 ‘./resource’ : { 24 //include : [], 25 //exclude : [’./ws/test/test1.js’] 26 } 27 }, 28 scanService : { 29 ‘./service’ : { 30 //include : [], 31 //exclude : [’./ws/test/test1.js’] 32 } 33 }, 34 scanController : { 35 ‘./ws’ : { 36 //include : [], 37 exclude : [’./ws/test/test1.js’] 38 } 39 } 40 }; 41 42 module.exports = appConfig;

debug.js

View Code 主入口

app.js

/**

  • @author solq
  • @deprecated blog: cnblogs.com/solq
  • */ var http = require(“http”), fs = require(“fs”), _path = require(“path”), appConfig = require(’./config.js’), debug = require(’./core/debug.js’).debug, _error = require(’./core/debug.js’).error, requestController = require(’./core/RequestController.js’), AppContext={};

//处理功能方法 //cp google var walk = function(dir,suffix) { var results = [] var list = fs.readdirSync(dir) list.forEach(function(file) { file = dir + ‘/’ + file var stat = fs.statSync(file) if (stat && stat.isDirectory()){ results = results.concat(walk(file,suffix)) } else { if(suffix!=null){ if(suffix!=_path.extname(file)){

            }
        }
        results.push(file)
    }
})
return results

}

var scanProcess = function(scanConfig,suffix,callFn){ for(var dir in scanConfig){ var files = walk(dir,suffix), obj = scanConfig[dir];

    for(var i in files){
        var filePath=files[i];
        
        if(obj.include!=null){
            if(obj.include.indexOf(filePath)<0){
                continue;
            }
        }
        
        if(obj.exclude!=null){
            if(obj.exclude.indexOf(filePath)>-1){
                continue;
            }
        }
        callFn(filePath); 
    }
} 

} var _injection = function(filePath,container){ var id = container.id; if(id == null){ id =_path.basename(filePath).replace(’.js’,’’); container.id = id; } container.filePath = filePath ; AppContext[id]!=null && _error(" injection key is has : " ,id); AppContext[id] = container; }

var _auto_injection_field = function(){ for(var id in AppContext){ var container = AppContext[id];

    for(var field in container){
        if(field.indexOf('auto_')>-1){
        
            var checkFn = container[field];
            if(typeof checkFn == 'function'){
                continue;
            }
            var injectionKey = field.replace('auto_','');
            
            if(AppContext[injectionKey]==null){
                _error('injection field not find id : ',field, id );
            }else{
                container[field] = AppContext[injectionKey]; 
                debug("injection field : ",injectionKey,id )
            } 
        } 
    } 
}

}

var _postConstruct = function(){ for(var id in AppContext){ var container = AppContext[id]; container.postConstruct!=null && container.postConstruct(); } }

//IOC 控制流程 // scanResource>scanService>scanController // end scan auto injection field // run postConstruct

// scanResource debug( “injection service : =============================================”); scanProcess(appConfig.scanResource,null,function(filePath){ var resource=require(filePath); _injection(filePath,resource); debug( "injection resource : ", filePath); }); debug( “end injection service : =========================================”);

// scanService debug( “injection service : =============================================”); scanProcess(appConfig.scanService,’.js’,function(filePath){ var service=require(filePath); _injection(filePath,service); debug( "injection service : ", filePath); }); debug( “end injection service : =========================================”);

//scanController debug( “injection controller : =============================================”); scanProcess(appConfig.scanController,’.js’,function(filePath){ var controller=require(filePath); _injection(filePath,controller); requestController.add(controller); debug( "injection controller : ", filePath); }); debug( “end injection controller : =========================================”);

debug( “injection field : =============================================”); _auto_injection_field(); debug( “end injection field : =============================================”); //postConstruct _postConstruct();

http.createServer(function(request, response) {

var result=null;
try{
     result=requestController.filter(request, response,AppContext);
}catch(e){ 
    response.writeHead(500, {"Content-Type": "text/plain;charset=utf-8"});
    response.write(e);
    response.end();
    return;
}

response.writeHead(200, {"Content-Type": "text/plain;charset=utf-8"});

if(result!=null){

    if(typeof result == 'string'){
        response.write(result);
    }else if(typeof result == 'object' 
        || Array.isArray(result)
    ){
        try{
            response.write(JSON.stringify(result));
        }catch(e){
            response.write(e);
        } 
    }else{
        response.write(result);
    }
    
}

response.end();

}).listen(appConfig.webPort);

/**

  • @author solq
  • @deprecated blog: cnblogs.com/solq
  • */ module.exports = { f1 :‘this f1’, auto_xxx:null, auto_ip:null, postConstruct : function(){ console.log(‘postConstruct’,this.auto_xxx,this.auto_ip.id); }, preDestroy : function(){ console.log(‘preDestroy’); } };

ws Controller js

/**

  • @author solq

  • @deprecated blog: cnblogs.com/solq

  • */ module.exports = {

    postConstruct : function(){ console.log(‘init ============================’); },

    ‘/testpath’:{ auth : [],//权限 methods : [‘GET’,‘POST’], controller : function(request, response){ console.log(“testpath”,arguments); } }, ’/testpath/{p1}/{p2}’:{ auth : [],//权限 methods : [‘GET’], controller : function(path_p2,path_p1){ console.log(“testpath==”,arguments); } }, ’/testparam’:{ auth : [],//权限 methods : [‘GET’], controller : function(param_p2,param_p1,body_xxx){ console.log(“testparam==”,arguments); } }, ’/testpathParam/{p1}/{p2}’:{ auth : [],//权限 methods : [‘GET’], controller : function(path_int_p2,path_p1,param_p1,param_p2){ console.log(“testpathParam”,arguments); } },

    ‘/index’:{ auth : [],//权限 methods : [‘GET’], controller : function(){ console.log(“hello controller”); return {‘code’:200,data:{‘xx’:‘测试中文’}}; } } }; core js

/**

  • @author solq
  • @deprecated blog: cnblogs.com/solq
  • */ var queryUrl = require(“url”), queryString = require( “querystring” ) debug = require(’…/core/debug.js’).debug, _error = require(’…/core/debug.js’).error, dateConverter = require(’…/core/dateConverter.js’);

var STRIP_COMMENTS = /((//.$)|(/*[\s\S]?*/))/mg;

var requestController={ _urlData : {}, _pathData : {}, _paramData : {}, _pathParamData : {}, /** 保存方法参数元数据结构 { sawName : 原始的参数名 name : 参数名 index : 参数下标 type : 参数类型 如 string array int date object pathIndex : 对应路径参数下标 mapType : 映身处理的类型 如 path param body other required : 是否注入 默认都为 true } */ getParamMetadata : function(params,url){ //path 处理 if(url.lastIndexOf(’/’) == url.length){ url = url.substring(0, url.length - 1).trim(); } var checkPath = url.replace(/{([^}]+)}/gm,function(a,b,c){ return ‘{}’; }); var checkGroup = checkPath.split("/").filter(function(e){return e}), sawGroup = url.split("/").filter(function(e){return e});

    var result = {
         pathParamData : {},
        paramData : {},
        otherData : {},
        bodyData : null,
        pathParamLength : 0,
        paramLength : 0,
        checkParamLength : 0,
        otherLength : 0 ,
        sawPath : url ,
        checkPath : checkPath,
        sawGroup : sawGroup,
        checkGroup : checkGroup
    };
    if(checkPath != url){
        var checkPathGroup={};
        for(var t in checkGroup){
            var __v=checkGroup[t];
            if('{'==__v[0]){
                continue;
            }
            
            checkPathGroup[t]= __v;
        }
        result.checkPathGroup = checkPathGroup;
    }
    var injectionkey = ['path_','param_','body_','def_','auto_','int_','date_','array_','object_'];
    var otherKey =['req','res','request', 'response','auto_'];
    for(var i in params){
        var param = params[i],
            name = param;
            metadata = {
                sawName : param,
                index : i,
                type : 'string',
                required : true
            };
        
        for(var j in injectionkey){
            name=name.replace(injectionkey[j],'');
        }
        
        metadata.name = name;
        
        if(result[name]!=null)
            _error('ParamMetadata register is Has: ',param, url);
        
        //参数类型处理 
        if(param.indexOf('date_')>-1){
            metadata.type = 'date';
        }else if(param.indexOf('object_')>-1){
            metadata.type = 'object';
        }else if(param.indexOf('array_')>-1){
            metadata.type = 'array';
        }else if(param.indexOf('int_')>-1){
            metadata.type = 'int';
        }
        //还有其它等等可扩展
        
        //映射类型处理
        
        if(otherKey.indexOf(param)>-1){
            metadata.mapType = 'other';
            result.otherLength++;
            if(result.otherData[name]!=null)
                _error('ParamMetadata register is Has: ',param, url); 
            
            result.otherData[name] = metadata;
            continue;
        }
        
        if(param.indexOf('path_')>-1){
            metadata.mapType = 'path';
            result.pathParamLength++;
            if(result.pathParamData[name]!=null)
                _error('ParamMetadata register is Has: ',param, url);
                
            var checkKey='{'+name+'}';
            var gi=sawGroup.indexOf(checkKey);
            if(gi<0)
                _error('ParamMetadata register path key not find: ',param, url , checkKey, sawGroup);
                
            metadata.pathIndex = gi;
            result.pathParamData[name] = metadata;
            
            continue;
        }

        if(param.indexOf('param_')>-1){
            metadata.mapType = 'param';
            result.paramLength++;
            
            if(param.indexOf('def_')<0){
                 result.checkParamLength++;
                metadata.required=false;
             }
            if(result.paramData[name]!=null)
                _error('ParamMetadata register is Has: ',param, url);
            
            result.paramData[name] = metadata;

            continue;
        } 
        
        if(param.indexOf('body_')>-1){
            metadata.mapType = 'body';
            if(result.bodyData!=null)
                _error('ParamMetadata register is Has: ',param, url);
            result.bodyData = metadata;
            continue;
        }
    }

    return result;
},
//public function
add : function(handle){

    /***
        1 url 映射处理
        2 参数处理
        3 注入外部上下文
    */
    for(var url in handle){ 
    
        if(url=='id'){
            continue;
        }
        if(url.indexOf('auto_')>-1){
            continue;
        }
    
        var obj = handle[url] ; 
        
        if(typeof obj!='string'){
            continue;
        }
        
        if(obj.methods==null || obj.methods.length==0){
            obj.methods = ['GET']; 
        } 
         for(var i in obj.methods){
            var method = obj.methods[i]; 
            var controller = obj.controller;
            var params = this.getParamNames(controller);
            var paramsMetadata = this.getParamMetadata(params,url);
            var key = this.getKey(method,url); 
            
            obj.paramsMetadata = paramsMetadata;
            
            //分类保存,只要是方便映射,更快处理请求 
            if(paramsMetadata.pathParamLength==0 && paramsMetadata.paramLength==0){ //没有任何路径,请求参数
                this.injectionUrlMap(key,obj);
            }else if(paramsMetadata.pathParamLength!=0 && paramsMetadata.paramLength==0){ //只有路径 参数
                
                key = this.getKey(method,paramsMetadata.checkPath);
                var groupNum = paramsMetadata.sawGroup.length;
                this.injectionPathMap(groupNum,key,obj);
            }else if(paramsMetadata.pathParamLength==0 && paramsMetadata.paramLength!=0){ //只有参数 
                this.injectionParamMap(key,obj);
            }else if(paramsMetadata.pathParamLength!=0 && paramsMetadata.paramLength!=0){ //都有
                 key = this.getKey(method,paramsMetadata.checkPath);
                var groupNum = paramsMetadata.sawGroup.length;
                this.injectionPathAndParamMap(groupNum,key,obj);
            }
     
            //debug("params=====",params,"url=====",url,"paramsMetadata=====",paramsMetadata); 
        } 
                
    } 
 },
findPathFilter : function(data,groupPath){
    var _ar = data[groupPath.length];
    
    if(_ar==null) return null;
    var _filter =null; 
     //debug("findPathFilter======", data,groupPath);
    //debug("findPathFilter======", _ar);
    for(var i in _ar){
        var _f = _ar[i];
        var paramsMetadata = _f.paramsMetadata,
            flag = true,
            checkPathGroup = paramsMetadata.checkPathGroup;
            
        
        for(var j in checkPathGroup){
            var checkValue = checkPathGroup[j];
            if( checkValue != groupPath[j] ){
                flag = false;
                break;
            }
        }
        //debug("findPathFilter======", checkPathGroup,groupPath,flag);

        if(flag){
            _filter = _f;
            break;
        }
    }
     
    return _filter;
},
filter : function(request, response,AppContext){
    
    var _url = request.url;
    if( _url == '/favicon.ico'){
        return;
    }
      
    var method = request.method ,
        urlObj = queryUrl.parse(_url),
        path = urlObj.pathname.trim(),
         queryObj = queryString.parse( urlObj.query );
    
    if(path.lastIndexOf('/') == path.length){
        path = path.substring(0, path.length - 1);
    }
    var paramLength = this.getMapSize(queryObj);
    var key=this.getKey(method,path),
        groupPath = path.split("/").filter(function(e){return e}) ;
    //TODO
    //auth check 

    // urlData>pathData>paramData>pathParamData
    var _filter=null,
        isCheckParamLength = false;
    if(paramLength==0){ 
        _filter=this._urlData[key]; 
        if(_filter==null ){ 
            _filter = this.findPathFilter(this._pathData,groupPath);
        }

    }else{ 
        isCheckParamLength = true;
        _filter=this._paramData[key];
        if(_filter==null ){
            _filter = this.findPathFilter(this._pathData,groupPath);
        }

        if(_filter==null ){
            _filter = this.findPathFilter(this._pathParamData,groupPath);
         } 
    } 
    
    if(_filter==null ){
        _error('not find controller : ' ,key); 
        return;
    } 
    
    var paramsMetadata = _filter.paramsMetadata,
        _paramLength = paramsMetadata.paramLength,
        _checkParamLength = paramsMetadata.checkParamLength,
        controller = _filter.controller;
    
    if( isCheckParamLength && _paramLength!=paramLength){
        //if(_checkParamLength!=paramLength){ //暂时不支持默认参数
            //TODO throw
            _error('paramLength length is wrong : ' ,key,' name : ',paramsMetadata.name,' length : ',_paramLength,paramLength);
            return;
        //} 
    } 
    //param length is right 
    //injection path
    //injection param
    //injection paramAndPath
    //injection other

    var callParams=[],
        resultMap = {} ;
     
    for(var name in paramsMetadata.pathParamData){
        var metadata = paramsMetadata.pathParamData[name];
        this.injectionParamProcess(request, response,AppContext,metadata,queryObj,groupPath,resultMap);
    }
    
    //paramData
    for(var name in paramsMetadata.paramData){
        var metadata = paramsMetadata.paramData[name];
        this.injectionParamProcess(request, response,AppContext,metadata,queryObj,null,resultMap);                    
    }
    
    //bodyData
    if(paramsMetadata.bodyData!=null){
        this.injectionParamProcess(request, response,AppContext,paramsMetadata.bodyData,queryObj,null,resultMap); 
    }
    //otherData 
    for(var name in paramsMetadata.otherData){
        var metadata = paramsMetadata.otherData[name];
        this.injectionParamProcess(request, response,AppContext,metadata,queryObj,null,resultMap);                    
    }
    
    //sort
    for(var k in resultMap){
        callParams.push(k);
    }
    for(var i in callParams){
        callParams[i]=resultMap[i];
    }
    
    //debug('callParams ==============',callParams);
    return controller.apply(controller,callParams);
             
},


 //private function 
injectionParamProcess : function(request, response,AppContext,metadata,queryObj,groupPath,resultMap){
    var index = metadata.index,
        name = metadata.name,
        type = metadata.type,
        mapType = metadata.mapType,
        required = metadata.required,
        value=null;
    switch(mapType){
        case 'param' :
            value = queryObj[name];
            if(value==null && required){
                //TODO throw
                _error('injectionParam parram is null : ',name);
            }
        break;
        case 'path' : 
            var pathIndex = metadata.pathIndex;
            value = groupPath[pathIndex]; 
            if(value==null){
                //TODO throw
                _error('injectionParam path is null : ',name);
            }
        break;
        case 'body' :
            value = queryObj;
        break;
        case 'other' :
            //debug(value," other value +++++++++++");
            var otherKey ={'req':request,'res':response,'request':request, 'response':response};
            value = otherKey[name];
        break;
    }
    if(value!=null ){
        switch(mapType){
            case 'path' :
            case 'param' :
            
                switch(type){
                    case 'int' :
                        var _v=parseInt(value); 
                        if(isNaN(_v) ){
                            //TODO throw
                            _error('injectionParam parram num is null : ',name,value);
                        }
                        value=_v;
                    break;
                    case 'array' : 
                        if(!Array.isArray(value)){
                            value=[value];
                        } 
                    break;
                    case 'date' : 
                        value=dateConverter.convert(value);
                    break;
                }
            break;
        }
    }
    
    
    resultMap[index] = value; 
},
injectionPathAndParamMap : function(groupNum,key,controller){
    if(this._pathParamData[groupNum]==null){
        this._pathParamData[groupNum] = {};
    }
    this._pathParamData[groupNum][key]!=null
    && _error("重复注册 REST injectionPathAndParamMap 处理器 : " ,key);
    debug("injectionPathAndParamMap : ",key);
    this._pathParamData[groupNum][key]=controller;
},
injectionPathMap : function(groupNum,key,controller){
    if(this._pathData[groupNum]==null){
        this._pathData[groupNum] = {};
    }
    this._pathData[groupNum][key]!=null
    && _error("重复注册 REST injectionPathMap 处理器 : " ,key);
    debug("injectionPathMap : ",key);

    this._pathData[groupNum][key]=controller;
},
injectionUrlMap : function(key,controller){
    this._urlData[key]!=null
    && _error("重复注册 REST injectionUrlMap 处理器 : " ,key);
    debug("injectionUrlMap : ",key);

    this._urlData[key]=controller;
},
injectionParamMap : function(key,controller){
    this._paramData[key]!=null
    && _error("重复注册 REST injectionParamMap 处理器 : " ,key);
    debug("injectionParamMap : ",key);

    this._paramData[key]=controller;
}, 
getMapSize : function(map){
    var num=0;
    for(var i in map) num++;
    return num;
},
//cp google
getParamNames : function(func) {
    var fnStr = func.toString().replace(STRIP_COMMENTS, '')
    var result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(/([^\s,]+)/g)
    if(result === null)
     result = []
    return result
},
getKey : function(method,url){
     if(url.lastIndexOf('/') == url.length){
        url = key.substring(0, url.length - 1);
    }
    return method.toLowerCase().trim() + "_"+ url.toLowerCase().trim(); 
},

};

module.exports = requestController;

8 回复

我发现,controller 是通过 return 来返回结果的。

如果 controller 中涉及异步的查询,那么所查询到的数据就无法通往外界。

在改进的过程中,controller 应该还是会变成接收 function (req, res) {} 的形式,并通过 res 来返回数据。

没有 github 地址吗?

不会玩 github 等能拿出手了会共享的

不该参照 Spring 的,应该参照 Rails

我看了一下 restify的风格,没有 spring 的好 我喜欢自动注入,反转控制

自动注入就是hibernate的转型而矣,IOC就是@,没啥难度,用nodejs自己写一个更容易实现。

基本注入已完成,就差路径映射变量处理

回到顶部