最近公司要开发企业微信端的 Worktile,之前作的是企业微信内部应用,因此只适用于私有部署客户,而对于公有云客户就没法使用。全部本文就准备开发企业微信的第三方应用,主要介绍在调研阶段遇到的山珍海味。node
开发以前你须要前注册为第三方服务商,而后用第三方服务商的帐号建立应用,建立以后只须要管理员受权应用,第三方服务商便可为用户提供服务。git
登录服务商官网,注册成为服务商,并登录服务商管理后台。github
在建立应用以前,首先要配置好通用开发参数npm
在填写系统事件接收 url 时,要正确响应企业微信验证 url 的请求。这个能够参考企业微信后台,自建应用的接收消息的 api 设置。
在企业的管理端后台,进入须要设置接收消息的目标应用,点击“接收消息”的“设置API接收”按钮,进入配置页面。json
要求填写应用的 URL、Token、EncodingAESKey 三个参数c#
当点击保存的时候,企业微信会发生一条 get 请求到填写的 urlsegmentfault
好比 url 设置的是https://api.worktile.com
, 企业微信将发送以下验证请求:api
请求地址:https://api.worktile.com/?msg...×tamp=13500001234&nonce=123412323&echostr=ENCRYPT_STR数组
参数 | 说明 |
---|---|
msg_signature | 企业微信加密签名,msg_signature 结合了企业填写的 token、请求中的 timestamp、nonce 参数、加密的消息体 |
timestamp | 时间戳 |
nonce | 随机数 |
echostr | 加密的字符串。须要解密获得消息内容明文,解密后有random、msg_len、msg、receiveid 四个字段,其中 msg 即为消息内容明文 |
首先要把刚才配置时随机生成的 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'; } }
密文解密过程:
const EncodingAESKey = '21IpFqj8qolJbaqPqe1rVTAK5sgkaQ3GQmUKiUQLwRe'; let aesKey = Buffer.from(EncodingAESKey + '=', 'base64');
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); }
res.end(result);
验证 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'); });
测试应用注意事项
已认证企业微信的服务商,可进入应用管理—点击提交上线—勾选应用—提交上线。
若是第三方应用须要在打开的网页里面携带用户的身份信息,第一步须要构造以下的连接来获取 code:
https://open.weixin.qq.com/co...
参数 | 必须 | 说明 |
---|---|---|
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 字节。
请求方式:GET(HTTPS)
请求地址:https://qyapi.weixin.qq.com/c...
参数 | 必须 | 说明 |
---|---|---|
access_token | 是 | 第三方应用的 suite_access_token,参见“获取第三方应用凭证” |
code | 是 | 经过成员受权获取到的 code,最大为 512 字节。每次成员受权带上的 code 将不同,code 只能使用一次,5 分钟未被使用自动过时。 |
请求方式:POST(HTTPS)
请求地址: https://qyapi.weixin.qq.com/c...
参数 | 是否必须 | 说明 |
---|---|---|
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 小时,开发者须要进行缓存,不可频繁获取。
企业微信服务器会定时(每十分钟)推送 ticket。ticket 会实时变动,并用于后续接口的调用。
请求方式:POST(HTTPS)
请求地址:https://api.ninesix.cc/workti...
在发生受权、通信录变动、ticket 变化等事件时,企业微信服务器会向应用的“指令回调 URL”推送相应的事件消息,nodejs 接收到的是 xml,解析后拿到 encrypt 字段,而后使用上面配置通用开发参数的 url 时用的解密方式,就能够获得 suite_ticket。
请求方式:POST(HTTPS)
请求地址:https://qyapi.weixin.qq.com/c...
{ "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/c...
推送的时候须要 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技术博客
欢迎访问交流更多关于技术及协做的问题。
文章转载请注明出处。