【微信公众号开发】【4】接收消息和消息回复
前言:
1,消息推送—— 当普通用户向公众帐号发消息时,微信服务器将POST该消息到填写的URL上(发消息=操作,包括扫描二维码,点击菜单等;URL:coreServlet)
2,消息回复——对每一个POST请求,开发者可以在响应包中返回特定XML结构,来对该消息进行响应(现支持回复文本、图片、图文、语音、视频、音乐)
3,消息回复——假如服务器无法保证在五秒内处理回复,则必须回复“success”或者“”(空串),否则微信后台会发起三次重试。
4,主动发送消息——模板消息(该章节并未提及)
正文:
1,接收消息的封装(message.req)
接收消息之基类
public class BaseMessage { private String ToUserName; // 开发者微信号 private String FromUserName; // 发送方帐号(一个OpenID) private long CreateTime; // 消息创建时间 (整型) private String MsgType; // 消息类型(text/image/location/link) private long MsgId; // 消息id,64位整型 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; } }
接收消息之文本消息
public class TextMessage extends BaseMessage { // 消息内容 private String Content; public String getContent() { return Content; } public void setContent(String content) { Content = content; } }
接收消息之图片消息
public class ImageMessage extends BaseMessage { // 图片链接 private String PicUrl; public String getPicUrl() { return PicUrl; } public void setPicUrl(String picUrl) { PicUrl = picUrl; } }
接收消息之地理位置消息
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; } }
接收消息之链接消息
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; } }
接收消息之语音消息
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; } }
2,响应消息的封装(message.resp)
响应消息之基类
public class BaseMessage { private String ToUserName; // 接收方帐号(收到的OpenID) private String FromUserName; // 开发者微信号 private long CreateTime; // 消息创建时间 (整型) private String MsgType; // 消息类型(text/music/news) private int FuncFlag; // 位0x0001被标志时,星标刚收到的消息 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; } }
响应消息之文本消息
public class TextMessage extends BaseMessage { // 回复的消息内容 private String Content; public String getContent() { return Content; } public void setContent(String content) { Content = content; } }
响应消息之音乐消息
public class MusicMessage extends BaseMessage { // 音乐 private Music Music; public Music getMusic() { return Music; } public void setMusic(Music music) { Music = music; } }
音乐消息中Music类的定义
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; } }
响应消息之图文消息
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; } }
图文消息中Article类的定义
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; } }
3,解析接收到的消息及将响应消息转换成xml返回
前言中说的URL
@ResponseBody @RequestMapping(value = "coreServlet", method = RequestMethod.POST) public void coreServletPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //获取微信POST过来的内容。消息为xml格式 // 将请求、响应的编码均设置为UTF-8(防止中文乱码) request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); //从请求中读取整个post数据 InputStream inputStream = request.getInputStream(); String postData = IOUtils.toString(inputStream, "UTF-8"); logger.info("postData = " + postData); // 调用核心业务类接收消息、处理消息 Message message = CoreService.processRequest(postData); String respMessage = message.getRespMessage(); // 响应消息 PrintWriter out = response.getWriter(); out.print(respMessage); out.close(); }
调用核心业务类接收消息、处理消息 CoreService.processRequest
private static String UPDATE_MENU = "menu";//更新自定义菜单的口令 public static Message processRequest(String msg) { Message message = new Message(); String param1 = "";//二维码带的参数 try { Map<String, String> requestMap = MessageUtil.parseXml(msg); // xml请求解析 String fromUserName = requestMap.get("FromUserName"); // 发送方帐号 String toUserName = requestMap.get("ToUserName"); // 接收方帐号 String msgType = requestMap.get("MsgType"); // 消息类型 String eventKey = requestMap.get("EventKey"); // 事件KEY值,与创建自定义菜单时指定的KEY值对应 String respContent = "请求处理中..."; // 默认返回的文本消息内容 // 回复文本消息 TextMessage textMessage = new TextMessage(); textMessage.setToUserName(fromUserName); textMessage.setFromUserName(toUserName); textMessage.setCreateTime(new Date().getTime()); textMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT); textMessage.setFuncFlag(0); // 创建图文消息 NewsMessage newsMessage = new NewsMessage(); newsMessage.setToUserName(fromUserName); newsMessage.setFromUserName(toUserName); newsMessage.setCreateTime(new Date().getTime()); newsMessage.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_NEWS); newsMessage.setFuncFlag(0); // 文本消息 if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) { respContent = "您发送的是文本消息!"; if((UPDATE_MENU).equals(requestMap.get("Content"))){ respContent = MenuManager.menuManager();//更新自定义菜单 } // 回复包含“定位跑” else if ( requestMap.get("Content").contains("定位跑")) { respContent = "您的愿望我们已经收到,请关注8月8日将发布的中奖名单信息"; } } // 图片消息 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) { respContent = "您发送的是图片消息!"; } // 地理位置消息 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) { respContent = "您发送的是地理位置消息!"; } // 链接消息 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) { respContent = "您发送的是链接消息!"; } // 音频消息 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) { } // 事件推送 else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) { // 事件类型 String eventType = requestMap.get("Event"); // 订阅 if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) { respContent = "欢迎关注爱关节网"; if(!StringUtils.isEmpty(eventKey)){ param1 = eventKey.substring(eventKey.indexOf("_")+1); } } // 扫描 if (eventType.equals(MessageUtil.EVENT_TYPE_SCAN)) { respContent = ""; if(!StringUtils.isEmpty(eventKey)){ param1 = eventKey; } } // 取消订阅 else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) { // TODO 取消订阅后用户再收不到公众号发送的消息,因此不需要回复消息 } // 自定义菜单点击事件 else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) { List<Article> articleList = new ArrayList<Article>(); if (eventKey.equals("13")) { respContent = "内容建设中,敬请期待"; } else if (eventKey.equals("14")) { //单图文消息 Article article = new Article(); article.setTitle("灵动中国|爱关节·不等待 广场舞公益活动川渝开赛"); article.setDescription("灵动中国|爱关节·不等待 广场舞公益活动川渝开赛"); article.setPicUrl(Config.domain() + "/UCenter/wx_marathon/img/zuimeiguangchangwu.png"); article.setUrl("http://mp.weixin.qq.com/s/6_-hwdJ6fFV58UD8SLAtcg"); articleList.add(article); // 设置图文消息个数 newsMessage.setArticleCount(articleList.size()); // 设置图文消息包含的图文集合 newsMessage.setArticles(articleList); // 将图文消息对象转换成xml字符串 respMessage = MessageUtil.newsMessageToXml(newsMessage); message.setParam1(""); message.setRespMessage(respMessage); message.setFromUserName(fromUserName); message.setToUserName(toUserName); return message; } } } textMessage.setContent(respContent); respMessage = MessageUtil.textMessageToXml(textMessage); message.setParam1(param1); message.setRespMessage(respMessage); message.setFromUserName(fromUserName); message.setToUserName(toUserName); } catch (Exception e) { e.printStackTrace(); } return message; }
MessageUtil
public class MessageUtil { public static final String RESP_MESSAGE_TYPE_TEXT = "text"; //返回消息类型:文本 public static final String RESP_MESSAGE_TYPE_MUSIC = "music"; //返回消息类型:音乐 public static final String RESP_MESSAGE_TYPE_NEWS = "news"; //返回消息类型:图文 public static final String REQ_MESSAGE_TYPE_TEXT = "text"; //请求消息类型:文本 public static final String REQ_MESSAGE_TYPE_IMAGE = "image"; //请求消息类型:图片 public static final String REQ_MESSAGE_TYPE_LINK = "link"; //请求消息类型:链接 public static final String REQ_MESSAGE_TYPE_LOCATION = "location"; //请求消息类型:地理位置 public static final String REQ_MESSAGE_TYPE_VOICE = "voice"; //请求消息类型:音频 public static final String REQ_MESSAGE_TYPE_EVENT = "event"; //请求消息类型:推送 public static final String EVENT_TYPE_SUBSCRIBE = "subscribe"; //事件类型:subscribe(订阅) public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe"; //事件类型:unsubscribe(取消订阅) public static final String EVENT_TYPE_CLICK = "CLICK"; //事件类型:CLICK(自定义菜单点击事件) public static final String EVENT_TYPE_SCAN = "SCAN"; //事件类型:SCAN(扫描) //解析微信发来的请求(XML) @SuppressWarnings("unchecked") public static Map<String, String> parseXml(String msg) throws IOException { // 将解析结果存储在HashMap中 Map<String, String> map = new HashMap<String, String>(); //读取输入流 SAXReader reader = new SAXReader(); try { InputStream inputStream = new ByteArrayInputStream(msg.getBytes("UTF-8")); Document document = reader.read(inputStream); Element root = document.getRootElement(); // 得到xml根元素 List<Element> elementList = root.elements(); // 得到根元素的所有子节点 for (Element e : elementList) map.put(e.getName(), e.getText()); inputStream.close(); inputStream = null; } catch (DocumentException e) { logger.error("解析失败",e); } return map; } //文本消息对象转换成xml public static String textMessageToXml(TextMessage textMessage) { xstream.alias("xml", textMessage.getClass()); return xstream.toXML(textMessage); } //音乐消息对象转换成xml public static String musicMessageToXml(MusicMessage musicMessage) { xstream.alias("xml", musicMessage.getClass()); return xstream.toXML(musicMessage); } //图文消息对象转换成xml public static String newsMessageToXml(NewsMessage newsMessage) { xstream.alias("xml", newsMessage.getClass()); xstream.alias("item", new Article().getClass()); return xstream.toXML(newsMessage); } //扩展xstream,使其支持CDATA块 private static XStream xstream = new XStream(new XppDriver() { public HierarchicalStreamWriter createWriter(Writer out) { return new PrettyPrintWriter(out) { // 对所有xml节点的转换都增加CDATA标记 boolean cdata = true; @SuppressWarnings("unchecked") public void startNode(String name, Class clazz) { super.startNode(name, clazz); } protected void writeText(QuickWriter writer, String text) { if (cdata) { writer.write("<![CDATA["); writer.write(text); writer.write("]]>"); } else { writer.write(text); } } }; } }); //将java对象转换为json对象 public static String Object2Json(Object obj){ JSONObject json = JSONObject.fromObject(obj); // 将json对象转换为字符串 String str = json.toString(); return str; } }