工欲善其事必先利其器!本篇内容主要讲解如何将微信公众平台定义的消息及消息相关的操作封装成工具类,方面后期的使用。这里需要明确的是消息其实是由用户发给你的公众帐号的,消息先被微信平台接收到,然后微信平台会将该消息转给你在开发模式接口配置中指定的URL地址。
微信公众平台消息接口
要接收微信平台发送的消息,我们需要先熟悉微信公众平台API中消息接口部分,点此进入,点击后将进入到消息接口指南部分,如下图所示:
在上图左侧可以看到微信公众平台目前开放的接口有三种:消息接口、通用接口和自定义菜单接口。通用接口和自定义菜单接口只有拿到内测资格才能调用,而内测资格的申请也已经关闭了,我们只有期待将来某一天微信会对大众用户开放吧,所以没有内测资格的用户就不要再浪费时间在这两个接口上,只需要用好消息接口就可以了。
消息推送和消息回复
下面将主要介绍消息接口。对于消息的接收、响应我们只需要关注上图中的“4 消息推送”和“5 消息回复”就足够了。
我们先来了解接口中的“消息推送”指的是什么,点击“4 消息推送”,可以看到接口中的“消息推送”指的是“当普通用户向公众帐号发消息时,微信服务器将POST该消息到填写的URL上”,即这里定义的是用户能够发送哪些类型的消息、消息有哪些字段、消息被微信服务器以什么方式转发给我们的公众帐号后台。
消息推送中定义了我们将会接收到的消息类型有5种:文本消息、图片消息、地理位置消息、链接消息和事件推送,其实语音消息我们也能够接收到的,只不过拿不到具体的语音文件而以(需要内测资格才能够获取语音文件)。
接口中的“消息回复”定义了我们能回复给用户的消息类型、消息字段和消息格式,微信公众平台的接口指南中是这样描述的:
上面说到我们能回复给用户的消息有5种,但目前在开发模式下能回复的消息只有3种:文本消息、音乐消息和图文消息,而语音消息和视频消息目前只能在编辑模式下使用。
消息的封装
接下来要做的就是将消息推送(请求)、消息回复(响应)中定义的消息进行封装,建立与之对应的Java类(Java是一门面向对象的编程语言,封装后使用起来更方便),下面的请求消息是指消息推送中定义的消息,响应消息指消息回复中定义的消息。
请求消息的基类
把消息推送中定义的所有消息都有的字段提取出来,封装成一个基类,这些公有的字段包括:ToUserName(开发者微信号)、FromUserName(发送方帐号,OPEN_ID)、CreateTime(消息的创建时间)、MsgType(消息类型)、MsgId(消息ID),封装后基类org.liufeng.course.message.req.BaseMessage的代码如下:
- package org.liufeng.course.message.req;
-
- public class BaseMessage {
-
- private String ToUserName;
-
- private String FromUserName;
-
- private long CreateTime;
-
- private String MsgType;
-
- 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;
- }
- }
请求消息之文本消息
- package org.liufeng.course.message.req;
-
- public class TextMessage extends BaseMessage {
-
- private String Content;
-
- public String getContent() {
- return Content;
- }
-
- public void setContent(String content) {
- Content = content;
- }
- }
请求消息之图片消息
- package org.liufeng.course.message.req;
-
- public class ImageMessage extends BaseMessage {
-
- private String PicUrl;
-
- public String getPicUrl() {
- return PicUrl;
- }
-
- public void setPicUrl(String picUrl) {
- PicUrl = picUrl;
- }
- }
请求消息之地理位置消息
- package org.liufeng.course.message.req;
-
- 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;
- }
- }
请求消息之链接消息
- package org.liufeng.course.message.req;
-
- 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;
- }
- }
请求消息之语音消息
- package org.liufeng.course.message.req;
-
- public class VoiceMessage extends BaseMessage {
-
- 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;
- }
- }
响应消息的基类
同样,把消息回复中定义的所有消息都有的字段提取出来,封装成一个基类,这些公有的字段包括:ToUserName(接收方帐号,用户的OPEN_ID)、FromUserName(开发者的微信号)、CreateTime(消息的创建时间)、MsgType(消息类型)、FuncFlag(消息的星标标识),封装后基类org.liufeng.course.message.resp.BaseMessage的代码如下:
- package org.liufeng.course.message.resp;
-
- public class BaseMessage {
-
- private String ToUserName;
-
- private String FromUserName;
-
- private long CreateTime;
-
- private String MsgType;
-
- 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;
- }
- }
响应消息之文本消息
- package org.liufeng.course.message.resp;
-
- public class TextMessage extends BaseMessage {
-
- private String Content;
-
- public String getContent() {
- return Content;
- }
-
- public void setContent(String content) {
- Content = content;
- }
- }
响应消息之音乐消息
- package org.liufeng.course.message.resp;
-
- public class MusicMessage extends BaseMessage {
-
- private Music Music;
-
- public Music getMusic() {
- return Music;
- }
-
- public void setMusic(Music music) {
- Music = music;
- }
- }
音乐消息中Music类的定义
- package org.liufeng.course.message.resp;
-
- public class Music {
-
- private String Title;
-
- private String Description;
-
- private String MusicUrl;
-
- 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;
- }
-
- }
响应消息之图文消息
- package org.liufeng.course.message.resp;
-
- import java.util.List;
-
- public class NewsMessage extends BaseMessage {
-
- private int ArticleCount;
-
- 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类的定义
- package org.liufeng.course.message.resp;
-
- public class Article {
-
- private String Title;
-
- private String Description;
-
- 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;
- }
-
- }
全部消息封装完成后,Eclipse工程中关于消息部分的结构应该与下图保持一致,如果不一致的(类名、属性名称不一致的)请检查后调整一致,因为后面的章节还要介绍如何将微信开发中通用的类方法、与业务无关的工具类封装打成jar包,以后再做微信项目只需要引入该jar包即可,这种工作做一次就可以了。
如何解析请求消息?
接下来解决请求消息的解析问题。微信服务器会将用户的请求通过doPost方法发送给我们,让我们再来回顾下上一章节已经写好的doPost方法的定义:
- public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
-
- }
doPost方法有两个参数,request中封装了请求相关的所有内容,可以从request中取出微信服务器发来的消息;而通过response我们可以对接收到的消息进行响应,即发送消息。
那么如何解析请求消息的问题也就转化为如何从request中得到微信服务器发送给我们的xml格式的消息了。这里我们借助于开源框架dom4j去解析xml(这里使用的是dom4j-1.6.1.jar),然后将解析得到的结果存入HashMap,解析请求消息的方法如下:
- @SuppressWarnings("unchecked")
- public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
-
- Map<String, String> map = new HashMap<String, String>();
-
-
- InputStream inputStream = request.getInputStream();
-
- SAXReader reader = new SAXReader();
- Document document = reader.read(inputStream);
-
- 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;
- }
如何将响应消息转换成xml返回?
我们先前已经将响应消息封装成了Java类,方便我们在代码中使用。那么,请求接收成功、处理完成后,该如何将消息返回呢?这里就涉及到如何将响应消息转换成xml返回的问题,这里我们将采用开源框架xstream来实现Java类到xml的转换(这里使用的是xstream-1.3.1.jar),代码如下:
- public static String textMessageToXml(TextMessage textMessage) {
- xstream.alias("xml", textMessage.getClass());
- return xstream.toXML(textMessage);
- }
-
- public static String musicMessageToXml(MusicMessage musicMessage) {
- xstream.alias("xml", musicMessage.getClass());
- return xstream.toXML(musicMessage);
- }
-
- public static String newsMessageToXml(NewsMessage newsMessage) {
- xstream.alias("xml", newsMessage.getClass());
- xstream.alias("item", new Article().getClass());
- return xstream.toXML(newsMessage);
- }
-
- private static XStream xstream = new XStream(new XppDriver() {
- public HierarchicalStreamWriter createWriter(Writer out) {
- return new PrettyPrintWriter(out) {
-
- 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);
- }
- }
- };
- }
- });
说明:由于xstream框架本身并不支持CDATA块的生成,40~62行代码是对xtream做了扩展,使其支持在生成xml各元素值时添加CDATA块。
消息处理工具的封装
知道怎么解析请求消息,也知道如何将响应消息转化成xml了,接下来就是将消息相关的处理方法全部封装到工具类MessageUtil中,该类的完整代码如下:
- package org.liufeng.course.util;
-
- import java.io.InputStream;
- import java.io.Writer;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
-
- import javax.servlet.http.HttpServletRequest;
-
- import org.dom4j.Document;
- import org.dom4j.Element;
- import org.dom4j.io.SAXReader;
- import org.liufeng.course.message.resp.Article;
- import org.liufeng.course.message.resp.MusicMessage;
- import org.liufeng.course.message.resp.NewsMessage;
- import org.liufeng.course.message.resp.TextMessage;
-
- import com.thoughtworks.xstream.XStream;
- import com.thoughtworks.xstream.core.util.QuickWriter;
- import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
- import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
- import com.thoughtworks.xstream.io.xml.XppDriver;
-
- 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";
-
-
- public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
-
-
- public static final String EVENT_TYPE_CLICK = "CLICK";
-
-
- @SuppressWarnings("unchecked")
- public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
-
- Map<String, String> map = new HashMap<String, String>();
-
-
- InputStream inputStream = request.getInputStream();
-
- SAXReader reader = new SAXReader();
- Document document = reader.read(inputStream);
-
- 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;
- }
-
-
- public static String textMessageToXml(TextMessage textMessage) {
- xstream.alias("xml", textMessage.getClass());
- return xstream.toXML(textMessage);
- }
-
-
- public static String musicMessageToXml(MusicMessage musicMessage) {
- xstream.alias("xml", musicMessage.getClass());
- return xstream.toXML(musicMessage);
- }
-
-
- public static String newsMessageToXml(NewsMessage newsMessage) {
- xstream.alias("xml", newsMessage.getClass());
- xstream.alias("item", new Article().getClass());
- return xstream.toXML(newsMessage);
- }
-
-
- private static XStream xstream = new XStream(new XppDriver() {
- public HierarchicalStreamWriter createWriter(Writer out) {
- return new PrettyPrintWriter(out) {
-
- 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);
- }
- }
- };
- }
- });
- }
OK,到这里关于消息及消息处理工具的封装就讲到这里,其实就是对请求消息/响应消息建立了与之对应的Java类、对xml消息进行解析、将响应消息的Java对象转换成xml。下一篇讲会介绍如何利用上面封装好的工具识别用户发送的消息类型,并做出正确的响应。
如果觉得文章对你有所帮助,请留言支持或关注微信公众帐号xiaoqrobot支持柳峰哦!