企业微信 JS-SDK 自建应用踩坑指南
前言
最近一直在用企业微信 JS-SDK 来开发企业微信的侧边栏,用得特别不爽。主要原因是在官方文档的客户端那一块没有讲特别详细,在服务端那里讲了,搞得我一开发客户端的一脸懵逼,而且官方文档也很久没有维护更新了,要排查问题简直难上加难。
这里特别感谢和我一起开发的后端大哥,很多问题都帮我解决了,么么。
什么是侧边栏
企业微信的侧边栏其实就是类似于我们微信的聊天小工具。
而企业微信将其增强了,可以自定义里面的工具,并能调用企业微信应用自己的 API,比如获取用户信息,选取外部联系人等等。其本质上就是 Hybird。
下面就是侧边栏的样例。
可以想象得到这个小工具的页面就是前端 HTML 嘛,因此,在这基础上加上企业微信提供的 JS-SDK 我们就可以愉快地调用应用的 API 了。
但是。。。真的愉快么?
文档
企业微信开发文档第一步就卡死了很多人。开发文档是在这里:
为什么要单独说,因为点击去客户端API这个 Tab,直接显示是的展开的小程序菜单,很容易就看到小程序那块去了。。。而且翻来翻去找不到 JS-SDK 的文档。
你来告诉我,JS-SDK 的文档在哪?
其实你不知道的是这个“小程序”菜单项收起来就可以看到 JS-SDK 的文档的。哦?怪我不折叠咯?
自建应用
首先你得有个企业,然后才能在里面自建应用,并配置到企业员工的侧边栏。具体怎么配置可以参考:
掘金:https://juejin.im/post/6844904132122247182 里的“搭建自建应用”部分。
步骤如下:
- 去 https://work.weixin.qq.com/wework_admin/frame#apps
- 点击应用管理 -> 创建应用 -> 填写应用信息 -> 点击应用 -> 配置到聊天工具栏的配置 -> 配置页面 -> 输入你的域名地址
- 最最最重要的一步:在应用配置的页面 -> 启用网页授权及JS-SDK,一定要做!不然用不了 JS-SDK
引入
首先我们来看怎么引入 JS-SDK。你可能会想:就这还用你来教?那我只能说 too young too naive。
这里先放一下官方文档是教我们怎么引入的:
NPM 包
现在毕竟都是 2020 年了,NPM 包用得多舒服啊,还有版本锁定,而且 NPM 包很多都带 TypeScript 类型声明文件的,用起来都不需要去看文档了。CDN 和 NPM 两个选择,我肯定选 NPM嘛。
因此我在网上找到了这个:
https://www.npmjs.com/package/wxwork-js-sdk,经检验,没什么用。
又找到这个:
https://npm.io/package/wxwork-jsapi,经检验,可用。
但是!这个包在 Windows 下不能正常调用 wx.config,导致所有其他功能都不能使用。而且这个包还在不断更新中,观察了几周,其版本就更新了几次,非常不稳定。
不过,这里也非常感谢这位作者的贡献,虽然目前他的这个包还不能投放到生产环境中,但是这种勇于创新的精神还是值得肯定的。希望有一天可以用得上这个稳定的包,毕竟我还为这个包写了个 TS 类型声明文件。
cdn 引入
说实话,Windows 上不能使用这个坑我找了4天的Bug才发现是这个包的问题。其实呢,官方也是不推荐使用 NPM 包的,只推荐使用 CDN 方式引入。好吧,算你狠。
按照官方文档:
<script src="//res.wx.qq.com/open/js/jweixin-1.2.0.js"></script>
如果你觉得这就完事了,那你就想得太简单了。在某些情况下,会出现 wx.agentConfig is not a function
的报错,如果不能正常调用 agentConfig,不好意思,你还是不能使用企业微信的 API。
这个问题我找了一圈,终于找到解决方案:
https://developers.weixin.qq.com/community/develop/article/doc/00022417118c78d4448af86625b413
官方文档提到,需要引入http://res.wx.qq.com/open/js/jweixin-1.2.0.js,但是经过大量测试验证,得出如下解决方案:
1、引入http://res.wx.qq.com/open/js/jweixin-1.2.0.js
企业微信Mac版:通过wx.config、wx.agentConfig
企业微信手机版:报错:wx.agentConfig is not a function2、引入https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js
企业微信Mac版:报错:window.wx未定义
企业微信手机版:通过wx.config、wx.agentConfig3、引入https://res.wx.qq.com/wwopen/js/jsapi/jweixin-1.0.0.js
企业微信Mac版:通过wx.config、wx.agentConfig
企业微信手机版:通过wx.config、wx.agentConfig总结:
在进行企业微信JS SDK对接时,正确引用的JS文件应该是https://res.wx.qq.com/wwopen/js/jsapi/jweixin-1.0.0.js,而这个文件链接在官方文档中根本就找不到,是笔者通过F12 Network标签反复比对出来的。与大家共享,希望少走弯路。
惊不惊喜?意不意外?你猜都猜不对怎么才能正常引入这个 SDK。
起步
引入了之后呢,就得按官方的步骤来了。https://work.weixin.qq.com/api/doc/90001/90144/90547
- 从服务端获取 ticket(企业 ticket 和 应用 ticket)
- 生成签名,注意:企业 ticket 对应的
wx.config
里面的签名,应用 ticket 对应的是wx.agentConfig
里的签名 - 最后
wx.config
再wx.agentConfig
但是,文档里面有一行小字
注意:从企业微信3.0.24及以后版本(可通过企业微信UA判断版本号),无须先调用wx.config,可直接
wx.agentConfig
。
这里可以做个小优化,不过你要是想偷懒,可以直接 config
和 agentConfig
都用上,反正不报错的。
文档里还提到了 wx.checkJsApi(fn)
,wx.ready(fn)
和 wx.error(fn)
,这三个玩意,官方文档里说是必须要写上的,其实用处不大,可以不写。因为 checkJsApi
的功能可以通过 agentConfig
就可以知道哪些 API 是可以使用的。而 wx.ready
和 wx.error
可以通过回调来做监听:
const onFail = async (res: any) => {
if (res.errMsg.indexOf('function not exist') > -1) {
message.error('版本过低请升级');
} else {
message.error(res.errMsg);
}
};
const init = (meta: ISetupMeta, url: string, cb: Function) => {
// 调用普通 API 的配置
const corpConfig: IConfig = {
...
fail: onFail,
};
// 调用需要权限的 API 的配置
const appConfig: IAgentConfig = {
...
success: cb,
fail: onFail,
};
// 企业微信 < 3.0.24 需要先调用 wx.config 再调用 agentConfig
// 企业微信 >= 3.0.24 可以直接调用 agentConfig
window.wx.config(corpConfig);
window.wx.agentConfig(appConfig);
};
假如我们要使用获取当前外部联系人userid这个功能,就可以这样:
const onGetCurExternalContact = () => {
setLoading(false);
if (res.err_msg === 'getCurExternalContact:ok') {
localStorage.setItem('userId', res.userId);
fetchUser(res.userId);
} else {
message.error(res.err_msg);
}
}
useEffect(() => {
init(setupMeta, currentUrl, () => {
window.wx.invoke('getCurExternalContact', {}, onGetCurExternalContact);
});
}, []);
agentConfig
成功之后,直接调用 getCurExternalContact。
参考官方文档的小字部分:
接口调用说明
所有接口通过wx对象(也可使用jWeixin对象)来调用,参数是一个对象,除了每个接口本身需要传的参数之外,还有以下通用参数:success:接口调用成功时执行的回调函数。
fail:接口调用失败时执行的回调函数。
complete:接口调用完成时执行的回调函数,无论成功或失败都会执行。
cancel:用户点击取消时的回调函数,仅部分有用户取消操作的api才会用到。
trigger: 监听Menu中的按钮点击时触发的方法,该方法仅支持Menu中的相关接口。
如果你不仔细看文档的小字,就会错过很多《最佳实践》哦。
获取自己 UserId 的流程
流程图如下:
是不是觉得怎么这么麻烦?OK,我们来反推一下你就知道为什么这个流程要这么长了。
-
首先服务端是要通过这个接口来获取 userId 的。其中用到
?suite_access_token=SUITE_ACCESS_TOKEN&code=CODE
。suite_access_token 一般是缓存到服务端的,不管。那就要看 code 是怎么来的。 -
code 其实是通过重定向这个方法来获取的。所以刚进入你的网页就要判断是否有缓存 userId?没有,那就构造URL并重定向,然后获取问号后面的 code的值,再发请求到后端换取 userId。
正推过来就是上面的流程图。
调试
侧边栏的调试,你可能会用 VConsole 来打 log,或者看 Network,毕竟这东西就没法给你 F12。但是。。。真的没法 F12 么?
参考: https://work.weixin.qq.com/api/doc/90001/90148/90457#%E5%AE%A2%E6%88%B7%E7%AB%AF%E8%B0%83%E8%AF%95
这上面写了 Windows 和 Mac 两种调试的方法,其中 Windows 的朋友在你最新的企业微信下已经有 devtools_resources.pak 这个玩意了,不需要你去下载再 Ctrl-C Ctrl-V。直接打开调试模式就可以了。
这里也是有坑的地方的,比如网络请求获取不全:你打开侧边栏,再打开调试工具会看到前面的请求没了。所以你最好,先打开侧边栏,打开调试工具,再刷新,这样的 log 和请求显示比较全。
坑
上面如果操作正确的话,应该是可以快乐使用企业微信的 JS-SDK 了,但是其实在我使用的时候还会有一些坑。
缓存
文档里不止一次说到要做缓存,服务端可以使用 Redis 来缓存 access token,客户端可能会想到 localStorage,但是不好意思,侧边栏的 localStorage 就是个摆设。
经过我多次实验, localStorage 每次进来都会被重置掉。而 IndexedDB 确不会被重置,所以我的缓存都是放到 IndexedDB 里的。
这里有人就会说了,啊这,我看 IndexedDB 用起来好麻烦呀,又监听这又监听那的,就不能像 localStorage 那样 key-value 直接存和取么?答案是是可以的,可以用 idb-keyval 这个库,然后可以这样做一个简单的封装:
import { clear, del, get, set } from 'idb-keyval';
const Storage = {
setItem: async (key: string, value: string, expires: string) => {
await set(key, value);
},
getItem: async (key: string): Promise<null | string> => {
return get<string>(key);
},
clear: async () => {
await clear();
},
del: async (key: string) => {
await del(key);
},
};
重定向路径
上面说到了获取 UserId 是需要重定向的,假如你的应用用到了 Hash 路由模式,就会有点问题了,请欣赏:
- https://developers.weixin.qq.com/community/develop/doc/000a448b7d06207a6e39f023d5b400?highLine=hash
- https://developers.weixin.qq.com/community/develop/doc/00082cde184dc8d0c8b9d9fe151800?highLine=hash
其实我也遇到了上面重定向之后 code=xxx 接到别的地方去的问题。试过 /#/user
,/#user
,#/user
,#user
都不行,反正就是不行,MD。
最后我妥协了,用 ?page=user
这种方式去切换路由,即:根据 page 的值切换不同的组件,如 UserPage
,LoginPage
等,有点 Low,但是能用。
最后
遇到问题多尝试,多去开放社区 找答案和问问题。你问为什么是去这里不是 Google?因为 JS-SDK 只有这个地方才能找到答案
祝你好运!