├── README ├── TimRestApiGear.js ├── TimSendGroupMsgPressTest.js ├── config └── config.js └── lib ├── TimGenerateSig.js └── TimRestApi.js /README: -------------------------------------------------------------------------------- 1 | 工具目录结构: 2 | |-- README 3 | |-- TimRestApiGear.js 4 | |-- TimSendGroupMsgPressTest.js 5 | |-- config 6 | | |-- config.js 7 | `-- lib 8 | |-- TimGenerateSig.js 9 | |-- TimRestApi.js 10 | 11 | config.js 为该工具所需APP基础配置信息。 12 | 13 | TimRestApiGear.js 为使用接口示例工具。 14 | 15 | TimRestApi.js 为接口具体实现; 16 | TimGenerateSig.js 用来生成usersig; 17 | TimSendGroupMsgPressTest.js 为群内发送消息压力测试脚本。 18 | 19 | 20 | 1.集成说明 21 | 支持独立模式 22 | 配置config.js文件,其中: 23 | identifier 为APP管理者账户; 24 | privateKey 为本地私钥位置; 25 | expireAfter 公钥有效时常,不填默认一个月 26 | 执行node TimRestApiGear.js 可看到接口示例工具访问命令(用法)。 27 | 28 | 2.压力测试脚本使用说明 29 | 该工具主要测试在群内发送消息时,客户端接收消息能力; 30 | 执行node TimSendGroupMsgPressTest.js 可看到压测脚本工具访问命令(用法); 31 | 压测前需要确保群组存在,并且群内人数尽可能少,减少其他因素影响。 32 | 33 | 34 | -------------------------------------------------------------------------------- /TimRestApiGear.js: -------------------------------------------------------------------------------- 1 | var config = require('./config/config.js'); 2 | var TimRestAPI = require('./lib/TimRestApi.js'); 3 | var send_group_msg = function(api, serviceName, commandName, dataArray) { 4 | var i = 0; 5 | var msgFrom = dataArray[i++]; 6 | var groupId = dataArray[i++]; 7 | var msgText = dataArray[i++]; 8 | var reqBody = { 9 | GroupId: groupId, 10 | MsgBody: [{ 11 | MsgType: "TIMTextElem", 12 | From_Account: msgFrom, 13 | MsgContent: { 14 | Text: msgText 15 | } 16 | }] 17 | }; 18 | api.request(serviceName, commandName, reqBody, 19 | function(err, data) { 20 | if (err) { 21 | console.log(err); 22 | return; 23 | } 24 | console.log(data); 25 | }); 26 | } 27 | 28 | var send_msg = function(api, serviceName, commandName, dataArray) { 29 | var i = 0; 30 | var fromId = dataArray[i++]; 31 | var toId = dataArray[i++]; 32 | var msgText = dataArray[i++]; 33 | 34 | var reqBody = { 35 | "To_Account": toId, 36 | //消息接收者 37 | "From_Account": fromId, 38 | //选填字段 39 | "MsgRandom": 123, 40 | //消息随机数 41 | "MsgBody": [{ 42 | "MsgType": "TIMTextElem", 43 | //文本消息类型 44 | "MsgContent": { 45 | "Text": msgText //具体文本消息 46 | } 47 | }] 48 | } 49 | api.request(serviceName, commandName, reqBody, 50 | function(err, data) { 51 | if (err) { 52 | console.log(err); 53 | return; 54 | } 55 | console.log(data); 56 | }); 57 | } 58 | 59 | var get_group_info = function(api, serviceName, commandName, dataArray) { 60 | var groupId = dataArray[0]; 61 | var reqBody = { 62 | "GroupIdList": [groupId] 63 | } 64 | api.request(serviceName, commandName, reqBody, 65 | function(err, data) { 66 | if (err) { 67 | console.log(err); 68 | return; 69 | } 70 | console.log(data); 71 | }); 72 | } 73 | 74 | function begin_process() { 75 | if (process.argv.length < 4) { 76 | console.log("usage:"); 77 | console.log("node " + process.argv[1] + " msg_interface.js (server_name) (command) args...eg:"); 78 | console.log("node " + process.argv[1] + " msg_interface.js openim sendmsg (account_id) (receiver) (text_content) 单发消息"); 79 | console.log("node " + process.argv[1] + " msg_interface.js group_open_http_svc send_group_msg (account_id) (group_id) (text_content) 群组中发送普通消息"); 80 | console.log("node " + process.argv[1] + " msg_interface.js group_open_http_svc get_group_info (group_id) 获取群组信息"); 81 | console.log("注:"); 82 | console.log("默认从配置文件config/config.js读取配置信息,其中:"); 83 | console.log("identifier 为APP管理员账户"); 84 | console.log('private_pem_path 为独立模式下私钥本地路径'); 85 | return - 1; 86 | } 87 | var serviceName = process.argv[2]; 88 | var commandName = process.argv[3]; 89 | var commandKey = serviceName + "." + commandName; 90 | 91 | var commandName; 92 | var commadKey; 93 | 94 | // command dictionary 95 | var commandArray = { 96 | "openim.sendmsg": send_msg, 97 | "group_open_http_svc.send_group_msg": send_group_msg, 98 | "group_open_http_svc.get_group_info": get_group_info 99 | }; 100 | 101 | if (!commandArray.hasOwnProperty(commandKey)) { 102 | console.log(commandKey); 103 | console.log("service_name or command_name error"); 104 | return; 105 | } 106 | 107 | var dataArray = new Array(); 108 | for (var i = 4; i < process.argv.length; i++) { 109 | dataArray[i - 4] = process.argv[i]; 110 | } 111 | 112 | var api = new TimRestAPI(config); 113 | api.init(function(err, data) { 114 | if (err) { 115 | // deal error 116 | console.log(err); 117 | return; 118 | } 119 | commandValue = commandArray[commandKey]; 120 | commandValue(api, serviceName, commandName, dataArray); 121 | }); 122 | } 123 | 124 | begin_process(); 125 | 126 | -------------------------------------------------------------------------------- /TimSendGroupMsgPressTest.js: -------------------------------------------------------------------------------- 1 | var config = require('./config/config.js'); 2 | var TimRestAPI = require('./lib/TimRestApi.js'); 3 | var util = require('util'); 4 | 5 | function begin_press_test(api, groupId, fromId, sendInterval, totalCount) { 6 | var sendedCount = 0; //count of package already send 7 | var cycleNum = 1; //loop manage num 8 | var increaseSeq = 1; //increase num, use for print local seq 9 | var beginTime = (new Date).getTime(); 10 | var failNum = 0; //rsp return fail num 11 | var errNum = 0; // exception error 12 | var reqBody = { 13 | "GroupIdList": [ //groupid list 14 | groupId] 15 | } 16 | api.request("group_open_http_svc", "get_group_info", reqBody, 17 | function(err, data) // get seq before send_group_msg 18 | { 19 | if (err) { 20 | console.log(err); 21 | return; 22 | } 23 | if (data["ActionStatus"] != "OK") { 24 | console.log("fail at get_group_info before group_msg request\n"); 25 | var strData = JSON.stringify(data); 26 | console.log(strData); 27 | return; 28 | } 29 | var beforeSeq = data["GroupInfo"][0].NextMsgSeq; 30 | var localSeq = increaseSeq; 31 | var timmer = setInterval(doRequest); 32 | function doRequest() { 33 | if (sendedCount++>=totalCount) { 34 | clearInterval(timmer); 35 | cycleNum--; 36 | return; 37 | } 38 | cycleNum++; 39 | var reqBody = { 40 | GroupId: groupId, 41 | MsgBody: [{ 42 | MsgType: "TIMTextElem", 43 | From_Account: fromId, 44 | MsgContent: { 45 | Text: util.format("hello, local seq = %d", localSeq) 46 | } 47 | }] 48 | }; 49 | var startTime = (new Date).getTime(); 50 | api.request("group_open_http_svc", "send_group_msg", reqBody, 51 | function(err, data) { 52 | if (err) { 53 | console.log(err); 54 | errNum++; 55 | return; 56 | } 57 | 58 | if (data["ActionStatus"] != "OK") { 59 | failNum++; 60 | } 61 | cycleNum--; 62 | localSeq = increaseSeq++; 63 | if (!err) { 64 | var strData = JSON.stringify(data); 65 | console.log('local seq = %d, timecost = %d, response body: %s', localSeq, ((new Date).getTime() - startTime), strData); 66 | } 67 | if (cycleNum == 0) { 68 | console.log('total cost time: %d ms', (new Date).getTime() - beginTime); 69 | 70 | var reqBody = { 71 | "GroupIdList": [groupId] 72 | } 73 | api.request("group_open_http_svc", "get_group_info", reqBody, 74 | function(err, data) { 75 | if (err) { 76 | console.log(err); 77 | return; 78 | } 79 | if (data["ActionStatus"] != "OK") { 80 | console.log("fail at get_group_info after group_msg request\n"); 81 | var strData = JSON.stringify(data); 82 | console.log(strData); 83 | return; 84 | } 85 | endSeq = data["GroupInfo"][0].NextMsgSeq; 86 | console.log('msg seq before: %d, after: %d', beforeSeq, endSeq); 87 | console.log('successNum is %s, totalNum is %s, errNum is %s, failNum is %s', totalCount - errNum - failNum, totalCount, errNum, failNum); 88 | }); 89 | }; 90 | }); 91 | } 92 | }); 93 | } 94 | 95 | function begin_process() { 96 | if (process.argv.length < 6) { 97 | console.log("usage:"); 98 | console.log("node " + process.argv[1] + " (groupid) (from_id) (speed) (totalCount)"); 99 | console.log(" groupid 群组ID,脚本在该群内发消息进行压测"); 100 | console.log(" from_id 消息发送者的id"); 101 | console.log(" speed 在群内每秒发送消息的条数"); 102 | console.log(" totalCount 脚本将在群内发消息总条数"); 103 | console.log("注: "); 104 | console.log("默认从配置文件config/config.js读取配置信息,其中:"); 105 | console.log("identifier 为APP管理员账户"); 106 | console.log("private_pem_path 为独立模式下私钥本地路径"); 107 | return - 1; 108 | } 109 | var groupId = process.argv[2]; 110 | var fromId = process.argv[3]; 111 | var speed = parseInt(process.argv[4]); 112 | if (speed < 0 || speed > 150) { 113 | console.error('invalid speed (0 < speed < 150)'); 114 | process.exit(0); 115 | } 116 | var sendInterval = 1000 / speed; 117 | 118 | var totalCount = parseInt(process.argv[5]); 119 | var api = new TimRestAPI(config); 120 | api.init(function(err, data) { 121 | if (err) { 122 | //deal error 123 | console.log(err); 124 | return; 125 | } 126 | begin_press_test(api, groupId, fromId, sendInterval, totalCount); 127 | }); 128 | } 129 | 130 | begin_process(); 131 | -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | sdkAppid: '1400001111', 3 | identifier: 'admin', 4 | accountType: '111', 5 | version: '201512300000', 6 | privateKey : '/path/private.pem', 7 | expireAfter: 30 * 24 * 3600, 8 | }; 9 | 10 | -------------------------------------------------------------------------------- /lib/TimGenerateSig.js: -------------------------------------------------------------------------------- 1 | var crypto = require('crypto'); 2 | var zlib = require('zlib'); 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | var base64url = {}; 7 | 8 | base64url.unescape = function(str) { 9 | return (str + Array(5 - str.length % 4)).replace(/_/g, '=').replace(/\-/g, '/').replace(/\*/g, '+'); 10 | }; 11 | 12 | base64url.escape = function(str) { 13 | return str.replace(/\+/g, '*').replace(/\//g, '-').replace(/=/g, '_'); 14 | }; 15 | 16 | base64url.encode = function(str) { 17 | return this.escape(new Buffer(str).toString('base64')); 18 | }; 19 | 20 | base64url.decode = function(str) { 21 | return new Buffer(this.unescape(str), 'base64').toString(); 22 | }; 23 | 24 | var Sig = function(config) { 25 | this.sdkAppid = config.sdkAppid; 26 | this.accountType = config.accountType; 27 | this.identifier = config.identifier; 28 | this.appidAt3rd = config.sdkAppid; 29 | this.expireAfter = (config.expireAfter || 30 * 24 * 3600).toString(); 30 | this.expireUntil = parseInt(Date.now() / 1000) + parseInt(this.expireAfter); 31 | this.privateKey = fs.readFileSync(path.join(__dirname, config.privateKey)).toString(); 32 | }; 33 | 34 | Sig.prototype._genSignContent = function(obj) { 35 | var ret = ''; 36 | for (var i in obj) { 37 | ret += i + ':' + obj[i] + '\n'; 38 | } 39 | return ret; 40 | }; 41 | 42 | Sig.prototype.genSig = function(evalSig, callback) { 43 | var obj = { 44 | 'TLS.appid_at_3rd': this.appidAt3rd, 45 | 'TLS.account_type': this.accountType, 46 | 'TLS.identifier': this.identifier, 47 | 'TLS.sdk_appid': this.sdkAppid, 48 | 'TLS.time': (Math.floor(Date.now() / 1000)).toString(), 49 | 'TLS.expire_after': this.expireAfter 50 | }; 51 | var content = this._genSignContent(obj); 52 | try { 53 | var signer = crypto.createSign('sha256'); 54 | signer.update(content, 'utf8'); 55 | var usrsig = signer.sign(this.privateKey, 'base64'); 56 | } catch(err) { 57 | callback(err); 58 | return; 59 | } 60 | obj['TLS.sig'] = usrsig; 61 | var text = JSON.stringify(obj); 62 | var compressed = zlib.deflateSync(new Buffer(text)).toString('base64'); 63 | evalSig(base64url.escape(compressed), this.expireUntil); 64 | if (callback) { 65 | callback(); 66 | } 67 | }; 68 | 69 | module.exports = Sig; -------------------------------------------------------------------------------- /lib/TimRestApi.js: -------------------------------------------------------------------------------- 1 | var Sig = require('./TimGenerateSig.js'); 2 | var https = require('https'); 3 | var util = require('util'); 4 | var maxSock = 10000; 5 | var keepAliveAgent = new https.Agent({ 6 | keepAlive: true, 7 | maxSockets: maxSock 8 | }); 9 | 10 | var TimRestAPI = function(config) { 11 | this.sdkAppid = config.sdkAppid; 12 | this.identifier = config.identifier; 13 | this.config = config; 14 | } 15 | 16 | TimRestAPI.prototype.init = function(callback) { 17 | var self = this; 18 | // get usersig 19 | var sig = new Sig(this.config); 20 | sig.genSig(function(usersig, expireUntil) { 21 | self.usersig = usersig; 22 | self.expireUntil = expireUntil; 23 | }, 24 | callback); 25 | } 26 | 27 | TimRestAPI.prototype.request = function(serviceName, cmdName, reqBody, callback) { 28 | var self = this; 29 | if (this.expireUntil < (Date.now() / 1000)) { 30 | var sig = new Sig(this.config); 31 | sig.genSig(function(usersig, expireUntil) { 32 | self.usersig = usersig; 33 | self.expireUntil = expireUntil; 34 | }); 35 | } 36 | var urlPath = util.format("/v4/%s/%s?usersig=%s&identifier=%s&sdkappid=%s&contenttype=json", serviceName, cmdName, this.usersig, this.identifier, this.sdkAppid); 37 | var requestArg = { 38 | agent: keepAliveAgent, 39 | host: 'console.tim.qq.com', 40 | method: 'post', 41 | path: urlPath 42 | } 43 | var chunkList = []; 44 | var req = https.request(requestArg, 45 | function(rsp) { 46 | rsp.setEncoding('utf8'); 47 | rsp.on('data', 48 | function(chunk) { 49 | chunkList.push(chunk); 50 | }); 51 | rsp.on('error', 52 | function(err) { 53 | if (callback) { 54 | callback(err); 55 | } 56 | }); 57 | rsp.on('end', 58 | function() { 59 | rspBody = chunkList.join(''); 60 | try { 61 | var rspJsonBody = JSON.parse(rspBody); 62 | } catch(err) { 63 | if (callback) { 64 | callback(err); 65 | } 66 | } 67 | if (callback) { 68 | callback(null, rspJsonBody); 69 | } 70 | }); 71 | }); 72 | req.write(JSON.stringify(reqBody)); 73 | req.end(); 74 | } 75 | 76 | module.exports = TimRestAPI; 77 | --------------------------------------------------------------------------------