企业微信自定义应用页面授权过程

一、背景

       最近接到一个项目,项目原团队(产品、技术)已经解散,交接情况不明,也无人接手。刚好我负责的业务跟这些项目有半毛钱关系,其他小组找到我,我也是一脸懵逼,一问三不知,也没有源码。于是找部门经理帮忙(gitlab权限,找整个公司的项目),找到项目源码后,fuck ,后台是PHP,无奈跟相关人说需要重构。自己研究相关的页面、PHP源码,弄清楚涉及什么业务,沉思三两天,终于知道所以然。。。

二、开发思路

  1、配置企业微信应用,前往企业微信后台管理配置,如下截图:

  2、页面地址,假如部署后的域名访问为:https://www.aaa.com/wp_wework/workphoto/home  ,需要配置这个可信赖域名,找到自定义的应用,配置如下:

 3、页面授权,用户使用企业微信,即可操作企业微信的应用页面,页面即可获取这个用户信息。那么它们之间的授权关系是怎么样的呢,这个是重点

     1)、获取企业的jsapi_ticket,相关操作如下: 

      A、涉及的参数:dto的字段

{

private String corpId;  // 企业微信ID
private String agentId; // 应用ID
private String url;   // 可信赖域名

}

public String getCorpTicket(String corpId, String agentId , QyAgentCommondUrlDTO dto) {
// 获取 redis 的缓存数据		
String res = repository.get(QYWEIXIN_JSAPI_TICKET_KEY+dto.getCorpId()+dto.getAgentId());
		String jsapi_ticket = "";
		if(StringUtil.isEmpty(res)){
            // 调用 企业微信的接口:获取企业的jsapi_ticket 
			String result = OAuthApi.getJsapiTicket().getJson();
			JSONObject resultJson = JSONObject.parseObject(result);
			if(resultJson.get("ticket") != null){
				jsapi_ticket = resultJson.getString("ticket");
				repository.setExpire(QYWEIXIN_JSAPI_TICKET_KEY+dto.getCorpId()+dto.getAgentId(),result,7200);
			}
		}else{
			JSONObject resJson = JSONObject.parseObject(res);
			jsapi_ticket = resJson.getString("ticket");
		}
		//生成的随机字符串
		String nonce_str = StringUtilsX.getRandomStringByLength(32);
		//时间戳
		Long timeStamp = System.currentTimeMillis() / 1000;
		String stringSignTemp = "jsapi_ticket="+jsapi_ticket+"&noncestr=" + nonce_str+"&timestamp=" + timeStamp
				+"&url="+dto.getUrl();

		String signature = StringUtilsX.getSha1(stringSignTemp);
		net.sf.json.JSONObject tickJson = new net.sf.json.JSONObject();
		tickJson.put("appId",dto.getCorpId());
		tickJson.put("timestamp",timeStamp);
		tickJson.put("nonceStr",nonce_str);
		tickJson.put("signature",signature);
		tickJson.put("isSuccess",Boolean.TRUE);
		tickJson.put("responseCode",0);
		tickJson.put("responseMsg","请求成功");
		logger.info("---getJsticket--results:"+tickJson);

		return tickJson.toString();
	}


// 加解密类
public class StringUtilsX {
    /**
     * StringUtils工具类方法 获取一定长度的随机字符串,范围0-9,a-z
     *
     * @return 一定长度的随机字符串
     */
    public static String getRandomStringByLength(int length) {
        String base = "abcdefghijklmnopqrstuvwxyz0123456789";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < length; i++) {
            int number = random.nextInt(base.length());
            sb.append(base.charAt(number));
        }
        return sb.toString();
    }

    /**
     * sha1 加密
     * @param str
     * @return
     */
    public static String getSha1(String str){
        if(str==null||str.length()==0){
            return null;
        }
        char hexDigits[] = {'0','1','2','3','4','5','6','7','8','9',
                'a','b','c','d','e','f'};
        try {
            MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
            mdTemp.update(str.getBytes("UTF-8"));

            byte[] md = mdTemp.digest();
            int j = md.length;
            char buf[] = new char[j*2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
                buf[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(buf);
        } catch (Exception e) {
            // TODO: handle exception
            return null;
        }
    }
}

前端界面js处理:这里主要是获取企业的jsapi_ticket 和 获取应用的jsapi_ticket,给界面授权。注意:wxCorpConfig 、 wxAgentConfig,目前用到wxCorpConfig,wxAgentConfig暂时没用上。

async function wxShareRequest({ url, title, desc, link, imgUrl, phone } = {}) {
    try {
        // 开发环境
        let domain = process.env.NODE_ENV == 'development' ? 'https://www.aaa.com/wp_wework/' : 'https://www.bbbb.com/wp_wework/';
        let wxCorpConfig = await getWxplatformCorpConfig()
        let wxAgentConfig = await getWxplatformAgentConfig()
        if (wxCorpConfig === false || wxAgentConfig === false) return
        wx.config({
            beta: true,// 必须这么写,否则wx.invoke调用形式的jsapi会有问题
            debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
            appId: wxCorpConfig.appId, // 必填,企业微信的corpID
            timestamp: wxCorpConfig.timestamp, // 必填,生成签名的时间戳
            nonceStr: wxCorpConfig.nonceStr, // 必填,生成签名的随机串
            signature: wxCorpConfig.signature, // 必填,签名,见 附录-JS-SDK使用权限签名算法
            jsApiList: ['openUserProfile','agentConfig', 'onMenuShareAppMessage', 'onMenuShareWechat', 'onMenuShareTimeline'] // 必填,需要使用的JS接口列表,凡是要调用的接口都需要传进来
        });

        wx.ready(function() {

            wx.onMenuShareAppMessage({
                title: '工作形象照', // 分享标题
                desc: '蓝月亮的小伙伴,快来制作你的专属企业微信头像吧!', //'快速制作企业微信头像 ', // 分享描述
                link: domain, // 分享链接;在微信上分享时,该链接的域名必须与企业某个应用的可信域名一致
                imgUrl: domain + (imgUrl || '/static/img/share.png'), // 分享图标
                success: function() {
                    // 用户确认分享后执行的回调函数
                },
                cancel: function() {
                    // 用户取消分享后执行的回调函数
                }
            });

            wx.onMenuShareWechat({
                title: '工作形象照', // 分享标题
                desc: '蓝月亮的小伙伴,快来制作你的专属企业微信头像吧!', // 分享描述
                link: domain, // 分享链接
                imgUrl: domain + (imgUrl || '/static/img/share.png'), // 分享图标
                success: function() {
                    // 用户确认分享后执行的回调函数
                },
                cancel: function() {
                    // 用户取消分享后执行的回调函数
                }
            });

            wx.onMenuShareTimeline({
                title: '工作形象照', // 分享标题
                link: domain, // 分享链接
                imgUrl: domain + (imgUrl || '/static/img/share.png'), // 分享图标
                success: function() {
                    // 用户确认分享后执行的回调函数
                },
                cancel: function() {
                    // 用户取消分享后执行的回调函数
                }
            });

        });
    } catch (error) {
        
    }
}

 2)、网页授权方式,网页授权登录,这个主要获取用户的信息,就如我们登录后,有个token,如下图所示:

这里分两步:

  第一步:点击应用,页面跳转。在页面初始过程需要配置跳转权限,这里侧重在路由跳转的设置,如下:

重点:获取code ,既是:window.location.href = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' + global.AppId + '&redirect_uri=' + encodeURIComponent(global.domain + to.fullPath) + '&response_type=code&scope=snsapi_base&agentid='+global.agentId+'&state=STATE#wechat_redirect'

// 白名单
const whiteList = ['/login', '/register'] // no redirect whitelist
const agent = window.navigator.userAgent.toLowerCase();

// 全局路由卫士
router.beforeEach((to, from, next) => {

    // 设置页面title
    document.title = getPageTitle(to.meta.title);

    // 确定用户是否已登录(cookie中是否有token) getToken();
    const hasToken = getToken(); 
    if (hasToken) {
        next()
    } else {
        console.log(to)

        /* has no token*/
        // 在免登录白名单,直接进入
        if (whiteList.indexOf(to.path) !== -1) {
            next()
        } else {
            // 没有访问权限的其他页面被重定向到登录页面。
            // redirect 后携带登录后要访问的页面 to.fullPath 可将query中的参数一并带入
            // next(`/login?redirect=${encodeURIComponent(to.fullPath)}`);

            /**
             * 微信授权
             * @description 微信环境则走微信授权获取个人信息及支持微信支付
             */
            // 判断是否为微信环境 utils.isWechat
            if (agent.match(/MicroMessenger/i) == "micromessenger") {
                // 判断是否微信授权过
                // 判断是否有微信授权回调 code
                if (!to.query.code) {
                    window.location.href = 'https://open.weixin.qq.com/connect/oauth2/authorize?appid=' + global.AppId + '&redirect_uri=' + encodeURIComponent(global.domain + to.fullPath) + '&response_type=code&scope=snsapi_base&agentid='+global.agentId+'&state=STATE#wechat_redirect'
                } else {
                    let corpId = global.AppId; // 企业微信ID
                    let agentId = global.agentId; // 应用ID
                    let param ={
                        corpId: corpId, 
                        agentId: agentId,
                        code: to.query.code.trim()
                    }
					// 通过code获取用户信息
                    authAction(param).then(response => {
                        if (response.data.success) {
                            let obj = response.data.data;
                            if (!obj){
                                console.log("后台返回为空的用户对象");
                                return ;
                            }
                            let item = {
                                name: obj.name,
                                employeeNo:obj.userid,
                                gender:obj.gender,
                                corpId: corpId,
                                agentId: agentId
                            }
                            let token = obj.userid;
                            setToken(token);
                            setUserInfo(item);
                        }
                        next('/');
                    }).catch(error => {
                        console.error(error)
                    })
                }
            } else {
                Toast({
                    message: '请在企业微信客户端打开',
                    forbidClick: true, // 禁用背景点击
                    duration: 2000
                });
            }

        }
    }

})

第二步:获取用户信息,方法为:authAction  ,这里的后台调用两个方法:根据code获取成员信息 和 读取成员  ,前者获取企业微信员工编号,后者根据员工编号获取用户信息。至此,整个页面授权成功。前端做好用户信息的缓存。

三、总结

  1、企业微信点击应用后授权问题(获取企业的jsapi_ticket网页授权登录),侧重在页面跳转既是路由跳转过程授权。

  2、企业微信接口调用的权限处理,需要知道企业微信的应用的配置信息。

  3、前端页面对用户授权后的用户信息缓存问题,推荐使用cookie。

  4、文章比较粗糙,旨在简述企业微信自定义应用的页面授权思路。简单来说是对哪些页面授权,现有提供授权的接口有哪些,传哪些参数处理,注意什么。

猜你喜欢

转载自blog.csdn.net/baidu_28068985/article/details/113174698