公衆號開發精品教程(5)——獲取用戶基本信息與網頁授權

系列文章傳送門

公衆號開發精品教程(1)——緒論及環境搭建

公衆號開發精品教程(2)——將項目接入微信及簡單交互

公衆號開發精品教程(3)——創建菜單

公衆號開發精品教程(4)——生成帶參數的二維碼及合成海報

公衆號開發精品教程(5)——獲取用戶基本信息與網頁授權

整個項目的源碼已經上傳到百度網盤(博主的Git在維護,就不拿出來丟人了),永久有效,免費,在ChatConf類中填寫自己的APPID和開發者密鑰,在相關地方替換一下外網域名,即可使用,如有任何問題,歡迎在下方評論:

鏈接:百度網盤傳送門
提取碼:03eb

目錄

 

前言

閱讀文檔,理清思路

第一種:直接調用相關接口,通過openid拉取

第二種:通過網頁授權的方式拉取

方式1——直接獲取用戶的基本信息

方式2——通過網頁授權獲取用戶信息

優化小細節

總結


前言

在上一篇文章中,我們生成了帶參數的二維碼,合成了海報,並且模擬了掃碼關注時的場景。但是這些依然不夠,我們的公衆號上線之後,總要知道誰關注了、以及每個關注者的信息,用於後期的數據消費。

回首我之前寫的四篇文章,再加上今天的這一篇,其實我們已經涵蓋微信公衆號開發的95%以上的場景了:

關注時自動回覆、發送消息時自動回覆(圖文、圖片、視頻等都可以,只不過之前的例子是回覆文本消息)、上傳素材、生成帶參數的二維碼、合成海報、創建菜單、獲取用戶的基本信息、網頁授權的方式獲取用戶信息、掃碼事件

以上的功能點雖然沒有涵蓋所有的微信API接口,但是已經滿足大部分的場景需要了,剩下的功能和消費就靠我們項目中的設計了。而且,各位如果學會了微信開發的套路和流程,調幾個API接口來新增功能,還不是探囊取物……

好了,話不多說,開始正題!

請大家將自己的主機映射到外網、登錄到自己的測試號、打開微信公衆平臺文檔。

閱讀文檔,理清思路

對於獲取用戶的基本信息,微信提供了兩種方式:

第一種:直接調用相關接口,通過openid拉取

這是最簡單的一種方式,我們只需要將access_token和用戶的openid傳入到接口參數中,就可獲取該用戶的基本信息。但是這個接口是有限制的,只有當用戶關注了你的公衆號,你纔可以拉取到相關信息,否則只返回一個字段來通知你該用戶未關注公衆號。

看一下文檔的對應部分:

 

第二種:通過網頁授權的方式拉取

這種方式是需要網頁授權的,它又分爲兩種形式:靜默授權和非靜默授權。

無論是靜默授權還是非靜默授權,用戶授權之後會跳到我們設置的回調頁面,並且微信會在回調頁面傳一個URL參數——code,我們拿到code之後,再調用微信的這個接口換取access_token和openid:

注意,這裏的access_token,跟之前的access_token不一樣,之前的是調用接口的憑證,這裏的是換取用戶信息的憑證。可以說,之前的access_token是共用的,是一個調用憑證,調取接口的地方都可以使用它 ;這裏的access_token是私有的,只對應某一個用戶,只可獲得某個指定用戶的信息,是一個用戶憑證,而且一般的習慣是用完即丟,因爲它只能獲取某一個用戶的信息,並且沒有調用上限,完全沒必要存起來

我們獲取到access_token和openid之後,調用如下接口,就可以拉取用戶的信息了:

 微信指出,當靜默授權時,只能獲取到用戶的openid,要拉取用戶的所有信息,scope需爲snsapi_userinfo,也就是非靜默授權,但其實這裏有一個bug,不知道算不算漏洞(前提是你的公衆號必須是正式號,並且已經是企業認證):

當靜默授權時獲取到code之後,調用上述接口,即使用戶未關注公衆號,仍然可以獲取到用戶信息!!!

靜默授權,用戶是無感知的,只是感覺頁面跳了一下(因爲先訪問了微信的授權頁面,靜默方式下微信會直接跳到我們設置的回調頁面);非靜默授權,會彈一個框出來,提示用戶是否同意授權,相信大家都見過。

總結一下兩種方式的使用場景(一己之見)

直接獲取用戶信息的方式,一般發生在公衆號內的場景,即用戶已經關注的情況下,比如關注之後我們通過openid獲取他的信息,然後寫入到我們的數據庫,用於後期數據消費;

