阅读 713
玩不转的企业微信侧边栏

玩不转的企业微信侧边栏

前言

如果你不知道 企业微信侧边栏 是什么,那就可以划走这篇文章了。如果你知道这是个啥,那你一定非常苦恼要怎么开始。

从去年就开始就一直有在做企业微信侧边栏的应用。说实话,开发和调试体验实在是太糟糕了,而且上手的时候跟本连怎么打开它都不知道。

最难顶的是官方文档也写得不仔细,连个最简单能跑的 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 都整上了:

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 的返回