1. 程式人生 > >微信公眾平臺開發實戰Java版之如何獲取公眾號的access_token以及快取access_token

微信公眾平臺開發實戰Java版之如何獲取公眾號的access_token以及快取access_token

一、access_token簡介

為了使第三方開發者能夠為使用者提供更多更有價值的個性化服務,微信公眾平臺 開放了許多介面,包括自定義選單介面、客服介面、獲取使用者資訊介面、使用者分組介面、群發介面等,

開發者在呼叫這些介面時,都需要傳入一個相同的引數 access_token,它是公眾賬號的全域性唯一票據,它是介面訪問憑證。

access_token是公眾號的全域性唯一票據,公眾號呼叫各介面時都需使用access_token。開發者需要進行妥善儲存。

access_token的儲存至少要保留512個字元空間。

access_token的有效期目前為2個小時,需定時重新整理,重複獲取將導致上次獲取的 access_token失效。

如果第三方不使用中控伺服器,而是選擇各個業務邏輯點各自去重新整理access_token,那麼就可能會產生衝突,導致服務不穩定。

公眾號可以使用AppID和AppSecret呼叫本介面來獲取access_token。AppID和AppSecret可在微信公眾平臺官網-開發者中心頁中獲得(需要已經成為開發者,且帳號沒有異常狀態)。注意呼叫所有微信介面時均需使用https協議。

access_token的有效期是7200秒(兩小時),在有效期內,可以一直使用,只有當access_token過期時,才需要再次呼叫介面 獲取access_token。在理想情況下,一個7x24小時執行的系統,每天只需要獲取12次access_token,即每2小時獲取一次。如果在 有效期內,再次獲取access_token,那麼上一次獲取的access_token將失效。

目前,獲取access_token介面的呼叫頻率限制為2000次/天,如果每次傳送客服訊息、獲取使用者資訊、群發訊息之前都要先呼叫獲取 access_token介面得到介面訪問憑證,這顯然是不合理的,一方面會更耗時(多了一次介面呼叫操作),另一方面2000次/天的呼叫限制恐怕也不 夠用。因此,在實際應用中,我們需要將獲取到的access_token儲存起來,然後定期呼叫access_token介面更新它,以保證隨時取出的 access_token都是有效的。

我們先看看官方的說明:

介面呼叫請求說明

引數說明

引數是否必須說明
grant_type 獲取access_token填寫client_credential
appid 第三方使用者唯一憑證
secret 第三方使用者唯一憑證金鑰,即appsecret

返回說明

正常情況下,微信會返回下述JSON資料包給公眾號:

{"access_token":"ACCESS_TOKEN","expires_in":7200}
引數說明
access_token 獲取到的憑證
expires_in 憑證有效時間,單位:秒


錯誤時微信會返回錯誤碼等資訊,JSON資料包示例如下(該示例為AppID無效錯誤):

{"errcode":40013,"errmsg":"invalid appid"}

二、封裝基本類

封裝一下token類:

package com.souvc.weixin.pojo;

/**
* 類名: Token </br>
* 描述: 憑證 </br>
* 開發人員: souvc </br>
* 建立時間:  2015-9-30 </br>
* 釋出版本:V1.0  </br>
 */
public class Token {
    // 介面訪問憑證
    private String accessToken;
    // 憑證有效期,單位:秒
    private int expiresIn;

    public String getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }

    public int getExpiresIn() {
        return expiresIn;
    }

    public void setExpiresIn(int expiresIn) {
        this.expiresIn = expiresIn;
    }
}

View Code

三、獲取token。

1.問題:如何通過獲取token?

解決方案:(1)直接通過瀏覽器訪問。(2)編寫程式,模擬https連線,獲得token。

解決詳細步驟如下:

(2)如何在程式中模擬傳送https請求,並且獲取到token呢?

對於https請求,我們需要一個證書信任管理器,這個管理器類需要自己定義,但需要實現X509TrustManager介面, 

首先定義一個MyX509TrustManager 類。

package com.souvc.weixin.util;

import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import javax.net.ssl.X509TrustManager;

/**
* 類名: MyX509TrustManager </br>
* 描述: 信任管理器 </br>
* 開發人員: souvc </br>
* 建立時間:  2015-9-30 </br>
* 釋出版本:V1.0  </br>
 */
public class MyX509TrustManager implements X509TrustManager {

    // 檢查客戶端證書
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }

    // 檢查伺服器端證書
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }

    // 返回受信任的X509證書陣列
    public X509Certificate[] getAcceptedIssuers() {
        return null;
    }
}

View Code

 建立一個token測試類:

package com.souvc.weixin.test;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

import com.souvc.weixin.util.MyX509TrustManager;

public class TokenTest {

    public static void main(String[] args) throws Exception {
    //修改appID,secret
        String tokenUrl="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
        //建立連線
        URL url = new URL(tokenUrl);
        HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();

        // 建立SSLContext物件,並使用我們指定的信任管理器初始化
        TrustManager[] tm = { new MyX509TrustManager() };
        SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
        sslContext.init(null, tm, new java.security.SecureRandom());
        // 從上述SSLContext物件中得到SSLSocketFactory物件
        SSLSocketFactory ssf = sslContext.getSocketFactory();

        httpUrlConn.setSSLSocketFactory(ssf);
        httpUrlConn.setDoOutput(true);
        httpUrlConn.setDoInput(true);

        // 設定請求方式(GET/POST)
        httpUrlConn.setRequestMethod("GET");

        // 取得輸入流
        InputStream inputStream = httpUrlConn.getInputStream();
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        //讀取響應內容
        StringBuffer buffer = new StringBuffer();
        String str = null;
        while ((str = bufferedReader.readLine()) != null) {
            buffer.append(str);
        }
        bufferedReader.close();
        inputStreamReader.close();
        // 釋放資源
        inputStream.close();
        httpUrlConn.disconnect();
        //輸出返回結果
        System.out.println(buffer);
    }


}

View Code

 微信伺服器返回的結果:

{"access_token":"E3kRcQTati3QBPz97ou7zG0NXFrZFbA5No_hs5FNUZ62ROT0jr0txWr-gG1w-t06kk0zBW0kFmJiicJAydFyHNZhIh2uqIw4B5t85huRLs4","expires_in":7200}

2.問題:微信伺服器返回的是json資料,如何從json裡面解析出來的值?

方案:可以通過一款開源的json開發工具包json-lib,將他轉換為java物件。

詳細實現方法:

首先引入jar包

jar包下載:  http://yunpan.cn/cH9UCqsQ9XVdE  訪問密碼 2711

封裝一個通用的工具類 CommonUtil ,用於專門獲取token

package com.souvc.weixin.util;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ConnectException;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

import net.sf.json.JSONException;
import net.sf.json.JSONObject;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.souvc.weixin.pojo.Token;

/**
* 類名: CommonUtil </br>
* 描述: 通用工具類 </br>
* 開發人員: souvc </br>
* 建立時間:  2015-9-30 </br>
* 釋出版本:V1.0  </br>
 */
public class CommonUtil {
    private static Logger log = LoggerFactory.getLogger(CommonUtil.class);

    // 憑證獲取(GET)
    public final static String token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";

    /**
     * 傳送https請求
     * 
     * @param requestUrl 請求地址
     * @param requestMethod 請求方式(GET、POST)
     * @param outputStr 提交的資料
     * @return JSONObject(通過JSONObject.get(key)的方式獲取json物件的屬性值)
     */
    public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) {
        JSONObject jsonObject = null;
        try {
            // 建立SSLContext物件,並使用我們指定的信任管理器初始化
            TrustManager[] tm = { new MyX509TrustManager() };
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new java.security.SecureRandom());
            // 從上述SSLContext物件中得到SSLSocketFactory物件
            SSLSocketFactory ssf = sslContext.getSocketFactory();

            URL url = new URL(requestUrl);
            HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
            conn.setSSLSocketFactory(ssf);

            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            // 設定請求方式(GET/POST)
            conn.setRequestMethod(requestMethod);

            // 當outputStr不為null時向輸出流寫資料
            if (null != outputStr) {
                OutputStream outputStream = conn.getOutputStream();
                // 注意編碼格式
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }

            // 從輸入流讀取返回內容
            InputStream inputStream = conn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }

            // 釋放資源
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
            inputStream = null;
            conn.disconnect();
            jsonObject = JSONObject.fromObject(buffer.toString());
        } catch (ConnectException ce) {
            log.error("連線超時:{}", ce);
        } catch (Exception e) {
            log.error("https請求異常:{}", e);
        }
        return jsonObject;
    }

    /**
     * 獲取介面訪問憑證
     * 
     * @param appid 憑證
     * @param appsecret 金鑰
     * @return
     */
    public static Token getToken(String appid, String appsecret) {
        Token token = null;
        String requestUrl = token_url.replace("APPID", appid).replace("APPSECRET", appsecret);
        // 發起GET請求獲取憑證
        JSONObject jsonObject = httpsRequest(requestUrl, "GET", null);

        if (null != jsonObject) {
            try {
                token = new Token();
                token.setAccessToken(jsonObject.getString("access_token"));
                token.setExpiresIn(jsonObject.getInt("expires_in"));
            } catch (JSONException e) {
                token = null;
                // 獲取token失敗
                log.error("獲取token失敗 errcode:{} errmsg:{}", jsonObject.getInt("errcode"), jsonObject.getString("errmsg"));
            }
        }
        return token;
    }
}

