上篇我们说到回复消息可以根据是否需要上传文件到微信服务器可划分为【普通消息】和【多媒体消息】,这里我们来讲述普通消息的回复实现,在消息回复中存在一个关键字段【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/。
可以自己折腾自己服务器的话,就用我这个,附件下载,直接丢在自己的服务器上就可以。这个的服务器是新浪的。