微信開發(fā)unionId獲取和企業(yè)微信接口調(diào)用配置簽名問(wèn)題總結(jié)

前言

  • 2019年我們公司的新的業(yè)務(wù)為了打通微信生態(tài)開始接入公眾號(hào)废膘,小程序的開發(fā),到今年四月又開始打通企業(yè)微信生態(tài)般又。微信當(dāng)時(shí)有的文檔特別的坑彼绷,坑到爆炸(可能現(xiàn)在已經(jīng)改正,暫無(wú)查證)茴迁。中間的開發(fā)工作也遇到很多的問(wèn)題苛预,耽誤了很多的時(shí)間。所以把問(wèn)題總結(jié)一下笋熬,防止后面犯類似的錯(cuò)誤。

一:微信的UnionID的獲取問(wèn)題

1:?jiǎn)栴}
  • 當(dāng)我們開發(fā)一個(gè)新的小程序并且需要和以前的公眾號(hào)的用戶打通的時(shí)候腻菇,我們發(fā)現(xiàn)了我們小程序的用戶拿不到unionid胳螟,無(wú)法和以前的公眾號(hào)做關(guān)聯(lián)
  • 還有個(gè)問(wèn)題就是我們的公眾號(hào)測(cè)試環(huán)境的用戶也沒(méi)法和生產(chǎn)的數(shù)據(jù)進(jìn)行打通。當(dāng)時(shí)的微信文檔只看到一個(gè)unionid的機(jī)制筹吐,沒(méi)有具體的頁(yè)面操作流程糖耸。
2:原因
  • UnionId 是微信給一個(gè)用戶做唯一識(shí)別的標(biāo)志,是用戶微信唯一的id丘薛。如果企業(yè)只有一個(gè)公眾號(hào)的時(shí)候嘉竟,我們是無(wú)法獲取到UnionID的,因?yàn)橹辽僖袃蓚€(gè)及以上的應(yīng)用(公眾號(hào)洋侨,小程序舍扰,企業(yè)微信,app應(yīng)用希坚,網(wǎng)站三方應(yīng)用)在微信開放平臺(tái)上進(jìn)行關(guān)聯(lián)才可以獲取到unionID
3:解決方案
  • 在微信開放平臺(tái)綁定主體(公眾號(hào)边苹,小程序),如果綁定的應(yīng)用在同一個(gè)主體下面裁僧,我們就可以獲取到用戶的unionId
  • 微信開放平臺(tái)綁定小程序和公眾號(hào)
  • 開發(fā)者平臺(tái)綁定的小程序也可以是體驗(yàn)版的小程序个束,綁定上以后我們的測(cè)試環(huán)境的用戶就能和公眾號(hào)的用戶(包含生產(chǎn)和測(cè)試環(huán)境)的用戶打通
  • 企業(yè)微信綁定小程序要到企業(yè)微信平臺(tái)設(shè)置慕购。企業(yè)微信->應(yīng)用管理->小程序->關(guān)聯(lián)小程序
  • 企業(yè)微信的關(guān)聯(lián)應(yīng)用要到企業(yè)微信->應(yīng)用管理->應(yīng)用->創(chuàng)建應(yīng)用設(shè)置,如果已有應(yīng)用要在第三方->添加第三方應(yīng)用設(shè)置
  • 微信添加第三方應(yīng)用

二:部分用戶拿不到unionId的問(wèn)題

1:?jiǎn)栴}
  • 在我們的小程序授權(quán)登錄的時(shí)候茬底,有一部分用戶拿不到微信的unionId沪悲,導(dǎo)致我們整個(gè)流程進(jìn)行不下去,查了很多的資料阱表,都是從wx.getUserInfo()里獲取殿如,但是取的解密值是沒(méi)有unionId字段的,如下
{"phoneNumber":"15251***648","watermark":{"appid":"wx4b101b***1f6b108","timestamp":1586253485},"purePhoneNumber":"15251**648","countryCode":"86"}
2:原因:
  • 暫時(shí)還沒(méi)有明白微信的wx.getUserInfo什么情況下會(huì)返回unionId的值
3:解決方案:
  • 前端如果傳的微信code授權(quán)獲取不到unionId捶枢,那么就要從前端第一次登錄授權(quán)獲取到的加密數(shù)據(jù)encryptedData和iv傳到后臺(tái)握截,然后后端再次登錄授權(quán)獲取到session_key 進(jìn)行解密,就能獲取到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");
        // 如果沒(méi)有獲取到就解析前端的加密數(shù)據(jù)
        if (null != unionId) {
            String str = AES.wxDecrypt(encryptedData, sessionKey, iv);
            JSONObject obj = JSONObject.parseObject(str);
            unionId = null != obj?obj.getString("unionId"):null;
        }
        return unionId
    }

三:企業(yè)微信的簽名機(jī)制和配置config獲取問(wèn)題

1:?jiǎn)栴}
  • 在企業(yè)微信開發(fā)中烂叔,我們一個(gè)需求是需要獲取當(dāng)前聊天人的信息然后入庫(kù)保存谨胞。我們制定的步驟就是前端先通過(guò)客戶端的API獲取當(dāng)前聊天人的userId,然后再通過(guò)服務(wù)端API獲取用戶的信息蒜鸡。在前端獲取客戶端api的時(shí)候必須要先獲取權(quán)限config的配置胯努,這個(gè)后端提供了接口給前端,包含簽名逢防,生成簽名的隨機(jī)串等信息叶沛,如圖
  • 但是前端通過(guò)這個(gè)獲取簽名獲取了userId以后,再調(diào)用后端接口的時(shí)候總會(huì)報(bào)非法的簽名信息忘朝。
2:原因
  • 企業(yè)微信的調(diào)用應(yīng)用接口的權(quán)限分為兩步:一是獲取權(quán)限驗(yàn)證的配置 二是獲取應(yīng)用的權(quán)限的配置
  • 我們通過(guò)我們的后端只返回給了前端一次簽名讓其去獲取權(quán)限驗(yàn)證的config灰署,然后前端又去調(diào)企業(yè)微信的接口,這個(gè)是行不通的局嘁,必須再獲取一次應(yīng)用的權(quán)限配置config溉箕,也就是要從后端獲取兩次簽名,用兩次不同的簽名獲取不同的config
  • 企業(yè)微信的config必須通過(guò)簽名去獲取悦昵,簽名由后端生成返回給前端
3:解決方案(java后端)
  • 1:返回給前端兩個(gè)不同的簽名
    //企業(yè)微信的jsapi_ticket地址
    private final static String GET_WX_TICKET = "https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token=ACCESS_TOKEN";

    //應(yīng)用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 {
        // 微信唯一標(biāo)識(shí)
        String appid = req.getAppid();
        // 微信調(diào)用接口憑證
        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("簽名返回內(nèi)容:{}", 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:獲取企業(yè)微信的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:隨機(jī)數(shù)生成規(guī)則
 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:簽名規(guī)則
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:前端根據(jù)返回的兩個(gè)config配置肴茄,去請(qǐng)求企業(yè)微信的接口,就能調(diào)的通了

三:總結(jié)

  • 微信的開發(fā)主要是因?yàn)槲臋n不清楚不連貫的導(dǎo)致開發(fā)者浪費(fèi)了大量的時(shí)間但指,我們遇到的主要問(wèn)題一個(gè)unionId的獲取不到寡痰,不知道如何配置才能獲取到unionId,那個(gè)時(shí)候微信文檔也沒(méi)有介紹(貌似現(xiàn)在有了)棋凳,一個(gè)前端企業(yè)微信接口調(diào)不通問(wèn)題拦坠,不清楚還要兩次獲取config,如何獲取config的簽名微信的文檔也沒(méi)有說(shuō)明怎么獲取剩岳,只說(shuō)了一個(gè)必填贪婉。解決了這些問(wèn)題我們的后期開發(fā)也順利了很多,希望以后騰訊系能更多支持java生態(tài)卢肃,封裝一些api的sdk給我們
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末疲迂,一起剝皮案震驚了整個(gè)濱河市才顿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌尤蒿,老刑警劉巖郑气,帶你破解...
    沈念sama閱讀 222,807評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異腰池,居然都是意外死亡尾组,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門示弓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)讳侨,“玉大人份名,你說(shuō)我怎么就攤上這事甚侣。” “怎么了疯特?”我有些...
    開封第一講書人閱讀 169,589評(píng)論 0 363
  • 文/不壞的土叔 我叫張陵囱皿,是天一觀的道長(zhǎng)勇婴。 經(jīng)常有香客問(wèn)我,道長(zhǎng)嘱腥,這世上最難降的妖魔是什么耕渴? 我笑而不...
    開封第一講書人閱讀 60,188評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮齿兔,結(jié)果婚禮上橱脸,老公的妹妹穿的比我還像新娘。我一直安慰自己分苇,他們只是感情好添诉,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,185評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著组砚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪掏颊。 梳的紋絲不亂的頭發(fā)上糟红,一...
    開封第一講書人閱讀 52,785評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音乌叶,去河邊找鬼盆偿。 笑死,一個(gè)胖子當(dāng)著我的面吹牛准浴,可吹牛的內(nèi)容都是我干的事扭。 我是一名探鬼主播,決...
    沈念sama閱讀 41,220評(píng)論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼乐横,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼求橄!你這毒婦竟也來(lái)了今野?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,167評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤罐农,失蹤者是張志新(化名)和其女友劉穎条霜,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涵亏,經(jīng)...
    沈念sama閱讀 46,698評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡宰睡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,767評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了气筋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拆内。...
    茶點(diǎn)故事閱讀 40,912評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖宠默,靈堂內(nèi)的尸體忽然破棺而出麸恍,到底是詐尸還是另有隱情,我是刑警寧澤光稼,帶...
    沈念sama閱讀 36,572評(píng)論 5 351
  • 正文 年R本政府宣布或南,位于F島的核電站,受9級(jí)特大地震影響艾君,放射性物質(zhì)發(fā)生泄漏采够。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,254評(píng)論 3 336
  • 文/蒙蒙 一冰垄、第九天 我趴在偏房一處隱蔽的房頂上張望蹬癌。 院中可真熱鬧,春花似錦虹茶、人聲如沸逝薪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)董济。三九已至,卻和暖如春要门,著一層夾襖步出監(jiān)牢的瞬間虏肾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工欢搜, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留封豪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,359評(píng)論 3 379
  • 正文 我出身青樓炒瘟,卻偏偏與公主長(zhǎng)得像吹埠,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,922評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容