企業微信側邊欄功能/微信企業授權


企業微信上線了側邊欄功能,對提高服務效率有很高的幫助。api文檔查看:https://open.work.weixin.qq.com/api/doc/90000/90136/91789

配置后大概是這個樣子,單個客戶會有一個“客戶資料”,群聊沒有這塊。不過“快捷回復”是一個比較常用的功能。但是很多時候不能滿足要求,比如不同群設置不同的差異化“快捷回復”/定時回復等。則需要自定義側邊欄來實現。

 

 

自定義側邊欄開發步驟如下。

1.在企業微信中新建自定義,並配置應用的可信域名,然后“配置到聊天側邊欄”

注意:應該用中的應用AgentId、Secret在授權時會用到。然后“配置到聊天側邊欄”設置自定義配置的網頁路徑(這個頁面會被側邊欄導入)

 

2.開發需要導入到側邊欄的頁面。

代碼中首先要通過config注入企業微信接口權限配置。

具體參考:https://open.work.weixin.qq.com/api/doc/90000/90136/90514

需要注意的是,要先獲取簽名signature,參考:https://open.work.weixin.qq.com/api/doc/90000/90136/90506

wx.config配置使用的是企業的jsapi_ticket

 

然后要通過agentConfig注入應用的權限

具體參考: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

 

【注意】:這一步會有很多出錯的可能,本人例了一下(包括文檔有說明的以及沒有說明的)

  • agentConfig與config的簽名算法完全一樣,但是jsapi_ticket的獲取方法不一樣,請特別注意,查看”獲取應用身份的ticket“.
  • 調用wx.agentConfig之前,必須確保先成功調用wx.config,也就是:wx.agentConfig需要在wx.ready中調用
  • 當前頁面url中的域名必須是在該應用中設置的可信域名。
  • agentConfig僅在企業微信2.5.0及以后版本支持,微信客戶端不支持(微信開發者工具也不支持)。
  • 引入的微信js文件必須時1.2.0,更高的版本不支持wx.agentConfig方法,鏈接:http://res.wx.qq.com/open/js/jweixin-1.2.0.js
  • access_token/jsapi_ticket都必須要在服務端獲取並緩存,客戶端獲取會遇到CORS跨域問題

給你一個福利。本人簡單封裝了一份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}&timestamp=${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}&timestamp=${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)
})
View Code

這個代碼簡單實現了一個企業應用的access_token/jsapi_ticket獲取/緩存(緩存有效期內不會再次請求微信的api)的node服務,通過接口調用返回對應的簽名。請求方式如下:

http://localhost:8880/wxapi/wxQyAuth?url=xxx   // 獲取企業微信簽名
http://localhost:8880/wxapi/wxAppAuth?url=xxx   // 獲取應用微信簽名
// 兩個簽名分別在config和agentConfig中使用

 

3.業務代碼中調用”分享消息到會話“

具體參考: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

簽名校驗:https://open.work.weixin.qq.com/api/jsapisign


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM