详解nodejs 开发企业微信第三方应用入门教程


Posted in NodeJs onMarch 12, 2019

最近公司要开发企业微信端的 Worktile,以前做的是企业微信内部应用,所以只适用于私有部署客户,而对于公有云客户就无法使用,所有就准备开发企业微信的第三方应用,本文主要介绍在调研阶段遇到的山珍海味。

开发之前你需要前注册为第三方服务商,然后用第三方服务商的账号创建应用,创建之后只需要管理员授权应用,第三方服务商即可为用户提供服务。

一、注册第三发服务商

登陆 服务商官网 ,注册成为服务商,并登陆服务商管理后台。

二、配置开发信息

在创建应用之前,首先要配置好通用开发参数

详解nodejs 开发企业微信第三方应用入门教程

在填写系统事件接收 url 时,要正确响应企业微信验证 url 的请求。这个可以参考企业微信后台,自建应用的接收消息的 api 设置。

在企业的管理端后台,进入需要设置接收消息的目标应用,点击“接收消息”的“设置API接收”按钮,进入配置页面。

详解nodejs 开发企业微信第三方应用入门教程

要求填写应用的 URL、Token、EncodingAESKey 三个参数

  • URL 是企业后台接收企业微信推送请求的访问协议和地址,支持 http 或 https 协议(为了提高安全性,建议使用 https)。
  • Token 可由企业任意填写,用于生成签名。
  • EncodingAESKey 用于消息体的加密,是 AES 密钥的 Base64 编码。

 2.1 验证 url 有效性

当点击保存的时候,企业微信会发生一条 get 请求到填写的 url

比如 url 设置的是 https://api.worktile.com , 企业微信将发送如下验证请求:

请求地址: https://api.worktile.com/?msg_signature=ASDFQWEXZCVAQFASDFASDFSS×tamp=13500001234&nonce=123412323&echostr=ENCRYPT_STR

参数 说明
msg_signature 企业微信加密签名,msg_signature 结合了企业填写的 token、请求中的 timestamp、nonce 参数、加密的消息体
timestamp 时间戳
nonce 随机数
echostr 加密的字符串。需要解密得到消息内容明文,解密后有random、msg_len、msg、receiveid 四个字段,其中 msg 即为消息内容明文

2.1.1 通过参数 msg_signature 对请求进行校验

首先要把刚才配置时随机生成的 token, timestamp, nonce, msg_encrypt 进行 sha1 加密,这里我们可以直接使用 npm 模块 sha1 进行加密,然后判断得到的 str 是否和 msg_signature 相等。

function sha1(str) {
 const md5sum = crypto.createHash('sha1');
 md5sum.update(str);
 const ciphertext = md5sum.digest('hex');
 return ciphertext;
}
function checkSignature(req, res, encrypt) {
 const query = req.query;
 console.log('Request URL: ', req.url);
 const signature = query.msg_signature;
 const timestamp = query.timestamp;
 const nonce = query.nonce;
 let echostr;
 console.log('encrypt', encrypt);
 if (!encrypt) {
  echostr = query.echostr;
 } else {
  echostr = encrypt;
 }
 console.log('timestamp: ', timestamp);
 console.log('nonce: ', nonce);
 console.log('signature: ', signature);
 // 将 token/timestamp/nonce 三个参数进行字典序排序
 const tmpArr = [token, timestamp, nonce, echostr];
 const tmpStr = sha1(tmpArr.sort().join(''));
 console.log('Sha1 String: ', tmpStr);
 // 验证排序并加密后的字符串与 signature 是否相等
 if (tmpStr === signature) {
  // 原样返回echostr参数内容
  const result = _decode(echostr);
  console.log('last', result);
  console.log('Check Success');
  return result;
 } else {
  console.log('Check Failed');
  return 'failed';
 }
}

2.1.2 解密 echostr 得到 msg 并返回

密文解密过程:

对刚才生成的 AESKey 进行 base64 解码

const EncodingAESKey = '21IpFqj8qolJbaqPqe1rVTAK5sgkaQ3GQmUKiUQLwRe';
let aesKey = Buffer.from(EncodingAESKey + '=', 'base64');

对 AESKey 进行 aes-256-cbc 解密

function _decode(data) {
 let aesKey = Buffer.from('21IpFqj8qolJbaqPqe1rVTAK5sgkaQ3GQmUKiUQLwRe' + '=', 'base64');
 let aesCipher = crypto.createDecipheriv("aes-256-cbc", aesKey, aesKey.slice(0, 16));
 aesCipher.setAutoPadding(false);
 let decipheredBuff = Buffer.concat([aesCipher.update(data, 'base64'), aesCipher.final()]);
 decipheredBuff = PKCS7Decoder(decipheredBuff);
 let len_netOrder_corpid = decipheredBuff.slice(16);
 let msg_len = len_netOrder_corpid.slice(0, 4).readUInt32BE(0);
 const result = len_netOrder_corpid.slice(4, msg_len + 4).toString();
 return result; // 返回一个解密后的明文-
}
function PKCS7Decoder (buff) {
 var pad = buff[buff.length - 1];
 if (pad < 1 || pad > 32) {
  pad = 0;
 }
 return buff.slice(0, buff.length - pad);
}

然后返回 result 即可

res.end(result);

2.2 回调 url 验证失败问题

验证 URL 时,经常会碰到 URL 验证失败的问题,解决思路是借助微信企业号 接口调试工具

三、创建应用

详解nodejs 开发企业微信第三方应用入门教程

四、测试应用

应用创建成功后,服务商可以授权 10 个测试企业

详解nodejs 开发企业微信第三方应用入门教程

从企业微信应用市场发起授权时,企业微信给刚才应用设置的 指令回调 url 发送一个 post 请求,比如:

https://api.worktile.com/worktile?msg_signature=b99605616153ffbfbe6ebbb500bd211e67ed714d×tamp=1551076894&nonce=1551709703 ,直接返回成功即可。

各个事件的回调,服务商在收到推送后都必须直接返回字符串 “success”,若返回值不是 “success”,企业微信会把返回内容当作错误信息。

app.post('/worktile', function (req, res) {
 console.log('req.body', req.body);
 res.send('success');
});

测试应用注意事项

  1. 用于安装测试的企业微信帐号需服务商自行注册,每个应用支持同时添加 10 个测试企业微信账号
  2. 安装测试的企业微信帐号使用的是当前的应用配置信息,后续的修改不会进行同步;如需更新应用信息请重新授权安装
  3. 同一企业微信帐号,不支持同时安装测试应用和正式发布的应用

五、应用上线

已认证企业微信的服务商,可进入应用管理—点击提交上线—勾选应用—提交上线。

六、用户网页授权登录

6.1 构造第三方应用网页授权链接

如果第三方应用需要在打开的网页里面携带用户的身份信息,第一步需要构造如下的链接来获取 code:

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

参数 必须 说明
appid 第三方应用 id(即 ww 或 wx 开头的 suite_id)。注意与企业的网页授权登录不同
redirect_uri 授权后重定向的回调链接地址,请使用 urlencode 对链接进行处理 ,注意域名需要设置为第三方应用的可信域名
response_type 返回类型,此时固定为:code
scope 应用授权作用域。snsapi_base:静默授权,可获取成员的基础信息(UserId与DeviceId);snsapi_userinfo:静默授权,可获取成员的详细信息,但不包含手机、邮箱等敏感信息;snsapi_privateinfo:手动授权,可获取成员的详细信息,包含手机、邮箱等敏感信息。
state 重定向后会带上 state 参数,企业可以填写 a-zA-Z0-9 的参数值,长度不可超过 128 个字节
#wechat_redirect 终端使用此参数判断是否需要带上身份信息

详解nodejs 开发企业微信第三方应用入门教程

企业员工点击后,页面将跳转至 redirect_uri?code=CODE&state=STATE,第三方应用可根据 code 参数获得企业员工的 corpid 与 userid。code 长度最大为 512 字节。

6.2 获取访问用户身份

请求方式:GET(HTTPS)

请求地址: https://qyapi.weixin.qq.com/cgi-bin/service/getuserinfo3rd?access_token=SUITE_ACCESS_TOKEN&code=CODE

参数 必须 说明
access_token 第三方应用的 suite_access_token,参见“获取第三方应用凭证”
code 通过成员授权获取到的 code,最大为 512 字节。每次成员授权带上的 code 将不一样,code 只能使用一次,5 分钟未被使用自动过期。

 6.2.1 获取第三方应用的 suite_access_token

