nodejs 开发企业微信第三方应用入门教程
最近公司要开发企业微信端的 Worktile,以前做的是企业微信内部应用,所以只适用于私有部署客户,而对于公有云客户就无法使用,所有就准备开发企业微信的第三方应用,本文主要介绍在调研阶段遇到的山珍海味。
开发之前你需要前注册为第三方服务商,然后用第三方服务商的账号创建应用,创建之后只需要管理员授权应用,第三方服务商即可为用户提供服务。
一、注册第三发服务商
登陆服务商官网,注册成为服务商,并登陆服务商管理后台。
二、配置开发信息
在创建应用之前,首先要配置好通用开发参数。

在填写系统事件接收 url 时,要正确响应企业微信验证 url 的请求。这个可以参考企业微信后台,自建应用的接收消息的 api 设置。
在企业的管理端后台,进入需要设置接收消息的目标应用,点击“接收消息”的“设置API接收”按钮,进入配置页面。

要求填写应用的 URL、Token、EncodingAESKey 三个参数
- URL 是企业后台接收企业微信推送请求的访问协议和地址,支持 http 或 https 协议(为了提高安全性,建议使用 https)。
- Token 可由企业任意填写,用于生成签名。
- EncodingAESKey 用于消息体的加密,是 AES 密钥的 Base64 编码。
2.1 验证 url 有效性
当点击保存的时候,企业微信会发生一条 get 请求到填写的 url
比如 url 设置的是 https://api.worktile.com , 企业微信将发送如下验证请求:
| 参数 | 说明 |
|---|---|
| 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 验证失败的问题,解决思路是借助微信企业号接口调试工具
三、创建应用

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

从企业微信应用市场发起授权时,企业微信给刚才应用设置的 指令回调 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');
});
测试应用注意事项
- 用于安装测试的企业微信帐号需服务商自行注册,每个应用支持同时添加 10 个测试企业微信账号
- 安装测试的企业微信帐号使用的是当前的应用配置信息,后续的修改不会进行同步;如需更新应用信息请重新授权安装
- 同一企业微信帐号,不支持同时安装测试应用和正式发布的应用
五、应用上线
已认证企业微信的服务商,可进入应用管理—点击提交上线—勾选应用—提交上线。
六、用户网页授权登录
6.1 构造第三方应用网页授权链接
如果第三方应用需要在打开的网页里面携带用户的身份信息,第一步需要构造如下的链接来获取 code:
| 参数 | 必须 | 说明 |
|---|---|---|
| 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 | 是 | 终端使用此参数判断是否需要带上身份信息 |

企业员工点击后,页面将跳转至 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。

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"
}
七、用户授权成功
首页

详情页

八、给用户发消息
我们可以给推送文本、图片、视频、文件、图文等类型。
请求方式:POST(HTTPS)
请求地址: https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=ACCESS_TOKEN
推送的时候需要 access_token 和 应用的 agentId,第三方服务商,可通过接口 获取企业授权信息 获取该参数值,其实可以直接通过 获取企业永久授权码直接取到这两个值。
在我们测试安装应用成功之后,企业微信会 post 一条请求给指令回调 URL,通过上面的解密方式,可以解析到 xml 中的 auth_code

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

