工欲善其事必先利其器!本篇内容主要解说怎样将微信公众平台定义的消息及消息相关的操作封装成工具类,方面后期的使用。这里须要明白的是消息事实上是由用户发给你的公众帐号的,消息先被微信平台接收到,然后微信平台会将该消息转给你在开发模式接口配置中指定的URL地址。
微信公众平台消息接口
在上图左側能够看到微信公众平台眼下开放的接口有三种:消息接口、通用接口和自己定义菜单接口。通用接口和自己定义菜单接口唯独拿到内測资格才干调用,而内測资格的申请也已经关闭了,我们唯独期待将来某一天微信会对大众用户开放吧,所以沒有内測资格的用户就不要再浪费时间在这两个接口上,仅仅须要用好消息接口就能够了。
消息推送和消息回复
以下将主要介绍消息接口。对于消息的接收、响应我们仅仅须要关注上图中的“4 消息推送”和“5 消息回复”就足够了。
我们先来了解接口中的“消息推送”指的是什么,点击“4 消息推送”,能够看到接口中的“消息推送”指的是“当普通用户向公众帐号发消息时,微信server将POST该消息到填写的URL上”,即这里定义的是用户能够发送哪些类型的消息、消息有哪些字段、消息被微信server以什么方式转发给我们的公众帐号后台。
消息推送中定义了我们将会接收到的消息类型有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;
/**
* 消息基类(普通用户 -> 公众帐号)
*
* @author liufeng
* @date 2013-05-19
*/
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;
}
请求消息之文本消息
* 文本消息
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;
响应消息的基类
相同,把消息回复中定义的全部消息都有的字段提取出来,封装成一个基类,这些公有的字段包含:ToUserName(接收方帐号,用户的OPEN_ID)、FromUserName(开发人员的微信号)、CreateTime(消息的创建时间)、MsgType(消息类型)、FuncFlag(消息的星标标识),封装后基类org.liufeng.course.message.resp.BaseMessage的代码例如以下:
package org.liufeng.course.message.resp;
* 消息基类(公众帐号 -> 普通用户)
// 接收方帐号(收到的OpenID)
// 消息类型(text/music/news)
// 位0x0001被标志时,星标刚收到的消息
private int FuncFlag;
public int getFuncFlag() {
return FuncFlag;
public void setFuncFlag(int funcFlag) {
FuncFlag = funcFlag;
响应消息之文本消息
// 回复的消息内容
响应消息之音乐消息
* 音乐消息
public class MusicMessage extends BaseMessage {
// 音乐
private Music Music;
public Music getMusic() {
return Music;
public void setMusic(Music music) {
Music = music;
音乐消息中Music类的定义
* 音乐model
public class Music {
// 音乐名称
// 音乐描写叙述
// 音乐链接
private String MusicUrl;
// 高质量音乐链接,WIFI环境优先使用该链接播放音乐
private String HQMusicUrl;
public String getMusicUrl() {
return MusicUrl;
public void setMusicUrl(String musicUrl) {
MusicUrl = musicUrl;
public String getHQMusicUrl() {
return HQMusicUrl;
public void setHQMusicUrl(String musicUrl) {
HQMusicUrl = musicUrl;
响应消息之图文消息
import java.util.List;
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类的定义
* 图文model
public class Article {
// 图文消息名称
// 图文消息描写叙述
// 图片链接,支持JPG、PNG格式,较好的效果为大图640*320,小图80*80,限制图片链接的域名须要与开发人员填写的基本资料中的Url一致
// 点击图文消息跳转链接
return null == Description ? "" : Description;
return null == PicUrl ? "" : PicUrl;
return null == Url ? "" : Url;
全部消息封装完成后,Eclipse工程中关于消息部分的结构应该与下图保持一致,假设不一致的(类名、属性名称不一致的)请检查后调整一致,因为后面的章节还要介绍怎样将微信开发中通用的类方法、与业务无关的工具类封装打成jar包,以后再做微信项目仅仅须要引入该jar包就可以,这样的工作做一次就能够了。
怎样解析请求消息?
接下来解决请求消息的解析问题。微信server会将用户的请求通过doPost方法发送给我们,让我们再来回忆下上一章节已经写好的doPost方法的定义:
/**
* 处理微信server发来的消息
*/
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO 消息的接收、处理、响应
}
doPost方法有两个參数,request中封装了请求相关的全部内容,能够从request中取出微信server发来的消息;而通过response我们能够对接收到的消息进行响应,即发送消息。
那么怎样解析请求消息的问题也就转化为怎样从request中得到微信server发送给我们的xml格式的消息了。这里我们借助于开源框架dom4j去解析xml(这里使用的是dom4j-1.6.1.jar),然后将解析得到的结果存入HashMap,解析请求消息的方法例如以下:
* 解析微信发来的请求(XML)
* @param request
* @return
* @throws Exception
@SuppressWarnings("unchecked")
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;
怎样将响应消息转换成xml返回?
我们先前已经将响应消息封装成了Java类,方便我们在代码中使用。那么,请求接收成功、处理完成后,该怎样将消息返回呢?这里就涉及到怎样将响应消息转换成xml返回的问题,这里我们将採用开源框架xstream来实现Java类到xml的转换(这里使用的是xstream-1.3.1.jar),代码例如以下:
* 文本消息对象转换成xml
* @param textMessage 文本消息对象
* @return xml
public static String textMessageToXml(TextMessage textMessage) {
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
* 音乐消息对象转换成xml
* @param musicMessage 音乐消息对象
public static String musicMessageToXml(MusicMessage musicMessage) {
xstream.alias("xml", musicMessage.getClass());
return xstream.toXML(musicMessage);
* 图文消息对象转换成xml
* @param newsMessage 图文消息对象
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 {
}
};
});
说明:因为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.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";
* 事件类型:subscribe(订阅)
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
* 事件类型:unsubscribe(取消订阅)
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
* 事件类型:CLICK(自己定义菜单点击事件)
public static final String EVENT_TYPE_CLICK = "CLICK";
* 解析微信发来的请求(XML)
*
* @param request
* @return
* @throws Exception
@SuppressWarnings("unchecked")
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;
* 文本消息对象转换成xml
* @param textMessage 文本消息对象
* @return xml
public static String textMessageToXml(TextMessage textMessage) {
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
* 音乐消息对象转换成xml
* @param musicMessage 音乐消息对象
public static String musicMessageToXml(MusicMessage musicMessage) {
xstream.alias("xml", musicMessage.getClass());
return xstream.toXML(musicMessage);
* 图文消息对象转换成xml
* @param newsMessage 图文消息对象
public static String newsMessageToXml(NewsMessage newsMessage) {
xstream.alias("xml", newsMessage.getClass());
xstream.alias("item", new Article().getClass());
return xstream.toXML(newsMessage);
* 扩展xstream,使其支持CDATA块
* @date 2013-05-19
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 {
}
};
}
});
OK,到这里关于消息及消息处理工具的封装就说到这里,事实上就是对请求消息/响应消息建立了与之相应的Java类、对xml消息进行解析、将响应消息的Java对象转换成xml。下一篇讲会介绍怎样利用上面封装好的工具识别用户发送的消息类型,并做出正确的响应。