網頁授權的方式,一般用於在公衆號中的網頁,比如你的用戶將一個活動鏈接分享到了朋友圈,你想統計都有誰看過這個活動,那麼你就可以在這個頁面進行網頁授權,一旦有人進來就獲取他的信息,然後入庫(當然有點賤……,就說那個意思吧!)。

最後提醒一下大家,需要獲取他人信息的地方,如果不是在公衆號內的場景,最好還是使用非靜默授權,明確告訴用戶我要獲取你的信息,不要惡意營銷、惡意消費,做技術要有底線,小心吃官司……

方式1——直接獲取用戶的基本信息

其實就是調用一個接口,我這裏就在用戶關注公衆號的時候,拉取一下用戶的信息,各位可以根據自己的產品邏輯和業務場景,決定在哪些地方使用。

另外,獲取用戶信息的時候,我們依然採用阻塞隊列異步實現,線上的話可以考慮RabbitMQ、Kafka等消息中間件。因爲對於用戶而言,信息入庫這件事他並不關心,甚至寫入時出錯了,跟用戶有什麼關係呢?用戶期待的就是立馬得到響應,所以我們在用戶關注公衆號時,把他的openid寫入到一個隊列,然後主線程就直接返回消息。

題外話:大家一定要有良好的系統優化思維、架構思維,要想着怎樣提高系統的吞吐量。我經常見到這種場景,一些業務的執行對於用戶來說並不重要,但是好多人就是要把這些業務與用戶的請求同步執行,導致用戶的請求等待、掛起,系統的吞吐量根本上不去。

老規矩,把接口地址寫到我們的配置類中:

//直接獲取用戶基本信息的接口
public static final String GET_INFO_URL = "https://api.weixin.qq.com/cgi-bin/user/info";

然後我們寫一個工具類,負責獲取用戶的基本信息:

package com.blog.wechat.utils;

import com.blog.wechat.conf.ChatConf;
import com.blog.wechat.conf.SSLConf;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;

/**
 *  獲取用戶基本信息的工具類
 * @author 秋楓豔夢
 * @date 2019-06-09
 * */
public class UserInfoUtil {

    /**
     *  根據openid直接獲取用戶的基本信息
     * @param openId 用戶的openid
     * @return 獲取到的信息,是一個JSON字符串
     * */
    public static String getInfoById(String openId){
        OkHttpClient client = null;
        Request request = null;
        Response response = null;
        String infoStr = "";

        try {
            client = new OkHttpClient.Builder()
                    .sslSocketFactory(SSLConf.getSslSocketFactory(),new SSLConf.TrustAllManager())
                    .hostnameVerifier(new SSLConf.TrustAllHost()).build();

            request = new Request.Builder()
                    .url(ChatConf.GET_INFO_URL+"?access_token="+ChatConf.getToken()+"&openid="+openId+"&lang=zh_CN")
                    .build();

            response = client.newCall(request).execute();
            if (response.isSuccessful()){
                infoStr = response.body().string();
            }
        }catch (IOException e){

        }finally {
            if (response!=null){
                response.close();
            }
            client.dispatcher().executorService().shutdown();
        }

        return infoStr;
    }
}

然後寫一個阻塞隊列的類,跟我們上一篇生成海報的阻塞隊列差不多:

package com.blog.wechat.queue;

import com.blog.wechat.utils.UserInfoUtil;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;

/**
 *  異步獲取用戶信息的阻塞隊列類
 * @author 秋楓豔夢
 * @date 2019-06-09
 * */
public class UserInfoQueue {
    //存放openid的阻塞隊列.openid即微信推送的數據包中的FromUserName
    public static BlockingQueue<String> userQueue = new LinkedBlockingDeque<>();
    //監聽隊列的線程數量,這裏我們開啓15個線程去處理(並不是越多越好),提高吞吐量
    public static final int THREADS = 15;

    /**
     *  監聽阻塞隊列,執行相關業務
     * */
    public static void startListen(){
        for (int i = 0; i < THREADS; i++) {
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    while (true){
                        try {
                            String openId = userQueue.take();
                            String info = UserInfoUtil.getInfoById(openId);
                            System.out.println("獲取到的用戶信息:\n"+info);
                            //這裏模擬一下即可
                            System.out.println("已寫入數據庫......");
                        }catch (Exception e){

                        }
                    }
                }
            };
            new Thread(runnable).start();
        }
    }
}

然後配置一下監聽器,項目一啓動就開始監聽這個隊列,這裏就不再列出代碼了,上一章已經列出過了。

