• 企业微信侧边栏功能/微信企业授权


    企业微信上线了侧边栏功能,对提高服务效率有很高的帮助。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

  • 相关阅读:
    typescript 装饰器
    typescript 中的模块
    深入理解typeScript中的泛型类
    泛型 泛型类
    vue跨域
    我喜欢的网站
    vuex
    路由导航守卫
    最多显示两行
    git命令
  • 原文地址:https://www.cnblogs.com/chuaWeb/p/12430140.html
Copyright © 2020-2023  润新知