微信开发unionId获取和企业微信接口调用配置签名问题总结

前言

  • 2019年我们公司的新的业务为了打通微信生态开始接入公众号,小程序的开发,到今年四月又开始打通企业微信生态。微信当时有的文档特别的坑,坑到爆炸(可能现在已经改正,暂无查证)。中间的开发工作也遇到很多的问题,耽误了很多的时间。所以把问题总结一下,防止后面犯类似的错误。

一:微信的UnionID的获取问题

1:问题
  • 当我们开发一个新的小程序并且需要和以前的公众号的用户打通的时候,我们发现了我们小程序的用户拿不到unionid,无法和以前的公众号做关联
  • 还有个问题就是我们的公众号测试环境的用户也没法和生产的数据进行打通。当时的微信文档只看到一个unionid的机制,没有具体的页面操作流程。
2:原因
  • UnionId 是微信给一个用户做唯一识别的标志,是用户微信唯一的id。如果企业只有一个公众号的时候,我们是无法获取到UnionID的,因为至少要有两个及以上的应用(公众号,小程序,企业微信,app应用,网站三方应用)在微信开放平台上进行关联才可以获取到unionID
3:解决方案
  • 在微信开放平台绑定主体(公众号,小程序),如果绑定的应用在同一个主体下面,我们就可以获取到用户的unionId
  • 微信开放平台绑定小程序和公众号
  • 开发者平台绑定的小程序也可以是体验版的小程序,绑定上以后我们的测试环境的用户就能和公众号的用户(包含生产和测试环境)的用户打通
  • 企业微信绑定小程序要到企业微信平台设置。企业微信->应用管理->小程序->关联小程序
  • 企业微信的关联应用要到企业微信->应用管理->应用->创建应用设置,如果已有应用要在第三方->添加第三方应用设置
  • 微信添加第三方应用

二:部分用户拿不到unionId的问题

1:问题
  • 在我们的小程序授权登录的时候,有一部分用户拿不到微信的unionId,导致我们整个流程进行不下去,查了很多的资料,都是从wx.getUserInfo()里获取,但是取的解密值是没有unionId字段的,如下
{"phoneNumber":"15251***648","watermark":{"appid":"wx4b101b***1f6b108","timestamp":1586253485},"purePhoneNumber":"15251**648","countryCode":"86"}
2:原因:
  • 暂时还没有明白微信的wx.getUserInfo什么情况下会返回unionId的值
3:解决方案:
  • 前端如果传的微信code授权获取不到unionId,那么就要从前端第一次登录授权获取到的加密数据encryptedData和iv传到后台,然后后端再次登录授权获取到session_key 进行解密,就能获取到unionId
  • 获取用户session_key和unionId代码如下
public String getUnionId(String code,String iv,String encryptedData) {
        RestTemplate restTemplate = new RestTemplate();
        StringBuilder unionIdRequestUrl = new StringBuilder(URI_UNIONID);
        unionIdRequestUrl.append("?appid="+appid);
        unionIdRequestUrl.append("&secret="+secret);
        unionIdRequestUrl.append("&js_code="+code);
        unionIdRequestUrl.append("&grant_type=authorization_code");
        String response = restTemplate.getForObject(unionIdRequestUrl.toString(), String.class);
        Map<String, Object> result = new ObjectMapper().readValue(response, Map.class);
        String sessionKey = MapUtils.getString(result, "session_key");
        String unionId = MapUtils.getString(result, "unionId");
        // 如果没有获取到就解析前端的加密数据
        if (null != unionId) {
            String str = AES.wxDecrypt(encryptedData, sessionKey, iv);
            JSONObject obj = JSONObject.parseObject(str);
            unionId = null != obj?obj.getString("unionId"):null;
        }
        return unionId
    }

三:企业微信的签名机制和配置config获取问题

1:问题
  • 在企业微信开发中,我们一个需求是需要获取当前聊天人的信息然后入库保存。我们制定的步骤就是前端先通过客户端的API获取当前聊天人的userId,然后再通过服务端API获取用户的信息。在前端获取客户端api的时候必须要先获取权限config的配置,这个后端提供了接口给前端,包含签名,生成签名的随机串等信息,如图
  • 但是前端通过这个获取签名获取了userId以后,再调用后端接口的时候总会报非法的签名信息。
2:原因
  • 企业微信的调用应用接口的权限分为两步:一是获取权限验证的配置 二是获取应用的权限的配置
  • 我们通过我们的后端只返回给了前端一次签名让其去获取权限验证的config,然后前端又去调企业微信的接口,这个是行不通的,必须再获取一次应用的权限配置config,也就是要从后端获取两次签名,用两次不同的签名获取不同的config
  • 企业微信的config必须通过签名去获取,签名由后端生成返回给前端
