企業微信上線了側邊欄功能,對提升服務效率有很高的幫助。api文檔查看:https://open.work.weixin.qq.com/api/doc/90000/90136/91789node
配置後大概是這個樣子,單個客戶會有一個「客戶資料」,羣聊沒有這塊。不過「快捷回覆」是一個比較經常使用的功能。可是不少時候不能知足要求,好比不一樣羣設置不一樣的差別化「快捷回覆」/定時回覆等。則須要自定義側邊欄來實現。ios
自定義側邊欄開發步驟以下。算法
注意:應該用中的應用AgentId、Secret在受權時會用到。而後「配置到聊天側邊欄」設置自定義配置的網頁路徑(這個頁面會被側邊欄導入)express
具體參考:https://open.work.weixin.qq.com/api/doc/90000/90136/90514json
須要注意的是,要先獲取簽名signature,參考:https://open.work.weixin.qq.com/api/doc/90000/90136/90506axios
wx.config配置使用的是企業的jsapi_ticketapi
具體參考:https://open.work.weixin.qq.com/api/doc/90000/90136/90515跨域
依然要先獲取簽名,參考:https://open.work.weixin.qq.com/api/doc/90000/90136/90506#%E7%AD%BE%E5%90%8D%E7%AE%97%E6%B3%95緩存
wx.agentConfig配置使用的是應用的jsapi_ticket:https://open.work.weixin.qq.com/api/doc/90000/90136/90506#%E8%8E%B7%E5%8F%96%E5%BA%94%E7%94%A8%E7%9A%84jsapi_ticket微信
【注意】:這一步會有不少出錯的可能,本人例了一下(包括文檔有說明的以及沒有說明的)
給你一個福利。本人簡單封裝了一份access_token/jsapi_ticket獲取後返回簽名的代碼。你只須要修改其中的base變量就能夠成爲你的代碼
/** * @description node程序,企業微信受權/應用受權 */ let express = require('express'); let app = express(); var axios = require('axios'); let base = { corpid: 'xxx', // 必填,企業微信的corpid,必須與當前登陸的企業一致 agentid: '1000247', // 必填,企業微信的應用id (e.g. 1000247) secret: 'xxxxx', // 測試應用1000247的密碼 timestamp: 'xxxxxx', // 必填,生成簽名的時間戳 nonceStr: 'xxxxxx' // 必填,生成簽名的隨機串 } /** * @description 經過企業id和應用密碼獲取企業應用最新的token和ticket * 函數內部有token和ticket的緩存,自動根據狀況獲取緩存仍是從新請求 * @param {String} corpid 企業id * @param {String} corpsecret 應用密碼 * @param {Boolean} isApp 是不是應用, 默認false *@return {Object} tokenAndTicket 返回最新有效token和ticket * tokenAndTicket.access_token token * tokenAndTicket.ticket wxTicket:企業ticket appTicket:應用ticket */ const getAccessTokenAndTicket = function(corpid, corpsecret, isApp) { getAccessTokenAndTicket[corpid] = getAccessTokenAndTicket[corpid] || {}; return new Promise((resolve, reject) => { // 引用的token已通過期 則須要從新獲取 // 每個應用的corpsecret不一樣,此處把corpsecret做爲一個應用的惟一標誌 // 初始化時默認應用的token是過時的,以便獲取新token if (!getAccessTokenAndTicket[corpid][corpsecret]) { getAccessTokenAndTicket[corpid][corpsecret] = { tokenExpired: true, wxTicketExpired: true, // 企業ticket appTicketExpired: true // 應用ticket }; } let appInfo = getAccessTokenAndTicket[corpid][corpsecret]; // 獲取企業ticket的函數 const getWxTicket = function(ACCESS_TOKEN) { return new Promise((resl, reje) => { if (!appInfo.wxTicketExpired) { // ticket未過時直接用 resl(appInfo.wxTicket); } else { // 已過時從新獲取 axios.get(`https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=${ACCESS_TOKEN}`) .then(res => { let data = res.data; appInfo.wxTicket = data.ticket; appInfo.wxTicketExpired = false; // 有效期倒計時,刷新ticket過時狀態 setTimeout(() => { appInfo.wxTicketExpired = true; }, data.expires_in * 1000); resl(appInfo.wxTicket); }) .catch(err => { reje(err); }) } }) }; // 獲取應用的ticket的函數 const getAppTicket = function(ACCESS_TOKEN) { return new Promise((resl, reje) => { if (!appInfo.appTicketExpired) { // ticket未過時直接用 resl(appInfo.appTicket); } else { // 已過時從新獲取 axios.get(`https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=${ACCESS_TOKEN}&type=agent_config`) .then(res => { let data = res.data; appInfo.appTicket = data.ticket; appInfo.appTicketExpired = false; // 有效期倒計時,刷新ticket過時狀態 setTimeout(() => { appInfo.appTicketExpired = true; }, data.expires_in * 1000); resl(appInfo.appTicket); }) .catch(err => { reje(err); }) } }) }; // 獲取token的函數,內部試用是統一的 const getToken = function(CORPID, CORPSECRET) { return new Promise((resl, reje) => { if (!appInfo.tokenExpired) { // token未過時直接用 resl(appInfo.access_token); } else { // 已過時從新獲取 axios.get(`https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=${CORPID}&corpsecret=${CORPSECRET}`) .then(res => { let data = res.data; appInfo.access_token = data.access_token; appInfo.tokenExpired = false; // 有效期倒計時,刷新token過時狀態 setTimeout(() => { appInfo.tokenExpired = true; }, data.expires_in * 1000); resl(appInfo.access_token); }) .catch(err => { reje(err); }) } }) } getToken(corpid, corpsecret).then(_token => { let getTicket = isApp ? getAppTicket : getWxTicket; getTicket(_token).then(_ticket => { resolve({ access_token: _token, ticket: _ticket }) }).catch(err => { reject(err); }) }).catch(err => { reject(err); }) }); }; function encodeUTF8(s) { var i; var r = []; var c; var x; for (i = 0; i < s.length; i++) { if ((c = s.charCodeAt(i)) < 0x80) r.push(c); else if (c < 0x800) r.push(0xC0 + (c >> 6 & 0x1F), 0x80 + (c & 0x3F)); else { if ((x = c ^ 0xD800) >> 10 == 0) // 對四字節UTF-16轉換爲Unicode { c = (x << 10) + (s.charCodeAt(++i) ^ 0xDC00) + 0x10000, r.push(0xF0 + (c >> 18 & 0x7), 0x80 + (c >> 12 & 0x3F)); } else r.push(0xE0 + (c >> 12 & 0xF)); r.push(0x80 + (c >> 6 & 0x3F), 0x80 + (c & 0x3F)); } }; return r; } // 字符串加密成 hex 字符串 function sha1(s) { var data = new Uint8Array(encodeUTF8(s)) var i, j, t; var l = ((data.length + 8) >>> 6 << 4) + 16; var s = new Uint8Array(l << 2); s.set(new Uint8Array(data.buffer)), s = new Uint32Array(s.buffer); for (t = new DataView(s.buffer), i = 0; i < l; i++)s[i] = t.getUint32(i << 2); s[data.length >> 2] |= 0x80 << (24 - (data.length & 3) * 8); s[l - 1] = data.length << 3; var w = []; var f = [ function() { return m[1] & m[2] | ~m[1] & m[3]; }, function() { return m[1] ^ m[2] ^ m[3]; }, function() { return m[1] & m[2] | m[1] & m[3] | m[2] & m[3]; }, function() { return m[1] ^ m[2] ^ m[3]; } ]; var rol = function(n, c) { return n << c | n >>> (32 - c); }; var k = [1518500249, 1859775393, -1894007588, -899497514]; var m = [1732584193, -271733879, null, null, -1009589776]; m[2] = ~m[0], m[3] = ~m[1]; for (i = 0; i < s.length; i += 16) { var o = m.slice(0); for (j = 0; j < 80; j++) { w[j] = j < 16 ? s[i + j] : rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1), t = rol(m[0], 5) + f[j / 20 | 0]() + m[4] + w[j] + k[j / 20 | 0] | 0, m[1] = rol(m[1], 30), m.pop(), m.unshift(t); } for (j = 0; j < 5; j++)m[j] = m[j] + o[j] | 0; }; t = new DataView(new Uint32Array(m).buffer); for (var i = 0; i < 5; i++)m[i] = t.getUint32(i << 2); var hex = Array.prototype.map.call(new Uint8Array(new Uint32Array(m).buffer), function(e) { return (e < 16 ? '0' : '') + e.toString(16); }).join(''); return hex; } /** 企業微信受權簽名 *@param {String} url 簽名用的url *@return {Object} res 返回最新有效簽名t * res.data.signature 簽名 */ app.get('/wxapi/wxQyAuth', function(req, res) { getAccessTokenAndTicket(base.corpid, base.secret) .then(data => { let str = `jsapi_ticket=${data.ticket}&noncestr=${base.nonceStr}×tamp=${base.timestamp}&url=${req.query.url}`; res.json({ code: '0', data: { signature: sha1(str) }, message: 'success' }); }); }) /** 企業應用微信受權簽名 *@param {String} url 簽名用的url *@return {Object} res 返回最新有效簽名t * res.data.signature 簽名 */ app.get('/wxapi/wxAppAuth', function(req, res) { getAccessTokenAndTicket(base.corpid, base.secret, true) .then(data => { let str = `jsapi_ticket=${data.ticket}&noncestr=${base.nonceStr}×tamp=${base.timestamp}&url=${req.query.url}`; res.json({ code: '0', data: { signature: sha1(str) }, message: 'success' }); }); }) var server = app.listen(8880, function() { var host = server.address().address var port = server.address().port console.log('應用實例,訪問地址爲 http://%s:%s', host, port) })
這個代碼簡單實現了一個企業應用的access_token/jsapi_ticket獲取/緩存(緩存有效期內不會再次請求微信的api)的node服務,經過接口調用返回對應的簽名。請求方式以下:
http://localhost:8880/wxapi/wxQyAuth?url=xxx // 獲取企業微信簽名 http://localhost:8880/wxapi/wxAppAuth?url=xxx // 獲取應用微信簽名 // 兩個簽名分別在config和agentConfig中使用
具體參考:https://open.work.weixin.qq.com/api/doc/90000/90136/91789
參考:
企業微信-企業內部開發步驟:https://work.weixin.qq.com/api/doc/10013
服務端api-企業內部開發指南:https://open.work.weixin.qq.com/api/doc/90000/90135/90664
js-sdk使用說明:https://open.work.weixin.qq.com/api/doc/90000/90136/90514