微信公衆賬號開發教程第4篇-消息及消息處理工具的封裝


工欲善其事必先利其器!本篇內容主要講解如何將微信公衆平臺定義的消息及消息相關的操做封裝成工具類,方面後期的使用。這裏須要明確的是消息實際上是由用戶發給你的公衆賬號的,消息先被微信平臺接收到,而後微信平臺會將該消息轉給你在開發模式接口配置中指定的URL地址。 php


微信公衆平臺消息接口 java

要接收微信平臺發送的消息,咱們須要先熟悉微信公衆平臺API中消息接口部分,點此進入,點擊後將進入到消息接口指南部分,以下圖所示: 編程

在上圖左側能夠看到微信公衆平臺目前開放的接口有三種:消息接口、通用接口和自定義菜單接口。通用接口和自定義菜單接口只有拿到內測資格才能調用,而內測資格的申請也已經關閉了,咱們只有期待未來某一天微信會對大衆用戶開放吧,因此沒有內測資格的用戶就不要再浪費時間在這兩個接口上,只須要用好消息接口就能夠了。 服務器


消息推送和消息回覆 微信

下面將主要介紹消息接口。對於消息的接收、響應咱們只須要關注上圖中的「4 消息推送」和「5 消息回覆」就足夠了。 微信開發

咱們先來了解接口中的「消息推送」指的是什麼,點擊「4 消息推送」,能夠看到接口中的「消息推送」指的是「當普通用戶向公衆賬號發消息時,微信服務器將POST該消息到填寫的URL上」,即這裏定義的是用戶可以發送哪些類型的消息、消息有哪些字段、消息被微信服務器以什麼方式轉發給咱們的公衆賬號後臺。 微信公衆平臺

消息推送中定義了咱們將會接收到的消息類型有5種:文本消息、圖片消息、地理位置消息、連接消息和事件推送,其實語音消息咱們也可以接收到的,只不過拿不到具體的語音文件而以(須要內測資格纔可以獲取語音文件)。 框架


接口中的「消息回覆」定義了咱們能回覆給用戶的消息類型、消息字段和消息格式,微信公衆平臺的接口指南中是這樣描述的: dom

上面說到咱們能回覆給用戶的消息有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;
	}
}
音樂消息中Music類的定義 
package org.liufeng.course.message.resp;

/**
 * 音樂model
 * 
 * @author liufeng
 * @date 2013-05-19
 */
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;
	}

}
響應消息之圖文消息
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;
	}
}
圖文消息中Article類的定義 
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 消息的接收、處理、響應  
}
doPost方法有兩個參數,request中封裝了請求相關的全部內容,能夠從request中取出微信服務器發來的消息;而經過response咱們能夠對接收到的消息進行響應,即發送消息。

那麼如何解析請求消息的問題也就轉化爲如何從request中獲得微信服務器發送給咱們的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 音樂消息對象
 * @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。下一篇講會介紹如何利用上面封裝好的工具識別用戶發送的消息類型,並作出正確的響應。


若是以爲文章對你有所幫助,請留言支持或關注微信公衆賬號xiaoqrobot支持柳峯哦!

相關文章
相關標籤/搜索