容联云通讯API的学习过程及 RestAPI Node.js 封装
进入正文~ 公司最近有个活动,会用到云通讯的功能。作为一个全栈工程师自然是对 JavaScript 比较熟一点,所以决定选择 Nodejs 来进行开发。看了一下云通讯的文档,没有 Nodejs 的 SDK (´°̥̥̥̥̥̥̥̥ω°̥̥̥̥̥̥̥̥`) 好在 Rest API 是基于 HTTP 协议的,用其他语言也能很容易地实现接口 (๑>◡<๑)
可能是因为对云通讯这种功能没什么概念吧,看了一遍文档还是云里雾里的。怎么办呢……于是决定对着文档一个一个接口试,试下来以后对云通讯的功能也有了个大概了解了。
理解下来,云通讯大概有三种接口:
- 1. voip 客户端,包括安卓、iOS、Windows 的 Client SDK 和基于 Flash 的 Web SDK,可以分配一个 voip 电话号码,实现类似落地电话一样的功能
- 2. 应用主动发起的 RestAPI,由应用向云通讯发起请求,可以实现子帐号、短信、双向回拨、语音验证码、话单下载以及创建电话会议等等操作
- 3. 云通讯向应用服务器发起请求,根据返回结果做出相应的操作。一般用于 ivr 相关功能
下面从第二种开说吧,因为这种操作比较能掌握主动权 ʅ(‾◡◝)ʃ
首先是子帐号,可以用来在 voip 客户端进行登录。对 RestAPI 的请求大部分需要通过主帐号发起,但是还有一些 API 是需要通过子帐号发起的。所以我们首先尝试来发起一个创建子帐号的请求吧。
创建子账户的文档在
http://docs.cloopen.com/index.php/%E5%88%9B%E5%BB%BA%E5%AD%90%E8%B4%A6%E6%88%B7
看文档上说请求地址是:
/{SoftVersion}/Accounts/{accountSid}/SubAccounts
额,这些是个嘛呢,看不懂啊…… 别急,文档下面还有一个关于“统一请求包头”的链接:
http://docs.cloopen.com/index.php/Rest%E4%BB%8B%E7%BB%8D#2_.E7.BB.9F.E4.B8.80.E8.AF.B7.E6.B1.82.E5.8C.85.E5.A4.B4
首先有一个 BaseURL:
https://sandboxapp.cloopen.com:8883/2013-12-26
从 sandboxapp 可以看出这个是测试用的 BaseURL(从云通讯控制台里可以看到生产的 BaseURL 前面是 https://app.cloopen.com:8883 ),而“2013-12-26”就是请求地址里的 {SoftVersion} 部分。
这个就是请求地址前面的部分,于是上面的请求的完整地址就是:
https://sandboxapp.cloopen.com:8883/2013-12-26/Accounts/{accountSid}/SubAccounts
接下来可以看到有两种 URL 格式:
/Accounts/{accountSid}/{func}/{funcdes}?sig={SigParameter}
/SubAccounts/{subAccountSid}/{func}/{funcdes}?sig={SigParameter}
第一种是通过主帐号发起,第二种是通过子帐号发起的。我们要实现的创建子帐号接口就是第一种,需要传递主帐号信息。大家登录云通讯控制台以后可以看到有一个 ACCOUNT SID, 就是这里的 {accountSid} 了。假设我的 ACCOUNT SID 是 xxxxxx,那请求地址就是:
https://sandboxapp.cloopen.com:8883/2013-12-26/Accounts/xxxxxx/SubAccounts
为了拼出个请求地址就罗里吧嗦写了这么多,是不是很烦?好了好了,下面进入比较重要的部分,就是统一请求包头。
云通讯的接口可能两种格式,即 xml 和 json。作为一个 JS 开发人员当然希望全都用 json 了,可惜有很多接口比如 ivr 相关接口只支持 xml。
在请求头里必须包括 Content-Type,Accept,和 Content-Length。 Content-Type 字段表示发起请求体的格式,Accept 表示希望返回的格式。如果你发出的是 xml,希望返回的也是 xml,那请求头就需要是
Accept:application/xml;
Content-Type:application/xml;charset=utf-8;
Content-Length 是请求体的长度,一般用到的请求库应该会帮忙算出来。我这里用到了 Node.js 的 request 库。
然后还里还必须带另外两个参数: SigParameter 和 Authorization。这两个就需要自己算了,根据文档说明:
2. SigParameter是REST API 验证参数
• URL后必须带有sig参数,例如:sig=ABCDEFG。
• 使用MD5加密(账户Id + 账户授权令牌 + 时间戳)。其中账户Id和账户授权令牌根据url的验证级别对应主账户或子账户。
• 时间戳是当前系统时间,格式"yyyyMMddHHmmss"。时间戳有效时间为24小时,如:20140416142030
• SigParameter参数需要大写
下面是实现:
// 实现一个计算md5的方法
crypto = require('crypto')
md5 = function(str, type='hex'){
md5sum = crypto.createHash('md5')
md5sum.update(str)
return md5sum.digest(type)
}
//计算 sig
// sid 即主账户或子账户的 sid, 本例中为主账户即上面提到的 xxxxxx
// token 是主账户或子账户的 token,本例中为主账户 token, 即控制台里看到的 AUTH TOKEN,假设我的为 yyyyyy
// timestamp 是格式为 yyyyMMddHHmmss 的时间戳,这里就不写生成 timestamp 的函数了
getSig = function(sid, token, timestamp){
return md5(sid + token + timestamp).toUpperCase()
}
看文档还以为 SigParameter 也需要加在请求头里,实践后发现在 querystring 里加一个 sig 参数就可以了
于是请求地址变为
https://sandboxapp.cloopen.com:8883/2013-12-26/Accounts/xxxxxx/SubAccounts?sig=算出来的结果
继续:
3. Authorization是包头验证信息
• 使用Base64编码(账户Id + 冒号 + 时间戳)其中账户Id根据url的验证级别对应主账户或子账户
• 冒号为英文冒号
• 时间戳是当前系统时间,格式"yyyyMMddHHmmss",需与SigParameter中时间戳相同。
Authorization 是需要加在请求头里的。实现:
//实现一个计算 base64 编码的方法
base64 = function(str){
return (new Buffer(str)).toString('base64')
}
getAuthorization = function(sid, timestamp){
return base64(sid + ':' + timestamp)
}
再回头看创建子帐号接口的文档,需要使用 POST 方法,并传递 appId 和 friendlyName。
要创建子帐号,需要先在云通讯控制台创建一个应用,这个 appId 就是创建过的应用的 id。friendlyName 算是子帐号的昵称吧,必填,每个子帐号的 friendlyName 是唯一的。
最后我们希望使用 json 格式来进行交互,于是发起请求的方法大概就是:
request = require('request')
sid = 'xxxxxx'
token = 'yyyyyy'
appId = 'zzzzzz'
friendlyName = 'newuser'
timestamp = getTimestamp()
request({
uri: 'https://sandboxapp.cloopen.com:8883/2013-12-26/Accounts/xxxxxx/SubAccounts',
qs: {sig: getSig(sid, token, timestamp)},
header: {
Accept: 'application/json;',
'Content-Type': 'application/json;charset=utf-8;'
Authorization: getAuthorization(sid, timestamp)
},
body: {
appId: appId,
friendlyName: friendlyName
}
}, function(err, response, body){
//回调函数
console.log(body)
})
结果:
{ statusCode: '000000',
SubAccount:
{ subAccountSid: 'cdxxxxxxxxxxxxxx90',
voipAccount: '8xxxxxxxx7',
dateCreated: '2014-06-01 12:40:06',
voipPwd: 'Hxxxxv',
subToken: '0xxxxxxxxxxxxxxxa' } }
大功告成!
接下来的接口都类似,于是就很容易了。但试到 ivr 相关的接口,却总是报错,一直不明所以,直到发现 ivr 的文档上没有 json 相应格式说明,这才想到:我勒个去,ivr 接口不支持 json!
于是就需要把 js 的对象序列化成 xml 了。放狗搜了一下,发现一个 js2xmlparser,使用也蛮简单的。比方说我们希望得到这样的 xml:
<?xml version='1.0' encoding='utf-8'?>
<Request>
<Appid>111222333444555666777888</Appid>
<CreateConf action="createconfresult.jsp" maxmember="5"/>
</Request>
序列化方法和传入对象格式就是:
js2xmlparser = require('js2xmlparser')
js2xmlparser('Request', {
Appid: '111222333444555666777888',
CreateConf:{
'@': {
'action': 'createconfresult.jsp',
'maxmember': '5'
}
}
})
js2xmlparser 详细的用法大家可以去官网看看:
https://github.com/michaelkourlas/node-js2xmlparser
好在返回的结果仍然可以指定为 'application/json;',这样会返回一个 json 字符串,需要自己调用 JSON.parse 解析一下。
最后把云通讯一些常用的接口封装了一下,写成了一个 Node.js 模块。还没来得及写文档和逐一测试,有兴趣的同学可以先看一下代码,欢迎各种砸砖报 bug~
https://github.com/ipy/yuntongxun