Java微信公眾平台開發(六)--微信開發中的token獲取

  • 2019 年 10 月 5 日
  • 筆記

(一)token的介紹

引用:access_token是公眾號的全局唯一票據,公眾號調用各介面時都需使用access_token。開發者需要進行妥善保存。access_token的存儲至少要保留512個字元空間。access_token的有效期目前為2個小時,需定時刷新,重複獲取將導致上次獲取的access_token失效!

(二)token的獲取參考文檔

獲取的流程我們完全可以參考微信官方文檔:http://mp.weixin.qq.com/wiki/14/9f9c82c1af308e3b14ba9b973f99a8ba.html 如圖:

(三)token獲取流程分析

  • 從公眾平台獲取帳號的AppID和AppSecret;
  • token獲取並解析存儲執行體;
  • 採用任務調度每隔兩小時執行一次token獲取執行體;

(四)token的獲取流程的具體實現

①獲取appid和appsecret

在微信公眾平台介面測試工具中可以查看到我們需要的兩個參數:

這裡我們將appid 和secret 定義到配置文件【wechat.properties】,在src目錄下新建【wechat.properties】文件,大致程式碼為:

#開發者的appid  appid=wx7e32765bc24XXXX  #開發者的AppSecret  AppSecret=d58051564fe9d86093f9XXXXX

②token獲取並解析存儲執行體的程式碼編寫

