腾讯云SDKforJS开发实战

 

前面曾经提到过的,我想要把自然语言处理相关的技术接入到我的毕设微信小程序里面。

由于腾讯云未提供JS的SDK,要自己编写HTTP请求来实现,之前觉得比较麻烦,相关说明文档没有整明白,不想尝试,后来觉得既然是自己选择的路,那么无论再苦再累,都要坚持走下去,无论结果是什么,也算对得起当初自己的豪情壮志了。

再贴一下我的参考文档:腾讯云文智自然语言处理API链接

大致了解了签名方法和技术之后,我就在网上搜关于JS的HmacSHA256加密和Base64编码的函数库,发现了Crypto.js,在官网上吧zip文件下下来之后,参照一篇文章细说CryptoJs使用(微信小程序加密解密)把core.js和sha256.js和enc-base64.js的内容放到一个叫crypto.js文件中,最后再加上module.exports = CryptoJS。(话说我之前看截图里是module.export = CryptoJS,我也就照搬下来,试了半天结果总是不行,后来发现export后面还有个s,真是尬)

随后测试了一下这个函数库,经过测试发现加密的结果与API文档中的样例一致,然后就开始下一步了。

关于具体的格式我也不是很清楚,经过大胆尝试之后总是说有问题,后来只能去学习Python SDK的源码了。

通过源码的探索,我了解到了从最初的传参到生成URL的整体过程,同时也注意到了一些文档中并没有提到的细节和注意事项。然而经过测试之后还是提示说鉴权失败,我真是日了狗了。不过最终我还是成功了,下面是几个记录:

  1. 我下载的SDK是Python SDK,而我要编写的是JS代码。其中的time获取函数不同,其中Python中为int(time.time()),获取到的是整数时间戳,而JS中的则为Date.now()获取到的是以毫秒为单位的所以要做进一步的转化为Math.floor(Date.now()/1000)
  2. 同时要使用SDK必须要带要有版本号如SDK_PYTHON_2.0.13,如果没有则提示错误信息:Exception ‘Version’,而腾讯云中并没有提到这一点,所以我们必须要模拟一个,即加一个这样的参数:RequestClient=SDK_PYTHON_2.0.13
  3. 同时最最他妈的坑爹的一点就是腾讯云中明确提到了签名方式用HmacSHA1或者HmacSHA256都可以,只要参数中说明即可,即加上:SignatureMethod: ‘HmacSHA1’即可,而我之前网上搜的大多使用256,所以我也使用256,但是一直报鉴权失败的问题,可是我的签名方式(拿腾讯云提供的demo测试了一下,得到的签名一致)和参数都没有问题,困扰了好久,最终我把Python的SDK源码改了改,如修改当前时间戳Timestamp、修改随机正整数Nonce等,发现了Timestamp不能乱改,和腾讯云的服务器的时间戳间隔不能超过2小时,同时时间戳一定的情况下Nonce不能重复,后来在Python SDK上尝试使用HmacSHA256发现也报错了!我的哥!又改回HmacSHA1发现又没问题了,不会是现在腾讯云后台不支持256吧,然后我把JS的代码相关部分也改成了使用HmacSHA1,皇天不负有心人,终于他妈的成功了!不行你倒是说一声啊,同时具体的一些细节也不交代清楚。

同时还有的是JS实现网络调用的库,因为不同的引擎不同的平台所以实现肯定会有所不同,我最终搜索到了Fly.js这个很好用的库。

简体中文版说明文档链接福利在这里!Flyio帮助文档

不同的环境Fly的入口文件不同。

前面提到了腾讯云没有JS的SDK,而作为读过了Python SDK的程序员,难以控制自己想要编写自己的SDK的冲动,一方面是也算是一个挑战和尝试,一方面是这样也方便了自己的使用,最后好像是莫名的强迫症,如果不做的话实在是受不了。

于是大致样式就开始照搬Python SDK的实现,同时由于自己仅仅是小尝试,简单成功就可以了,至于module的健壮性和架构也就忽略掉了。

我的是在Webstorm上编写调试JS的,引擎是NodeJs。由于报import、from的错,所以只能含泪把es6的语法写成了CommonJS的形式。

最终的成果长成这么个吊样子,文件名为qcloud.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/*不同的应用平台fly.js的引入方式不同,同时fly的内部实现也略有不同
毕竟不同的平台js的引擎也不同,所以相关网络请求访问的底层实现也会有所不同
比如说浏览器环境中是构造XmlHttpRequest执行ajax调用
而微信小程序则是使用底层api:wx.request函数
node环境依赖http模块及net模块的底层实现
详情参见 https://wendux.github.io/dist/#/doc/flyio/readme
不同环境下文件引入实例:
具体文件自己去找,去下
浏览器环境
const Fly = require('./fly.umd.min')
微信小程序环境
const Fly = require('./fly')
node环境
const Fly=require("flyio/src/node")*/
//以下是node环境下引入fly的方式
const Fly = require("flyio/src/node");
const CryptoJS = require('./crypto');