最後,我們在用戶關注的時候,往隊列中寫入一個openid,通知阻塞隊列開始工作,修改部分代碼:

注意:接口測試號是不能搜索到的,只能通過在測試號系統掃碼關注,所以我們把這個事件放在掃碼關注時

//如果是被關注事件,向用戶回覆內容,只需要將整理好的XML文本參數返回給微信即可
            if (map.get("Event").equals("subscribe")){
                //如果沒有EventKey,說明是普通關注,否則是掃碼關注事件
                String eventKey = map.get("EventKey");
                if (eventKey==null){
                    content = "歡迎關注秋楓豔夢的測試公衆號!";
                }else {
                    String param = eventKey.substring(eventKey.indexOf("_")+1);
                    //爲了簡單,這裏直接返回一句話,實際業務場景要更復雜
                    content = "您是由openid爲"+param+"的用戶引進來的,我們已對其進行了獎勵,您也可以生成海報,分享給朋友,可獲得獎勵";
                    //寫入到阻塞隊列
                    try {
                        UserInfoQueue.userQueue.put(fromUser);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

運行一下項目,我們重新關注公衆號:

可以看到,我們已經獲取到用戶的基本信息了。

方式2——通過網頁授權獲取用戶信息

前面提到,網頁授權有靜默和非靜默兩種,博主這裏就演示非靜默方式的拉取,靜默的跟這個一樣,非靜默只是彈一個窗口讓用戶去選擇。其實,公衆號內的操作大多是靜默授權,我們有很多種方式去獲取用戶的信息,比如直接調用獲取信息的接口,或者從我們的數據庫查詢(用戶關注的時候我們已經入庫了),這裏只是做一下演示。

首先我們需要有一個HTML文件,網頁授權嘛,首先得有網頁啊。這裏就繼續使用我們之前的訂單頁面——home.html。

另外,微信的API接口是不支持跨域的,所以你沒辦法直接通過Ajax訪問其接口拉取信息,微信也說了,必須在服務器端發起請求,所以我們的做法是前端拿到code之後,通過Ajax調用後端的接口,後端根據code去換取用戶的信息,再返回給前端

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--不要忘了引jQuery-->
    <script src="../statics/js/jquery-3.3.1.js" type="text/javascript"></script>
</head>
<script>
    /**
     *
     *  獲取URL中的參數
     * */
    function getUrlParam(url,name){
        var pattern = new RegExp("[?&]"+name+"\=([^&]+)", "g");
        var matcher = pattern.exec(url);
        var items = null;
        if(null != matcher){
            try{
                items = decodeURIComponent(decodeURIComponent(matcher[1]));
            }catch(e){
                try{
                    items = decodeURIComponent(matcher[1]);
                }catch(e){
                    items = matcher[1];
                }
            }
        }
        return items;
    }

    //app_id
    var APPID = "wx98166c786c5e760b";
    //回調頁面即是當前頁
    var destUrl = decodeURIComponent(location.href);
    //微信返回來的code
    var code = getUrlParam(location.href,"code");
    //如果當前URL參數中沒有code,說明用戶剛進來,還沒有走微信授權
    if (code==null||code==""){
        //引導用戶到授權頁面,這裏採用靜默授權,用戶是無感知的
        location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+APPID+"&redirect_uri="+destUrl+"&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
    }else {
        //訪問我們自己的接口,傳入code,獲取用戶的信息之後展示到頁面上
        $.ajax({
            url:"http://vxye92.natappfree.cc/user/get/info/"+code,
            dataType:"json",
            success:function (str) {
                $("#headImg").attr("src",str.headimgurl);
                $("#nickName").html(str.nickname);
            },
            error:function () {
                alert("錯誤");
            }
        });
    }
</script>
<body>
秋楓豔夢,公衆號——訂單頁面
獲取到您的基本信息:
頭像:<img src="" id="headImg">
暱稱:<span id="nickName"></span>
其餘省略
</body>
</html>

現在前端寫好了,我們把後端的部分補上。

首先,先把需要用到的兩個接口保存起來:

//通過code換取網頁授權access_token的接口地址
public static final String GET_USER_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid="+ChatConf.APPID+"&secret="+ChatConf.SECRET+"&code={code}&grant_type=authorization_code";
//根據網頁授權access_token和openid換取用戶信息的接口地址
public static final String GET_CODE_INFO_URL = "https://api.weixin.qq.com/sns/userinfo";

然後在我們剛纔創建的UserInfoUtil類中,擴展一個方法,根據code獲取用戶的基本信息:

/**
     *  網頁授權,根據code獲取用戶的信息
     * @param code 微信回調頁面時傳來的code
     * @return 獲取到的信息
     * */
    public static String getInfoByCode(String code){
        OkHttpClient client = null;
        Request request = null;
        Response response = null;
        String infoStr = "";

        try {
            client = new OkHttpClient.Builder()
                    .sslSocketFactory(SSLConf.getSslSocketFactory(),new SSLConf.TrustAllManager())
                    .hostnameVerifier(new SSLConf.TrustAllHost()).build();

            //先根據code換取access_token和openid
            request = new Request.Builder().url(ChatConf.GET_USER_TOKEN_URL.replace("{code}",code)).get().build();
            response = client.newCall(request).execute();
            JSONObject jsonObject = JSON.parseObject(response.body().string());
            String accessToken = jsonObject.getString("access_token");
            String openId = jsonObject.getString("openid");

            //再根據access_token和openid,獲取用戶的基本信息
            request = new Request.Builder()
                    .url(ChatConf.GET_CODE_INFO_URL+"?access_token="+accessToken+"&openid="+openId+"&lang=zh_CN")
                    .build();
            response = client.newCall(request).execute();
            infoStr = response.body().string();
        }catch (IOException e){

        }finally {
            if (response!=null){
                response.close();
            }
            client.dispatcher().executorService().shutdown();
        }

        return infoStr;
    }

然後我們再創建一個控制器,供前端訪問:

package com.blog.wechat.controller;

import com.alibaba.fastjson.JSON;
import com.blog.wechat.utils.UserInfoUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping(value = "/user",produces = {"application/json;charset=utf-8"})
@ResponseBody
public class UserController {

    @RequestMapping(value = "/get/info/{code}")
    public Object getUserInfo(@PathVariable String code){
        return JSON.parseObject(UserInfoUtil.getInfoByCode(code));
    }
}

因爲我內網穿透的域名變了,所以點擊之前的菜單已經進入不到我的home.html了,所以我重新創建一下,大家也要注意一下這個問題。這裏就不再貼出來代碼了,不懂的可參考我之前的文章。

最後一步,需要在測試號配置回調域名,只有在這個域名下的連接,微信才能正確回調,如http://vxye92.natappfree.cc/home.html:

注意:這裏不需要加http://。而且測試號直接這樣配置就行,正式號還需要下載一個文本文件到你的網站根目錄下,確保可以訪問到才行,就行之前的http://vxye92.natappfree.cc/statics/img/back.jpg一樣,確保定位到這個資源才行。

點擊確認之後,我們重新運行項目,重新關注公衆號,點擊“我的訂單”按鈕:

因爲我已經關注這個公衆號了,所以哪怕指定非靜默方式,微信也不會再彈出那個框了。由於測試號只能我們自己訪問,大家可以試一下,把這個鏈接分享到你的朋友圈,然後你取消關注,再從朋友圈點擊這個鏈接,就會提示你授權,你同意授權之後,一樣可以拿到信息。

優化小細節

細心的朋友可以發現,當我們進入到“我的訂單”頁面後,點擊返回按鈕想要退出頁面時,發現又重新授權然後進入到了這個頁面,需要快速連續點擊兩次才能退出。

爲什麼?因爲可以我們授權登錄的操作,是通過在home.html頁面通過location.href打開了微信的授權頁面,但是微信回調之後,對於瀏覽器來說,上一級頁面就是微信的授權頁面,所以點擊返回按鈕瀏覽器就退回到了授權頁面,然後授權頁面再回調我們的home.html,如此往復……博主當時腦袋抽抽了,這麼簡單的問題都沒想到,差點通宵……

那怎麼辦呢?因爲我們這是公衆號的一個菜單,菜單要跳轉的頁面肯定是不變的吧?那我們在創建菜單的時候,直接把“我的訂單”按鈕的url屬性設置成微信的授權頁不就行了麼?

代碼如下:

//我的訂單URL,跳向當前項目中的頁面
    private static String ORDER;
    static {
        try {
            ORDER = "https://open.weixin.qq.com/connect/oauth2/authorize?appid="+ChatConf.APPID+"&redirect_uri="+ URLEncoder.encode("http://vxye92.natappfree.cc/home.html","GBK") +"&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect";
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

重新創建菜單,再次點擊“我的訂單”,就會是一種不錯的效果了!

總結

微信開發之旅,到這裏已經進行的差不多了,可以滿足大家的需要了。

如果博主發現新的大坑、或者有價值的東西,將持續更新!

歡迎關注、轉載!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章