UNPKG

gcl

Version:

码农村nodejs类库,完成解决回调陷阱,配置框架,IOC框架,统一多DB访问框架,可扩展日志框架和io/db/net/thread/pool 克隆表达式,加解密算法等等一系列基础类库和算法的实现

756 lines (705 loc) 50.7 kB
# gcl ## 目录 - [安装](#Install) - [简介](#Introduction) - [Tool基础类库](#Tool) - [Config配置框架](#config) - [Middler IOC框架](#middler) - [Ni 多数据类型DB访问框架](#ni) - [Log日志框架](#log) - [Debugging and reporting problems](#debugging-and-reporting-problems) - [Running tests](#running-tests) - [Todo](#todo) - [redis](#redis) - [Java](#加解密对接) ## Install ```sh $ npm install gcl ``` 码农村 nodejs基本类库 ## Introduction gcl为同名C#类库的nodejs翻译版本,同时借鉴VESH框架下VJ的部分类库完成,总体上分为 * 基础类(com/coooders/common/tool) * 可扩展配置文件Config框架(com/coooders/io/config) * IOC容器Middler框架(com/coooders/bean/middler) * 跨数据库DB访问层Ni框架(com/coooders/db/ni) * 可配置日志文件Log框架(com/coooders/io/log) [VJ URL]: https://github.com/baibing0004/VJ Here is an example on how to use it: ```js var V = require('gcl/com/coooders/common/tool'); //callback(func,[paras]) 同步或者异步方法的Promise封装 主要用于将系统默认的异步方法同步化 或者兼容3种不同的同步异步调用方式为异步调用。 //但是请注意 不管是async 或者 callback都会导致返回值是一个Promise 此方法为0.2.9版本的核心 const test = async ()=>{ console.log(await V.callback(()=>1));//第一种非undefined 视作同步 console.log(await V.callback(call=>call(null,2)));//第二种直接返回undefined使用异步需要使用默认传入的最后一个参数进行回调(call(err,data)) console.log(await V.callback(()=>call=>call(null,3)));//第三种返回的是一个函数,则函数默认只有一个参数是call回调(call(err,data)) console.log(await V.callback(()=>new Promise(……)));//第四种返回Promise则会自动调用await方法判断其返回值,然后重复如上3种逻辑 console.log(await V.callback((...args)=>{console.log(args);return true;},1,2))//前四种都可以按照顺序在function后写入调用的参数,这里会显示args有3个最后一个是自动添加的call回调。 //所以对于最后一个回调是call(err,data) 这种类型的操作就可以做下面的异步转同步操作 console.log(await V.callback(fs.stat,path)); console.log(await V.callback(call=>fs.exists(path,ist=>call(null,ist))));//exists非常特殊 最后回调没有err所以要这么写 } //V.forC,V.each,V.whileC,V.next,V.finalC 中调用的函数体都会按照callback的方法调用。除forC使用(k,v,call)方式传入参数外,其它方法一般都是(v,call)方式传入 //异步循环处理对象属性 V.forC({a:1,b:2},(k,v)=>{ //do something like console.log("${k}:${v}"); }); V.forC({a:1,b:2},(k,v)=>{ //do something like console.log("${k}:${v}"); }).then(()=>{ //do something like console.log("it is end"); }); //或者同步循环处理 V.forC({a:1,b:2},(k,v)=>{ //do something like console.log("${k}:${v}"); },true).then(()=>{ //do something like console.log("it is end"); }); //与此类似的异步同步循环处理方式 还有 V.each(处理数组),V.whileC(首个参数是function,当返回null或者undefined,循环终止!).V.next(保证多个函数顺序执行),V.finalC(保证多个函数乱序结束,但是最后一个方法最后执行) ``` 从这个例子当中,我们可以知道: * GCL提供了一系列基本方法和框架保证在nodejs异步处理环境下 尽可能简单的完成连贯的业务逻辑和处理 避免回调陷阱。 ## Tool ```js var V = require('gcl/com/coooders/common/tool'); ``` ### 对象判断 ```js V.isValid("") == false; V.getValue = ()=>V.isValid(data) ? data : defaultData; V.isArray(array); V.getType(obj); ``` * V.isValid、V.getValue 很简单仅仅是判断是否为非0,非false,非undefined,非null的有效值,并根据是否有效返回默认值 * V.isArray 判断是否是数组 * V.getType 比typeof操作除基本类型判断外更进一步说明Object类型下其继承的类型 ### 字符串处理 ```js V.format("{k}:<%=v%>",{k:'Key',v:'Value'}) var sb = V.sb(); var ehtml = V.encHtml('<html ……>'); var dhtml = V.decHtml(ehtml); var price = V.formatPrice(22.2222); var jstr = V.toJsonString({}); var jobj = V.json(jstr); ``` * V.format 根据{}或者<%= 将数据中对应的Key/Value 生成字符串 * V.sb 生成StringBuilder方式的隐藏类. 含有append,appendFormat,insert(start,data),insertFormat,remove(start,length),toString,clear等等方法 方法参数一定与您想要的参数和顺序一致不做解释,不太一致的已经给出了括号中的特别说明,请笑纳。特别地clear方法在清理内存时同时返回toString的结果,应该是个必备好方法吧。 * 同时本类默认给String基类添加trim/startWith/endWith/eq等方法,看名知意。 * V.encHtml与V.decHtml与encodeURICompent不同,enc仅仅对特殊字符标点符号进行转义文字不转义使得字符串转义后不会过大。 * V.formatPrice(num[,decimals,point,sep]) 定义对数字转换成标准价格,包括定义数字,小数位(2),小数点符号(.),千分位符号(,) * V.toJsonString与V.json分别调用JSON对象的stringfy与parse功能完成json转换操作,但是建议对应不正规json的解析使用eval('('+str+')'),有奇效. ### 异常处理 ```js V.isDebug = true;//为真时showException才会显示内容 V.showException(desc,e);//会将e的message和stack给console.log打印出来 V.tryC(func[,catchfun]); V.tryC2(err,func[,catchfun]); ``` * V.tryC/V.tryC2 专门为异常扑捉与显示使用,参数名一目了然,tryC2需要解释的是用于nodejs标准方式回调函数定义(err,data)=>V.tryC2(err,()=>{/*bala bala bala*/callback(null);},callback);特别合适省去判断是否已经报错的回调。 ### 日期处理 ```js new Date().add('n',3)//添加三分钟 V.watch([restart]); ``` * VJ为日期对象基类添加add(field,num)/diff(field,Date)/sub(field,num)/toString('yyyy/MM/dd HH:mm:ss') 等方法 * 除toString方法外field定义如下 y 年,q 季度,m 月,d 日,h 小时,n 分钟,s 秒,ms 毫秒 * watch([restart]) 方法为计算两次调用watch方法之间时间差(ms),当restart为真时重新计时,否则连续计时。 ### 异步同步处理 ```js V.once(()=>{}[,time]); //延迟time秒后执行,否则默认异步执行此方法(time=1) V.whileC(()=>[].shift(),(v)=>{/*each time*/}[,()=>{/*end*/},false]); V.whileC2(()=>[].shift(),(v,next)=>{/*each time if you continue next()*/next()}[,()=>{/*end*/},false]); V.each([],(v)=>{/*each time*/},[()=>{/*end*/},false]); V.each2([],(v,next)=>{/*each time if you continue next()*/next()},[()=>{/*end*/},false]); V.forC({},(k,v)=>{/*each time*/}[,()=>{/*end*/},false]); V.forC2({},(k,v,next)=>{/*each time if you continue next()*/next()}[,()=>{/*end*/},false]); V.next((data,next)=>{next()},(data,next)=>{next()},……); V.finalC((data,next)=>{next()},(data,next)=>{next()},……); ``` * 以V.once为基础,V.whileC/V.each/V.forC 分别对函数,数组,对象执行异步/同步循环操作,并提供最后两个参数为截止时调用的方法与异步(false)/同步(true)设置。 * V.whileC2,V.each2,V.forC2 与其第一版本的参数定义基本一致,仅仅是循环体函数提供多一个参数next:function 用于当本次循环异步调用成功时由开发者决定是否可以继续循环。 * V.nextV.finalC 提供多个函数体的顺序与不严格顺序执行。其中对每个方法提供data{}与next:function两个参数,data用于多个函数或者顺序或者随机地共享一些参数与结果,next 用于继续循环。而两个方法的最后一个参数方法视作最终方法均保证无论前面的方法顺序执行或者随机执行与结束,最终方法都会获取到完整的data对象并结束整个函数 最终方法可以不调用next,但是调用next也不回出错。 * 特别地如上方法均自动调用V.tryC保证循环体方法一旦报错,next均将继续调用。 ### 随机值与hash ```js var i = V.random(); //对Math.random的修正,保证同一毫秒执行也不回重复的随机数 var h = V.hash(str[,caseSensitive])//JS hash 是否要求区分大小写默认为否全部使用小写 var h2 = V.hash2(str)//JS hash2 使用crypto类库按照sha1进行hash的base64位hash ``` ### TJSON ```js var strs = V.toTJson([[{},{}]]); var dataset = V.evalTJson(strs); ``` * TJSON 格式是使用数组方式表示JSON标准数组 JSON标准数组 一般由4维度组成分别是 数据集>数据表>数据行>数据=>[[{key:value},{},{},{}……]] 根据这个结构 TJON 分成3层 数据集>数据表>数据名行+数据值行+……=>[[[数据Keys],[数据Values1],[数据values2]……]] 这样可以减少规律数据在传输过程中过大 * 在VESH客户端JS VJ中有对应的evalTJson方法供使用 ### 异步回调与对象合并 ```js V.callback(func,params,call); var data = V.merge(data,data2,data3……); V.merge(data,data2,data3……,true); var ret = V.merge({a:22,c:23},{a:34,b:33},{d:"2334",f:true,g:function(){alert("hahaha");}},{h:[1,2,3,4]}); var ret = V.merge({a:[{a:2},{b:3}]},{a:[{moveIndex:3,j:3},{k:4}],b:25}); var ret = V.merge({a:[{a:2},{b:3}]},{a:[{mergeIndex:3,j:3},{k:4}],b:25}); ``` * V.merge 用于数组,对象的深度合并功能,使用递归方法将后面的数据逐次叠加和覆盖到前面的数据上,完成合并,当然也可以使用VJ.merge({},json)实现json的clone,对其内部属性的操作都不会影响到原始对象。 * 当V.merge最后一个参数为true时,会自动覆盖第一个对象的属性值。 * V.merge 处理对象的属性时,判断如果是对象则递归合并,如果是数组则进行合并,如果普通属性则进行覆盖。 * V.merge 当对象属性是数组 可以通过moveIndex属性用于设定移动至的位置,mergeIndex只用于合并数组中的第几个对象 ### 继承与创建 ```js // 第一种继承方式 var father = function(paras){ var _ = this,__ = {paras:paras};//分别定义公共与私有属性 方法 _.func1 = ()=>{console.log('father')}; __.func2 = ()=>{console.log('father2');} }; var son = function(array,paras){ var _ = this,__ = {array:array}; V.inherit.apply(_,[father,[paras]]); __.func1 = _.func1; _.func1 = ()=>{console.log('son');__.func1();} __.func2 = ()=>{console.log('son2');} } // 第二种继承方式 var father = function(paras){ var _ = this;//分别定义公共与私有属性 方法 _._paras = paras; _._func2 = ()=>{console.log('father2');} }; V.merge(father.prototype,{ _.func1 = function(){console.log('father')}; },true); var son = function(array,paras){ var _ = this; _._array = array; father.apply(_,[paras]); _._func2 = ()=>{console.log('son2');} } V.inherit2(son,father,{ func1:function(){console.log('son');son.func1();}//注意这里son.func1实际是father.prototype.func1 在inherit2中被自动赋值 }); var obj = V.create(son,[array,paras]); obj = V.create2(son,[array,paras]); ``` * 第一种类定义方法 非常容易实现共有,私有方法和属性,且其私有方法和属性不可随继承而修改可以保证使用,保证可访问性,可连续继承性和父类方法的覆盖和调用。缺点是频繁创建时效率不高。 * 第二种类定义方法 只有伪私有方法和属性(实质是公共方法容易发生覆盖),语法较为繁琐,但是频繁创建时效率较高,而且也可以保证可访问性,连续继承性与父类方法的覆盖和调用。然后可以使用V.merge(prototype,{fun1:function(){},func2:function(){}},true) 方式添加公共方法,特别注意不能使用()=>方式定义函数,否则无法继承。 * V.inherit适用于第一种类定义方法的继承调用时必须是V.inherit.apply(son's this,[father,[father's paras]]); * V.inherit2适用于第二种类定义方式的继承,调用时首先保证子类调用了father.apply(this,[father's paras]);然后调用V.inherit(son,father,{func1:function(){}) * V.create与V.create2参数一致,可以创建第一第二中类方式创建的类实例或者普通传统方式定义的类实例,不同在于create方法使用eval方式创建对象,而create2使用继承方式创建对象当频繁创建同一类实例时create2的效率较高。与Object.create不同的是可以使用类定义的构造函数 ### 引用 ```js var type = V.include('/com/tools'); ``` * V.include 调用require功能完成模块加载工作,特别注意当include参数为/开头时,默认按照nodejs启动文件的路径为绝对路径开始,而不是以文件夹为绝对路径,而且include允许按照绝对路径先尝试访问js文件失败后尝试访问njs同名文件失败后最后尝试直接访问require。njs文件放置js被窃取下载。 ### 事件与方法 ```js var func = function(){}; var obj = new func(); V.applyCommandAndEvent(func); V.applyCommandAndEvent(obj); func.callCommand('com1',[ss]); func.callEvent('eve1',[ss]); func.registCommand('com1',function(ss){}); func.registEvent('eve1',function(ss){}); obj.callCommand('com1',[ss]); obj.callEvent('eve1',[ss]); obj.registCommand('com1',function(ss){}); obj.registEvent('eve1',function(ss){}); //func定义的事件和命令与obj定义的同名事件和命令是两回事奥,因为绑定的对象不同。 ``` * V.applyCommandAndEvent 使用apply方式给对应的对象添加了一系列与方法事件相关的方法和属性。调用此方法后即可根据被调用的对象来分别触发和使用命令(命令模式)与事件(观察者模式) * 当然它与系统emiter不同的地方在于 它支持命令(一次注册,多次使用)模式,而且支持异步定义——即先调用后定义方式。它允许保留最后一次的调用信息直到命令或者事件被注册后触发。 * 当然命令是同一个名字只能定义一个对应的方法,而且最后一次定义的方法会取消前面定义的同名方法。事件不是它允许多次被注册和调用。调用时如果未有注册信息,则保留最后一次调用信息到注册时触发,如果已有注册信息,则保留一分钟实现凡是一分钟内第一次注册的事件处理函数都可以再自动触发一次。 * 它还支持hasCommand(name),cleanCommand(name),hasEvent(name),cleanEvent(name)方式来处理命令和事件的判断。 * 同时给绑定的类或者对象提供了getSettings(key,{}),extendSettings(key,{}),clearSettings()方法用于提供对命名属性的定义扩展与清理。是命令事件模式的基础。调用getSettings('events')和getSettings('comms')即可分别获取事件注册者列表与命令注册者列表 ## config ```js //首先定义base.npcf文件 { ConfigConverts:{ base:null, AppSettings:{type:'AppSettingsConfigConvert',path:'/com/coooders/io/config'}, Middler:{type:'MiddlerConfigConvert',path:'/com/coooders/bean/middler'}, Ni:{type:'NiDataConfigConvert',path:'/com/coooders/db/ni'} } } //其次定义test.npcf文件(假设启动的文件名为test.js) { AppSettings:{ a:111, b:222 }, Middler:{ 'VESH.view': { pack: '', method: 'constructor', mode: 'instance', //基本证明 Logger: {type:'Logger', mode: 'static', path: "/com/coooders/io/log",method:"constructorbean",constructorparalength:"1",params: [ {type:'LogRecord',method:"constructor",params:[0,true,true,false]}, {name:'Lisener',type:'LogLisener',method:"constructor",params:[[ {type:'LogTypeTrigger',method:'constructor',params:[0,3]}, {type:'RegexTrigger',method:'constructor',params:[' test ']} ],{type:'LogRecord',method:"constructor",params:[0,true,true,true]},[{type:'FileResource',method:"constructor",params:['./log_{name}.log','yyyyMMddHH']}],true]} ] } } }, Ni:{ 'test11': { 'command': 'select * from ac_case limit ?limit1;select ?limit1 as l1;', 'params': { 'limit1': {type:'Int'} }, 'template': 'template1' }, 'ajaxtest22': { 'command': 'SELECT top 10 *,@limit1 as "l1" FROM [dbo].[ssidb_User];select @limit1 as "l1";', 'params': { 'limit1':{type:'Int'} }, 'template': 'template2' }, 'ajaxtest33': { 'command': 'test', 'params': { 'limit1':{type:'Int'} }, 'template': 'template3' }, 'ajaxtest44': { 'command': 'insert into abc<a> (a,cacheValue) values (?a,?cacheValue);', 'params': { 'a':{type:'Int'},'cacheValue':{type:'String'} }, 'template': 'template5' }, 'ajaxtest55': { 'command': 'select cacheValue from abc<a> where a=?a', 'params': { 'a':{type:'Int'},'cacheValue':{type:'String'} }, 'template': 'template5' }, 'ajaxtest66': { 'command': 'update abc<a> set cacheValue = ?cacheValue where a=?a', 'params': { 'a':{type:'Int'},'cacheValue':{type:'String'} }, 'template': 'template5' }, 'ajaxtest77': { 'command': 'delete from abc<a> where a=?a', 'params': { 'a':{type:'Int'},'cacheValue':{type:'String'} }, 'template': 'template5' }, 'ajaxtest11.Set': { 'command': 'insert into abc<cacheKey> (cacheKey,cacheValue) values (?cacheKey,?cacheValue);', 'params': { 'cacheKey':{type:'String'},'cacheValue':{type:'String'} }, 'template': 'template4' }, 'ajaxtest11.Cache': { 'command': 'select cacheValue from abc<cacheKey> where cacheKey=?cacheKey;', 'params': { 'cacheKey':{type:'String'} }, 'template': 'template4' } } } var C = require('gcl/com/coooders/io/config'); C.getApplicationConfigManager((err,conf)=>{ var value = conf.getConfigValue('AppSettings','a'); var config = conf.getConfig("Middler"); config.getValue(new function(){this.getValue = config=>config.getValueByName("VESH.view",'Logger')}).info(value);// }); var conf = C.getApplicationConfigManagerSync(); ``` * C.getApplicationConfigManager和C.getApplicationConfigManagerSync 分别是异步与同步情况下调用了启动文件所在文件夹的同名文件.npcf并嵌套同一文件夹下的base.npcf文件来完成配置转换类与配置定义的。 * 如上两个方法返回的是分别是一个ConfigManager的实例,ConfigManager拥有getConfig(configkey),getConfigValue(configkey,key),setConfigValue(configkey,key,value),update 4个方法,其getConfig返回的Config类实例拥有getValue(key)与setValue(key,value)两个方法,update用于更新配置文件。 * ConfigManager实例是一个可以自嵌套而成的ConfigManager树,ConfigManager实例实现自叶子节点到根节点的查找,当当前文件使用getConfig或者getConfigValue值为空时会按照节点链继续向上查找。 * ConfigManager的getConfigValue返回的值类型要根据base.npcf下对应根一级节点的解析器决定, * 譬如 conf.getConfigValue('AppSettings','a'); 是根据base.npcf中定义的AppSettings节点的解析器AppSettingsConfigConvert解析test.npcf的AppSettings节点生成的Config实例的getValue方法返回的值 结果为111 * 而conf.getConfig("Middler")是根据base.npcf中MiddlerConfigConvert解析test.npcf中的Middler节点生成的Config,它getValue生成的对象是根据配置new 出来的/com/coooders/io/log中定义的Logger类实例。当然Middler节点中对Logger的生成定义了一系列构造参数,Middler的强大功能在下面的章节(#Middler IOC框架)中详细介绍 * 除此之外require('gcl/com/coooders/io/config')还提供了一系列方法和基础类用于实现更复杂的嵌套和用户自定义节点解析器 ### Config框架还有那些方法 ```js var C = require('gcl/com/coooders/io/config'); C.getApplicationConfigManager((err,conf)=>{ var cm2 = C.getConfigManagerFromObj(conf,{ Middler:{ Ni:{ template:{type:'NiTemplate',path:'gcl/com/coooders/db/ni'} } } }); var template = cm2.getConfigValue('Middler','Ni/template'); ………… }); var cm2 = C.getConfigManagerFromJS(parent,path); C.getConfigManagerFromFile(parent,path,(err,cm2)=>{/*…………*/}); var cm2 = C.getConfigManagerFromFileSync(parent,path); C.getConfigManagerFromDir(parent,path,(err,cm2)=>{/*…………*/}) var cm2 = C.getConfigManagerFromDirSync(parent,path); ``` * 如上所述,Config框架提供从nodejs文件(getConfigManagerFromJS),json对象(getConfigManagerFromObj),json文件(getConfigManagerFromFile),文件夹下所有json文件(getConfigManagerFromDir) * 而且文件和文件夹还提供了同步方法getConfigManagerFromFileSync,getConfigManagerFromDirSync * 对于对应启动文件的默认文件也提供了(getApplicationConfigManagerFromJS),json对象(getApplicationConfigManagerFromObj),json文件(getApplicationConfigManagerFromFile,getApplicationConfigManagerFromFileSync),文件夹下所有json文件(getApplicationConfigManagerFromDir,getApplicationConfigManagerFromDirSync)等等方法 * 特别注意的是FromDir类型的操作都会把文件夹下的所有文件视作json文件并通过V.merge方式合并为一个对象所以存在同名覆盖问题。 ### Config框架如何扩展 ```js var C = require('gcl/com/coooders/io/config'); C.ConfigConvert = function(){ var _ = this; _.toConfig = function(val){return null;}; _.toStrings = function(config){return "";}; _.needConfig = false; }; C.AppSettingsConfigConvert = function(){ var _ = this; { V.inherit.apply(_,[C.ConfigConvert,[]]); } _.toConfig = function(val){ var conf = new C.Config(); val = V.getValue(val,{}); for(var i in val){ conf.data[i] = val[i]; } return conf; }; }; ``` * 以AppSettingsConfigConvert为例子,需要使用inherit继承C.ConfigConvert,然后覆盖toConfig(json) 产生一个C.Config对象即可 可以直接使用new C.Config() 并通过.data[k]=v 方式进行设置。或者重新生成自己的Config类也可以。 * needConfig参数默认为false当为真时,getConfig函数会收到两个参数(val,cm) cm的值是调用解析器的ConfigManager实例。可以用于调用其它Config配置。 * 然后为了在所有的都能访问到最好在base.npcf中设置新的节点名和解析器 AppSettings:{type:'AppSettingsConfigConvert',path:'/com/coooders/io/config'}, * 它的规则是type为require(path)后的属性类,path当以'/'开头时以启动文件为根目录,而不是以盘符为根目录,其余规则与require一致。 # middler ## Middler IOC框架 ```js var C = require('gcl/com/coooders/io/config'); var M = require('gcl/com/coooders/bean/middler'); C.getApplicationConfigManager((err,conf)=>{ var middler = new M.Middler(conf); var template = cm2.getConfigValue('Middler','Ni/template'); var template = middler.getObjectByAppName('Ni','template'); ………… middler.setObjectByAppName("Ni",'template',template); }); ``` * new M.Middler是处理以ConfigManager为基础的一个简单封装类,重要的是gcl/com/coooders/bean/middler下的MiddlerConfigConvert类,它负责将Middler下定义的Json格式数据转换成不同名称的实例对象Json内容转换成VJ.config.Config的一个子类并覆盖getValue方法将对应的json配置转换成对应的JS对象。 * 当然也可以使用M.getMiddlerFormJS(path,call(err,middler)),M.getMiddlerFormFile(json,call(err,middler)),M.getMiddlerFromFile(path,call(err,middler)) 三种方式获得在getApplicationConfigManager基础上的3级ConfigManager的Middler对象,代码保证getApplicationConfigManager不会重复生成对象。 * 当然还可以直接使用M.getObjectByAppName(cm,app,name)方法来获取实例或者直接使用M.getTypeByAppName(cm,app,name)方法来获取实例的类 * Middler有两个通用的方法getObjectByAppName(app,name)和setObjectByAppName(app,name,obj)和一个特有的方法getTypeByAppName(app,name)一般地如果创建的对象保持方式是池类型,必须setObjectByAppName否则就不能释放占用。所以为了稳妥起见,建议用户在使用完对象后使用setObjectByAppName方法将对象返回Middler ```js //Middler下的一级属性叫做App,App只能定义{type……},{params:[……]} 两种类型,分别对应对象和对象数组 Middler:{ //在一个AppName或者params对象数组下可以设置默认的method,mode,path,pack等等参数 appName:{ //method 说明该AppName下的默认对象构造方法,如果不定义则默认为constructor,该值共有五种 constructor(构造函数也就是new这种方式)/bean(无参构造函数+set/get属性名方式设置属性方式+无set/get直接进行属性设置)/factory(直接调用js方法方式)/constructorbean(构造函数+bean方式)/factorybean(工厂方法+bean方式) 后两种方式需要配合constructorparalength参数说明构造函数的参数个数 method:'', //mode 说明该AppName下的对象保持方式,如果不定义则默认为Static,该值共有3种 static(静态单例)/instance(每次请求都新建)/pool(对象池模式保证池内对象数随着使用频率增减) mode:'', //path 说明该AppName下的JS对象都来自于一个V.include地址,其特别处理/开始的根目录。而且具体实例的path属性可使用~开头复用App默认的Path地址 path:'', //pack 说明该AppName下的JS对象的类名头部,如果不定义则默认为空,即使定义也仅限于具体对象的type属性以'.'开头时生效 pack:'', //type属性定义了对象的类名如果以'.'开头则将AppName的pack属性值+type属性值构成完整的type属性值 如果不定义type属性则被视作Objects数组定义,只返回params对象的转换值。特别地对于nodejs中直接将module.exports = function 这种定义方式适用 type="" 这种定义方式,切不可没有。其在factory时应该是class.method格式或者method格式 //path属性定义了js对象的所在V.include路径 如果不定义那么使用AppName下的默认值,如果多个对象的path值相同那么使用于module.export = function(){}, module.export.Class1 = function(){} 这种定义方式。 //method属性定义js对象的构造方法,如果不定义那么使用AppName下的默认值 //mode属性定义js对象的保持方式,如果不定义那么使用AppName下的默认值 //constructorparalength属性仅在method为constructorbean/factorybean时生效,以区分构造函数或者工厂方法需要的参数个数,其后的参数使用bean方式进行设置 //params 属性定义js对象的构造参数数组,其类型有'',boolean,number,date,null,当其类型为{}时,可以定义{type,path,method…………}一个完整的新实例,{ref="App/Name"}一个引用实例,{middler:true} 当前的Middler本身,{self:true} 当前的ConfigManager本身,{params:[……]} 一个对象数组,{param:'name',params:[key,value]} 调用新实例的setName(key,value)方法或者{a:1,b:2,c:3……}的一个json对象 ObjectName:{type:'',path:'',method:'',mode:'',constractparalength:'',params:[ //一个新的JS对象定义 其各个属性值都可以使用父对象的属性值作为默认值 当然其params属性还可以类似的进一步定义下去。这个js对象因为没有ObjectName所以无法被外部通过middler.getObjectByAppName方式访问,但是会被随机命名后放置在当前Middler的config空间中保存。从而实现CMD模型。 {type:'',path:'',method:'',mode:'',constractparalength:''}, //一个已有的JS对象引用,ref的定义格式使用'appName/objectName'方式定义路径,如果没有appName则默认是当前AppName下的对象 {ref:''}, //一个json方式设置的属性值,如果在constructor模式下,会作为参数之一传递给构造函数,如果是bean模式下会自动添加在js对象上 {a:1,b:2}, //一个json方式设置的属性值,如果在constructor模式下,会作为参数之一传递给构造函数,如果是bean模式下会自动添加在js对象上 其定义效果与上个参数相同但是同名属性会覆盖上个参数中的同名属性 {a:3}, //一个json方式设置的属性值,如果在constructor模式下,会作为参数之一直接传递给构造函数,如果是bean模式下会调用js对象的set属性名方法,将剩余属性作为两个参数调用该方法同样的属性值既可以是默认类型也可以是middler规定的js对象定义格式。 {param:'属性名',params["key",{a:1,b:2}]}, //传统的js数据,不能作为bean方式下调用的数据 '', //传统的js数据,不能作为bean方式下调用的数据 1, true, //middler对象将当前的完整Config链实例作为参数传入仅能在constructor或者factory下使用或者作为bean方法模式下的一个子值 {self:"true"}, //middler对象允许通过这种方式获取定义对象的类作为参数,从而供这个类对象的构造函数获取到父类实现AMD功能 {type:'VJ.middler.getTypeByAppName',params:[{self:true},'VESH.view','panel'],method:'factory',mode:'static'}, //middler对象允许通过这种方式设置ObjectName的一个方法setType来进行复制但是仅限method为bean,constructorbean,factorybean。 {name:'Type',type:'VJ.middler.getTypeByAppName',params:[{self:true},'VESH.view','panel'],method:'factory',mode:'static'} ]}, //middler框架中对象数组的定义方式(不定义type属性,而且其各个属性值作为参数中的默认值向下传递,返回的数组对象为其params中定义的值 ObjectsName:{path:'',method:'',mode:'',constractparalength:'',params:[ {type:'',path:'',method:'',mode:'',constractparalength:''}, {ref:''} ]} } } ``` * Middler框架支持创建对象的三种保持方式即mode属性(static,instance,pool),应用于创建完成后如何在Middler中保持,其中pool方法要求具有size属性设定池大小,且在get后必须setObjectByAppName才能释放适用的对象被其它对象使用。 * Middler框架支持创建对象的五种创建方式即method属性(constructor,bean,factory,constructorbean,factorybean),分别是构造函数,无参属性设置构造函数,工厂,构造+属性设置,工厂+属性设置。便于使用配置文件完成对象的创建和保持。 * Middler框架通过例子中的配置文件,支持nodejs中定义的各种对象和实例的创建和构造 * method 说明对象构造方法,如果不定义则默认为constructor,该值共有五种 constructor(构造函数也就是new这种方式)/bean(无参构造函数+set/get属性名方式设置属性方式+无set/get直接进行属性设置)/factory(直接调用js方法方式)/constructorbean(构造函数+bean方式)/factorybean(工厂方法+bean方式) 后两种方式需要配合constructorparalength参数说明构造函数的参数个数 * mode 说明对象保持方式,如果不定义则默认为Static,该值共有3种 static(静态单例)/instance(每次请求都新建)/pool(对象池模式保证池内对象数随着使用频率增减) * path 说明对象地址都来自于一个V.include地址,其特别处理/开始的根目录。而且具体实例的path属性可使用~开头复用App默认的Path地址 * type 定义了对象的类名如果以'.'开头则将AppName的pack属性值+type属性值构成完整的type属性值 如果不定义type属性则被视作Objects数组定义,只返回params对象的转换值。特别地对于nodejs中直接将module.exports = function 这种定义方式适用 type="" 这种定义方式,切不可没有。其在factory时应该是class.method格式 ## ni ### 多数据类型DB访问框架 ```js //npcf文件中Ni部分 Middler:{ 'Ni':{ constructorparalength: false, size: 50, method: 'constructor', mode: 'static', path: '/com/coooders/db/ni', mysqlfactory: { type: 'NiMySQLDataFactory' }, mssqlfactory: { type: 'NiMsSQLDataFactory' }, objfactory: { type: 'NiObjectDataFactory',params:[{middler:true}]}, memfactory: { type: 'NiMemSQLDataFactory',params:[{type:'NoSQLParser',path:'/com/coooders/db/nosql/nosql'}]}, mongofactory: { type: 'NiMongoSQLDataFactory',params:[{type:'NoSQLParser',path:'/com/coooders/db/nosql/nosql'}]}, cm: { path:'/com/coooders/io/config', type: 'getApplicationConfigManagerFromFileSync', params: ['/ni.npcf'], method: 'factory' }, template1: { type: 'NiTemplate', mode: 'instance', params: [ { type: 'NiStaticDataResource', params: [{ ref: 'mysqlfactory' }, { host:'127.0.0.1',localAddress:'127.0.0.1',port:'3306',user:'root2',password:'111111',database:'cv_zc',charset:'utf8_general_ci', timeout: 60000}] }, { ref: 'cm' } ] }, template2: { type: 'NiTemplate', mode: 'instance', params: [ { type: 'NiStaticDataResource', params: [{ ref: 'mssqlfactory' }, { server:'127.0.0.1',localAddress:'127.0.0.1',port:'1433',user:'sa',password:'sa',database:'SSIDB',pool: {min: 0, max:10,idleTimeoutMillis: 3000}, timeout: 60000}] }, { ref: 'cm' } ] }, template3: { type: 'NiTemplate', mode: 'instance', params: [ { type: 'NiStaticDataResource', params: [{ ref: 'objfactory' }, { app:'VESH.view',name:'Logger' }] }, { ref: 'cm' } ] }, template4: { type: 'NiTemplate', mode: 'instance', params: [ { type: 'NiStaticDataResource', params: [{ ref: 'memfactory' }, { hosts:'localhost:11211',datetime:60000,retries:10,retry:10000,remove:true,failOverServers:['192.168.1.106:11211'] }] }, { ref: 'cm' } ] }, template5: { type: 'NiTemplate', mode: 'instance', params: [ { type: 'NiStaticDataResource', params: [{ ref: 'mongofactory' }, { hosts:'mongodb://localhost:27017/myproject' }] }, { ref: 'cm' } ] }, template: { type: 'NiMultiTemplateDecorator', mode: 'instance', params: [ { type: 'NiStaticDataResource', params: [{ ref: 'mysqlfactory' }, { host:'127.0.0.1',localAddress:'127.0.0.1',port:'3306',user:'root2',password:'111111',database:'cv_zc',charset:'utf8_general_ci', timeout: 60000}] }, { ref: 'cm' }, { middler: true }, 'Ni' ] }, templatemanager:{ type:'NiTemplateManager',params:[{middler:true},'Ni'] } } } var C = require('gcl/com/coooders/io/config'); var M = require('gcl/com/coooders/bean/middler'); C.getApplicationConfigManager((err,conf)=>{ var middler = new M.Middler(conf); var temp = middler.getObjectByAppName("Ni","template"); temp.execute('home.getTopNews',{limit:10},(err,result)=>{ if(result.hasData()){ var data = result.last(); } middler.setObjectByAppName("Ni","template",temp); }) }); ``` * Ni框架以template为核心,以middler为基础,使用middler创建和使用template对象。 * template对象拥有execute(cmd,paras,callback(err,result)),commit()方法,和transaction属性(默认false),result属性 * 当需要transaction事务执行时,先将transaction设置为true,然后正常调用execute最后再commit即可,特别注意execute中需要使用result.get(index),而不要使用last方法。template本身的result属性也可以在方法外直接调用。 * Ni框架本身已经支持MySQL,SQLServer,mongodb, memcache,object对象 五种数据库类型 * 同时支持Template装饰器类NiTemplateDecorator和NiMultiTemplateDecorator,前者用于支持cmd+'.Set'和cmd+'.Cache'命令在命令集中自动查找和调用对应sql完成缓存操作。后者在前者基础上支持根据命令集的template属性自动调用不同的template完成缓存和修饰功能。 ### ni文件的定义 ```js //ni.npcf文件中Ni部分 Ni:{ 'ajaxtest11': { 'command': 'select * from ac_case limit ?limit1;select ?limit1 as l1;', 'params': { 'limit1': {type:'Int'} }, 'template': 'template1' }, 'ajaxtest22': { 'command': 'SELECT top 10 *,@limit1 as "l1" FROM [dbo].[ssidb_User];select @limit1 as "l1";', 'params': { 'limit1':{type:'Int'} }, 'template': 'template2' }, 'ajaxtest33': { 'command': 'test', 'params': { 'limit1':{type:'Int'} }, 'template': 'template3' }, 'ajaxtest44': { 'command': 'insert into abc<a> (a,cacheValue) values (?a,?cacheValue);', 'params': { 'a':{type:'Int'},'cacheValue':{type:'String'} }, 'template': 'template5' }, 'ajaxtest55': { 'command': 'select cacheValue from abc<a> where a=?a', 'params': { 'a':{type:'Int'},'cacheValue':{type:'String'} }, 'template': 'template5' }, 'ajaxtest66': { 'command': 'update abc<a> set cacheValue = ?cacheValue where a=?a', 'params': { 'a':{type:'Int'},'cacheValue':{type:'String'} }, 'template': 'template5' }, 'ajaxtest77': { 'command': 'delete from abc<a> where a=?a', 'params': { 'a':{type:'Int'},'cacheValue':{type:'String'} }, 'template': 'template5' }, 'ajaxtest11.Set': { 'command': 'insert into abc<cacheKey> (cacheKey,cacheValue) values (?cacheKey,?cacheValue);', 'params': { 'cacheKey':{type:'String'},'cacheValue':{type:'String'} }, 'template': 'template4' }, 'ajaxtest11.Cache': { 'command': 'select cacheValue from abc<cacheKey> where cacheKey=?cacheKey;', 'params': { 'cacheKey':{type:'String'} }, 'template': 'template4' } } ``` * Ni节点下的文件定义以 命令:{command:sql,params:{ 'paraname':{type:'Int/string/bool/Date/float'} },template:'tempatename'} 为基本格式 * 其中命令即为sql语句,params定义了参数的类型,template可以输入很多参数但是只转换参数中定义的部分,template参数说明其调用的template名,预留out属性,type属性作为未来支持可出参数和存储过程类型只用(目前调用存储过程应该是exec procedurename) * 一般地其sql中定义的前缀?或者@ 由其DBFactory来决定转换方式,Ni框架要求输入的参数都是不带前缀的方便Factory进行转换 * 在ObjectDB类型的command定义中为对象的方法名 以实现除数据库和缓存之外的对象调用保证Ni框架可以无缝将直连数据改为DB层方式。只要这个方法按顺序接收参数并返回[[{},{}][{},{}]]格式的数据即可。 * 未来将实现WebSocketDB ### Ni框架的数据源扩展 ```js N.NiMySQLDataFactory = function(){ var _ = this,__= {}; { V.inherit.apply(_,[N.NiDataFactory,[]]); __.MySQLConnection = function(){ var _ = this,__ = {cmds:[]}; { V.inherit.apply(_,[N.NiDataConnection,[]]);………… } _.invoke = (cmd,func)=>V.tryC(()=>{ if(_.transaction){ //事务性处理 __.cmds.push({cmd:cmd,func:func}); } else __.invoke(cmd.command,cmd.params,func); },func); _.commit = function(func){ if(_.transaction){ …… } else func(); }; _.open = function(func){ //https://www.npmjs.com/package/mysql#connection-options _.conn = S.createConnection(_.params); _.conn.connect(err=>V.tryC2(err,()=>{ __.open();func(null); },func)); }; _.close = function(){ _.conn.end(err=>{ if(err) V.showException('',err); delete __.cmds; __.close(); }); }; }; } _.createDBConnection = function(){return new __.MySQLConnection();}; _.backDBConnection = function(conn){conn.close();}; }; })(require('mysql')); ``` * 基本上新的DBFactory类需要继承N.NiDataFactory 然后实现createDBConnection,backDBConnection方法产生新的DBConnection子类实现open,close,invoke,commit方法调用NiDBCommand的属性来实现对应的功能。 * 注意Connection的params参数是从Middler框架对Template的定义中merge进入实例的,所以其需要的参数不同的DB是不一样的。 * DBFactory还继承有createDBCommand方法一般地直接使用原方法里面的NiDataCommand实例来完成。NiDataCommand类拥有connection,params,command参数分别与ni.npcf中定义的命令参数合并后使用,还定义了execute方法用于根据transaction属性,调用connection的invoke方法来具体实现函数体的调用。connection.commit方法由template直接调用 * Ni框架还定义了数据Connection的保持方式分别是 NiStaticResource,NiInstanceResource,NiPoolResource需要在创建template的时候设置。具体名称一目了然不做深入介绍。 ## log ```js //Middler Logger: {type:'Logger', mode: 'static', path: "/com/coooders/io/log",method:"constructorbean",constructorparalength:"1",params: [ {type:'LogRecord',method:"constructor",params:[0,true,true,false]}, {name:'Lisener',type:'LogLisener',method:"constructor",params:[[ {type:'LogTypeTrigger',method:'constructor',params:[0,3]}, {type:'RegexTrigger',method:'constructor',params:[' test ']} ],{type:'LogRecord',method:"constructor",params:[0,true,true,true]},[{type:'FileResource',method:"constructor",params:['./log_{name}.log','yyyyMMddHH']}],true]} ] } ``` * 依托Middler框架,Log框架可以被Middler中直接创建出来,它位于gcl/com/coooders/io/log类中 使用一个LogRecord类作为默认参数类 拥有setLisener方法设置监听器 * LogRecord的构造函数定义了logtype,needDateTime,needDiscript,needSeparator等几个参数用于生成的每行日志信息的内容 * 其中LogType类型为 RELEASE,ERROR,WARN,INFO,DEBUG,TEST,ALL 并按照0~255的顺序较低优先级。needDateTime说明是否需要日期打印,needDiscript说明是否需要日志级别打印,needSeparator说明是否需要每行分隔符----------------------. * name="Lisener" 设置了Logger.setLisener方法将多个lisener设置成为Logger的监听器 * 监听器有四个构造参数,一个是触发器列表,其决定何时真实调用Resource进行记录,一个是formatter格式化类实例这里使用一个LogRecord来负责完成toString(logRecord)方法,第三个是多个数据源需要实现write(data)方法来完成真实的日志打印。第四个参数是触发器的共同触发还是仅一个触发即可记录日志。 * Logger类拥有test,debug,info,error,release等等方法来进行(text,params,num)记录。 * Logger实例还有一个enable(true/false)方法用于是否启动真实的日志记录功能。当然也有dispose方法用于Middler自动销毁Logger对象 ### 如何扩展Logger框架 ```js var L = require('gcl/com/coooders/io/log'); var RegexTrigger = function(reg){ var _ = this,__={reg:reg}; V.inherit.apply(_,[L.ATrigger,[]]); _.test=(record)=>record.data().match(__.reg)!=null || record.params().join(' ').match(__.reg)!=null; }; var FileResource = function(path,nameformat,waittime){ var _ = this,__ = {……}; V.inherit.apply(_,[L.AResource,[]]); _.write=data=>{ …… }; _.close = ()=>{……}; }; ``` * 很简单继承AResource然后分别实现write和close方法即可 * 当然ATrigger的扩展也很简单 继承L.ATrigger实现test(text)=>true/false即可。 ## 其它工具类 ```js var N = require('gcl/com/coooders/net/tool'); N.request({},(err,data)=>{}); N.get(url,(err,data)=>{}); N.post(url,json,(err,data)=>{}); N.postJson(url,json,(err,data)=>{}); N.upload(ulr,filepath,(err,data)=>{});//filepath ;号隔开可上传多个 N.download(ulr,filepath,(err,data)=>{}); var client = new N.WebClient() //保持会话信息的client拥有如上各种方法 var server = N.createReverseProxy(80,[{host:'192.168.1.104',port:8001},{host:'192.168.1.105',port:8002}])//17行的简单强悍反向代理服务效率非常高。仅用于端口负载不区分协议和网址。调用即启动。 var MIME = require('gcl/com/coooders/net/mime'); console.log(MIME.txt);//直接属性引用或者调用get('.txt')方法返回MIME类型 var I= require('gcl/com/coooders/io/tool'); I.copyFile(source,target,(err)=>{});//用于复制文件与linux下使用rename不同,其会先一级一级检查和创建目标文件夹 I.moveFile(source,target,(err)=>{});//移动文件 I.copyDir(source,target,(err)=>{});//复制文件夹 I.moveDir(source,target,(err)=>{});//移动文件夹 var checker = I.checker(()=>{/*final*/});//多流终止检查器 var stream1 = F.openFileStream(path); var stream2 = F.openFileStream(path2); checker.push(stream1); checker.push(stream2); /*read or write or pipe stream1,stream2*/ //checker回调将发生在stream1或者stream2都发生end或者finish事件之后。 ``` ## debugging-and-reporting-problems * that is ALL 在 gcl/com/coooders/module或者gcl/com/coooders/thread 还会持续增加一些各种相关算法计算的有趣的类 * EMail baibing0004@sohu.com * QQ 26342049 ## Running tests ```js node gcl/test.js; ``` ### 配置例子文件 gcl/test.npcf; gcl/ni.npcf; ## Todo * 根据不同DBFactory实现 Procedure与Out参数 * 考虑Linux下cluster server功能的实现 # redis ## String ### redis-string 支持根据key,插入(修改),查询,删除操作,可以同时设置过期时间,支持多条语句,事务 ```sql insert into inserts<key> (key,value) values (?key,?value) datetime 43200000; select value from inserts<key> where key=?key; delete from inserts<key> where key=?key; ``` ## Hash ### redis-hash 支持根据key,插入(修改),查询,删除操作,支持多条语句,事务,为区分其它类型,insert,select,delete前面加h ```sql hinsert into inserth<key> (key,value) values (?key,?value); hselect value from inserth<key> where key=?key; hdelete from inserth<key> where key=?key; ``` ## sort set ### redis-sortset 支持根据key(在redis中对应value,不能重复),插入(修改),查询,删除操作,支持多条语句,事务,为区分其它类型,insert,select,delete前面加z ```sql zinsert into sortset<key> (key,score) values (?key,?score); zselect score from sortset<key> where score=30 order by score desc limit ?limits;//如果要加排序,只能根据score排序,而且有order关键词的话,必需加上where条件(可以随便写时间);如果不是取区间(根据score来判断),limit后面只能是一个数字,表示要正序或倒序取前N条。 zselect score from sortset<key> where score>30 and score<=300 order by score desc limit ?limits,5; //查区间时,必需写区间值(连续),否则不会返回正确结果,zselect后面写score或value即可;limit后面要写两个数值,表示从第几个开始,取N个;order by 表示是要正序取,还是倒序取。 zdelete from sortset<key> where key=?key; ``` ## List ### redis-list 支持根据key,插入,删除操作,允许重复值,先进先出规则,支持多条语句,事务,为区分其它类型,insert,delete前面加l ```sql linsert into alist<key> (key) values (?key); lpopselect value from alist<key> where key=1; //用lpopselect表示出栈操作, where条件可以随便写 ldelete from alist<key> where key=?key; // 删除全部key=?key的数据 ``` ## 后期其它类型与更多操作,进行补充 # 加解密对接 ## SHA1PRNG ### java => nodejs 对于SHA1PRNG的Java类型转换 ```js //引用自 https://www.zhihuclub.com/149867.shtml var V from 'gcl'; console.log(V.SHA1PRNG('abc')); // 这句话等于Java // SecureRandom sr = SecureRandom.getInstance('SHA1PRNG'); // sr.setSeed('abc'.getBytes); // KeyGenerator kg = new KeyGenerator.getInstance('AES'); // kg.init(128,sr); // return kg.generateKey().getEncoded(); ``` ## AES ### java => nodejs 对于使用SHA1PRNG的Java AES类型转换 ```js //引用自 https://www.zhihuclub.com/149867.shtml var V from 'gcl'; console.log(V.xdcrypt_a1e(string,V.SHA1PRNG('abc'));//加密为V.xcrypt_a1e // 这句话等于Java // KeyGenerator keygen=KeyGenerator.getInstance("AES"); //2.根据key规则初始化密钥生成器 //生成一个128位的随机源,根据传入的字节数组 // SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); // random.setSeed(key.getBytes()); // keygen.init(128, random); // //3.产生原始对称密钥 // SecretKey okey=keygen.generateKey(); // //4.获得原始对称密钥的字节数组 // byte [] raw=okey.getEncoded(); // //5.根据字节数组生成AES密钥 // SecretKey skey=new SecretKeySpec(raw, "AES"); // //6.根据指定算法AES自成密码器 // Cipher cipher=Cipher.getInstance("AES"); // //7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密(Decrypt_mode)操作,第二个参数为使用的KEY // cipher.init(Cipher.DECRYPT_MODE, skey); // //8.将加密并编码后的内容解码成字节数组 // byte [] byte_content= new BASE64Decoder().decodeBuffer(content); // /* // * 解密 // */ // byte [] byte_decode=cipher.doFinal(byte_content); // String AES_decode=new String(byte_decode,"utf-8"); // // System.out.println("根据输入的规则"+defaultKey+"解密后的密文是:"+AES_decode); // return AES_decode; ```