(一)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獲取到,這里我是將其存儲在項目變量中,這里看一下我們的效果圖和項目總的目錄結構:
附件:今天需要導入的包有很多,最后我就給大家打一個壓縮包,大家在運行之前將包全部導入即可。點擊下載