由於在這裡我們需要通過http的get請求向微信伺服器獲取時效性為7200秒的token,所以我在這裡寫了一個http請求的工具類HttpUtils,以方便我們的使用,如下:(這裡需要導入文末的http協議包)

  1 package com.gede.wechat.util;    2 import java.io.BufferedInputStream;    3 import java.io.BufferedReader;    4 import java.io.IOException;    5 import java.io.InputStream;    6 import java.io.InputStreamReader;    7 import java.io.OutputStreamWriter;    8 import java.net.MalformedURLException;    9 import java.net.URI;   10 import java.net.URL;   11 import java.net.URLConnection;   12 import java.util.ArrayList;   13 import java.util.List;   14 import java.util.Map;   15 import java.util.Set;   16 import java.util.zip.GZIPInputStream;   17   18 import org.apache.http.HttpResponse;   19 import org.apache.http.NameValuePair;   20 import org.apache.http.client.ClientProtocolException;   21 import org.apache.http.client.HttpClient;   22 import org.apache.http.client.entity.UrlEncodedFormEntity;   23 import org.apache.http.client.methods.HttpGet;   24 import org.apache.http.client.methods.HttpPost;   25 import org.apache.http.entity.StringEntity;   26 import org.apache.http.impl.client.DefaultHttpClient;   27 import org.apache.http.message.BasicNameValuePair;   28 import org.apache.http.protocol.HTTP;   29 import org.apache.http.util.EntityUtils;   30 /**   31 * @author gede   32 * @version date:2019年5月26日 下午5:43:36   33 * @description :   34 */   35 public class HttpUtils {   36   37     /**   38      * @Description: http get請求共用方法   39      * @param @param reqUrl   40      * @param @param params   41      * @param @return   42      * @param @throws Exception   43      */   44     @SuppressWarnings("resource")   45     public static String sendGet(String reqUrl, Map<String, String> params)   46             throws Exception {   47         InputStream inputStream = null;   48         HttpGet request = new HttpGet();   49         try {   50             String url = buildUrl(reqUrl, params);   51             HttpClient client = new DefaultHttpClient();   52   53             request.setHeader("Accept-Encoding", "gzip");   54             request.setURI(new URI(url));   55   56             HttpResponse response = client.execute(request);   57   58             inputStream = response.getEntity().getContent();   59             String result = getJsonStringFromGZIP(inputStream);   60             return result;   61         } finally {   62             if (inputStream != null) {   63                 inputStream.close();   64             }   65             request.releaseConnection();   66         }   67   68     }   69   70     @SuppressWarnings("resource")   71     public static String sendPost(String reqUrl, Map<String, String> params)   72             throws Exception {   73         try {   74             Set<String> set = params.keySet();   75             List<NameValuePair> list = new ArrayList<NameValuePair>();   76             for (String key : set) {   77                 list.add(new BasicNameValuePair(key, params.get(key)));   78             }   79             if (list.size() > 0) {   80                 try {   81                     HttpClient client = new DefaultHttpClient();   82                     HttpPost request = new HttpPost(reqUrl);   83   84                     request.setHeader("Accept-Encoding", "gzip");   85                     request.setEntity(new UrlEncodedFormEntity(list, HTTP.UTF_8));   86   87                     HttpResponse response = client.execute(request);   88   89                     InputStream inputStream = response.getEntity().getContent();   90                     try {   91                         String result = getJsonStringFromGZIP(inputStream);   92   93                         return result;   94                     } finally {   95                         inputStream.close();   96                     }   97                 } catch (Exception ex) {   98                     ex.printStackTrace();   99                     throw new Exception("網路連接失敗,請連接網路後再試");  100                 }  101             } else {  102                 throw new Exception("參數不全,請稍後重試");  103             }  104         } catch (Exception ex) {  105             ex.printStackTrace();  106             throw new Exception("發送未知異常");  107         }  108     }  109  110     public static String sendPostBuffer(String urls, String params)  111             throws ClientProtocolException, IOException {  112         HttpPost request = new HttpPost(urls);  113  114         StringEntity se = new StringEntity(params, HTTP.UTF_8);  115         request.setEntity(se);  116         // 發送請求  117         @SuppressWarnings("resource")  118         HttpResponse httpResponse = new DefaultHttpClient().execute(request);  119         // 得到應答的字元串,這也是一個 JSON 格式保存的數據  120         String retSrc = EntityUtils.toString(httpResponse.getEntity());  121         request.releaseConnection();  122         return retSrc;  123  124     }  125  126     public static String sendXmlPost(String urlStr, String xmlInfo) {  127         // xmlInfo xml具體字元串  128  129         try {  130             URL url = new URL(urlStr);  131             URLConnection con = url.openConnection();  132             con.setDoOutput(true);  133             con.setRequestProperty("Pragma:", "no-cache");  134             con.setRequestProperty("Cache-Control", "no-cache");  135             con.setRequestProperty("Content-Type", "text/xml");  136             OutputStreamWriter out = new OutputStreamWriter(  137                     con.getOutputStream());  138             out.write(new String(xmlInfo.getBytes("utf-8")));  139             out.flush();  140             out.close();  141             BufferedReader br = new BufferedReader(new InputStreamReader(  142                     con.getInputStream()));  143             String lines = "";  144             for (String line = br.readLine(); line != null; line = br  145                     .readLine()) {  146                 lines = lines + line;  147             }  148             return lines; // 返回請求結果  149         } catch (MalformedURLException e) {  150             e.printStackTrace();  151         } catch (IOException e) {  152             e.printStackTrace();  153         }  154         return "fail";  155     }  156  157     private static String getJsonStringFromGZIP(InputStream is) {  158         String jsonString = null;  159         try {  160             BufferedInputStream bis = new BufferedInputStream(is);  161             bis.mark(2);  162             // 取前兩個位元組  163             byte[] header = new byte[2];  164             int result = bis.read(header);  165             // reset輸入流到開始位置  166             bis.reset();  167             // 判斷是否是GZIP格式  168             int headerData = getShort(header);  169             // Gzip 流 的前兩個位元組是 0x1f8b  170             if (result != -1 && headerData == 0x1f8b) {  171                 // LogUtil.i("HttpTask", " use GZIPInputStream  ");  172                 is = new GZIPInputStream(bis);  173             } else {  174                 // LogUtil.d("HttpTask", " not use GZIPInputStream");  175                 is = bis;  176             }  177             InputStreamReader reader = new InputStreamReader(is, "utf-8");  178             char[] data = new char[100];  179             int readSize;  180             StringBuffer sb = new StringBuffer();  181             while ((readSize = reader.read(data)) > 0) {  182                 sb.append(data, 0, readSize);  183             }  184             jsonString = sb.toString();  185             bis.close();  186             reader.close();  187         } catch (Exception e) {  188             e.printStackTrace();  189         }  190  191         return jsonString;  192     }  193  194     private static int getShort(byte[] data) {  195         return (data[0] << 8) | data[1] & 0xFF;  196     }  197  198     /**  199      * 構建get方式的url  200      *  201      * @param reqUrl  202      *            基礎的url地址  203      * @param params  204      *            查詢參數  205      * @return 構建好的url  206      */  207     public static String buildUrl(String reqUrl, Map<String, String> params) {  208         StringBuilder query = new StringBuilder();  209         Set<String> set = params.keySet();  210         for (String key : set) {  211             query.append(String.format("%s=%s&", key, params.get(key)));  212         }  213         return reqUrl + "?" + query.toString();  214     }  215  216 }

我們在做http請求的時候需要目標伺服器的url,這裡在項目中為了方便對url的管理我們src目錄下建立了interface_url.properties用於存放目標url,這裡我們將請求token的url存入:

#獲取token的url  tokenUrl=https://api.weixin.qq.com/cgi-bin/token

我們需要將我們配置的配置文件在項目初始化後能得到啟動,所以我在這裡加入一個項目初始化的程式碼InterfaceUrlIntiServlet來實現,用於項目啟動初始化interface_url.properties和wechat.properties中的配置:

 1 package com.gede.web.start;   2 import javax.servlet.ServletConfig;   3 import javax.servlet.ServletException;   4 import javax.servlet.http.HttpServlet;   5 /**   6 * @author gede   7 * @version date:2019年5月26日 下午7:42:14   8 * @description :   9 */  10 public class InterfaceUrlIntiServlet extends HttpServlet {  11  12     private static final long serialVersionUID = 1L;  13  14     @Override  15     public void init(ServletConfig config) throws ServletException {  16         InterfaceUrlInti.init();  17     }  18 }

初始化的具體實現,將初始化過後的方法都存入到GlobalConstants中方便項目中隨意調用,如下:

 1 package com.gede.web.start;   2 import java.io.IOException;   3 import java.io.InputStream;   4 import java.util.Properties;   5 import com.gede.web.util.GlobalConstants;   6 /**   7 * @author gede   8 * @version date:2019年5月26日 下午7:42:37   9 * @description :  10 */  11 public class InterfaceUrlInti {  12  13     public synchronized static void init(){  14         ClassLoader cl = Thread.currentThread().getContextClassLoader();  15         Properties props = new Properties();  16         if(GlobalConstants.interfaceUrlProperties==null){  17             GlobalConstants.interfaceUrlProperties = new Properties();  18         }  19         InputStream in = null;  20         try {  21             in = cl.getResourceAsStream("interface_url.properties");  22             props.load(in);  23             for(Object key : props.keySet()){  24                 GlobalConstants.interfaceUrlProperties.put(key, props.get(key));  25             }  26  27             props = new Properties();  28             in = cl.getResourceAsStream("wechat.properties");  29             props.load(in);  30             for(Object key : props.keySet()){  31                 GlobalConstants.interfaceUrlProperties.put(key, props.get(key));  32             }  33  34         } catch (IOException e) {  35             e.printStackTrace();  36         }finally{  37             if(in!=null){  38                 try {  39                     in.close();  40                 } catch (IOException e) {  41                     e.printStackTrace();  42                 }  43             }  44         }  45         return;  46     }  47  48 }

這裡用到的GlobalConstants,我們新建在web.util包裡面,程式碼如下:

package com.gede.web.util;  import java.util.Properties;  /**  * @author gede  * @version date:2019年5月26日 下午7:45:27  * @description :  */  public class GlobalConstants {      public static Properties interfaceUrlProperties;    /**   * @Description: TODO   * @param @param key   * @param @return   */      public static String getInterfaceUrl(String key) {          return (String) interfaceUrlProperties.get(key);      }  }

當我們把所有的準備工作都做好了之後我們可以開始真正的去獲取token了,這裡我們將獲取到的token解析之後依然存儲到GlobalConstants中方便使用,簡單程式碼如下:(這裡需要導入我們附件中的json包)

 1 package com.gede.wechat.common;   2 import java.text.SimpleDateFormat;   3 import java.util.Date;   4 import java.util.HashMap;   5 import java.util.Map;   6   7 import com.gede.web.util.GlobalConstants;   8 import com.gede.wechat.util.HttpUtils;   9  10 import net.sf.json.JSONObject;  11 /**  12 * @author gede  13 * @version date:2019年5月26日 下午7:50:38  14 * @description :  15 */  16 public class WeChatTask {  17     /**  18      * @Description: 任務執行體  19      * @param @throws Exception  20      */  21     public void getToken_getTicket() throws Exception {  22         Map<String, String> params = new HashMap<String, String>();  23         params.put("grant_type", "client_credential");  24         params.put("appid", GlobalConstants.getInterfaceUrl("appid"));  25         params.put("secret", GlobalConstants.getInterfaceUrl("AppSecret"));  26         String jstoken = HttpUtils.sendGet(  27                 GlobalConstants.getInterfaceUrl("tokenUrl"), params);  28         String access_token = JSONObject.fromObject(jstoken).getString(  29                 "access_token"); // 獲取到token並賦值保存  30         GlobalConstants.interfaceUrlProperties.put("access_token", access_token);  31                 System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())+"token為=============================="+access_token);  32     }  33  34 }

(三)採用任務調度每隔兩小時執行一次token獲取執行體

我們閱讀過微信的文檔會發現我們的token獲取的介面每天是有調用次數限制的,為了防止我們業務量比較大的情況下token的直接調用的介面次數不夠用,所以我們需要根據token的時效性(7200s)在自己的業務伺服器上做到token的快取並定時獲取,我這裡用到的任務調度的方式是採用quartz,下面具體程式碼的實現:

 1 package com.gede.wechat.quartz;   2 import org.apache.log4j.Logger;   3 import com.gede.wechat.common.WeChatTask;   4   5 /**   6 * @author gede   7 * @version date:2019年5月26日 下午8:00:43   8 * @description :   9 */  10 public class QuartzJob{  11     private static Logger logger = Logger.getLogger(QuartzJob.class);  12     /**  13      * @Description: 任務執行獲取token  14      * @param  15      */  16     public void workForToken() {  17         try {  18             WeChatTask timer = new WeChatTask();  19             timer.getToken_getTicket();  20         } catch (Exception e) {  21             logger.error(e, e);  22         }  23     }  24 }

這裡新建配置文件spring-quartz.xml以方便quartz任務的管理和啟用,這裡將我們需要用到的workForToken()加入到執行任務中:

 1 <?xml version="1.0" encoding="UTF-8"?>   2 <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">   3   4 <beans>   5     <!-- 要調用的工作類 -->   6     <bean id="quartzJob" class="com.gede.wechat.quartz.QuartzJob"></bean>   7     <!-- 定義調用對象和調用對象的方法 -->   8     <bean id="jobtaskForToken"   9         class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">  10         <!-- 調用的類 -->  11         <property name="targetObject">  12             <ref bean="quartzJob" />  13         </property>  14         <!-- 調用類中的方法 -->  15         <property name="targetMethod">  16             <value>workForToken</value>  17         </property>  18     </bean>  19     <!-- 定義觸發時間 -->  20     <bean id="doTimeForToken" class="org.springframework.scheduling.quartz.CronTriggerBean">  21         <property name="jobDetail">  22             <ref bean="jobtaskForToken" />  23         </property>  24         <!-- cron表達式 -->  25         <property name="cronExpression">  26             <value>0 0/1 * * * ?</value>  27         </property>  28     </bean>  29     <!-- 總管理類 如果將lazy-init='false'那麼容器啟動就會執行調度程式 -->  30     <bean id="startQuertz" lazy-init="false" autowire="no"  31         class="org.springframework.scheduling.quartz.SchedulerFactoryBean">  32         <property name="triggers">  33             <list>  34                 <ref bean="doTimeForToken" />  35             </list>  36         </property>  37     </bean>  38 </beans>

這裡我為了測試將執行間隔時間設置成了1分鐘一次,根據需要可以自行修改執行時間。

好了到這裡我們就已經大功告成,就差初始化載入InterfaceUrlIntiServlet和開啟quartz的使用:修改web.xml ,加入相關語句,程式碼如下:

 1 <?xml version="1.0" encoding="UTF-8"?>   2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">   3   <display-name>mychat</display-name>   4   <listener>   5     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>   6   </listener>   7  <context-param>   8     <param-name>contextConfigLocation</param-name>   9     <param-value>classpath:applicationContext.xml,classpath:spring-quartz.xml</param-value>  10   </context-param>  11  12   <servlet>  13       <servlet-name>appServlet</servlet-name>  14         <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  15         <init-param>  16             <param-name>contextConfigLocation</param-name>  17             <param-value>  18             classpath:appServlet.xml  19             </param-value>  20         </init-param>  21       <load-on-startup>1</load-on-startup>  22   </servlet>  23  24   <context-param>  25         <param-name>log4jConfigLocation</param-name>  26         <param-value>classpath:log4j.properties</param-value>  27     </context-param>  28     <listener>  29         <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>  30   </listener>  31  32   <servlet>  33         <servlet-name>interface_url-init_servlet</servlet-name>  34         <servlet-class>com.gede.web.start.InterfaceUrlIntiServlet</servlet-class>  35         <load-on-startup>1</load-on-startup>  36   </servlet>  37  38   <servlet-mapping>  39       <servlet-name>appServlet</servlet-name>  40       <url-pattern>/</url-pattern>  41   </servlet-mapping>  42 </web-app>

當這一切都準備完畢之後我們啟動項目,會發現每間隔一分鐘就會有token獲取到,這裡我是將其存儲在項目變數中,這裡看一下我們的效果圖和項目總的目錄結構:

附件:今天需要導入的包有很多,最後我就給大家打一個壓縮包,大家在運行之前將包全部導入即可。點擊下載

Tags: