GitHub源碼:https://github.com/shirayner/weixin_gz
一、本節要點
1.回調url
上一節,我們啟用服務器配置的時候,填寫了一個服務器地址(url),如下圖,這個url就是回調url,是開發者用來接收微信消息和事件的接口URL 。也就是說,用戶在微信公眾號中發送的消息會被推送到這個回調url,而我們可以接收用戶的消息,並進行回復。
2.被動回復消息的流程
官方文檔:
我們在上一節中設置的消息加解密方式是安全模式。因此在用戶發給公眾號的消息(接收消息)以及公眾號被動回復用戶消息(回復消息)都會加密,
流程:
用戶發送消息之后,微信服務器將消息傳遞給 第三方服務器,第三方服務器接收到消息后,再對消息做出相應的回復消息。
接收消息:需先從request請求對象的輸入流中獲取請求參數和已加密的請求消息,再對已加密的請求消息進行解密操作,即可獲得明文。
然后就行對明文消息的業務處理了。
回復消息:封裝好回復消息后,需先對回復消息進行加密,獲得已已加密消息,然后再通過http請求調用被動回復消息的接口,來發送消息。
3.被動回復消息加解密
3.1接收消息的 解密
(1)從請求的輸入流中獲取加密的請求消息

//1.獲取加密的請求消息:使用輸入流獲得加密請求消息postData ServletInputStream in = request.getInputStream(); BufferedReader reader =new BufferedReader(new InputStreamReader(in)); String tempStr=""; //作為輸出字符串的臨時串,用於判斷是否讀取完畢 while(null!=(tempStr=reader.readLine())){ postData+=tempStr; } logger.info("postData:"+postData);
(2)對加密的請求消息進行解密獲得明文

WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(Env.TOKEN,Env.ENCODING_AES_KEY,Env.APP_ID); result=wxcpt.DecryptMsg(msgSignature, timeStamp, nonce, postData);
(3)解密算法
直接調用微信官方的 WXBizMsgCrypt 類的 DecryptMsg(String, String, String, String) 方法即可。
3.2 回復消息的加密
直接用官方加解密工具類。

wxcpt = new WXBizMsgCrypt(Env.TOKEN,Env.ENCODING_AES_KEY,Env.APP_ID); replyMsg=wxcpt.EncryptMsg(replyMsg, timeStamp, nonce);
4.消息對象的封裝
根據官方文檔消息的xml傳輸格式,我們可以將消息封裝成對象。請參見后面的代碼實現部分
5.數據傳輸—對象 轉成 xml字符串
根據官方文檔,數據是以XML數據包的形式進行傳輸的。因此,我們需要
(1)解析微信發來的請求(xmlStr),從xml字符串中獲取需要的信息
(2)回復消息時,將消息對象轉成xml字符串。
我們是使用dom4j,xstream來進行這個轉換的,因此需要導入jar包,maven依賴如下:
<!-- 7.XML解析 --> <dependency> <groupId>dom4j</groupId> <artifactId>dom4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>com.thoughtworks.xstream</groupId> <artifactId>xstream</artifactId> <version>1.4.10</version> </dependency>
具體請參見代碼實現的 MessageUtil 部分。
5.1 解析微信發來的請求(XML),獲取請求參數