3:解决方案(java后端)
  • 1:返回给前端两个不同的签名
    //企业微信的jsapi_ticket地址
    private final static String GET_WX_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=ACCESS_TOKEN";

    //应用tictet地址
    private final static String GET_APP_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/ticket/get?access_token=ACCESS_TOKEN&type=agent_config";public Object getShareSign(@RequestBody ReqWeiXinShare req) Exception {
        // 微信唯一标识
        String appid = req.getAppid();
        // 微信调用接口凭证
        String appSecret = req.getAppSecret();
        // 分享url
        String locationUrl = req.getLocationUrl();
        if (!StringUtils.isEmpty(appid) && !StringUtils.isEmpty(appSecret)
                && !StringUtils.isEmpty(locationUrl)) {
            String accessToken = wxCpUserService.accessToken();
            Map<String,Object> resultMap = new HashMap<>();
            Map<String, String> jsApiMap = this.sign(GET_WX_TICKET, accessToken,req.getLocationUrl());
            if (null != jsApiMap) {
                resultMap.put("config_nonceStr",jsApiMap.get("nonceStr"));
                resultMap.put("config_ticket",jsApiMap.get("ticket"));
                resultMap.put("config_timestamp",jsApiMap.get("timestamp"));
                resultMap.put("config_signature",jsApiMap.get("signature"));
            }
            Map<String, String> ticketMap = this.sign(GET_APP_TICKET, accessToken,req.getLocationUrl());
            if (null != ticketMap) {
                resultMap.put("agent_nonceStr",ticketMap.get("nonceStr"));
                resultMap.put("agent_ticket",ticketMap.get("ticket"));
                resultMap.put("agent_timestamp",ticketMap.get("timestamp"));
                resultMap.put("agent_signature",ticketMap.get("signature"));
            }
            resultMap.put("appid", appid);
            resultMap.put("url", locationUrl);
            return resultMap;
        } else {
            return null;
        }
    } 
  • 2:获取签名的公共方法
private Map<String, String> sign(String url, String accessToken,String redirectUrl) {
        String ticket = this.getTicket(url,accessToken);
        Map<String, String> ret = new HashMap<String, String>();
        String noncestr = getRandomString(16);
        String timestamp = Long.toString(System.currentTimeMillis()).substring(0,10);
        String signature = "";
        String params = "jsapi_ticket=" + ticket + "&noncestr=" + noncestr
                + "&timestamp=" + timestamp + "&url=" + redirectUrl;
        log.info("签名返回内容:{}", params);
        try {
            MessageDigest crypt = MessageDigest.getInstance("SHA-1");
            crypt.reset();
            crypt.update(params.getBytes("UTF-8"));
            signature = this.byteToHex(crypt.digest());
        } catch (NoSuchAlgorithmException var10) {
            var10.printStackTrace();
        } catch (UnsupportedEncodingException var11) {
            var11.printStackTrace();
        }
        ret.put("url", url);
        ret.put("ticket", ticket);
        ret.put("nonceStr", noncestr);
        ret.put("timestamp", timestamp);
        ret.put("signature", signature);
        return ret;
    }
  • 3:获取企业微信的ticket
private String getTicket(String url,String accessToke) {
        String ticket = "";
        RestTemplate restTemplate = new RestTemplate();
        url = url.replace("ACCESS_TOKEN",accessToke);
        try {
            String response = restTemplate.getForObject(url, String.class);
            JSONObject demoJson = JSON.parseObject(response);
            if (demoJson.getString("errcode").equals("40001")) {
                return "error";
            }
            ticket = demoJson.getString("ticket");
            System.out.println(ticket);
        } catch (Exception var6) {
            var6.printStackTrace();
        }
        return ticket;
    }
  • 4:随机数生成规则
 private static String getRandomString(int length){
        String keyString = "ergrfewfwdgggcvv;uihefujsncjdvngrjegeuirgverggvbergbvuigverug";
        int len = keyString.length();
        StringBuffer str = new StringBuffer();
        for(int i=0;i<length;i++){
            str.append(keyString.charAt((int) Math.round(Math.random() * (len - 1))));
        }
        return str.toString();
    }
  • 5:签名规则
private String byteToHex(byte[] hash) {
        Formatter formatter = new Formatter();
        byte[] var3 = hash;
        int var4 = hash.length;
        for (int var5 = 0; var5 < var4; ++var5) {
            byte b = var3[var5];
            formatter.format("%02x", b);
        }
        String result = formatter.toString();
        formatter.close();
        return result;
    }
  • 6:前端根据返回的两个config配置,去请求企业微信的接口,就能调的通了

三:总结

  • 微信的开发主要是因为文档不清楚不连贯的导致开发者浪费了大量的时间,我们遇到的主要问题一个unionId的获取不到,不知道如何配置才能获取到unionId,那个时候微信文档也没有介绍(貌似现在有了),一个前端企业微信接口调不通问题,不清楚还要两次获取config,如何获取config的签名微信的文档也没有说明怎么获取,只说了一个必填。解决了这些问题我们的后期开发也顺利了很多,希望以后腾讯系能更多支持java生态,封装一些api的sdk给我们
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351

推荐阅读更多精彩内容