张墨轩的技术宅

不忘初心,方得始终

云之讯融合通讯开放平台Nodejs版本SDK

最近用到云之讯平台的短信和语音验证码功能,因为没有在他们官网上发现nodejs版本的SDK,所以我按照他们的PHP版本实现了一个nodejs版本的SDK供大家使用。原版PHPSDK也一并上传了,方便大家对比,另外就是XML协议部分并未实现。

在您的nodejs项目中用 npm install ucpaas-sdk --save 命令进行安装。

示例代码如下:
var ucpaasClass = require('ucpaas-sdk/lib/ucpaasClass');
var options = {
    accountsid: 'XXXX193c69eaXXXXXXbe89017fcXXXXX',
    token: 'XXXXXXdfe88a37XXXXXX288ccaXXXXXX'
};
var ucpaas = new ucpaasClass(options);

//开发者账号信息查询
ucpaas.getDevinfo(function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//语音验证码
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
var verifyCode = '1256';
var to = '18612345678';
ucpaas.voiceCode(appId, verifyCode, to, function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//短信验证码
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
var to = '18612345678';
var templateId = '16021';
var param = '1256,5';
ucpaas.templateSMS(appId, to, templateId, param, function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//双向回拨
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
var fromClient = '63314039999129';
var to = '13412345678';
var fromSerNum = '4008800800';
var toSerNum = '18612345678';
ucpaas.callBack(appId, fromClient, to, fromSerNum, toSerNum, function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//申请client账号
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
var clientType = '0';
var charge = '0';
var friendlyName = '';
var mobile = '13412345678';
ucpaas.applyClient(appId, clientType, charge, friendlyName, mobile, function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//释放client账号
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
var clientNumber = '63314039999129';
ucpaas.releaseClient(clientNumber, appId, function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//分页获取Client列表
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
ucpaas.getClientList(appId, '0', '100', function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//以Client账号方式查询Client信息
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
ucpaas.getClientInfo(appId, '63314039999129', function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//以手机号码方式查询Client信息
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
ucpaas.getClientInfoByMobile(appId, '18612345678', function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

//应用话单下载
var appId = 'XXXXXX2fd25eXXXXb8XXXbaXXXXXXX7a';
ucpaas.getBillList(appId, 'week', function (status, responseText) {
    console.log('code: ' + status + ', text: ' + responseText);
});

代码已经提交到github上,您可以在如下地址访问到:
https://github.com/phonegapX/ucpaas

同时也已经发布到了npmjs.com上,可以直接在node中用 npm install ucpaas-sdk 命令进行安装。

这次具体改写过程也比较简单,没有太多好说的,唯一一个小知识点就是如何用JS中的闭包来模拟php类中的私有变量和私有函数
比如php中:
<?php
class Ucpaas
{
    private $timestamp;
    public function __construct()
    {
        $this->timestamp = 100;
    }
    private function getResult()
    {
        return $this->timestamp;
    }
    public function getDevinfo()
    {
        return getResult();
    }
}
?>
这个PHP类包含了一个私有变量$timestamp,一个构造函数,一个public函数,一个private函数,那么对应JS怎么写比较好呢,如下:
(function () {
    //模拟PHP中的私有变量
    var timestamp;
    //构造函数
    function Ucpaas() {
        timestamp = 100;
    }
    //模拟private函数
    function getResult() {
        return timestamp;
    }
    //模拟public函数
    Ucpaas.prototype.getDevinfo = function() {
        return getResult();
    };
    //导出类
    module.exports = Ucpaas;
}());

这样就很好的利用了JS中闭包的特性来模拟了一个PHP类。

最后附上云之讯的网址:
http://www.ucpaas.com/


nodejs中process对象浅析

本文是基于win7 32位系统。 通过阅读nodejs项目的源代码可以看到,nodejs由 c/c++和javascript俩部分实现,其中js部分的核心为node.js文件。 既然分为2部分 那么中间通讯配合的问题如何解决,其中有一个很关键的对象process就与此有关。我们来看看此对象。 这个对象在js层面是一个全局对象可以直接使用。他的实现代码在src\node.cc中 主要看看其中的SetupProcessObject函数,下面是部分代码截图:

这些原生函数都对应了js中process对象的方法。比如js代码 process. binding( 'xxx') 实际上就调用了c/c++代码中的Binding函数,同理process.dlopen实际上是调用了c/c++代码中的DLOpen函数.而DLOpen中又调用了uv_dlopen, 对于windows版本的uv_dlopen函数位于deps\uv\src\win\dl.c中,如图:
最终调用了LoadLibaryExW加载dll文件。
回过头来再看看 process. binding 这个函数,那么这个函数到底是干什么用的呢?这个函数实际上是用来绑定原生代码中的内置模块用的,简单来说就是js代码利用process. binding( 'xxx') 获取由c/c++实现的内置模块'xxx'的引用,然后js代码就可以通过这个引用访问内置模块'xxx'所提供的各种功能了。
我们知道node可以加载两种模块 一种由js代码编写的模块,后缀名为.js, 还有一种由c/c++实现的原生模块,后缀名为.node。我们看看nodejs中是如何处理的,实现代码在
lib\moudle.js中,如图:
其中加载.json文件比较简单,这里不做讨论。 我们先看看如何加载.js模块。 可以看到代码将进入module._compile函数,在_compile函数中又经过层层调用获取了内置模块
'contextify'的引用,代码如:var binding = process. binding( 'contextify'); 然后通过这个引用调用runInThisContext等内置模块中提供的功能。此函数位于src\node_contextify.cc中,
如图:
RunInThisContext函数:
RunInThisContext函数中又将调用EvalMachine函数,然后EvalMachine函数中会利用v8引擎执行js代码。至此.js模块加载完毕。

接下来看看如何加载.node原生模块,这个就比较简单了,直接调用process.dlopen就加载原生模块了。如前所叙.node模块其实就是原生模块,比如在windows下面.node模块其实就是dll文件,linux下面其实就是so文件。process.dlopen(js代码)=>DLOpen(c/c++)=>uv_dlopen(c/c++)=>LoadLibaryExW(windows版本)。
自此,我们在js代码中就可以通过全局对象process直接或间接的加载各种各样的模块了,也正因为有了如此能力node平台基本上是无所不能了。
那么js代码中这个全局对象process本身又是怎么获取到的了,答案就在node.js这个文件中。代码如下:
整个文件的入口是个函数,函数只有一个参数 就是process,然后程序会将这个process保存为全局对象,代码如下:
那这个process是哪里传进来的了,这个js入口函数又是由谁来调用的呢,调用代码就在src/node.cc中的LoadEnvironment函数中:
void LoadEnvironment(Environment* env) {
  HandleScope handle_scope(env->isolate());

  V8::SetFatalErrorHandler(node::OnFatalError);
  V8::AddMessageListener(OnMessage);

  // Compile, execute the src/node.js file. (Which was included as static C
  // string in node_natives.h. 'natve_node' is the string containing that
  // source code.)

  // The node.js file returns a function 'f'
  atexit(AtExit);

  TryCatch try_catch;

  // Disable verbose mode to stop FatalException() handler from trying
  // to handle the exception. Errors this early in the start-up phase
  // are not safe to ignore.
  try_catch.SetVerbose( false);

  Local<String> script_name = FIXED_ONE_BYTE_STRING(env->isolate(), "node.js");
  Local<Value> f_value = ExecuteString(env, MainSource(env), script_name);
  if (try_catch.HasCaught())  {
    ReportException(env, try_catch);
    exit(10);
  }
  assert(f_value->IsFunction());
  Local<Function> f = Local<Function>::Cast(f_value);

  // Now we call 'f' with the 'process' variable that we've built up with
  // all our bindings. Inside node.js we'll take care of assigning things to
  // their places.

  // We start the process this way in order to be more modular. Developers
  // who do not like how 'src/node.js' setups the module system but do like
  // Node's I/O bindings may want to replace 'f' with their own function.

  // Add a reference to the global object
  Local<Object> global = env->context()->Global();

#if defined HAVE_DTRACE || defined HAVE_ETW
  InitDTrace(env, global);
#endif

#if defined HAVE_PERFCTR
  InitPerfCounters(env, global);
#endif

  // Enable handling of uncaught exceptions
  // (FatalException(), break on uncaught exception in debugger)
  //
  // This is not strictly necessary since it's almost impossible
  // to attach the debugger fast enought to break on exception
  // thrown during process startup.
  try_catch.SetVerbose( true);

  NODE_SET_METHOD(env->process_object(), "_rawDebug", RawDebug);

  Local<Value> arg = env->process_object();
  f->Call(global, 1, &arg);
}
参见代码最后 f->Call(global, 1, &arg); 就是调用之前的js入口函数,其中arg就是传给js层面的process对象,  Local<Value> arg = env->process_object();
当然c/c++与js之间具体的交互细节就属于v8这个js引擎的工作了,这个又是后话了。


简单解析child_process模块fork方法调用流程

本文测试环境是win7 32位。 先下载nodejs源代码以备查看之用。同时安装好node-inspector环境并配置好,命令:  npm install -g node-inspector
写一段测试代码如下:
var childProcess = require('child_process');
var n = childProcess. fork( './son.js');
保存为parent.js 然后调试运行 node --debug-brk  parent.js  并且打开GoogleChrome进行调试
其中第一行 "(function (exports, require, module, __filename, __dirname) {" 是nodejs内核对我们编写的js代码的包装,将我们写的js代码包装到一个函数内,相关详细可自行查阅资料。
开始跟踪fork方法,跟踪堆栈如下:
首先fork将进入exports.fork。 我们打开源代码查看,源码位于child_process.js中
exports.fork = function (modulePath /*, args, options*/) {

 
 // Get options and args arguments.
 
 var options, args, execArgv;
 
 if ( util. isArray( arguments[ 1])) {
   
 args = arguments [1 ];
   
 options = util ._extend ({}, arguments [2 ]);
  }
 else if (arguments [1 ] && typeof arguments[1] !== 'object' ) {
   
 throw new TypeError( 'Incorrect value of args option');
  }
 else {
   
 args = [];
   
 options = util ._extend ({}, arguments [1 ]);
  }

 
 // Prepare arguments for fork:
 
 execArgv = options .execArgv || process.execArgv;
 
 args = execArgv. concat([modulePath], args);

 
 // Leave stdin open for the IPC channel. stdout and stderr should be the
  // same as the parent's if silent isn't set.
 
 options. stdio = options. silent ? [ 'pipe', 'pipe', 'pipe', 'ipc'] :
      [
0, 1, 2, 'ipc' ];

 
 options. execPath = options. execPath || process. execPath;

 
 return spawn(options.execPath, args, options);
};

流程将从exports.fork=>spawn中。

打开spawn函数,代码仍然位于child_process.js中:
var spawn = exports. spawn = function( /*file, args, options*/) {
 
 var opts = normalizeSpawnArguments.apply( null, arguments);
 
 var options = opts.options;
 
 var child = new ChildProcess();

 
 child. spawn({
   
 file: opts.file,
   
 args: opts.args,
   
 cwd: options .cwd ,
   
 windowsVerbatimArguments : !!options .windowsVerbatimArguments ,
   
 detached: !!options .detached ,
   
 envPairs: opts.envPairs ,
   
 stdio: options .stdio ,
   
 uid: options .uid ,
   
 gid: options .gid
 
 });

 
 return child;
};

流程将从exports.fork=>spawn=>ChildProcess. spawn中

接下来看 ChildProcess. spawn函数,代码仍然位于child_process.js中,此函数比较长 这里只贴出关键代码:
var Process = process. binding( 'process_wrap').Process;
function ChildProcess() {
  .......  
 
 this. _handle = new Process();
  .......
}
ChildProcess. prototype. spawn = function(options) {
  .......
 
 var err = this._handle.spawn(options);
  .......
}

流程将进入this. _handle. spawn中,此函数已经属于原生函数了,原理可自行了解process. binding的实现。
代码位于src\process_wrap.cc中

  NODE_SET_PROTOTYPE_METHOD(constructor, "spawn", Spawn);

  static void Spawn(const FunctionCallbackInfo<Value>& args) {
         .......
         int err = uv_spawn(env->event_loop(), &wrap->process_, &options);
       ......
  }

流程将进入uv_spawn函数,此函数位于deps\uv\src\win\process.c中

int uv_spawn(uv_loop_t* loop,
             uv_process_t* process,
             const uv_process_options_t* options) {
  .........
  if (!CreateProcessW(application_path,
                     arguments,
                     NULL,
                     NULL,
                     1,
                     process_flags,
                     env,
                     cwd,
                     &startup,
                     &info)) {
    /* CreateProcessW failed. */
    err = GetLastError();
    goto done;
  }
  .........
}

这下子终于看到windows下创建进程的api函数CreateProcessW了。
总结:
exports.fork(js层面)=>spawn(js层面)=>ChildProcess. spawn(js层面)=>Spawn(原生层面)=>uv_spawn(原生层面)=>CreateProcessW(操作系统api)


«1»

Powered By Z-Blog 2.2 Prism Build 140101

Copyright phonegap.me Rights Reserved.