function sortKeys(obj) {
let newobj = {};
Object.keys(obj).sort().forEach(value => newobj[value] = obj[value]);
return newobj
}


function codeObj(obj) {
let arr = [];
for (let k in obj) {
arr.push(k + '=' + obj[k])
}
return arr.join('&')
}


class QCloud {
constructor(){
this.config = {
protocol:'https://',
path:'/v2/index.php',
method:'GET',
region:'gz',
domain:'.api.qcloud.com',
requestClient:'SDK_PYTHON_2.0.13',
signatureMethod:'HmacSHA1',
secretId:'',
secretKey:''
}
this.params = {}
}
init(config){
Object.assign(this.config,config);
this.fly = new Fly;
this.fly.config.baseURL = this.config.protocol+this.config.module+this.config.domain
}

getParams() {
return this.config.method+this.config.module+this.config.domain+
this.config.path+'?'+codeObj(sortKeys(this.params))
}

getUrl(action,params){
this.initParams();
this.params.Action = action;
Object.assign(this.params,params);
this.sign();
return this.config.protocol+this.config.module+this.config.domain+this.config.path+'?'+this.getParams()
}
use(action,params,fthen,fcatch){
this.initParams();
this.params.Action = action;
Object.assign(this.params,params)
this.sign()
this.request(fthen,fcatch)
}
sign(){
let pa = this.getParams();
let signnature = CryptoJS.enc.Base64.stringify(CryptoJS.HmacSHA1(pa,this.config.secretKey));
this.params.Signature = signnature
}
initParams(){
this.params = {
Region: this.config.region,
Nonce: Math.floor(Math.random()*Number.MAX_SAFE_INTEGER),
Timestamp: Math.floor(Date.now()/1000),
RequestClient: this.config.requestClient,
SignatureMethod: this.config.signatureMethod,
SecretId: this.config.secretId,
}
}
request(fthen,fcatch){
this.fly.get(this.config.path,this.params).then(fthen).catch(fcatch)
}

}

module.exports = new QCloud();

这个不写注释的问题好像确实要改。。。

下面是测试代码文件(testMyModule.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const QCloud = require('./qclooud')
QCloud.init({
module:'wenzhi',
secretId:'自己的secretId',
secretKey:'自己的secretKey'
})

let params = {
'content':'人生苦短,please Python。太祖、刘邦、朱元璋哪个更厉害?!'
}

QCloud.use('TextClassify',params,function (responce) {
console.log(responce.data)
},function (error) {
console.log(error)
})

下面是输出结果:

1
{"code":0,"message":"","codeDesc":"Success","classes":[{"class":"\u6587\u5316","class_num":61,"conf":0.713},{"class":"\u5386\u53f2","class_num":95,"conf":0.221},{"class":"\u672a\u5206\u7c7b","class_num":0,"conf":0.066}]}

关于上面\u问题的出现,我们使用eval函数把json string转成json object就可以了,如:

1
console.log(eval('('+responce.data+')'))

即可得下面的结果:

1
2
3
4
5
6
7
{ code: 0,
message: '',
codeDesc: 'Success',
classes:
[ { class: '文化', class_num: 61, conf: 0.713 },
{ class: '历史', class_num: 95, conf: 0.221 },
{ class: '未分类', class_num: 0, conf: 0.066 } ] }

因为JS本身异步调用的特点,使得我们对于事件的业务逻辑处理许多时候都是通过回调函数来解决而并非Python常见的同步处理。
下面是相关的Python调用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from QcloudApi.qcloudapi import QcloudApi

from settings import secretId,secretKey

import json

from pprint import pprint

module = 'wenzhi'

config = {
'secretId': secretId,
'secretKey': secretKey,
'Region': 'gz',
'method': 'POST'
}

action = 'TextClassify' #文本分类

params = {
'content':'人生苦短,please Python。太祖、刘邦、朱元璋哪个更厉害?!'
}

try:
service = QcloudApi(module,config)

print(service.generateUrl(action,params))

pprint(json.loads(service.call(action,params)))

except Exception as e:
print('Exception',e)

看起来我实现的module还是有点样子的嘛哈哈!

我已经把相关的源码放到了我的GitHub上,欢迎各位有需要的看客们下载使用!