前言
如果你不知道 企业微信侧边栏 是什么,那就可以划走这篇文章了。如果你知道这是个啥,那你一定非常苦恼要怎么开始。
从去年就开始就一直有在做企业微信侧边栏的应用。说实话,开发和调试体验实在是太糟糕了,而且上手的时候根本连怎么打开它都不知道。
最难顶的是官方文档也写得不仔细,连个最简单能跑的 Demo 都没有,找开发讨论区吧,官方的客服也不太懂代码,问了等于没问,很多贴子都是网友互助的。
所以为了能帮助更多人上手侧边栏,我写了一个 教程网站,以及 前端 和 后端 两个 Demo。
这个教程和 Demo 都放在 这个 Organization 上了,也可以关注一下。
当然本文也不是简单的水文,所以下面简单来聊聊 企业微信侧边栏 一些重要的部分吧。
是什么
企业微信侧边栏(下称企微侧栏)其实就是企业微信右边的一个侧栏(WebView)。
但是并不是所有对话(session)都能打开这个侧边栏的,只有在 外部联系人 和 外部联系群 的对话中才能右下角打开侧栏的按钮。
那 外部联系人 和 外部联系群 又是个啥?为什么只有在这种情况下才能打开呢?这就要说到侧栏到底要解决的什么问题。
为什么
侧栏真正要解决的痛点其实是 社群/客户运营和管理。
可能大家微信上只有 300 左右的好友,但是有没有想过如果作为一个销售人员,他可能需要添加 1000 的客户,要创建 100 的群聊,每天可能都会收到成百上千条消息。
这些客户可能被分为:想买产品的、有意愿想买产品的、已经买产品的、普通客户、VIP、SVIP等。
群聊可能被分为:2020年11月学习群、VIP 群、粉丝群等等。
这么多的客户和群聊,对于单一个销售人员来说就非常头疼。很容易就忘记这个客户是哪个分区、哪个类别、哪种标签的。
而且销售人员主要的工作就是要精细化运营、每天都要和客户以及群聊 聊天。什么时候聊、怎么聊、聊什么都是大学问,而且一旦和这么多客户、群聊聊天更是难上加难。类比一下,时间管理大师最多也只能和 10 个人聊也已经顶天了。
所以企业微信就想:能不能在聊天会话当中有一个工具箱,销售人员就可以在这个工具箱里查看客户/群聊的业务数组,或者通过这个工具箱更好地运营。这就是侧边栏的由来。
上面的这些 “客户” 和 “群聊” 则被称为:外部联系人 和 外部联系群,这里的 “外部” 指的就是 不是自己企业内部员工。
侧栏应用架构
侧边栏本质上也是一个 WebView,所以我们只需要写好前端,无论是普通 html 或者 SPA App 都能被放在侧边栏上。
但是普通的前端还是不够的,如果你想和 企业微信 进行一定的交互,比如发消息、立即创建群聊、打开个人信息弹窗,那就需要企业微信提供的 JS-SDK,具体文档请看这里。
可是 JS-SDK 是需要先 config 才能正常使用,而 config 的参数需要从 企业微信服务端 获取 jsapi_ticket
来生成 signature
才能正常初始化 JS-SDK。
wx.agentConfig({
corpid: '', // 必填,企业微信的corpid,必须与当前登录的企业一致
agentid: '', // 必填,企业微信的应用id (e.g. 1000247)
timestamp: , // 必填,生成签名的时间戳
nonceStr: '', // 必填,生成签名的随机串
signature: '',// 必填,签名,见附录-JS-SDK使用权限签名算法
jsApiList: ['selectExternalContact'], //必填,传入需要使用的接口名称
success: function(res) {
// 回调
},
fail: function(res) {
if(res.errMsg.indexOf('function not exist') > -1){
alert('版本过低请升级')
}
}
});
然后问题又来了,从 企业微信服务端 获取 jsapi_ticket
又需要 access_token
这个关键参数,而 access_token
又不能直接缓存到前端,因此,还需要另外一个后端(中间层)来缓存 access_token
,并提供 企业微信服务端 API 的转发服务。
所以,总得来说,侧边栏看似是前端的东西,但其实它的基础架构起码有 侧边栏
、业务服务端
和 企微服务端
。
企微的服务端已经由企业微信提供了,那我们要实现的就是 侧边栏 和 业务服务端 了。如果你是第一次搞侧边栏,一定会被弄得非常烦,所以建议 Fork 我的 侧边栏(前端)模板 和 后端模板,然后在这基础上进行修改。
开发关键部分
因为这里面的细节太多了,想了解具体实现还是去看那两个模板,这里仅讲一些比较重要的点。
转发服务
首先来说这个转发服务吧,需要对 企微服务端 API 进行转发,而服务端的请求是需要 access_token
作为重要参数来转的,但是 access_token
不能一直请求获取,所以需要进行 redis 缓存。
而 redis 又需要 Docker 来启动,不得不说为了做个简单网页,连 Docker 都整上了:
代码语言:javascript复制version: '3'
services:
redis:
image: redis
container_name: 'wecom-sidebar-redis'
ports:
- "6379:6379"
restart: always
转发服务在模板里我使用简单的 Koa 实现,redis 客户端的 NPM 包,可以使用 ioredis 来缓存。
转发就很简单了,直接使用 axios 就可以了,但是要注意 proxy
要设置为 false
,不然会报错:
const axios = require('axios')
const baseURL = 'https://qyapi.weixin.qq.com/cgi-bin';
const proxy = axios.create({
baseURL,
proxy: false // 不指定会报错 SSL routines:ssl3_get_record:wrong version number,参考:https://github.com/guzzle/guzzle/issues/2593
})
module.exports = proxy
具体的转发调用 API 相信大家都会怎么写就不多说了。
重定向获取 userId
这种 userId 的获取机制和微信网页开发是差不多的,需要先重定向某个 url,然后从 search
参数获取 code
,再用这个 code
通过上面的转发服务向企业微信服务端换取 userId
,具体实现可以看 文档这里。
/**
* 获取重定位的 OAuth 路径
* @returns {string}
*/
const generateOAuthUrl = (config: Config) => {
const [redirectUri] = window.location.href.split('#');
const searchObj = {
appid: config.corpId,
redirect_uri: encodeURIComponent(redirectUri),
response_type: 'code',
scope: 'snsapi_base',
agentid: config.agentId,
state: 'A1',
};
const search = Object.entries(searchObj)
.map(entry => {
const [key, value] = entry;
return `${key}=${value}`;
})
.join('&');
return `https://open.weixin.qq.com/connect/oauth2/authorize?${search}#wechat_redirect`;
};
/**
* 判断当前网页是否需要重定向
*/
const checkRedirect = async (config: Config, getUserId: GetUserId) => {
if (isMock) return
const userId = Cookies.get('userId')
const unAuth = !userId || userId === 'undefined' || userId === 'null'
const codeExist = window.location.search.includes('code');
// 判断是否需要重定向
if (unAuth && !codeExist) {
window.location.replace(generateOAuthUrl(config));
}
// 判断是否需要重新获取 userId
if (unAuth) {
const code = qs.parse(window.location.search.slice(1)).code as string
const newUserId = await getUserId(code)
Cookies.set('userId', newUserId)
}
};
JS-SDK 初始化
这应该是使用客户端 API 时最重要的一个步骤了,而企微的 JS-SDK API 用起来很麻烦,所以我将它的 API 又再度封装了一下,主要做的是 promisify
的操作:
const agentConfig = (agentSetting: Omit<wx.AgentConfigParams, 'success' | 'fail'>): Promise<wx.WxFnCallbackRes> => {
return new Promise((resolve, reject) => {
wx.agentConfig({
...agentSetting,
success: resolve,
fail: reject,
});
});
};
然后从我们的转发服务获取 signature
并调用 agentConfig
(3.0.24 以上可以不调用 config
),
const initSdk = async (config: Config, getSignatures: GetSignatures) => {
const { corpId, agentId } = config;
// 获取 ticket
const signaturesRes = await getSignatures();
const agentConfigRes = await jsSdk.agentConfig({
corpid: corpId,
agentid: agentId,
timestamp: signaturesRes.meta.timestamp,
nonceStr: signaturesRes.meta.nonceStr,
signature: signaturesRes.app.signature,
jsApiList: apis,
}).catch(e => {
console.error(e)
});
console.log('agentConfig res', agentConfigRes);
wx.error(console.error);
};
本地开发
本地开发应该是所有前端开发都想要的一个理想环境。但是在配置侧边栏应用的 HTML 地址时,你是不能直接填 localhost
的,必须是可信域名!网上有些教程可能会让你直接改 hosts 文件来将域名转向 localhost
。
我给出的解决方案是使用 Whistle 来将企业微信的流量代理到本地 localhost
,这样就可以在本地进行开发了,具体操作可看这里。
# 代理前端(侧边栏页面代理到本地的 3000 端口),这里要改为你自己配置H5的地址就好
//service-aj0odbig-1253834571.gz.apigw.tencentcs.com http://127.0.0.1:3000
# 代理后端(后端模板的 baseURL 写死为 backend.com,这里代理到本地的 5000 端口)
//backend.com http://127.0.0.1:5000
不过,在企业微信侧边栏上调试我们的应用还是很麻烦,我们更希望的是可以直接在浏览器上调试程序,等开发差不多了,再去真实的侧边栏环境下调试。
考虑到这一点,我在我的前端模板里也实现 Mock 模式。具体怎么玩可以看 这里,可以直接 Mock 客户端 API 的返回值和用户身份信息。能大大提高开发效率。
总结
总的来说,个人觉得虽然侧边栏开发真是个麻烦事,还有好多 bug 和兼容问题,但是确实是客户/社群运营一个非常好用的工具。
希望这篇文章能带给大家侧边栏一些基础概念,想了解更多可以去 我的教程 深入上手。