/** * @desc :1.解析微信發來的請求(XML),獲取請求參數 * * @param request * @return * @throws Exception Map<String,String> */ public static Map<String, String> parseXml(HttpServletRequest request) throws Exception { // 將解析結果存儲在HashMap中 Map<String, String> map = new HashMap<String, String>(); // 從request中取得輸入流 InputStream inputStream = request.getInputStream(); // 讀取輸入流 SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); // 得到xml根元素 Element root = document.getRootElement(); // 得到根元素的所有子節點 List<Element> elementList = root.elements(); // 遍歷所有子節點 for (Element e : elementList) map.put(e.getName(), e.getText()); // 釋放資源 inputStream.close(); inputStream = null; return map; }
5.2 將文本消息轉成xml字符串

/** * 2.文本消息對象轉換成xml * * @param textMessage 文本消息對象 * @return xml */ public static String textMessageToXml(TextMessage textMessage) { xstream.alias("xml", textMessage.getClass()); return xstream.toXML(textMessage); }
二、代碼實現
1.微信配置類—Env.java
微信公眾號接入配置類

package com.ray.weixin.gz.config; /**@desc : 微信公眾號接入配置 * * @author: shirayner * @date : 2017年9月27日 下午4:57:36 */ public class Env { /** * 1. 企業應用接入秘鑰相關 */ // public static final String APP_ID = "wx4ddse2334debebef2cc"; //public static final String APP_SECRET = "068e2599abf88ba72frrgbfs6f3c56e"; //測試號 public static final String APP_ID = "wxa00642deff56g062"; public static final String APP_SECRET = "fcc96fefdgdhtj1a46af7993784917"; /** * 2.服務器配置: * 啟用並設置服務器配置后,用戶發給公眾號的消息以及開發者需要的事件推送,將被微信轉發到該URL中 */ public static final String TOKEN = "weixin"; public static final String ENCODING_AES_KEY = "JvJ1Dww6tjUU2psC3pdewegreHfovfWP3LfX1xrriz1"; }
2.HTTP請求工具類—HttpHelper.java

package com.ray.weixin.gz.util; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import org.apache.http.Consts; import org.apache.http.Header; import org.apache.http.HeaderElement; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; import org.apache.http.entity.FileEntity; import org.apache.http.entity.StringEntity; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.util.EntityUtils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; /** * HTTP請求封裝,建議直接使用sdk的API */ public class HttpHelper { /** * @desc :1.發起GET請求 * * @param url * @return JSONObject * @throws Exception */ public static JSONObject doGet(String url) throws Exception { //1.生成一個請求 HttpGet httpGet = new HttpGet(url); //2.配置請求的屬性 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(5000).build();//2000 httpGet.setConfig(requestConfig); //3.發起請求,獲取響應信息 //3.1 創建httpClient CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; try { //3.2 發起請求,獲取響應信息 response = httpClient.execute(httpGet, new BasicHttpContext()); //如果返回結果的code不等於200,說明出錯了 if (response.getStatusLine().getStatusCode() != 200) { System.out.println("request url failed, http code=" + response.getStatusLine().getStatusCode() + ", url=" + url); return null; } //4.解析請求結果 HttpEntity entity = response.getEntity(); //reponse返回的數據在entity中 if (entity != null) { String resultStr = EntityUtils.toString(entity, "utf-8"); //將數據轉化為string格式 System.out.println("GET請求結果:"+resultStr); JSONObject result = JSON.parseObject(resultStr); //將String轉換為 JSONObject if(result.getInteger("errcode")==null) { return result; }else if (0 == result.getInteger("errcode")) { return result; }else { System.out.println("request url=" + url + ",return value="); System.out.println(resultStr); int errCode = result.getInteger("errcode"); String errMsg = result.getString("errmsg"); throw new Exception("error code:"+errCode+", error message:"+errMsg); } } } catch (IOException e) { System.out.println("request url=" + url + ", exception, msg=" + e.getMessage()); e.printStackTrace(); } finally { if (response != null) try { response.close(); //釋放資源 } catch (IOException e) { e.printStackTrace(); } } return null; } /** 2.發起POST請求 * @desc : * * @param url 請求url * @param data 請求參數(json) * @return * @throws Exception JSONObject */ public static JSONObject doPost(String url, Object data) throws Exception { //1.生成一個請求 HttpPost httpPost = new HttpPost(url); //2.配置請求屬性 //2.1 設置請求超時時間 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(100000).setConnectTimeout(100000).build(); httpPost.setConfig(requestConfig); //2.2 設置數據傳輸格式-json httpPost.addHeader("Content-Type", "application/json"); //2.3 設置請求實體,封裝了請求參數 StringEntity requestEntity = new StringEntity(JSON.toJSONString(data), "utf-8"); httpPost.setEntity(requestEntity); //3.發起請求,獲取響應信息 //3.1 創建httpClient CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; try { //3.3 發起請求,獲取響應 response = httpClient.execute(httpPost, new BasicHttpContext()); if (response.getStatusLine().getStatusCode() != 200) { System.out.println("request url failed, http code=" + response.getStatusLine().getStatusCode() + ", url=" + url); return null; } //獲取響應內容 HttpEntity entity = response.getEntity(); if (entity != null) { String resultStr = EntityUtils.toString(entity, "utf-8"); System.out.println("POST請求結果:"+resultStr); //解析響應內容 JSONObject result = JSON.parseObject(resultStr); if(result.getInteger("errcode")==null) { return result; }else if (0 == result.getInteger("errcode")) { return result; }else { System.out.println("request url=" + url + ",return value="); System.out.println(resultStr); int errCode = result.getInteger("errcode"); String errMsg = result.getString("errmsg"); throw new Exception("error code:"+errCode+", error message:"+errMsg); } } } catch (IOException e) { System.out.println("request url=" + url + ", exception, msg=" + e.getMessage()); e.printStackTrace(); } finally { if (response != null) try { response.close(); //釋放資源 } catch (IOException e) { e.printStackTrace(); } } return null; } /** * @desc : 3.上傳文件 * * @param url 請求url * @param file 上傳的文件 * @return * @throws Exception JSONObject */ public static JSONObject uploadMedia(String url, File file) throws Exception { HttpPost httpPost = new HttpPost(url); CloseableHttpResponse response = null; CloseableHttpClient httpClient = HttpClients.createDefault(); RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(5000).build(); httpPost.setConfig(requestConfig); //2.3 設置請求實體,封裝了請求參數 HttpEntity requestEntity = MultipartEntityBuilder.create().addPart("media", new FileBody(file, ContentType.create("multipart/form-data", Consts.UTF_8), file.getName())).build(); //FileEntity requestEntity = new FileEntity(file,ContentType.MULTIPART_FORM_DATA); httpPost.setEntity(requestEntity); try { response = httpClient.execute(httpPost, new BasicHttpContext()); if (response.getStatusLine().getStatusCode() != 200) { System.out.println("request url failed, http code=" + response.getStatusLine().getStatusCode() + ", url=" + url); return null; } HttpEntity entity = response.getEntity(); if (entity != null) { String resultStr = EntityUtils.toString(entity, "utf-8"); JSONObject result = JSON.parseObject(resultStr); //上傳臨時素材成功 if (result.getString("errcode")== null) { // 成功 //result.remove("errcode"); //result.remove("errmsg"); return result; } else { System.out.println("request url=" + url + ",return value="); System.out.println(resultStr); int errCode = result.getInteger("errcode"); String errMsg = result.getString("errmsg"); throw new Exception("error code:"+errCode+", error message:"+errMsg); } } } catch (IOException e) { System.out.println("request url=" + url + ", exception, msg=" + e.getMessage()); e.printStackTrace(); } finally { if (response != null) try { response.close(); //釋放資源 } catch (IOException e) { e.printStackTrace(); } } return null; } /** * @desc : 上傳PDF * 見微信電子發票章節 * 9. 向用戶提供發票或其它消費憑證PDF * * @param url * @param file * @return * @throws Exception * JSONObject */ public static JSONObject uploadPDF(String url, File file) throws Exception { HttpPost httpPost = new HttpPost(url); CloseableHttpResponse response = null; CloseableHttpClient httpClient = HttpClients.createDefault(); RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(5000).build(); httpPost.setConfig(requestConfig); //2.3 設置請求實體,封裝了請求參數 HttpEntity requestEntity = MultipartEntityBuilder.create().addPart("media", new FileBody(file, ContentType.create("multipart/form-data", Consts.UTF_8), file.getName())).build(); httpPost.setEntity(requestEntity); try { response = httpClient.execute(httpPost, new BasicHttpContext()); if (response.getStatusLine().getStatusCode() != 200) { System.out.println("request url failed, http code=" + response.getStatusLine().getStatusCode() + ", url=" + url); return null; } HttpEntity entity = response.getEntity(); if (entity != null) { String resultStr = EntityUtils.toString(entity, "utf-8"); JSONObject result = JSON.parseObject(resultStr); //上傳臨時素材成功 if (result.getString("errcode")== null) { // 成功 //result.remove("errcode"); //result.remove("errmsg"); return result; } else { System.out.println("request url=" + url + ",return value="); System.out.println(resultStr); int errCode = result.getInteger("errcode"); String errMsg = result.getString("errmsg"); throw new Exception("error code:"+errCode+", error message:"+errMsg); } } } catch (IOException e) { System.out.println("request url=" + url + ", exception, msg=" + e.getMessage()); e.printStackTrace(); } finally { if (response != null) try { response.close(); //釋放資源 } catch (IOException e) { e.printStackTrace(); } } return null; } /** * @desc : 4.下載文件 -get * * @param url 請求url * @param fileDir 下載路徑 * @return * @throws Exception File */ public static File downloadMedia(String url, String fileDir) throws Exception { //1.生成一個請求 HttpGet httpGet = new HttpGet(url); //2.配置請求屬性 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(100000).setConnectTimeout(100000).build(); httpGet.setConfig(requestConfig); //3.發起請求,獲取響應信息 //3.1 創建httpClient CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; //4.設置本地保存的文件 //File file = new File(fileDir); File file = null; try { //5. 發起請求,獲取響應信息 response = httpClient.execute(httpGet, new BasicHttpContext()); System.out.println("HttpStatus.SC_OK:"+HttpStatus.SC_OK); System.out.println("response.getStatusLine().getStatusCode():"+response.getStatusLine().getStatusCode()); System.out.println("http-header:"+JSON.toJSONString( response.getAllHeaders() )); System.out.println("http-filename:"+getFileName(response) ); //請求成功 if(HttpStatus.SC_OK==response.getStatusLine().getStatusCode()){ //6.取得請求內容 HttpEntity entity = response.getEntity(); if (entity != null) { //這里可以得到文件的類型 如image/jpg /zip /tiff 等等 但是發現並不是十分有效,有時明明后綴是.rar但是取到的是null,這點特別說明 System.out.println(entity.getContentType()); //可以判斷是否是文件數據流 System.out.println(entity.isStreaming()); //6.1 輸出流 //6.1.1獲取文件名,拼接文件路徑 String fileName=getFileName(response); fileDir=fileDir+fileName; file = new File(fileDir); //6.1.2根據文件路徑獲取輸出流 FileOutputStream output = new FileOutputStream(file); //6.2 輸入流:從釘釘服務器返回的文件流,得到網絡資源並寫入文件 InputStream input = entity.getContent(); //6.3 將數據寫入文件:將輸入流中的數據寫入到輸出流 byte b[] = new byte[1024]; int j = 0; while( (j = input.read(b))!=-1){ output.write(b,0,j); } output.flush(); output.close(); } if (entity != null) { entity.consumeContent(); } } } catch (IOException e) { System.out.println("request url=" + url + ", exception, msg=" + e.getMessage()); e.printStackTrace(); } finally { if (response != null) try { response.close(); //釋放資源 } catch (IOException e) { e.printStackTrace(); } } return file; } /** * @desc : 5.下載文件 - post * * @param url 請求url * @param data post請求參數 * @param fileDir 文件下載路徑 * @return * @throws Exception File */ public static File downloadMedia(String url, Object data, String fileDir) throws Exception { //1.生成一個請求 HttpPost httpPost = new HttpPost(url); //2.配置請求屬性 //2.1 設置請求超時時間 RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(100000).setConnectTimeout(100000).build(); httpPost.setConfig(requestConfig); //2.2 設置數據傳輸格式-json httpPost.addHeader("Content-Type", "application/json"); //2.3 設置請求參數 StringEntity requestEntity = new StringEntity(JSON.toJSONString(data), "utf-8"); httpPost.setEntity(requestEntity); //3.發起請求,獲取響應信息 //3.1 創建httpClient CloseableHttpClient httpClient = HttpClients.createDefault(); CloseableHttpResponse response = null; //4.設置本地保存的文件 //File file = new File(fileDir); File file = null; try { //5. 發起請求,獲取響應信息 response = httpClient.execute(httpPost, new BasicHttpContext()); System.out.println("HttpStatus.SC_OK:"+HttpStatus.SC_OK); System.out.println("response.getStatusLine().getStatusCode():"+response.getStatusLine().getStatusCode()); System.out.println("http-header:"+JSON.toJSONString( response.getAllHeaders() )); System.out.println("http-filename:"+getFileName(response) ); //請求成功 if(HttpStatus.SC_OK==response.getStatusLine().getStatusCode()){ //6.取得請求內容 HttpEntity entity = response.getEntity(); if (entity != null) { //這里可以得到文件的類型 如image/jpg /zip /tiff 等等 但是發現並不是十分有效,有時明明后綴是.rar但是取到的是null,這點特別說明 System.out.println(entity.getContentType()); //可以判斷是否是文件數據流 System.out.println(entity.isStreaming()); //6.1 輸出流 //6.1.1獲取文件名,拼接文件路徑 String fileName=getFileName(response); fileDir=fileDir+fileName; file = new File(fileDir); //6.1.2根據文件路徑獲取輸出流 FileOutputStream output = new FileOutputStream(file); //6.2 輸入流:從釘釘服務器返回的文件流,得到網絡資源並寫入文件 InputStream input = entity.getContent(); //6.3 將數據寫入文件:將輸入流中的數據寫入到輸出流 byte b[] = new byte[1024]; int j = 0; while( (j = input.read(b))!=-1){ output.write(b,0,j); } output.flush(); output.close(); } if (entity != null) { entity.consumeContent(); } } } catch (IOException e) { System.out.println("request url=" + url + ", exception, msg=" + e.getMessage()); e.printStackTrace(); } finally { if (response != null) try { response.close(); //釋放資源 } catch (IOException e) { e.printStackTrace(); } } return file; } /** 5. 獲取response header中Content-Disposition中的filename值 * @desc : * * @param response 響應 * @return String */ public static String getFileName(HttpResponse response) { Header contentHeader = response.getFirstHeader("Content-Disposition"); String filename = null; if (contentHeader != null) { HeaderElement[] values = contentHeader.getElements(); if (values.length == 1) { NameValuePair param = values[0].getParameterByName("filename"); if (param != null) { try { //filename = new String(param.getValue().toString().getBytes(), "utf-8"); //filename=URLDecoder.decode(param.getValue(),"utf-8"); filename = param.getValue(); } catch (Exception e) { e.printStackTrace(); } } } } return filename; } }
3、接收消息的封裝
3.1 消息基類—BaseMessage

package com.ray.weixin.gz.model.message.request; /** * @desc : 消息基類(普通用戶 -> 公眾帳號) * * @author: shirayner * @date : 2017年11月13日 上午10:58:08 */ public class BaseMessage { // 開發者微信號 private String ToUserName; // 發送方帳號(一個OpenID) private String FromUserName; // 消息創建時間 (整型) private long CreateTime; // 消息類型(text/image/location/link) private String MsgType; // 消息id,64位整型 private long MsgId; public String getToUserName() { return ToUserName; } public void setToUserName(String toUserName) { ToUserName = toUserName; } public String getFromUserName() { return FromUserName; } public void setFromUserName(String fromUserName) { FromUserName = fromUserName; } public long getCreateTime() { return CreateTime; } public void setCreateTime(long createTime) { CreateTime = createTime; } public String getMsgType() { return MsgType; } public void setMsgType(String msgType) { MsgType = msgType; } public long getMsgId() { return MsgId; } public void setMsgId(long msgId) { MsgId = msgId; } }
3.2 文本消息—TextMessage

package com.ray.weixin.gz.model.message.request; /** * @desc : 文本消息 * * @author: shirayner * @date : 2017年11月13日 上午11:04:09 */ public class TextMessage extends BaseMessage { // 消息內容 private String Content; public String getContent() { return Content; } public void setContent(String content) { Content = content; } }
3.3 圖片消息—ImageMessage

package com.ray.weixin.gz.model.message.request; /** * @desc : 圖片消息 * * @author: shirayner * @date : 2017年11月13日 上午11:04:33 */ public class ImageMessage extends BaseMessage { // 圖片鏈接 private String PicUrl; public String getPicUrl() { return PicUrl; } public void setPicUrl(String picUrl) { PicUrl = picUrl; } }
3.4 鏈接消息—LinkMessage

package com.ray.weixin.gz.model.message.request; /** * @desc :鏈接消息 * * @author: shirayner * @date : 2017年11月13日 上午11:05:46 */ public class LinkMessage extends BaseMessage { // 消息標題 private String Title; // 消息描述 private String Description; // 消息鏈接 private String Url; public String getTitle() { return Title; } public void setTitle(String title) { Title = title; } public String getDescription() { return Description; } public void setDescription(String description) { Description = description; } public String getUrl() { return Url; } public void setUrl(String url) { Url = url; } }
3.5 地理位置消息—LocationMessage

package com.ray.weixin.gz.model.message.request; /** * @desc : 地理位置消息 * * @author: shirayner * @date : 2017年11月13日 上午11:07:39 */ public class LocationMessage extends BaseMessage { // 地理位置維度 private String Location_X; // 地理位置經度 private String Location_Y; // 地圖縮放大小 private String Scale; // 地理位置信息 private String Label; public String getLocation_X() { return Location_X; } public void setLocation_X(String location_X) { Location_X = location_X; } public String getLocation_Y() { return Location_Y; } public void setLocation_Y(String location_Y) { Location_Y = location_Y; } public String getScale() { return Scale; } public void setScale(String scale) { Scale = scale; } public String getLabel() { return Label; } public void setLabel(String label) { Label = label; } }
3.6 音頻消息—VoiceMessage

package com.ray.weixin.gz.model.message.request; /** * @desc :音頻消息 * * @author: shirayner * @date : 2017年11月13日 上午11:08:25 */ public class VoiceMessage extends BaseMessage { // 媒體ID private String MediaId; // 語音格式 private String Format; public String getMediaId() { return MediaId; } public void setMediaId(String mediaId) { MediaId = mediaId; } public String getFormat() { return Format; } public void setFormat(String format) { Format = format; } }
4. 回復消息的封裝
4.1 消息基類—BaseMessage

package com.ray.weixin.gz.model.message.response; /** * @desc : 消息基類(公眾帳號 -> 普通用戶) * * @author: shirayner * @date : 2017年11月13日 上午11:10:32 */ public class BaseMessage { // 接收方帳號(收到的OpenID) private String ToUserName; // 開發者微信號 private String FromUserName; // 消息創建時間 (整型) private long CreateTime; // 消息類型(text/music/news) private String MsgType; // 位0x0001被標志時,星標剛收到的消息 private int FuncFlag; public String getToUserName() { return ToUserName; } public void setToUserName(String toUserName) { ToUserName = toUserName; } public String getFromUserName() { return FromUserName; } public void setFromUserName(String fromUserName) { FromUserName = fromUserName; } public long getCreateTime() { return CreateTime; } public void setCreateTime(long createTime) { CreateTime = createTime; } public String getMsgType() { return MsgType; } public void setMsgType(String msgType) { MsgType = msgType; } public int getFuncFlag() { return FuncFlag; } public void setFuncFlag(int funcFlag) { FuncFlag = funcFlag; } }
4.2 文本消息—TextMessage

package com.ray.weixin.gz.model.message.response; /** * @desc : * * @author: shirayner * @date : 2017年11月13日 上午11:10:58 */ public class TextMessage extends BaseMessage { // 回復的消息內容 private String Content; public String getContent() { return Content; } public void setContent(String content) { Content = content; } }
4.3 音樂消息—MusicMessage
Music

package com.ray.weixin.gz.model.message.response; /** * @desc : 音樂model * * @author: shirayner * @date : 2017年11月13日 上午11:12:47 */ public class Music { // 音樂名稱 private String Title; // 音樂描述 private String Description; // 音樂鏈接 private String MusicUrl; // 高質量音樂鏈接,WIFI環境優先使用該鏈接播放音樂 private String HQMusicUrl; public String getTitle() { return Title; } public void setTitle(String title) { Title = title; } public String getDescription() { return Description; } public void setDescription(String description) { Description = description; } public String getMusicUrl() { return MusicUrl; } public void setMusicUrl(String musicUrl) { MusicUrl = musicUrl; } public String getHQMusicUrl() { return HQMusicUrl; } public void setHQMusicUrl(String musicUrl) { HQMusicUrl = musicUrl; } }
MusicMessage

package com.ray.weixin.gz.model.message.response; /** * @desc : 音樂消息 * * @author: shirayner * @date : 2017年11月13日 上午11:12:06 */ public class MusicMessage extends BaseMessage { // 音樂 private Music Music; public Music getMusic() { return Music; } public void setMusic(Music music) { Music = music; } }
4.4 圖文消息—NewsMessage
Article

package com.ray.weixin.gz.model.message.response; /** * @desc : 圖文model * * @author: shirayner * @date : 2017年11月13日 上午11:15:30 */ public class Article { // 圖文消息名稱 private String Title; // 圖文消息描述 private String Description; // 圖片鏈接,支持JPG、PNG格式,較好的效果為大圖640*320,小圖80*80,限制圖片鏈接的域名需要與開發者填寫的基本資料中的Url一致 private String PicUrl; // 點擊圖文消息跳轉鏈接 private String Url; public String getTitle() { return Title; } public void setTitle(String title) { Title = title; } public String getDescription() { return null == Description ? "" : Description; } public void setDescription(String description) { Description = description; } public String getPicUrl() { return null == PicUrl ? "" : PicUrl; } public void setPicUrl(String picUrl) { PicUrl = picUrl; } public String getUrl() { return null == Url ? "" : Url; } public void setUrl(String url) { Url = url; } }
NewsMessage

package com.ray.weixin.gz.model.message.response; import java.util.List; /** * @desc : 圖文消息 * * @author: shirayner * @date : 2017年11月13日 上午11:13:36 */ public class NewsMessage extends BaseMessage { // 圖文消息個數,限制為10條以內 private int ArticleCount; // 多條圖文消息信息,默認第一個item為大圖 private List<Article> Articles; public int getArticleCount() { return ArticleCount; } public void setArticleCount(int articleCount) { ArticleCount = articleCount; } public List<Article> getArticles() { return Articles; } public void setArticles(List<Article> articles) { Articles = articles; } }
5.接收微信消息和事件—WeiXinServlet.java

package com.ray.weixin.gz.controller; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.qq.weixin.mp.aes.AesException; import com.qq.weixin.mp.aes.WXBizMsgCrypt; import com.ray.weixin.gz.config.Env; import com.ray.weixin.gz.service.message.ReplyMessageService; /** * Servlet implementation class WeiXinServlet */ public class WeiXinServlet extends HttpServlet { private static final Logger logger = LogManager.getLogger(WeiXinServlet.class); private static final long serialVersionUID = 1L; /** * Default constructor. */ public WeiXinServlet() { // TODO Auto-generated constructor stub } //1.接收 回調模式 的請求 protected void doGet(HttpServletRequest request, HttpServletResponse response) { logger.info("get--------------"); //一、校驗URL //1.准備校驗參數 // 微信加密簽名 String msgSignature = request.getParameter("signature"); // 時間戳 String timeStamp = request.getParameter("timestamp"); // 隨機數 String nonce = request.getParameter("nonce"); // 隨機字符串 String echoStr = request.getParameter("echostr"); PrintWriter out=null; try { //2.校驗url //2.1 創建加解密類 WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(Env.TOKEN,Env.ENCODING_AES_KEY,Env.APP_ID); //2.2進行url校驗 //不拋異常就說明校驗成功 String sEchoStr= wxcpt.verifyUrl_WXGZ(msgSignature, Env.TOKEN, timeStamp, nonce,echoStr); //2.3若校驗成功,則原樣返回 echoStr out = response.getWriter(); out.print(sEchoStr); } catch (AesException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if (out != null) { out.close(); out = null; //釋放資源 } } } //2.接收 微信消息和事件 的請求 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { logger.info("post--------------"); //1.將請求、響應的編碼均設置為UTF-8(防止中文亂碼) request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); //2.調用消息業務類接收消息、處理消息 String respMessage = ReplyMessageService.reply(request); //3.響應消息 PrintWriter out = response.getWriter(); out.print(respMessage); out.close(); } }
6.被動回復消息業務類—ReplyMessageService.java

package com.ray.weixin.gz.service.message; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Date; import java.util.Map; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.alibaba.fastjson.JSON; import com.qq.weixin.mp.aes.AesException; import com.qq.weixin.mp.aes.WXBizMsgCrypt; import com.ray.weixin.gz.config.Env; import com.ray.weixin.gz.model.message.response.TextMessage; import com.ray.weixin.gz.util.MessageUtil; /**@desc : 發送消息-被動回復消息業務類 * Passive reply message * @author: shirayner * @date : 2017年10月31日 下午12:24:41 */ public class ReplyMessageService { private static final Logger logger = LogManager.getLogger(ReplyMessageService.class); /** * @desc :1.回復消息 * * @param request * @return * String 回復消息的加密xml字符串 */ public static String reply( HttpServletRequest request ) { String parms=JSON.toJSONString(request.getParameterMap()); logger.info("parms:"+parms); //1.解密:從request中獲取消息明文 String xmlMsg=decryptMsg(request); logger.info(xmlMsg); //2.獲取回復消息(明文) String replyMsg = getReplyMsg( xmlMsg); //3.根據消息加密方式判斷是否加密 String timeStamp = request.getParameter("timestamp"); // 時間戳 String nonce = request.getParameter("nonce"); // 隨機數 String encryptType=request.getParameter("encrypt_type"); //3.1 安全模式-加密:將回復消息加密 if(null!=encryptType) { WXBizMsgCrypt wxcpt=null; try { wxcpt = new WXBizMsgCrypt(Env.TOKEN,Env.ENCODING_AES_KEY,Env.APP_ID); replyMsg=wxcpt.EncryptMsg(replyMsg, timeStamp, nonce); } catch (AesException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return replyMsg; } /** * @desc :2.從request中獲取消息明文 * 從request中獲取加密消息,將其解密並返回 * @param request * @return String 消息明文 */ public static String decryptMsg(HttpServletRequest request) { String postData=""; // 密文,對應POST請求的數據 String result=""; // 明文,解密之后的結果 String msgSignature = request.getParameter("msg_signature"); // 微信加密簽名 String timeStamp = request.getParameter("timestamp"); // 時間戳 String nonce = request.getParameter("nonce"); // 隨機數 String encryptType=request.getParameter("encrypt_type"); try { //1.獲取加密的請求消息:使用輸入流獲得加密請求消息postData ServletInputStream in = request.getInputStream(); BufferedReader reader =new BufferedReader(new InputStreamReader(in)); String tempStr=""; //作為輸出字符串的臨時串,用於判斷是否讀取完畢 while(null!=(tempStr=reader.readLine())){ postData+=tempStr; } logger.info("postData:"+postData); //2.獲取消息明文:對加密的請求消息進行解密獲得明文 if(null!=encryptType) { logger.info("安全模式:消息被加密"); WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(Env.TOKEN,Env.ENCODING_AES_KEY,Env.APP_ID); result=wxcpt.DecryptMsg(msgSignature, timeStamp, nonce, postData); }else { logger.info("明文模式"); result=postData; } } catch (IOException e) { e.printStackTrace(); } catch (AesException e) { e.printStackTrace(); } return result; } /** * @desc :獲取回復消息 * * @param request * @return String 返回加密后的回復消息 */ public static String getReplyMsg(String xmlMsg){ String replyMsg = null; try { //2.解析微信發來的請求,解析xml字符串 Map<String, String> requestMap= MessageUtil.parseXml(xmlMsg); //3.獲取請求參數 //3.1 企業微信CorpID String fromUserName = requestMap.get("FromUserName"); //3.2 成員UserID String toUserName = requestMap.get("ToUserName"); //3.3 消息類型與事件 String msgType = requestMap.get("MsgType"); String eventType = requestMap.get("Event"); String eventKey = requestMap.get("EventKey"); logger.info("fromUserName:"+fromUserName); logger.info("toUserName:"+toUserName); logger.info("msgType:"+msgType); logger.info("Event:"+eventType+" eventKey:"+eventKey); //4.組裝 回復文本消息 TextMessage textMessage = new TextMessage(); textMessage.setToUserName(fromUserName); textMessage.setFromUserName(toUserName); textMessage.setCreateTime(new Date().getTime()); textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT); //4.1.獲取回復消息的內容 :消息的分類處理 String replyContent=getReplyContentByMsgType(msgType, eventType, eventKey); textMessage.setContent(replyContent); System.out.println("replyContent:"+replyContent); //5.獲取xml字符串: 將(被動回復消息型的)文本消息對象 轉成 xml字符串 replyMsg = MessageUtil.textMessageToXml(textMessage); } catch (Exception e) { e.printStackTrace(); } return replyMsg; } /** * @desc :3.處理消息:根據消息類型獲取回復內容 * * @param msgType 消息類型 * @return String 回復內容 */ public static String getReplyContentByMsgType(String msgType,String eventType,String eventKey){ String replyContent=""; //1.文本消息 if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) { replyContent = "您發送的是文本消息!"; } //2.圖片消息 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) { replyContent = "您發送的是圖片消息!"; } //3.地理位置消息 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) { replyContent = "您發送的是地理位置消息 !"; } //4.鏈接消息 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) { replyContent = "您發送的是鏈接消息!"; } //5.音頻消息 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) { replyContent = "您發送的是音頻消息!"; } //6.事件推送 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) { replyContent=getReplyContentByEventType(eventType, eventKey); } //7.請求異常 else { replyContent="請求處理異常,請稍候嘗試!"; } return replyContent; } /** * @desc :5.處理消息:根據事件類型獲取回復內容 * * @param eventType 事件類型 * @param eventKey 事件key值 * @return * String */ public static String getReplyContentByEventType(String eventType,String eventKey){ String respContent=""; // 訂閱 if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) { respContent = "歡迎關注!"; } // 取消訂閱 else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) { // TODO 取消訂閱后用戶再收不到公眾號發送的消息,因此不需要回復消息 } //上報地理位置事件 else if(eventType.equals("LOCATION")){ } // 自定義菜單點擊事件 else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) { if (eventKey.equals("12")) { } else if (eventKey.equals("13")) { respContent = "周邊搜索菜單項被點擊!"; } else if (eventKey.equals("14")) { respContent = "歷史上的今天菜單項被點擊!"; } else if (eventKey.equals("21")) { respContent = "歌曲點播菜單項被點擊!"; } else if (eventKey.equals("22")) { respContent = "經典游戲菜單項被點擊!"; } else if (eventKey.equals("23")) { respContent = "美女電台菜單項被點擊!"; } else if (eventKey.equals("24")) { respContent = "人臉識別菜單項被點擊!"; } else if (eventKey.equals("25")) { respContent = "聊天嘮嗑菜單項被點擊!"; } else if (eventKey.equals("31")) { respContent = "Q友圈菜單項被點擊!"; } else if (eventKey.equals("32")) { respContent = "電影排行榜菜單項被點擊!"; } else if (eventKey.equals("33")) { respContent = "幽默笑話菜單項被點擊!"; } } return respContent; } }
三、參考資料