玩不轉(zhuǎn)的企業(yè)微信側(cè)邊欄

前言

如果你不知道 企業(yè)微信側(cè)邊欄 是什么,那就可以劃走這篇文章了。如果你知道這是個(gè)啥,那你一定非??鄲酪趺撮_始。

從去年就開始就一直有在做企業(yè)微信側(cè)邊欄的應(yīng)用。說實(shí)話,開發(fā)和調(diào)試體驗(yàn)實(shí)在是太糟糕了,而且上手的時(shí)候跟本連怎么打開它都不知道。

[圖片上傳失敗...(image-39edf-1634365911621)]

最難頂?shù)氖枪俜轿臋n也寫得不仔細(xì),連個(gè)最簡(jiǎn)單能跑的 Demo 都沒有,找開發(fā)討論區(qū)吧,官方的客服也不太懂代碼,問了等于沒問,很多貼子都是網(wǎng)友互助的。

所以為了能幫助更多人上手側(cè)邊欄,我寫了一個(gè) 教程網(wǎng)站,以及 前端后端 兩個(gè) Demo。

[圖片上傳失敗...(image-2393a7-1634365911621)]

這個(gè)教程和 Demo 都放在 這個(gè) Organization 上了,也可以關(guān)注一下。

當(dāng)然本文也不是簡(jiǎn)單的水文,所以下面簡(jiǎn)單來聊聊 企業(yè)微信側(cè)邊欄 一些重要的部分吧。

是什么

企業(yè)微信側(cè)邊欄(下稱企微側(cè)欄)其實(shí)就是企業(yè)微信右邊的一個(gè)側(cè)欄(WebView)。

[圖片上傳失敗...(image-813319-1634365911621)]

但是并不是所有對(duì)話(session)都能打開這個(gè)側(cè)邊欄的,只有在 外部聯(lián)系人外部聯(lián)系群 的對(duì)話中才能右下角打開側(cè)欄的按鈕。

外部聯(lián)系人外部聯(lián)系群 又是個(gè)啥?為什么只有在這種情況下才能打開呢?這就要說到側(cè)欄到底要解決的什么問題。

為什么

側(cè)欄真正要解決的痛點(diǎn)其實(shí)是 社群/客戶運(yùn)營(yíng)和管理。

可能大家微信上只有 300 左右的好友,但是有沒有想過如果作為一個(gè)銷售人員,他可能需要添加 1000+ 的客戶,要?jiǎng)?chuàng)建 100+ 的群聊,每天可能都會(huì)收到成百上千條消息。

[圖片上傳失敗...(image-4e84ad-1634365911621)]

這些客戶可能被分為:想買產(chǎn)品的、有意愿想買產(chǎn)品的、已經(jīng)買產(chǎn)品的、普通客戶、VIP、SVIP等。

群聊可能被分為:2020年11月學(xué)習(xí)群、VIP 群、粉絲群等等。

這么多的客戶和群聊,對(duì)于單一個(gè)銷售人員來說就非常頭疼。很容易就忘記這個(gè)客戶是哪個(gè)分區(qū)、哪個(gè)類別、哪種標(biāo)簽的。

而且銷售人員主要的工作就是要精細(xì)化運(yùn)營(yíng)、每天都要和客戶以及群聊 聊天。什么時(shí)候聊、怎么聊、聊什么都是大學(xué)問,而且一旦和這么多客戶、群聊聊天更是難上加難。類比一下,時(shí)間管理大師最多也只能和 10 個(gè)人聊也已經(jīng)頂天了。

所以企業(yè)微信就想:能不能在聊天會(huì)話當(dāng)中有一個(gè)工具箱,銷售人員就可以在這個(gè)工具箱里查看客戶/群聊的業(yè)務(wù)數(shù)組,或者通過這個(gè)工具箱更好地運(yùn)營(yíng)。這就是側(cè)邊欄的由來。

上面的這些 “客戶” 和 “群聊” 則這被稱為:外部聯(lián)系人外部聯(lián)系群,這里的 “外部” 指的就是 不是自己企業(yè)內(nèi)部員工

側(cè)欄應(yīng)用架構(gòu)

側(cè)邊欄本質(zhì)上也是一個(gè) WebView,所以我們只需要寫好前端,無論是普通 html 或者 SPA App 都能被放在側(cè)邊欄上。

但是普通的前端還是不夠的,如果你想和 企業(yè)微信 進(jìn)行一定的交互,比如發(fā)消息、立即創(chuàng)建群聊、打開個(gè)人信息彈窗,那就需要企業(yè)微信提供的 JS-SDK,具體文檔請(qǐng)看這里

[圖片上傳失敗...(image-40e761-1634365911621)]

可是 JS-SDK 是需要先 config 才能正常使用,而 config 的參數(shù)需要從 企業(yè)微信服務(wù)端 獲取 jsapi_ticket 來生成 signature 才能正常初始化 JS-SDK。

wx.agentConfig({
    corpid: '', // 必填,企業(yè)微信的corpid,必須與當(dāng)前登錄的企業(yè)一致
    agentid: '', // 必填,企業(yè)微信的應(yīng)用id (e.g. 1000247)
    timestamp: , // 必填,生成簽名的時(shí)間戳
    nonceStr: '', // 必填,生成簽名的隨機(jī)串
    signature: '',// 必填,簽名,見附錄-JS-SDK使用權(quán)限簽名算法
    jsApiList: ['selectExternalContact'], //必填,傳入需要使用的接口名稱
    success: function(res) {
        // 回調(diào)
    },
    fail: function(res) {
        if(res.errMsg.indexOf('function not exist') > -1){
            alert('版本過低請(qǐng)升級(jí)')
        }
    }
});

