您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關微信開發入門學習的示例分析,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。
在開始做之前,先簡單介紹了微信公眾平臺的基本原理。
微信服務器就相當于一個轉發服務器,終端(手機、Pad等)發起請求至微信服務器,微信服務器然后將請求轉發給我們的應用服務器。應用服務器處理完畢后,將響應數據回發給微信服務器,微信服務器再將具體響應信息回復到微信App終端。
通信協議為:HTTP
數據傳輸格式為:XML
具體的流程如下圖所示:
來一張更加直觀的圖吧:
我們需要做的事情,就是對微信服務器轉發的HTTP請求做出響應。具體的請求內容,我們按照特定的XML格式去解析,處理完畢后,也要按照特定的XML格式返回。
在微信公眾平臺開發者文檔上,關于公眾號接入這一節內容在接入指南上寫的比較詳細的,文檔中說接入公眾號需要3個步驟,分別是:
1、填寫服務器配置
2、驗證服務器地址的有效性
3、依據接口文檔實現業務邏輯
其實,第3步已經不能算做公眾號接入的步驟,而是接入之后,開發人員可以根據微信公眾號提供的接口所能做的一些開發。
第1步中服務器配置包含服務器地址(URL)、Token和EncodingAESKey。
服務器地址即公眾號后臺提供業務邏輯的入口地址,目前只支持80端口,之后包括接入驗證以及任何其它的操作的請求(例如消息的發送、菜單管理、素材管理等)都要從這個地址進入。接入驗證和其它請求的區別就是,接入驗證時是get請求,其它時候是post請求;
Token可由開發者可以任意填寫,用作生成簽名(該Token會和接口URL中包含的Token進行比對,從而驗證安全性);
EncodingAESKey由開發者手動填寫或隨機生成,將用作消息體加解密密鑰。本例中全部以未加密的明文消息方式,不涉及此配置項。
第2步,驗證服務器地址的有效性,當點擊“提交”按鈕后,微信服務器將發送一個http的get請求到剛剛填寫的服務器地址,并且攜帶四個參數:
接到請求后,我們需要做如下三步,若確認此次GET請求來自微信服務器,原樣返回echostr參數內容,則接入生效,否則接入失敗。
1. 將token、timestamp、nonce三個參數進行字典序排序
2. 將三個參數字符串拼接成一個字符串進行sha1加密
3. 開發者獲得加密后的字符串可與signature對比,標識該請求來源于微信
下面我們用Java代碼來演示一下這個驗證過程
使用IDE(Eclipse或者IntelliJ IDEA)創建一個JavaWeb項目,這里我使用的是IntelliJ IDEA,項目目錄結構如下圖所示:
編寫一個servlevt,在其中的doGet方法中定義校驗方法,具體代碼如下:
package me.gacl.wx.web.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; /** * Created by xdp on 2016/1/25. * 使用@WebServlet注解配置WxServlet,urlPatterns屬性指明了WxServlet的訪問路徑 */ @WebServlet(urlPatterns="/WxServlet") public class WxServlet extends HttpServlet { /** * Token可由開發者可以任意填寫,用作生成簽名(該Token會和接口URL中包含的Token進行比對,從而驗證安全性) * 比如這里我將Token設置為gacl */ private final String TOKEN = "gacl"; protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { System.out.println("開始校驗簽名"); /** * 接收微信服務器發送請求時傳遞過來的4個參數 */ String signature = request.getParameter("signature");//微信加密簽名signature結合了開發者填寫的token參數和請求中的timestamp參數、nonce參數。 String timestamp = request.getParameter("timestamp");//時間戳 String nonce = request.getParameter("nonce");//隨機數 String echostr = request.getParameter("echostr");//隨機字符串 //排序 String sortString = sort(TOKEN, timestamp, nonce); //加密 String mySignature = sha1(sortString); //校驗簽名 if (mySignature != null && mySignature != "" && mySignature.equals(signature)) { System.out.println("簽名校驗通過。"); //如果檢驗成功輸出echostr,微信服務器接收到此輸出,才會確認檢驗完成。 //response.getWriter().println(echostr); response.getWriter().write(echostr); } else { System.out.println("簽名校驗失敗."); } } /** * 排序方法 * * @param token * @param timestamp * @param nonce * @return */ public String sort(String token, String timestamp, String nonce) { String[] strArray = {token, timestamp, nonce}; Arrays.sort(strArray); StringBuilder sb = new StringBuilder(); for (String str : strArray) { sb.append(str); } return sb.toString(); } /** * 將字符串進行sha1加密 * * @param str 需要加密的字符串 * @return 加密后的內容 */ public String sha1(String str) { try { MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.update(str.getBytes()); byte messageDigest[] = digest.digest(); // Create Hex String StringBuffer hexString = new StringBuffer(); // 字節數組轉換為 十六進制 數 for (int i = 0; i < messageDigest.length; i++) { String shaHex = Integer.toHexString(messageDigest[i] & 0xFF); if (shaHex.length() < 2) { hexString.append(0); } hexString.append(shaHex); } return hexString.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } return ""; } }
我這里用的Servlet3.0,使用Servlet3.0的好處就是可以直接使用@WebServlet注解映射Servlet的訪問路徑,不再需要在web.xml文件中進行配置.
將WxStudy項目部署到Tomcat服務器中運行,直接啟動項目,然后用ngrok將本地8080端口映射到外網(如何使用ngrok請參考博客《微信開發—微信開發環境搭建》)。如下圖所示:
測試是否可以通過http://xdp.ngrok.natapp.cn地址正常訪問,測試結果如下:
可以看到,我們的項目已經可以被外網正常訪問到了。
進入微信測試公眾號管理界面,在接口配置信息中填入映射的外網地址和token,如下圖所示:
點擊提交按鈕,頁面會提示配置成功,
IDE的控制臺中輸出了校驗通過的信息,如下圖所示:
到此,我們的公眾號應用已經能夠和微信服務器正常通信了,也就是說我們的公眾號已經接入到微信公眾平臺了。
我們的公眾號和微信服務器對接成功之后,接下來要做的就是根據我們的業務需求調用微信公眾號提供的接口來實現相應的邏輯了。在使用微信公眾號接口中都需要一個access_token。
關于access_token,在微信公眾平臺開發者文檔上的獲取接口調用憑據有比較詳細的介紹:access_token是公眾號的全局唯一票據,公眾號調用各接口時都需使用access_token,開發者需要妥善保存access_token的存儲至少要保留512個字符空間。access_token的有效期目前為2個小時,需定時刷新,重復獲取將導致上次獲取的access_token失效。并且每天調用獲取access_token接口的上限是2000次。
總結以上說明,access_token需要做到以下兩點:
1.因為access_token有2個小時的時效性,要有一個機制保證最長2個小時重新獲取一次。
2.因為接口調用上限每天2000次,所以不能調用太頻繁。
關于access_token的獲取方式,在微信公眾平臺開發者文檔上有說明,公眾號可以調用一個叫"獲取access token"的接口來獲取access_token。
獲取access token接口調用請求說明
http請求方式: GET
請求的URL地址:https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
我們可以看到,調用過程中需要傳遞appID和AppSecret,appID和AppSecret是在申請公眾號的時候自動分配給公眾號的,相當于公眾號的身份標示,使用微信公眾號的注冊帳號登錄到騰訊提供的微信公眾號管理后臺就可以看到自己申請的公眾號的AppID和AppSecret,如下圖所示:
這是我申請公眾號測試帳號時分配到的AppID和AppSecret。
這里采用的方案是這樣的,定義一個默認啟動的servlet,在init方法中啟動一個Thread,這個進程中定義一個無限循環的方法,用來獲取access_token,當獲取成功后,此進程休眠7000秒(7000秒=1.944444444444444小時),否則休眠3秒鐘繼續獲取。流程圖如下:
下面正式開始在工程中實現以上思路,因為返回的數據都是json格式,這里會用到阿里的fastjson庫,為構造請求和處理請求后的數據序列化和反序列化提供支持。
1.定義一個AccessToken實體類
package me.gacl.wx.entry; /** * AccessToken的數據模型 * Created by xdp on 2016/1/25. */ public class AccessToken { //獲取到的憑證 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; } }
2.定義一個AccessTokenInfo類,用于存放獲取到的AccessToken,代碼如下:
package me.gacl.wx.Common; import me.gacl.wx.entry.AccessToken; /** * Created by xdp on 2016/1/25. */ public class AccessTokenInfo { //注意是靜態的 public static AccessToken accessToken = null; }
3.編寫一個用于發起https請求的工具類NetWorkHelper,代碼如下:
package me.gacl.wx.util; import javax.net.ssl.*; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; /** * 訪問網絡用到的工具類 */ public class NetWorkHelper { /** * 發起Https請求 * @param reqUrl 請求的URL地址 * @param requestMethod * @return 響應后的字符串 */ public String getHttpsResponse(String reqUrl, String requestMethod) { URL url; InputStream is; String resultData = ""; try { url = new URL(reqUrl); HttpsURLConnection con = (HttpsURLConnection) url.openConnection(); TrustManager[] tm = {xtm}; SSLContext ctx = SSLContext.getInstance("TLS"); ctx.init(null, tm, null); con.setSSLSocketFactory(ctx.getSocketFactory()); con.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String arg0, SSLSession arg1) { return true; } }); con.setDoInput(true); //允許輸入流,即允許下載 //在android中必須將此項設置為false con.setDoOutput(false); //允許輸出流,即允許上傳 con.setUseCaches(false); //不使用緩沖 if (null != requestMethod && !requestMethod.equals("")) { con.setRequestMethod(requestMethod); //使用指定的方式 } else { con.setRequestMethod("GET"); //使用get請求 } is = con.getInputStream(); //獲取輸入流,此時才真正建立鏈接 InputStreamReader isr = new InputStreamReader(is); BufferedReader bufferReader = new BufferedReader(isr); String inputLine; while ((inputLine = bufferReader.readLine()) != null) { resultData += inputLine + "\n"; } System.out.println(resultData); } catch (Exception e) { e.printStackTrace(); } return resultData; } X509TrustManager xtm = new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() { return null; } @Override public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } @Override public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { } }; }
getHttpsResponse方法是請求一個https地址,參數requestMethod為字符串“GET”或者“POST”,傳null或者“”默認為get方式。
4.定義一個默認啟動的servlet,在init方法中啟動一個新的線程去獲取accessToken
package me.gacl.wx.web.servlet; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import me.gacl.wx.Common.AccessTokenInfo; import me.gacl.wx.entry.AccessToken; import me.gacl.wx.util.NetWorkHelper; import javax.servlet.ServletException; import javax.servlet.annotation.WebInitParam; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; /** * 用于獲取accessToken的Servlet * Created by xdp on 2016/1/25. */ @WebServlet( name = "AccessTokenServlet", urlPatterns = {"/AccessTokenServlet"}, loadOnStartup = 1, initParams = { @WebInitParam(name = "appId", value = "wxbe4d433e857e8bb1"), @WebInitParam(name = "appSecret", value = "ccbc82d560876711027b3d43a6f2ebda") }) public class AccessTokenServlet extends HttpServlet { @Override public void init() throws ServletException { System.out.println("啟動WebServlet"); super.init(); final String appId = getInitParameter("appId"); final String appSecret = getInitParameter("appSecret"); //開啟一個新的線程 new Thread(new Runnable() { @Override public void run() { while (true) { try { //獲取accessToken AccessTokenInfo.accessToken = getAccessToken(appId, appSecret); //獲取成功 if (AccessTokenInfo.accessToken != null) { //獲取到access_token 休眠7000秒,大約2個小時左右 Thread.sleep(7000 * 1000); //Thread.sleep(10 * 1000);//10秒鐘獲取一次 } else { //獲取失敗 Thread.sleep(1000 * 3); //獲取的access_token為空 休眠3秒 } } catch (Exception e) { System.out.println("發生異常:" + e.getMessage()); e.printStackTrace(); try { Thread.sleep(1000 * 10); //發生異常休眠1秒 } catch (Exception e1) { } } } } }).start(); } /** * 獲取access_token * * @return AccessToken */ private AccessToken getAccessToken(String appId, String appSecret) { NetWorkHelper netHelper = new NetWorkHelper(); /** * 接口地址為https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET,其中grant_type固定寫為client_credential即可。 */ String Url = String.format("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appId, appSecret); //此請求為https的get請求,返回的數據格式為{"access_token":"ACCESS_TOKEN","expires_in":7200} String result = netHelper.getHttpsResponse(Url, ""); System.out.println("獲取到的access_token="+result); //使用FastJson將Json字符串解析成Json對象 JSONObject json = JSON.parseObject(result); AccessToken token = new AccessToken(); token.setAccessToken(json.getString("access_token")); token.setExpiresin(json.getInteger("expires_in")); return token; } }
AccessTokenServlet采用注解的方式進行配置
至此代碼實現完畢,將項目部署,看到控制臺輸出如下:
為了方便看效果,可以把休眠時間設置短一點,比如10秒獲取一次,然后將access_token輸出。
下面做一個測試jsp頁面,并把休眠時間設置為10秒,這樣過10秒刷新頁面,就可以看到變化
<%-- Created by IntelliJ IDEA. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ page import="me.gacl.wx.Common.AccessTokenInfo"%> <html> <head> <title></title> </head> <body> 微信學習 <hr/> access_token為:<%=AccessTokenInfo.accessToken.getAccessToken()%> </body> </html>
10秒鐘后刷新頁面,access_token變了,如下圖所示:
經過上述的三步,我們開發前的準備工作已經完成了,接下來要做的就是接收微信服務器發送的消息并做出響應
從微信公眾平臺接口消息指南中可以了解到,當用戶向公眾帳號發消息時,微信服務器會將消息通過POST方式提交給我們在接口配置信息中填寫的URL,而我們就需要在URL所指向的請求處理類WxServlet的doPost方法中接收消息、處理消息和響應消息。
編寫處理消息的工具欄,工具類代碼如下:
package me.gacl.wx.util; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import javax.servlet.http.HttpServletRequest; import java.io.InputStream; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 消息處理工具類 * Created by xdp on 2016/1/26. */ public class MessageHandlerUtil { /** * 解析微信發來的請求(XML) * @param request * @return map * @throws Exception */ public static Map<String,String> parseXml(HttpServletRequest request) throws Exception { // 將解析結果存儲在HashMap中 Map<String,String> map = new HashMap(); // 從request中取得輸入流 InputStream inputStream = request.getInputStream(); System.out.println("獲取輸入流"); // 讀取輸入流 SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); // 得到xml根元素 Element root = document.getRootElement(); // 得到根元素的所有子節點 List<Element> elementList = root.elements(); // 遍歷所有子節點 for (Element e : elementList) { System.out.println(e.getName() + "|" + e.getText()); map.put(e.getName(), e.getText()); } // 釋放資源 inputStream.close(); inputStream = null; return map; } // 根據消息類型 構造返回消息 public static String buildXml(Map<String,String> map) { String result; String msgType = map.get("MsgType").toString(); System.out.println("MsgType:" + msgType); if(msgType.toUpperCase().equals("TEXT")){ result = buildTextMessage(map, "孤傲蒼狼在學習和總結微信開發了,構建一條文本消息:Hello World!"); }else{ String fromUserName = map.get("FromUserName"); // 開發者微信號 String toUserName = map.get("ToUserName"); result = String .format( "<xml>" + "<ToUserName><![CDATA[%s]]></ToUserName>" + "<FromUserName><![CDATA[%s]]></FromUserName>" + "<CreateTime>%s</CreateTime>" + "<MsgType><![CDATA[text]]></MsgType>" + "<Content><![CDATA[%s]]></Content>" + "</xml>", fromUserName, toUserName, getUtcTime(), "請回復如下關鍵詞:\n文本\n圖片\n語音\n視頻\n音樂\n圖文"); } return result; } /** * 構造文本消息 * * @param map * @param content * @return */ private static String buildTextMessage(Map<String,String> map, String content) { //發送方帳號 String fromUserName = map.get("FromUserName"); // 開發者微信號 String toUserName = map.get("ToUserName"); /** * 文本消息XML數據格式 * <xml> <ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test]]></Content> <MsgId>1234567890123456</MsgId> </xml> */ return String.format( "<xml>" + "<ToUserName><![CDATA[%s]]></ToUserName>" + "<FromUserName><![CDATA[%s]]></FromUserName>" + "<CreateTime>%s</CreateTime>" + "<MsgType><![CDATA[text]]></MsgType>" + "<Content><![CDATA[%s]]></Content>" + "</xml>", fromUserName, toUserName, getUtcTime(), content); } private static String getUtcTime() { Date dt = new Date();// 如果不需要格式,可直接用dt,dt就是當前系統時間 DateFormat df = new SimpleDateFormat("yyyyMMddhhmm");// 設置顯示格式 String nowTime = df.format(dt); long dd = (long) 0; try { dd = df.parse(nowTime).getTime(); } catch (Exception e) { } return String.valueOf(dd); } }
為了方便解析微信服務器發送給我們的xml格式的數據,這里我們借助于開源框架dom4j去解析xml(這里使用的是dom4j-2.0.0-RC1.jar)
WxServlet的doPost方法的代碼如下:
/** * 處理微信服務器發來的消息 */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO 接收、處理、響應由微信服務器轉發的用戶發送給公眾帳號的消息 // 將請求、響應的編碼均設置為UTF-8(防止中文亂碼) request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); System.out.println("請求進入"); String result = ""; try { Map<String,String> map = MessageHandlerUtil.parseXml(request); System.out.println("開始構造消息"); result = MessageHandlerUtil.buildXml(map); System.out.println(result); if(result.equals("")){ result = "未正確響應"; } } catch (Exception e) { e.printStackTrace(); System.out.println("發生異常:"+ e.getMessage()); } response.getWriter().println(result); }
到此,我們的WxServlet已經可以正常處理用戶的請求并做出響應了.接下來我們測試一下我們開發好的公眾號應用是否可以正常和微信用戶交互
將WxStudy部署到Tomcat服務器,啟動服務器,記得使用ngrok將本地Tomcat服務器的8080端口映射到外網,保證接口配置信息的URL地址:http://xdp.ngrok.natapp.cn/WxServlet可以正常與微信服務器通信
登錄到我們的測試公眾號的管理后臺,然后用微信掃描一下測試號的二維碼,如下圖所示:
關注成功后,我們開發好的公眾號應用會先給用戶發一條提示用戶操作的文本消息,微信用戶根據提示操作輸入"文本",我們的公眾號應用接收到用戶請求后就給用戶回復了一條我們自己構建好的文本消息,如下圖所示:
我們的公眾號應用響應給微信用戶的文本消息的XML數據如下:
<xml> <ToUserName><![CDATA[ojADgs0eDaqh7XkTM9GvDmdYPoDw]]></ToUserName> <FromUserName><![CDATA[gh_43df3882c452]]></FromUserName> <CreateTime>1453755900000</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[孤傲蒼狼在學習和總結微信開發了,構建一條文本消息:Hello World!]]></Content> </xml>
測試公眾號的管理后臺也可以看到關注測試號的用戶列表,如下圖所示:
關于“微信開發入門學習的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,使各位可以學到更多知識,如果覺得文章不錯,請把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。