请求方式:POST(HTTPS)

请求地址: https://qyapi.weixin.qq.com/cgi-bin/service/get_suite_token

参数 是否必须 说明
suite_id 以 ww 或 wx 开头应用 id(对应于旧的以 tj 开头的套件 id)
suite_secret 应用 secret
suite_ticket 企业微信后台推送的 ticket

由于第三方服务商可能托管了大量的企业,其安全问题造成的影响会更加严重,故 API 中除了合法来源 IP 校验之外,还额外增加了 suite_ticket 作为安全凭证。

获取 suite_access_token 时,需要 suite_ticket 参数。suite_ticket 由企业微信后台定时推送给“指令回调 URL”,每十分钟更新一次,见推送 suite_ticket 。

suite_ticket 实际有效期为 30 分钟,可以容错连续两次获取 suite_ticket 失败的情况,但是请永远使用最新接收到的 suite_ticket。

通过本接口获取的 suite_access_token 有效期为 2 小时,开发者需要进行缓存,不可频繁获取。

6.2.2 获取推送 suite_ticket

企业微信服务器会定时(每十分钟)推送 ticket。ticket 会实时变更,并用于后续接口的调用。

请求方式:POST(HTTPS)

请求地址: https://api.ninesix.cc/worktile?msg_signature=87276aaf15a13e1eb2ebb6d93732ca668c3ddef8×tamp=1551850300&nonce=1551051655

在发生授权、通讯录变更、ticket 变化等事件时,企业微信服务器会向应用的“指令回调 URL”推送相应的事件消息,nodejs 接收到的是 xml,解析后拿到 encrypt 字段,然后使用上面配置通用开发参数的 url 时用的解密方式,就可以得到 suite_ticket。

详解nodejs 开发企业微信第三方应用入门教程

6.3 获取用户敏感信息

请求方式:POST(HTTPS)

请求地址: https://qyapi.weixin.qq.com/cgi-bin/service/getuserdetail3rd?access_token=SUITE_ACCESS_TOKEN

{
  "user_ticket": "USER_TICKET"
}

参数 必须 说明
access_token 第三方应用的 suite_access_token,参见“获取第三方应用凭证”
user_ticket 成员票据

返回结果:

{
  "errcode": 0,
  "errmsg": "ok",
  "corpid": "wwxxxxxxyyyyy",
  "userid": "lisi",
  "name": "李四",
  "mobile": "15913215421",
  "gender": "1",
  "email": "xxx@xx.com",
  "avatar": "http://shp.qpic.cn/bizmp/xxxxxxxxxxx/0",
  "qr_code": "https://open.work.weixin.qq.com/wwopen/userQRCode?vcode=vcfc13b01dfs78e981c"
}

七、用户授权成功

首页

详解nodejs 开发企业微信第三方应用入门教程

详情页

详解nodejs 开发企业微信第三方应用入门教程

八、给用户发消息

我们可以给推送文本、图片、视频、文件、图文等类型。

请求方式:POST(HTTPS)

请求地址: https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN

推送的时候需要 access_token 和 应用的 agentId,第三方服务商,可通过接口 获取企业授权信息 获取该参数值,其实可以直接通过获取企业永久授权码 直接取到这两个值。

在我们测试安装应用成功之后,企业微信会 post 一条请求给指令回调 URL,通过上面的解密方式,可以解析到 xml 中的 auth_code

详解nodejs 开发企业微信第三方应用入门教程

然后通过 https://qyapi.weixin.qq.com/cgi-bin/service/get_permanent_code?suite_access_token=SUITE_ACCESS_TOKENauth_code 可以获取到 access_token 和 agentId,返回的 agent 是一个数组,但仅旧的多应用套件授权时会返回多个agent,对新的单应用授权,永远只返回一个 agent。

再通过 access_token 和 agentId 就可以愉快的给用户发送消息了。

详解nodejs 开发企业微信第三方应用入门教程

当点击链接时,可以跳到指定任务或者日程等,只不过返回时还是在企业微信的消息模块,并不能自动打开第三方应用,客服回复不支持这么做。

九、注意事项

api 可能有时效性,如有差异,以官方 api 为准。

完整 demo

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

