工欲善其事必先利其器!本篇內容主要講解如何將微信公眾平台定義的消息及消息相關的操作封裝成工具類,方面後期的使用。這裡需要明確的是消息其實是由用戶發給你的公眾帳號的,消息先被微信平台接收到,然後微信平台會將該消息轉給你在開發模式接口配置中指定的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; /** * 消息基類(普通用戶 -> 公眾帳號) * * @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; } }
請求消息之文本消息
package org.liufeng.course.message.req; /** * 文本消息 * * @author liufeng * @date 2013-05-19 */ 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; /** * 圖片消息 * * @author liufeng * @date 2013-05-19 */ 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; /** * 地理位置消息 * * @author liufeng * @date 2013-05-19 */ 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; /** * 鏈接消息 * * @author liufeng * @date 2013-05-19 */ 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; /** * 音頻消息 * * @author liufeng * @date 2013-05-19 */ 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; /** * 消息基類(公眾帳號 -> 普通用戶) * * @author liufeng * @date 2013-05-19 */ public class BaseMessage { // 接收方帳號(收到的OpenID) private String ToUserName; // 開發者微信號 private String FromUserName; // 消息創建時間 (整型) private long CreateTime; // 消息類型(text/music/news) private String MsgType; // 位0x0001被標志時,星標剛收到的消息 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; /** * 文本消息 * * @author liufeng * @date 2013-05-19 */ 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; /** * 音樂消息 * * @author liufeng * @date 2013-05-19 */ public class MusicMessage extends BaseMessage { // 音樂 private Music Music; public Music getMusic() { return Music; } public void setMusic(Music music) { Music = music; } }
響應消息之圖文消息
package org.liufeng.course.message.resp; import java.util.List; /** * 文本消息 * * @author liufeng * @date 2013-05-19 */ 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; } }
package org.liufeng.course.message.resp; /** * 圖文model * * @author liufeng * @date 2013-05-19 */ 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; } }
全部消息封裝完成後,Eclipse工程中關於消息部分的結構應該與下圖保持一致,如果不一致的(類名、屬性名稱不一致的)請檢查後調整一致,因為後面的章節還要介紹如何將微信開發中通用的類方法、與業務無關的工具類封裝打成jar包,以後再做微信項目只需要引入該jar包即可,這種工作做一次就可以了。
如何解析請求消息?
接下來解決請求消息的解析問題。微信服務器會將用戶的請求通過doPost方法發送給我們,讓我們再來回顧下上一章節已經寫好的doPost方法的定義:
/** * 處理微信服務器發來的消息 */ public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // TODO 消息的接收、處理、響應 }
如何將響應消息轉換成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 音樂消息對象 * @return xml */ public static String musicMessageToXml(MusicMessage musicMessage) { xstream.alias("xml", musicMessage.getClass()); return xstream.toXML(musicMessage); } /** * 圖文消息對象轉換成xml * * @param newsMessage 圖文消息對象 * @return xml */ 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 { 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; /** * 消息工具類 * * @author liufeng * @date 2013-05-19 */ 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 音樂消息對象 * @return xml */ public static String musicMessageToXml(MusicMessage musicMessage) { xstream.alias("xml", musicMessage.getClass()); return xstream.toXML(musicMessage); } /** * 圖文消息對象轉換成xml * * @param newsMessage 圖文消息對象 * @return xml */ 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 { writer.write(text); } } }; } }); }
OK,到這裡關於消息及消息處理工具的封裝就講到這裡,其實就是對請求消息/響應消息建立了與之對應的Java類、對xml消息進行解析、將響應消息的Java對象轉換成xml。下一篇講會介紹如何利用上面封裝好的工具識別用戶發送的消息類型,並做出正確的響應。