上篇我們說到回復消息可以根據是否需要上傳文件到微信服務器可划分為【普通消息】和【多媒體消息】,這里我們來講述普通消息的回復實現,在消息回復中存在一個關鍵字段【openid】,它是微信用戶對於公眾號的唯一標識,這里不做過多解釋后面將給出時間專門來講解微信生態中的關鍵字!
(一)回復文本消息
在前面我們已經完成了對消息的分類和回復消息實體的建立,這里回復文本消息需要用到的就是我們的TextMessage,我們把回復文本消息在【文本消息】類型中給出回復!在我們做消息回復的時候需要設置消息的接收人ToUserName(openid)、消息的發送方FromUserName、消息類型MsgType、創建時間CreateTime以及消息體Content,由於我們我們的消息回復格式是需要為xml,所以最終我們需要將其裝換成xml再做返回輸出!
首先我們在工具類MessageUtil的代碼做出部分修改和添加,實現最后版本為:
1 package com.gede.wechat.util; 2 import java.io.InputStream; 3 import java.io.Writer; 4 import java.util.HashMap; 5 import java.util.List; 6 import java.util.Map; 7 import javax.servlet.http.HttpServletRequest; 8 import org.dom4j.Document; 9 import org.dom4j.Element; 10 import org.dom4j.io.SAXReader; 11 import com.gede.wechat.response.*; 12 import com.thoughtworks.xstream.XStream; 13 import com.thoughtworks.xstream.core.util.QuickWriter; 14 import com.thoughtworks.xstream.io.HierarchicalStreamWriter; 15 import com.thoughtworks.xstream.io.xml.DomDriver; 16 import com.thoughtworks.xstream.io.xml.PrettyPrintWriter; 17 import com.thoughtworks.xstream.io.xml.XppDriver; 18 19 /** 20 * @author gede 21 * @version date:2019年5月22日 下午3:44:27 22 * @description : 23 */ 24 public class MessageUtil { 25 26 /** 27 * 返回消息類型:文本 28 */ 29 public static final String RESP_MESSAGE_TYPE_TEXT = "text"; 30 31 /** 32 * 返回消息類型:音樂 33 */ 34 public static final String RESP_MESSAGE_TYPE_MUSIC = "music"; 35 36 /** 37 * 返回消息類型:圖文 38 */ 39 public static final String RESP_MESSAGE_TYPE_NEWS = "news"; 40 41 /** 42 * 返回消息類型:圖片 43 */ 44 public static final String RESP_MESSAGE_TYPE_Image = "image"; 45 46 /** 47 * 返回消息類型:語音 48 */ 49 public static final String RESP_MESSAGE_TYPE_Voice = "voice"; 50 51 /** 52 * 返回消息類型:視頻 53 */ 54 public static final String RESP_MESSAGE_TYPE_Video = "video"; 55 56 /** 57 * 請求消息類型:文本 58 */ 59 public static final String REQ_MESSAGE_TYPE_TEXT = "text"; 60 61 /** 62 * 請求消息類型:圖片 63 */ 64 public static final String REQ_MESSAGE_TYPE_IMAGE = "image"; 65 66 /** 67 * 請求消息類型:鏈接 68 */ 69 public static final String REQ_MESSAGE_TYPE_LINK = "link"; 70 71 /** 72 * 請求消息類型:地理位置 73 */ 74 public static final String REQ_MESSAGE_TYPE_LOCATION = "location"; 75 76 /** 77 * 請求消息類型:音頻 78 */ 79 public static final String REQ_MESSAGE_TYPE_VOICE = "voice"; 80 81 /** 82 * 請求消息類型:視頻 83 */ 84 public static final String REQ_MESSAGE_TYPE_VIDEO = "video"; 85 86 /** 87 * 請求消息類型:推送 88 */ 89 public static final String REQ_MESSAGE_TYPE_EVENT = "event"; 90 91 /** 92 * 事件類型:subscribe(訂閱) 93 */ 94 public static final String EVENT_TYPE_SUBSCRIBE = "subscribe"; 95 96 /** 97 * 事件類型:unsubscribe(取消訂閱) 98 */ 99 public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe"; 100 101 /** 102 * 事件類型:CLICK(自定義菜單點擊事件) 103 */ 104 public static final String EVENT_TYPE_CLICK = "CLICK"; 105 106 /** 107 * 事件類型:VIEW(自定義菜單 URl 視圖) 108 */ 109 public static final String EVENT_TYPE_VIEW = "VIEW"; 110 111 /** 112 * 事件類型:LOCATION(上報地理位置事件) 113 */ 114 public static final String EVENT_TYPE_LOCATION = "LOCATION"; 115 116 /** 117 * 事件類型:LOCATION(上報地理位置事件) 118 */ 119 public static final String EVENT_TYPE_SCAN = "SCAN"; 120 121 @SuppressWarnings("unchecked") 122 public static Map<String, String> parseXml(HttpServletRequest request) 123 throws Exception { 124 // 將解析結果存儲在 HashMap 中 125 Map<String, String> map = new HashMap<String, String>(); 126 // 從 request 中取得輸入流 127 InputStream inputStream = request.getInputStream(); 128 // 讀取輸入流 129 SAXReader reader = new SAXReader(); 130 Document document = reader.read(inputStream); 131 // 得到 xml 根元素 132 Element root = document.getRootElement(); 133 // 得到根元素的所有子節點 134 List<Element> elementList = root.elements(); 135 136 // 遍歷所有子節點 137 for (Element e : elementList) 138 map.put(e.getName(), e.getText()); 139 140 // 釋放資源 141 inputStream.close(); 142 inputStream = null; 143 144 return map; 145 } 146 147 public static String textMessageToXml(TextMessage textMessage) { 148 XStream xstream = new XStream(new DomDriver("utf-8")); 149 xstream.alias("xml", textMessage.getClass()); 150 return xstream.toXML(textMessage); 151 } 152 public static String newsMessageToXml(NewsMessage newsMessage) { 153 xstream.alias("xml", newsMessage.getClass()); 154 xstream.alias("item", new Article().getClass()); 155 return xstream.toXML(newsMessage); 156 } 157 158 public static String imageMessageToXml(ImageMessage imageMessage) { 159 xstream.alias("xml", imageMessage.getClass()); 160 return xstream.toXML(imageMessage); 161 } 162 163 public static String voiceMessageToXml(VoiceMessage voiceMessage) { 164 xstream.alias("xml", voiceMessage.getClass()); 165 return xstream.toXML(voiceMessage); 166 } 167 168 public static String videoMessageToXml(VideoMessage videoMessage) { 169 xstream.alias("xml", videoMessage.getClass()); 170 return xstream.toXML(videoMessage); 171 } 172 173 public static String musicMessageToXml(MusicMessage musicMessage) { 174 xstream.alias("xml", musicMessage.getClass()); 175 return xstream.toXML(musicMessage); 176 } 177 178 /** 179 * 對象到 xml 的處理 180 */ 181 private static XStream xstream = new XStream(new XppDriver() { 182 public HierarchicalStreamWriter createWriter(Writer out) { 183 return new PrettyPrintWriter(out) { 184 // 對所有 xml 節點的轉換都增加 CDATA 標記 185 boolean cdata = true; 186 187 @SuppressWarnings("rawtypes") 188 public void startNode(String name, Class clazz) { 189 super.startNode(name, clazz); 190 } 191 192 protected void writeText(QuickWriter writer, String text) { 193 if (cdata) { 194 writer.write("<![CDATA["); 195 writer.write(text); 196 writer.write("]]>"); 197 } else { 198 writer.write(text); 199 } 200 } 201 }; 202 } 203 }); 204 }
我們回復文本消息的簡單實現:修改MsgDispatcher,在消息分類為【文本消息】中加入如下代碼:
1 package com.gede.wechat.dispatcher; 2 3 import java.util.Date; 4 import java.util.Map; 5 6 import com.gede.wechat.response.TextMessage; 7 import com.gede.wechat.util.MessageUtil; 8 9 /** 10 * @author gede 11 * @version date:2019年5月23日 下午6:49:11 12 * @description : 13 */ 14 public class MsgDispatcher { 15 public static String processMessage(Map<String, String> map) { 16 String openid=map.get("FromUserName"); //用戶openid 17 String mpid=map.get("ToUserName"); //公眾號原始ID 18 //普通文本消息 19 TextMessage txtmsg=new TextMessage(); 20 txtmsg.setToUserName(openid); 21 txtmsg.setFromUserName(mpid); 22 txtmsg.setCreateTime(new Date().getTime()); 23 txtmsg.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT); 24 if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) { // 文本消息 25 txtmsg.setContent("你好,歡迎您的關注!"); 26 return MessageUtil.textMessageToXml(txtmsg); 27 } 28 if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) { // 圖片消息 29 System.out.println("==============這是圖片消息!"); 30 } 31 if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) { // 鏈接消息 32 System.out.println("==============這是鏈接消息!"); 33 } 34 if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) { // 位置消息 35 System.out.println("==============這是位置消息!"); 36 } 37 if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_VIDEO)) { // 視頻消息 38 System.out.println("==============這是視頻消息!"); 39 } 40 if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) { // 語音消息 41 System.out.println("==============這是語音消息!"); 42 } 43 44 return null; 45 } 46 }
此時從邏輯上來說,代碼已完成,但是從完整的微信響應來看,我們只是完成了回復內容的編輯,並沒有去響應微信服務器讓服務器去回復消息,所以我們還需要修改WechatSecurity這個控制類,修改的時候我們還要主要本地服務器和微信服務器編碼的問題,為了避免麻煩我們統一設置成utf-8。
1 package com.gede.wechat.controller; 2 3 import java.io.PrintWriter; 4 import java.util.Map; 5 6 import javax.servlet.http.HttpServletRequest; 7 import javax.servlet.http.HttpServletResponse; 8 9 import org.apache.log4j.Logger; 10 import org.springframework.stereotype.Controller; 11 import org.springframework.web.bind.annotation.RequestMapping; 12 import org.springframework.web.bind.annotation.RequestMethod; 13 import org.springframework.web.bind.annotation.RequestParam; 14 15 import com.gede.wechat.dispatcher.EventDispatcher; 16 import com.gede.wechat.dispatcher.MsgDispatcher; 17 import com.gede.wechat.util.MessageUtil; 18 import com.gede.wechat.util.SignUtil; 19 20 /** 21 * @author gede 22 * @version date:2019年5月22日 下午2:53:46 23 * @description : 24 */ 25 @Controller 26 @RequestMapping("/wechat") 27 public class WechatSecurity { 28 private static Logger logger = Logger.getLogger(WechatSecurity.class); 29 30 @RequestMapping(value = "security", method = RequestMethod.GET) 31 public void doGet( 32 HttpServletRequest request, 33 HttpServletResponse response, 34 @RequestParam(value = "signature", required = true) String signature, 35 @RequestParam(value = "timestamp", required = true) String timestamp, 36 @RequestParam(value = "nonce", required = true) String nonce, 37 @RequestParam(value = "echostr", required = true) String echostr) { 38 try { 39 if (SignUtil.checkSignature(signature, timestamp, nonce)) { 40 PrintWriter out = response.getWriter(); 41 out.print(echostr); 42 out.close(); 43 } else { 44 logger.info("這里存在非法請求!"); 45 } 46 } catch (Exception e) { 47 logger.error(e, e); 48 } 49 } 50 51 /** 52 * @Description: 接收微信端消息處理並做分發 53 * @param @param request 54 * @param @param response 55 * @author dapengniao 56 * @date 2016年3月7日 下午4:06:47 57 */ 58 @RequestMapping(value = "security", method = RequestMethod.POST) 59 public void DoPost(HttpServletRequest request,HttpServletResponse response) { 60 try{ 61 Map<String, String> map=MessageUtil.parseXml(request); 62 String msgtype=map.get("MsgType"); 63 if(MessageUtil.REQ_MESSAGE_TYPE_EVENT.equals(msgtype)){ 64 request.setCharacterEncoding("UTF-8"); 65 response.setCharacterEncoding("UTF-8"); 66 String msgrsp=EventDispatcher.processEvent(map); //進入事件處理 67 PrintWriter out = response.getWriter(); 68 out.print(msgrsp); 69 out.close(); 70 }else{ 71 request.setCharacterEncoding("UTF-8"); 72 response.setCharacterEncoding("UTF-8"); 73 String msgrsp =MsgDispatcher.processMessage(map); //進入消息處理 74 PrintWriter out = response.getWriter(); 75 out.print(msgrsp); 76 out.close(); 77 } 78 }catch(Exception e){ 79 logger.error(e,e); 80 } 81 } 82 }
啟動項目,當我們發送任何文本消息后我們可以看到我們的回復內容,如圖:
(二)圖文消息回復
圖文消息的回復和文本消息的實現模式是一樣的,只不過對應消息體的字段有所區別而已,這里為了和文本消息能有所區分我在【圖片消息】實現圖文消息的回復,修改MsgDispatcher:
1 NewsMessage newmsg=new NewsMessage(); 2 newmsg.setToUserName(openid); 3 newmsg.setFromUserName(mpid); 4 newmsg.setCreateTime(new Date().getTime()); 5 newmsg.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_NEWS); 6 if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) { // 圖片消息 7 System.out.println("==============這是圖片消息!"); 8 Article article=new Article(); 9 article.setDescription("這是圖文消息1"); //圖文消息的描述 10 article.setPicUrl("https://i.loli.net/2019/05/26/5cea3d137aa1469348.jpg"); //圖文消息圖片地址 11 article.setTitle("圖文消息1"); //圖文消息標題 12 article.setUrl("https://www.cnblogs.com/gede"); //圖文url鏈接 13 List<Article> list=new ArrayList<Article>(); 14 list.add(article); //這里發送的是單圖文,如果需要發送多圖文則在這里list中加入多個Article即可! 15 newmsg.setArticleCount(list.size()); 16 newmsg.setArticles(list); 17 return MessageUtil.newsMessageToXml(newmsg); 18 }
啟動項目,當我們發送任何圖片消息后我們可以看到我們的回復內容,如圖:
最后在這里分享一下自己一直使用的免費圖床網站。如果圖省事,直接進入這個網址,上傳圖片就行了,只不過服務器不在國內,有點慢。地址:https://sm.ms/。
可以自己折騰自己服務器的話,就用我這個,附件下載,直接丟在自己的服務器上就可以。這個的服務器是新浪的。