然后問題又來了,從 企業(yè)微信服務(wù)端 獲取 jsapi_ticket 又需要 access_token 這個(gè)關(guān)鍵參數(shù),而 access_token 又不能直接緩存到前端,因此,還需要另外一個(gè)后端(中間層)來緩存 access_token,并提供 企業(yè)微信服務(wù)端 API 的轉(zhuǎn)發(fā)服務(wù)。

所以,總得來說,側(cè)邊欄看似是前端的東西,但其實(shí)它的基礎(chǔ)架構(gòu)起碼有 側(cè)邊欄、業(yè)務(wù)服務(wù)端企微服務(wù)端。

[圖片上傳失敗...(image-386d6e-1634365911621)]

企微的服務(wù)端已經(jīng)由企業(yè)微信提供了,那我們要實(shí)現(xiàn)的就是 側(cè)邊欄業(yè)務(wù)服務(wù)端 了。如果你是第一次搞側(cè)邊欄,一定會(huì)被弄得非常煩,所以建議 Fork 我的 側(cè)邊欄(前端)模板后端模板,然后在這基礎(chǔ)上進(jìn)行修改。

開發(fā)關(guān)鍵部分

因?yàn)檫@里面的細(xì)節(jié)太多了,想了解具體實(shí)現(xiàn)還是去看那兩個(gè)模板,這里僅講一些比較重要的點(diǎn)。

轉(zhuǎn)發(fā)服務(wù)

首先來說這個(gè)轉(zhuǎn)發(fā)服務(wù)吧,需要對(duì) 企微服務(wù)端 API 進(jìn)行轉(zhuǎn)發(fā),而服務(wù)端的請(qǐng)求是需要 access_token 作為重要參數(shù)來轉(zhuǎn)的,但是 access_token 不能一直請(qǐng)求獲取,所以需要進(jìn)行 redis 緩存。

而 redis 又需要 Docker 來啟動(dòng),不得不說為了做個(gè)簡(jiǎn)單網(wǎng)頁,連 Docker 都整上了:

version: '3'
services:
  redis:
    image: redis
    container_name: 'wecom-sidebar-redis'
    ports:
      - "6379:6379"
    restart: always

轉(zhuǎn)發(fā)服務(wù)在模板里我使用簡(jiǎn)單的 Koa 實(shí)現(xiàn),redis 客戶端的 NPM 包,可以使用 ioredis 來緩存。

轉(zhuǎn)發(fā)就很簡(jiǎn)單了,直接使用 axios 就可以了,但是要注意 proxy 要設(shè)置為 false,不然會(huì)報(bào)錯(cuò):

const axios = require('axios')

const baseURL = 'https://qyapi.weixin.qq.com/cgi-bin';

const proxy = axios.create({
  baseURL,
  proxy: false // 不指定會(huì)報(bào)錯(cuò) SSL routines:ssl3_get_record:wrong version number,參考:https://github.com/guzzle/guzzle/issues/2593
})

module.exports = proxy

具體的轉(zhuǎn)發(fā)調(diào)用 API 相信大家都會(huì)怎么寫就不多說了。

重定向獲取 userId

這種 userId 的獲取機(jī)制和微信網(wǎng)頁開發(fā)是差不多的,需要先重定向某個(gè) url,然后從 search 參數(shù)獲取 code,再用這個(gè) code 通過上面的轉(zhuǎn)發(fā)服務(wù)向企業(yè)微信服務(wù)端換取 userId,具體實(shí)現(xiàn)可以看 文檔這里。

[圖片上傳失敗...(image-d6b307-1634365911621)]

/**
 * 獲取重定位的 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`;
};

/**
 * 判斷當(dāng)前網(wǎng)頁是否需要重定向
 */
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 初始化

這應(yīng)該是使用客戶端 API 時(shí)最重要的一個(gè)步驟了,而企微的 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,
    });
  });
};

然后從我們的轉(zhuǎn)發(fā)服務(wù)獲取 signature 并調(diào)用 agentConfig(3.0.24 以上可以不調(diào)用 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);
};

本地開發(fā)

本地開發(fā)應(yīng)該是所有前端開發(fā)都想要的一個(gè)理想環(huán)境。但是在配置側(cè)邊欄應(yīng)用的 HTML 地址時(shí),你是不能直接填 localhost 的,必須是可信域名!網(wǎng)上有些教程可能會(huì)讓你直接改 hosts 文件來將域名轉(zhuǎn)向 localhost。

我給出的解決方案是使用 Whistle 來將企業(yè)微信的流量代理到本地 localhost,這樣就可以在本地進(jìn)行開發(fā)了,具體操作可看這里。

# 代理前端(側(cè)邊欄頁面代理到本地的 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

不過,在企業(yè)微信側(cè)邊欄上調(diào)試我們的應(yīng)用還是很麻煩,我們更希望的是可以直接在瀏覽器上調(diào)試程序,等開發(fā)差不多了,再去真實(shí)的側(cè)邊欄環(huán)境下調(diào)試。

考慮到這一點(diǎn),我在我的前端模板里也實(shí)現(xiàn) Mock 模式。具體怎么玩可以看 這里,可以直接 Mock 客戶端 API 的返回值和用戶身份信息。能大大提高開發(fā)效率。

總結(jié)

總的來說,個(gè)人覺得雖然側(cè)邊欄開發(fā)真是個(gè)麻煩事,還有好多 bug 和兼容問題,但是確實(shí)是客戶/社群運(yùn)營(yíng)一個(gè)非常好用的工具。

希望這篇文章能帶給大家側(cè)邊欄一些基礎(chǔ)概念,想了解更多可以去 我的教程 深入上手。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容