工欲善其事必先利其器!本篇内容主要解說怎樣将微信公衆平台定義的消息及消息相關的操作封裝成工具類,方面後期的使用。這裡須要明白的是消息事實上是由使用者發給你的公衆帳号的,消息先被微信平台接收到,然後微信平台會将該消息轉給你在開發模式接口配置中指定的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。下一篇講會介紹怎樣利用上面封裝好的工具識别使用者發送的消息類型,并做出正确的響應。