当点击链接时,可以跳到指定任务或者日程等,只不过返回时还是在企业微信的消息模块,并不能自动打开第三方应用,客服回复不支持这么做。
九、注意事项
Worktile官网:www.worktile.com
本文作者:王鹏
文章首发于「Worktile官方博客」,转载请注明来源。
nodejs 开发企业微信第三方应用入门教程的更多相关文章
- 从零开始的微信小程序入门教程(一)
之前说要和同事一起开发个微信小程序项目,现在也在界面设计,功能定位等需求上开始实施了.所以在还未正式写项目前,打算在空闲时间学习下小程序.本意是在学习过程中结合实践整理出一个较为入门且不是很厚的教程, ...
- 微信小程序入门教程之四:API 使用
今天是这个系列教程的最后一篇. 上一篇教程介绍了,小程序页面如何使用 JavaScript 脚本.有了脚本以后,就可以调用微信提供的各种能力(即微信 API),从而做出千变万化的页面.本篇就介绍怎么使 ...
- 开发微信小程序入门教程,含破解工具
2016年09月21日晚 微信发不了微信“小程序”的内测版,一时间整个互联网都炸了锅.个大新闻.论坛都在讨论这个事情. 作为互联网的一猿,我们怎能不紧跟时代的脚步.于是第二天上午也对微信发布的“小程序 ...
- 微信小程序入门教程
首先请看demo 很简单的静态js就可以实现一款小程序开发. js.json.html.css四个核心文件 序言 开始开发应用号之前,先看看官方公布的「小程序」教程吧!(以下内容来自微信官方公布的「小 ...
- 微信小程序入门教程之三:脚本编程
这个系列教程的前两篇,介绍了小程序的项目结构和页面样式. 今天,接着往下讲,教大家为小程序加入 JavaScript 脚本,做出动态效果,以及如何跟用户互动.学会了脚本,就能做出复杂的页面了. 本篇的 ...
- 微信小程序入门教程之一:初次上手
微信是中国使用量最大的手机 App 之一,日活跃用户超过3亿,月活跃用户超过11亿(2019年底统计),市场极大. 2017年,微信正式推出了小程序,允许外部开发者在微信内部运行自己的代码,开展业务. ...
- java微信开发框架wechat4j入门教程
wechat4j What is wechat4j? wechat develop framework for java(微信开发框架JAVA版,最简单易用微信开发框架) wechat4j可以用来干什 ...
- 微信小程序入门教程(一)API接口数据记录
今天测试用小程序调用API接口,发现有些数据打印都是对象,怎么全部打印详细点来 小程序代码: httpsearch: function (name, offset, type, cb) { wx.re ...
- 基于Nodejs生态圈的TypeScript+React开发入门教程
基于Nodejs生态圈的TypeScript+React开发入门教程 概述 本教程旨在为基于Nodejs npm生态圈的前端程序开发提供入门讲解. Nodejs是什么 Nodejs是一个高性能Ja ...
随机推荐
- wGenerator如何编写模板
编写模板大致分为4个步骤: 确定开发语言与数据库类型 分析表数据类型与语言的类型对应 分析代码中哪些可以规整为模板 编写与测试模板 我们对照流程图看的话会更加清晰: 1.如何分析表数据与开发语言的类型 ...
- Redis持久化方式的选择
本文将介绍Redis持久化的两种方式:快照持久化和AOF持久化,并对两种方法进行分析和对比,方便在实际中做出选择. 持久化 什么是持久化 Redis所有数据保存在内存中,对数据的更新将异步地保存到磁盘 ...
- C#之Socket的简单使用
Socket是一种通信TCP/IP的通讯接口,也就是HTTP的抽象层,就是Socket在Http之上,Socket也就是发动机.实际上,传输层的TCP是基于网络层的IP协议的,而应用层的HTTP协议又 ...
- 我不是bug神(JVM问题排查)
Story background 回望2018年12月,这也许是程序员们日夜不得安宁的日子,皆因各种前线的系统使用者都需要冲业绩等原因,往往在这个时候会向系统同时写入海量的数据,当我们的应用或者数据库 ...
- BootStrap:轮播插件
前述 利用 BootStrap 实现图片轮播,包括 基本轮播.带标题的轮播.设置轮播速度的轮播.控制前后的轮播 实例 基本轮播 代码 1.引入bootstrap和jQuery文件 <!-- jQ ...
- 基于 HTTP 请求拦截,快速解决跨域和代理 Mock
近几年,随着 Web 开发逐渐成熟,前后端分离的架构设计越来越被众多开发者认可,使得前端和后端可以专注各自的职能,降低沟通成本,提高开发效率. 在前后端分离的开发模式下,前端和后端工程师得以并行工作. ...
- spring源码 — 五、事务
spring提供了可配置.易扩展的事务处理框架,本文主要从一下几个方面说明spring事务的原理 基本概念 事务配置解析 事务处理过程 基本概念 事务隔离级别 在同时进行多个事务的时候,可能会出现脏读 ...
- String字符串类总结
object类 int hashCode() Object定义的hashCode方法能为不同对象返回不同的整数.实际上是把JVM给对象分配的地址转化为整数,确保了逻辑上的唯一性.而转化的散列算法,可能 ...
- arcgis api 4.x for js 结合 react 入门开发系列"esri-loader"篇(附源码下载)
基于上篇的介绍,虽然有比较esri-loader.@arcgis/webpack-plugin,还是觉得有必要需要讲述一下“esri-loader”的开发模式,待大家体验后也会有更直观的感受.本篇文章 ...
- Proj.Net 投影介绍
Proj.Net是开源地图投影库Proj.4的.net版本,许多GIS开源软件的投影都直接或间接地使用Proj.4的库,Proj.4是用C语言编写..Net下的开源GIS项目NetTopologySu ...