1、前言html
最近一段時間都在進行公衆號的開發,以前在實訓的時候也開發過一次,可是過得過久早就忘記了如何作了,此次開發也是至關於從新開始,其實開發的步驟微信公衆號開發文檔上面寫的也很清楚,因此整體來講開發的步驟也不是特別複雜。只要配置好須要的URL、TOKEN、和回調的域名就能夠了。java
2、開發過程git
(1)配置服務器信息數據庫
在測試上面有兩個重要信息。分別是appID 和 appsecret。這是開啓公衆號兩個重要祕鑰,一個公衆號對應一個appID。有了這兩個東西你就能夠隨心所欲隨心所欲猥瑣欲爲了。json
而後配置你的URL地址和token,其中URL是開發者用來接收微信消息和事件的接口URL。Token可由開發者能夠任意填寫,用做生成簽名(該Token會和接口URL中包含的Token進行比對,從而驗證安全性)。api
URL做爲開發接口,主要告訴公衆號,這是屬於本身的服務器,之後公衆號接收到的信息都會給個人服務器。配置這個信息時,公衆號會給這個接口發送一個GET請求,並要接受四個參數安全
開發者經過檢驗signature對請求進行校驗(下面有校驗方式)。若確認這次GET請求來自微信服務器,請原樣返回echostr參數內容,則接入生效,成爲開發者成功,不然接入失敗。服務器
此代碼段是開放給公衆號的接口。在這裏接受GET請求的四個參數。微信
package com.itlink.servlet; import java.io.IOException; import java.io.PrintWriter; import java.util.Calendar; import java.util.Map; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.internal.runners.TestMethod; import com.itlink.domain.TextMessage; import com.itlink.utils.MessageUtil; import com.itlink.utils.SignUtil; public class wx extends HttpServlet { //private static final Logger logger = Logger.get public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8"); String signature = request.getParameter("signature"); String timestamp = request.getParameter("timestamp"); String nonce = request.getParameter("nonce"); String echostr = request.getParameter("echostr"); if(echostr != null && SignUtil.checkSignature(signature, timestamp, nonce)){ System.out.println("[signature: "+signature + "]<-->[timestamp: "+ timestamp+"]<-->[nonce: "+nonce+"]<-->[echostr: "+echostr+"]"); response.getOutputStream().println(echostr); } } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } }
加密/校驗流程以下:app
1)將token、timestamp、nonce三個參數進行字典序排序
2)將三個參數字符串拼接成一個字符串進行sha1加密
3)開發者得到加密後的字符串可與signature對比,標識該請求來源於微信
package com.itlink.utils; import java.security.MessageDigest; import java.util.Arrays; public class SignUtil { private static String token = "activty"; public static boolean checkSignature(String signature, String timestamp, String nonce) { String checktext = null; String[] params = new String[]{token, timestamp, nonce}; Arrays.sort(params); String content = params[0].concat(params[1]).concat(params[2]); try{ MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] digest = md.digest(content.toString().getBytes()); checktext = bytetostr(digest); }catch(Exception e){ e.printStackTrace(); } return checktext !=null ? checktext.equals(signature.toUpperCase()) : false; } private static String bytetostr(byte[] digest) { String str = ""; for (int i = 0; i < digest.length; i++) { str += byteToHexStr(digest[i]); } return str; } private static String byteToHexStr(byte myByte) { char[] Digit = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; char[] tampArr = new char[2]; tampArr[0] = Digit[(myByte >>> 4) & 0X0F]; tampArr[1] = Digit[myByte & 0X0F]; String str = new String(tampArr); return str; } }
此時提交的配置信息就會成功。(這是真的,若是仍是不行的話,那是不存在的)
(2)獲取網頁權限。
打開第三方網頁時須要獲取到用戶信息,就須要用戶受權。全部操做的權限是經過微信公衆號開放的接口獲取的。
在微信公衆號文檔中也明確提到。在用戶受權以後進行網頁跳轉時須要配置用戶的回調地址。這是一個關鍵信息。若是沒有的話,在網頁跳轉的時候就會報錯,至關於找不到回家的路。
關於網頁受權的兩種scope的區別說明
一、以snsapi_base爲scope發起的網頁受權,是用來獲取進入頁面的用戶的openid的,而且是靜默受權並自動跳轉到回調頁的。這種方式不用用戶手動受權。
二、以snsapi_userinfo爲scope發起的網頁受權,是用來獲取用戶的基本信息的。但這種受權須要用戶手動贊成,而且因爲用戶贊成過,因此無須關注,就可在受權後獲取該用戶的基本信息。
三、用戶管理類接口中的「獲取用戶基本信息接口」,是在用戶和公衆號產生消息交互或關注後事件推送後,才能根據用戶OpenID來獲取用戶基本信息。這個接口,包括其餘微信接口,都是須要該用戶(即openid)關注了公衆號後,才能調用成功的。
關於網頁受權access_token和普通access_token的區別
微信網頁受權是經過OAuth2.0機制實現的,在用戶受權給公衆號後,公衆號能夠獲取到一個網頁受權特有的接口調用憑證(網頁受權access_token),經過網頁受權access_token能夠進行受權後接口調用,如獲取用戶基本信息;其餘微信接口,須要經過基礎支持中的「獲取access_token」接口來獲取到的普通access_token調用。
獲取用戶信息的基本步驟:
一、引導用戶進入受權頁面贊成受權,獲取code
二、經過code換取網頁受權access_token
三、若是須要,開發者能夠刷新網頁受權access_token,避免過時
四、經過網頁受權access_token和openid獲取用戶基本信息
跳轉
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
REDIRECT_URI:是用戶須要跳轉的路徑(個人代碼中是一個servlet)
經過一個回調的路徑跳轉到一個頁面並獲取到code。
//String code = request.getParameter("code");//從受權頁面獲取到code,用於回去用戶的openid //code做爲換取access_token的票據,每次用戶受權帶上的code將不同,code只能使用一次,5分鐘未被使用自動過時。 //經過code獲取到token_access //AccessToken access_token = WeixinUtil.getAccessToken(WeixinUtil.appid, WeixinUtil.appsecret); //JSONObject json1 = WeixinUtil.getOAuthAccessToken(WeixinUtil.appid, WeixinUtil.appsecret, code); String access_token = dao.getAccessToken().getAccess_token(); response.sendRedirect("activity/index.html?user="+openid); // JSONObject user_json = WeixinUtil.getUserinfo(access_token, openid);
微信工具類。
package com.itlink.utils; 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 org.junit.Test; import com.itlink.domain.AccessToken; import com.itlink.domain.Menu; import net.sf.json.JSONObject; public class WeixinUtil { public static String appid = "";//本身公衆號的APPID和appsecret public static String appsecret = ""; //處理請求 public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) { JSONObject jsonObject = null; StringBuffer buffer = new StringBuffer(); try { TrustManager[] tm = { new MyX509TrustManager() }; SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE"); sslContext.init(null, tm, new java.security.SecureRandom()); SSLSocketFactory ssf = sslContext.getSocketFactory(); URL url = new URL(requestUrl); HttpsURLConnection httpUrlConn = (HttpsURLConnection) url.openConnection(); httpUrlConn.setSSLSocketFactory(ssf); httpUrlConn.setDoOutput(true); httpUrlConn.setDoInput(true); httpUrlConn.setUseCaches(false); httpUrlConn.setRequestMethod(requestMethod); if ("GET".equalsIgnoreCase(requestMethod)) httpUrlConn.connect(); if (null != outputStr) { OutputStream outputStream = httpUrlConn.getOutputStream(); outputStream.write(outputStr.getBytes("UTF-8")); outputStream.close(); } InputStream inputStream = httpUrlConn.getInputStream(); InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String str = null; while ((str = bufferedReader.readLine()) != null) { buffer.append(str); } bufferedReader.close(); inputStreamReader.close(); inputStream.close(); inputStream = null; httpUrlConn.disconnect(); jsonObject = JSONObject.fromObject(buffer.toString()); } catch (ConnectException ce) { // log.info("Weixin server connection timed out."); } catch (Exception e) { // log.info("https request error:"+e); } return jsonObject; } //獲取access_token接口 public final static String access_token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET"; public static AccessToken getAccessToken(String appid, String appsecret){ String url = access_token_url.replace("APPID", appid).replace("APPSECRET", appsecret); JSONObject jsonObject = httpsRequest(url, "GET", null); AccessToken accessToken = new AccessToken(); accessToken.setExpires_in((Integer)jsonObject.get("expires_in")); accessToken.setAccess_token(jsonObject.getString("access_token")); return accessToken; } //獲取用戶基本信息(UnionID機制) public final static String userinfo_url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN"; public static JSONObject getUserinfo(String access_token,String openid){ String url = userinfo_url.replace("ACCESS_TOKEN", access_token).replace("OPENID", openid); JSONObject jsonObject = httpsRequest(url, "GET", null); return jsonObject; } //獲取用戶權限 public final static String access_token_oauth_url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code"; public static JSONObject getOAuthAccessToken(String appid,String appsecret,String code){ String url = access_token_oauth_url.replace("APPID", appid).replace("SECRET", appsecret).replace("CODE", code); JSONObject jsonObject = httpsRequest(url, "GET", null); return jsonObject; }
//拉取用戶信息 public final static String user_info_oauth_url = "https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN"; public static JSONObject getUserInfoByOAuth(String access_token,String openid){ String url = user_info_oauth_url.replace("ACCESS_TOKEN", access_token).replace("OPENID", openid); JSONObject jsonObject = httpsRequest(url, "GET", null); return jsonObject; } }
MyX509TrustManager類證書信任管理器(用於https請求)
package com.itlink.utils; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import javax.net.ssl.X509TrustManager; public class MyX509TrustManager implements X509TrustManager { public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } }
網頁受權方式基本上就能夠獲取到用戶的信息了。
(三)開發過程當中遇到的坑
(1)遇到access_token過時。在開發過程當中過一點時間就會沒法訪問到本身的網站,後面才發現微信受權調用的憑票過一段時間就會過去,就須要從新獲取。access_token保存的時長是7200s,也就是兩個小時,因此須要通過段時間就要去從新獲取access_token。這個時間就須要作一個定時器定時的去執行任務。並將憑票保存起來,能夠放在數據庫中,也能夠用一個文件保存起來,每次須要用到的時候就去獲取。
package com.itlink.domain; public class AccessToken { private String access_token; private int expires_in; public String getAccess_token() { return access_token; } public void setAccess_token(String access_token) { this.access_token = access_token; } public int getExpires_in() { return expires_in; } public void setExpires_in(int expires_in) { this.expires_in = expires_in; } @Override public String toString() { return "AccessToken [access_token=" + access_token + ", expires_in=" + expires_in + "]"; } }
將access_token保存起來
package com.itlink.dao; import org.dom4j.Document; import org.dom4j.Element; import org.junit.Test; import com.itlink.domain.AccessToken; import com.itlink.utils.WeixinUtil; import com.itlink.utils.dom4jUtil; public class AccessTokenDao { AccessToken accessToken = null; public AccessToken getAccessToken(){ //獲取保存的信息 } public void setAccessToken(AccessToken accesstoken){ //this.accessToken = new AccessToken(); //保存信息 } }
開啓一個線程,每隔一段時間就獲取信息。
package com.itlink.main; import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import org.junit.Test; import com.itlink.dao.AccessTokenDao; import com.itlink.domain.AccessToken; import com.itlink.utils.WeixinUtil; public class TokenThread implements Runnable { public static AccessToken accessToken = null; @Override public void run() { accessToken = WeixinUtil.getAccessToken(WeixinUtil.appid, WeixinUtil.appsecret); System.out.println("開始獲取"); try { if(accessToken != null){ System.out.println("accessToken獲取成功:"+ accessToken.getExpires_in()); //將access——token存放起來 new AccessTokenDao().setAccessToken(accessToken); //7000秒後從新獲取 Thread.sleep((accessToken.getExpires_in()-200)*1000); }else{ System.out.println("獲取失敗"); Thread.sleep(60*1000); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
(2)啓用開發者模式後,原先定義的菜單欄會被停用。要使用自定義菜單隻能經過公衆號給你提供的接口設置。
public final static String create_menu_url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN"; public static Integer createMenu(String access_token, Menu menu){ String url = create_menu_url.replace("ACCESS_TOKEN", access_token); String outputStr = JSONObject.fromObject(menu).toString(); System.out.println(outputStr); JSONObject jsonObject = httpsRequest(url, "POST", outputStr); Integer result = null; if(null!=jsonObject){ result = jsonObject.getInt("errcode"); } return result; }
本身定義一個Menu類,將你須要的定義的菜單以json類型的格式以post請求的方式傳遞過去。
規定格式:
{ "button":[ { "type":"click", "name":"今日歌曲", "key":"V1001_TODAY_MUSIC" }, { "name":"菜單", "sub_button":[ { "type":"view", "name":"搜索", "url":"http://www.soso.com/" }, { "type":"miniprogram", "name":"wxa", "url":"http://mp.weixin.qq.com", "appid":"wx286b93c14bbf93aa", "pagepath":"pages/lunar/index" }, { "type":"click", "name":"贊一下咱們", "key":"V1001_GOOD" }] }] }
(四)最後
基本配置的格式就差很少是這樣了。這個項目是在測試號上面進行開發的,測試號上面全部的權限都提供給了開發者,在實際開發中不一樣類型的公衆號的權限是不同的,因此在開發時候仍是須要根據實際狀況進行業務的調整。