用nodejs访问ActiveX对象,以操作Access数据库为例。

起因
有人提问“如果用nodejs访问sql server?”
找了找资料,发现有两类解决方法,使用第三方nodejs插件:https://github.com/orenmazor/node-tds、使用ADODB.ConnectionActiveX对象
参考:
http://stackoverflow.com/questions/857670/how-to-connect-to-sql-server-database-from-javascript
http://stackoverflow.com/questions/4728385/connecting-to-a-remote-microsoft-sql-server-from-node-js

如果用 ActiveX那么在Windows下nodejs将会无所不能,类似写asp。那它们怎么通信?得动手试试

经过
思路
nodejs通过 cscript.exe (windows脚本进程)间接访问 ActiveX
cscript能解析 jscriptvbscript两种脚本,无疑为方便维护选 jscript开发。
参考: http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/cscript_overview.mspx?mfr=true

需解决的问题
1、跨进程通信
新版的nodejs里增加了对子进程的操作,跨进程通信不是问题。
http://nodejs.org/docs/latest/api/all.html#child_Processes
var   util = require( 'util' ),
    exec = require( 'child_process' ).exec,
    child;

child = exec( 'cat *.js bad_file | wc -l' ,
    function   (error, stdout, stderr) {
    console.log( 'stdout: '   + stdout);
    console.log( 'stderr: '   + stderr);
      if   (error !== null) {
      console.log( 'exec error: '   + error);
    }
});
如例我们可以拿到控制台的输出内容 stdout 

2、数据库访问相关ActiveX,ADODB.Connection
参考: http://msdn.microsoft.com/en-us/library/windows/desktop/aa746471%28v=vs.85%29.aspx
var   connection =   new   ActiveXObject( "ADODB.Connection" );
var   result =   'ok' ;
try {
    connection.Open( "Provider=Microsoft.Jet.OLEDB.4.0;Data Source="   + params.accessfile);
    connection.Execute(params.sql);
}   catch (ex){
    result = ex.message;
}
return   {
    result: result
};
connection.Open( connectionString),链接字符串参数可以设置访问sql server。
参考: http://www.connectionstrings.com/sql-server-2005

3、为方便维护,特别将cscript和nodejs的脚本合并,用 typeof exports判断当前运行环境。

4、字符编码cscript代码使用ascii编码
非ascii码字符进行“\uHHHH”Unicode编码。

5、命令行字符需转义,双引号、百分号在命令行有特殊意义。
参数传递使用base64编码,避免冲突
cscript环境MSXML2.DOMDocument可以做base64编解码
function base64Decode(base64){
     var  xmldom =  new  ActiveXObject( "MSXML2.DOMDocument" );
     var  adostream =  new  ActiveXObject( "ADODB.Stream" );
     var  temp = xmldom.createElement( "temp" );
    temp.dataType =  "bin.base64" ;
    temp.text = base64;

    adostream.Charset =  "utf-8" ;
    adostream.Type =  1 ;  // 1=adTypeBinary 2=adTypeText
    adostream.Open();
    adostream.Write(temp.nodeTypedValue);
    adostream.Position =  0 ;
    adostream.Type =  2 ;  // 1=adTypeBinary 2=adTypeText
     var  result = adostream.ReadText(- 1 );  // -1=adReadAll
    adostream.Close();
    adostream = null;
    xmldom = null;
     return  result;
}

总结
调用流程
1、创建子进程,传递经过编码的参数;
2、子进程处理完毕将数据JSON格式化输出到控制台;(子进程自动结束)
3、读取控制台的数据,执行回调函数。

优势
1、使nodejs拥有访问ActiveX对象的能力;
2、实现简单,开发维护方便。

劣势
1、只能运行在Windows平台;
2、数据编解码会消耗更多cpu;
3、每次调用需要创建一个子进程重新连接。(可改进)

总结
1、具有一定实用性;
2、跨进程通信性能可继续探索。


模块代码:

var Access = {
	create: function(params){
		var fso = new ActiveXObject("Scripting.FileSystemObject");
		var result = 'ok';
		if (!fso.FileExists(params.accessfile)){
			var adoxcatalog = new ActiveXObject("ADOX.Catalog");
			try {
				adoxcatalog.Create("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + params.accessfile);
			} catch(ex) {
				result = ex.message;
				return;
			}
			adoxcatalog = null;
		} else {
			result = 'exists';
		}
		return {
			result: result
		};
	},
	existsTable: function(params){
		var connection = new ActiveXObject("ADODB.Connection");
		var result = 'ok', exists = false;
		try{
			connection.Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + params.accessfile);
			var recordset = connection.OpenSchema(20/*adSchemaTables*/);
			recordset.MoveFirst();
			while (!recordset.EOF){
				if (recordset("TABLE_TYPE") == "TABLE" && recordset("TABLE_NAME") == params.tablename){
					exists = true;
					break;
				}
				recordset.MoveNext();
			}
			recordset.Close();
			recordset = null;
		} catch(ex){
			result = ex.message;
		}
		return {
			"result": result,
			"exists": exists
		};
	},
	execute: function(params){
		var connection = new ActiveXObject("ADODB.Connection");
		var result = 'ok';
		try{
			connection.Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + params.accessfile);
			connection.Execute(params.sql);
		} catch(ex){
			result = ex.message;
		}
		return {
			result: result
		};
	},
	query: function(params){
		var connection = new ActiveXObject("ADODB.Connection");
		var result = 'ok', records = [];
		try{
			connection.Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" + params.accessfile);
			var recordset = new ActiveXObject("ADODB.Recordset");
			recordset.Open(params.sql, connection);
			var fields = [];
			var enumer = new Enumerator(recordset.Fields);
			for (; !enumer.atEnd(); enumer.moveNext()){
				fields.push(enumer.item().name);
			}

			recordset.MoveFirst();
			while (!recordset.EOF) {
				var item = {};
				for (var i = 0; i < fields.length; i++){
					var fieldname = fields[i];
					item[fieldname] = recordset(fieldname).value;
				}
				records.push(item);
				recordset.MoveNext();
			}
			recordset.Close();
			recordset = null;
		} catch(ex){
			result = ex.message;
		}
		return {
			result: result,
			records: records
		};
	}
};

if (/^u/.test(typeof exports)){ // cscript
	void function(){
		//from http://tangram.baidu.com/api.html#baidu.json
		var JSON = {
			stringify: (function () {
				/**
				 * 字符串处理时需要转义的字符表
				 * @private
				 */
				var escapeMap = {
					"\b": '\\b',
					"\t": '\\t',
					"\n": '\\n',
					"\f": '\\f',
					"\r": '\\r',
					'"' : '\\"',
					"\\": '\\\\'
				};
				
				/**
				 * 字符串序列化
				 * @private
				 */
				function encodeString(source) {
					if (/["\\\x00-\x1f]/.test(source)) {
						source = source.replace(
							/["\\\x00-\x1f]/g, 
							function (match) {
								var c = escapeMap[match];
								if (c) {
									return c;
								}
								c = match.charCodeAt();
								return "\\u00" 
										+ Math.floor(c / 16).toString(16) 
										+ (c % 16).toString(16);
							});
					}
					return '"' + source + '"';
				}
				
				/**
				 * 数组序列化
				 * @private
				 */
				function encodeArray(source) {
					var result = ["["], 
						l = source.length,
						preComma, i, item;
						
					for (i = 0; i < l; i++) {
						item = source[i];
						
						switch (typeof item) {
						case "undefined":
						case "function":
						case "unknown":
							break;
						default:
							if(preComma) {
								result.push(',');
							}
							result.push(JSON.stringify(item));
							preComma = 1;
						}
					}
					result.push("]");
					return result.join("");
				}
				
				/**
				 * 处理日期序列化时的补零
				 * @private
				 */
				function pad(source) {
					return source < 10 ? '0' + source : source;
				}
				
				/**
				 * 日期序列化
				 * @private
				 */
				function encodeDate(source){
					return '"' + source.getFullYear() + "-" 
						+ pad(source.getMonth() + 1) + "-" 
						+ pad(source.getDate()) + "T" 
						+ pad(source.getHours()) + ":" 
						+ pad(source.getMinutes()) + ":" 
						+ pad(source.getSeconds()) + '"';
				}
				
				return function (value) {
					switch (typeof value) {
					case 'undefined':
						return 'undefined';
						
					case 'number':
						return isFinite(value) ? String(value) : "null";
						
					case 'string':
						return encodeString(value).replace(/[^\x00-\xff]/g, function(all) {
							return "\\u" + (0x10000 + all.charCodeAt(0)).toString(16).substring(1);
						});
					case 'boolean':
						return String(value);
						
					default:
						if (value === null) {
							return 'null';
						}
						if (value instanceof Array) {
							return encodeArray(value);
						}
						if (value instanceof Date) {
							return encodeDate(value);
						}
						var result = ['{'],
							encode = JSON.stringify,
							preComma,
							item;
							
						for (var key in value) {
							if (Object.prototype.hasOwnProperty.call(value, key)) {
								item = value[key];
								switch (typeof item) {
								case 'undefined':
								case 'unknown':
								case 'function':
									break;
								default:
									if (preComma) {
										result.push(',');
									}
									preComma = 1;
									result.push(encode(key) + ':' + encode(item));
								}
							}
						}
						result.push('}');
						return result.join('');
					}
				};
			})(),
			parse: function (data) {
				return (new Function("return (" + data + ")"))();
			}
		}

		//http://blog.csdn.net/cuixiping/article/details/409468
		function base64Decode(base64){
			var xmldom = new ActiveXObject("MSXML2.DOMDocument");
			var adostream = new ActiveXObject("ADODB.Stream");
			var temp = xmldom.createElement("temp");
			temp.dataType = "bin.base64";
			temp.text = base64;

			adostream.Charset = "utf-8";
			adostream.Type = 1; // 1=adTypeBinary 2=adTypeText
			adostream.Open();
			adostream.Write(temp.nodeTypedValue);
			adostream.Position = 0;
			adostream.Type = 2; // 1=adTypeBinary 2=adTypeText
			var result = adostream.ReadText(-1); // -1=adReadAll
			adostream.Close();
			adostream = null;
			xmldom = null;
			return result;
		}
		WScript.StdOut.Write('<json>');
		var method = Access[WScript.Arguments(0)];
		var result = null;
		if (method){
			result = method(JSON.parse(base64Decode(WScript.Arguments(1))));
		}
		WScript.StdOut.Write(JSON.stringify(result));
		WScript.StdOut.Write('</json>');
	}();
} else { // nodejs
	void function(){
		function json4stdout(stdout){
			if (!stdout) return;
			var result = null;
			
			String(stdout).replace(/<json>([\s\S]+)<\/json>/, function(){
				result = JSON.parse(arguments[1]);
			});
			return result;
		}
		var util = require('util'), exec = require('child_process').exec;
		for (var name in Access){
			exports[name] = (function(funcname){
				return function(params, callback){
					console.log([funcname, params]);
					exec(
						util.format(
							'cscript.exe /e:jscript "%s" %s "%s"', __filename,
							funcname,
							(new Buffer(JSON.stringify(params))).toString('base64')
						),
						function (error, stdout, stderr) {
							if (error != null) {
								console.log('exec error: ' + error);
								return;
							}
							console.log('stdout: ' + stdout);
							callback && callback(json4stdout(stdout));
						}
					);
				}
			})(name);
		}
	}();
}

调用代码:

var access = require('./access.js');
var util = require('util');

var accessfile = 'demo.mdb';
access.create({ accessfile: accessfile }, function(data){
	console.log(data);
});
access.existsTable({ accessfile: accessfile, tablename: 'demo' }, function(data){
	if (data.result == 'ok' && !data.exists){
		access.execute({
			accessfile: 'demo.mdb',
			sql: "CREATE TABLE demo(id Counter Primary key, data Text(100))"
		});
	}
});
access.execute({
	accessfile: 'demo.mdb',
	sql: util.format("INSERT INTO demo(data) VALUES('zswang 路过!%s')", +new Date)
}, function(data){
	console.log(data);
});
access.query({
	accessfile: 'demo.mdb',
	sql: "SELECT * FROM demo"
}, function(data){
	console.log(data);
});

最新代码: http://code.google.com/p/nodejs-demo/source/browse/#svn%2Ftrunk%2Fdatabase

你可能感兴趣的:(sql,数据库,function,server,null,Access,callback)