NodeJs 相关文章推荐
轻松创建nodejs服务器(3):代码模块化
Dec 18 NodeJs
Nodejs下DNS缓存问题浅析
Nov 16 NodeJs
Nodejs下用submit提交表单提示cannot post错误的解决方法
Nov 21 NodeJs
详解nodejs 文本操作模块-fs模块(五)
Dec 23 NodeJs
初探nodeJS
Jan 24 NodeJs
详解nodejs微信公众号开发——2.自动回复
Apr 10 NodeJs
详解nodejs微信公众号开发——5.素材管理接口
Apr 11 NodeJs
详解Windows下安装Nodejs步骤
May 18 NodeJs
nodejs制作爬虫实现批量下载图片
May 19 NodeJs
Nodejs连接mysql并实现增、删、改、查操作的方法详解
Jan 04 NodeJs
nodejs通过钉钉群机器人推送消息的实现代码
May 05 NodeJs
nodeJs的安装与npm全局环境变量的配置详解
Jan 06 NodeJs
详解NodeJS Https HSM双向认证实现
Mar 12 #NodeJs
NodeJs入门教程之定时器和队列
Mar 08 #NodeJs
nodejs npm错误Error:UNKNOWN:unknown error,mkdir 'D:\Develop\nodejs\node_global'at Error
Mar 02 #NodeJs
nodejs同步调用获取mysql数据时遇到的大坑
Mar 02 #NodeJs
Nodejs中怎么实现函数的串行执行
Mar 02 #NodeJs
Nodejs让异步变成同步的方法
Mar 02 #NodeJs
nodejs使用async模块同步执行的方法
Mar 02 #NodeJs
You might like
phpphp图片采集后按原路径保存图片示例
2014/02/18 PHP
PHP IDE PHPStorm配置支持友好Laravel代码提示方法
2015/05/12 PHP
php验证身份证号码正确性的函数
2016/07/20 PHP
PHP下的浮点运算不准的解决方法
2016/10/27 PHP
老生常谈PHP面向对象之解释器模式
2017/05/17 PHP
PHP基于phpqrcode类生成二维码的方法示例详解
2020/08/07 PHP
js 调整select 位置的函数
2008/02/21 Javascript
JavaScript中的集合及效率
2010/01/08 Javascript
jquery 弹出层注册页面等(asp.net后台)
2010/06/17 Javascript
NodeJs中的非阻塞方法介绍
2012/06/05 NodeJs
js将控件隐藏的方法及display属性介绍
2013/07/04 Javascript
JavaScript中创建类/对象的几种方法总结
2013/11/29 Javascript
jquery获取css中的选择器(实例讲解)
2013/12/02 Javascript
JQuery+Ajax无刷新分页的实例代码
2014/02/08 Javascript
nodejs下打包模块archiver详解
2014/12/03 NodeJs
Lua表达式和控制结构学习笔记
2014/12/15 Javascript
node.js中的fs.rmdirSync方法使用说明
2014/12/16 Javascript
通过Tabs方法基于easyUI+bootstrap制作工作站
2016/03/28 Javascript
基于canvas的二维码邀请函生成插件
2017/02/14 Javascript
JS交互点击WKWebView中的图片实现预览效果
2018/01/05 Javascript
深入理解 JS 垃圾回收
2019/06/03 Javascript
vue实现标签云效果的方法详解
2019/08/28 Javascript
[07:06]2018DOTA2国际邀请赛寻真——卫冕冠军Team Liquid
2018/08/10 DOTA
python 计算一个字符串中所有数字的和实例
2019/06/11 Python
python覆盖写入,追加写入的实例
2019/06/26 Python
pycharm如何使用anaconda中的各种包(操作步骤)
2020/07/31 Python
南非领先的在线旅行社:Travelstart南非
2016/09/04 全球购物
Schutz鞋官方网站:Schutz Shoes
2017/12/13 全球购物
企业管理部经理岗位职责
2013/12/24 职场文书
新品发布会主持词
2014/04/02 职场文书
优秀研究生主要事迹
2014/06/03 职场文书
五月的鲜花活动方案
2014/08/21 职场文书
乡镇创先争优活动总结
2014/08/28 职场文书
党员检讨书
2014/10/13 职场文书
2016党员党课心得体会
2016/01/07 职场文书
《槐乡的孩子》教学反思
2016/02/20 职场文书