个人NODEJS REST 轻量级框架 源码已共享 - CNode技术社区

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

作者 : 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自己写一个更容易实现。

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

回到顶部

AltStyle によって変換されたページ (->オリジナル) /