View Code

修改Token測試類:

package com.souvc.weixin.test;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;

import org.junit.Test;

import com.souvc.weixin.pojo.Token;
import com.souvc.weixin.util.CommonUtil;
import com.souvc.weixin.util.MyX509TrustManager;

public class TokenTest {

    @Test
    public void testGetToken1() throws Exception {
        String tokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=appID&secret=appsecret";
        // 建立連線
        URL url = new URL(tokenUrl);
        HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection();

        // 建立SSLContext物件,並使用我們指定的信任管理器初始化
        TrustManager[] tm = { new MyX509TrustManager() };
        SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
        sslContext.init(null, tm, new java.security.SecureRandom());
        // 從上述SSLContext物件中得到SSLSocketFactory物件
        SSLSocketFactory ssf = sslContext.getSocketFactory();

        httpUrlConn.setSSLSocketFactory(ssf);
        httpUrlConn.setDoOutput(true);
        httpUrlConn.setDoInput(true);

        // 設定請求方式(GET/POST)
        httpUrlConn.setRequestMethod("GET");

        // 取得輸入流
        InputStream inputStream = httpUrlConn.getInputStream();
        InputStreamReader inputStreamReader = new InputStreamReader(
                inputStream, "utf-8");
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        // 讀取響應內容
        StringBuffer buffer = new StringBuffer();
        String str = null;
        while ((str = bufferedReader.readLine()) != null) {
            buffer.append(str);
        }
        bufferedReader.close();
        inputStreamReader.close();
        // 釋放資源
        inputStream.close();
        httpUrlConn.disconnect();
        // 輸出返回結果
        System.out.println(buffer);
    }

    @Test
    public void testGetToken2() {
        Token token = CommonUtil.getToken("appID","appsecret");
        System.out.println("access_token:"+token.getAccessToken());
        System.out.println("expires_in:"+token.getExpiresIn());
    }
}

View Code

控制檯輸出效果如下,說明我們獲取到了access_token和expires_in

access_token:2amR6pr1eN-BuSBgho-nzo5tofxJ6BdEnRJQ87Zs5bj4ny4CGB8w-1D3YtjG2PzmEvVm1INrsVg-5BjyHCkWmBKsLPDSF3r_bdaPxMpKtbw
expires_in:7200

四、快取access_token。

問題:問題又來來,可是我們如何把這個值快取起來呢,並且需要2倆小時獲取一次?

方案:

(1)可以通過快取框架把該值通過key-values形式快取在記憶體中 。

(2)可以把該值存入資料庫中,需要的時候就去提取。

使用資料庫的話,大概思路就是這樣的。第一次使用將其access_token儲存起來,下次需要access_token則將其查出。若是失效則,重新建立並更新資料庫.若是沒有失效,則直接使用。

建立資料庫 souvc

create database  souvc ;

建立資料庫表 t_token 來存放token值

複製程式碼
CREATE TABLE `t_token` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `access_token` varchar(1024) NOT NULL,
  `expires_in` int(11) NOT NULL,
  `createTime` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
複製程式碼

現在我們通過jdbc來讀取這個token值。

匯入jdbc的jar包

http://yunpan.cn/cH9IRaKemL3II  訪問密碼 2ffa

匯入mysql 的jar包

http://yunpan.cn/cmv3BGe9CkUr3  訪問密碼 3601

新建一個數據庫配置檔案db.properties

複製程式碼
#Oracle