https://www.iteye.com/blog/tuposky-2017429
前面兩章已經介紹了如何接入微信公眾平台,這一章說說消息的接收和發送
可以先了解公眾平台的消息api接口(接收消息,發送消息)
http://mp.weixin.qq.com/wiki/index.php
接收消息
當普通微信用戶向公眾賬號發消息時,微信服務器將POST消息的XML數據包到開發者填寫的URL上。
http://mp.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%99%AE%E9%80%9A%E6%B6%88%E6%81%AF
接收的消息類型有6種,分別為:
可以根據官方的api提供的字段建立對應的實體類
如:文本消息
有很多屬性是所有消息類型都需要的,可以把這些信息提取出來建立一個基類
- package com.ifp.weixin.entity.Message.req;
- /**
- * 消息基類(用戶 -> 公眾帳號)
- *
- */
- 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 com.ifp.weixin.entity.Message.req;
- /**
- * 文本消息
- */
- public class TextMessage extends BaseMessage {
- /**
- * 回復的消息內容
- */
- private String Content;
- public String getContent() {
- return Content;
- }
- public void setContent(String content) {
- Content = content;
- }
- }
接收的圖片消息
- package com.ifp.weixin.entity.Message.req;
- public class ImageMessage extends BaseMessage{
- private String picUrl;
- public String getPicUrl() {
- return picUrl;
- }
- public void setPicUrl(String picUrl) {
- this.picUrl = picUrl;
- }
- }
接收的鏈接消息
- package com.ifp.weixin.entity.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 com.ifp.weixin.entity.Message.req;
- /**
- * 語音消息
- *
- * @author Caspar
- *
- */
- 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;
- }
- }
接收的地理位置消息
- package com.ifp.weixin.entity.Message.req;
- /**
- * 位置消息
- *
- * @author caspar
- *
- */
- 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;
- }
- }
發送被動響應消息
對於每一個POST請求,開發者在響應包(Get)中返回特定XML結構,對該消息進行響應(現支持回復文本、圖片、圖文、語音、視頻、音樂)。請注意,回復圖片等多媒體消息時需要預先上傳多媒體文件到微信服務器,只支持認證服務號。
同樣,建立響應消息的對應實體類
也把公共的屬性提取出來,封裝成基類
響應消息的基類
- package com.ifp.weixin.entity.Message.resp;
- /**
- * 消息基類(公眾帳號 -> 用戶)
- */
- public class BaseMessage {
- /**
- * 接收方帳號(收到的OpenID)
- */
- private String ToUserName;
- /**
- * 開發者微信號
- */
- private String FromUserName;
- /**
- * 消息創建時間 (整型)
- */
- private long CreateTime;
- /**
- * 消息類型
- */
- 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 com.ifp.weixin.entity.Message.resp;
- /**
- * 文本消息
- */
- public class TextMessage extends BaseMessage {
- /**
- * 回復的消息內容
- */
- private String Content;
- public String getContent() {
- return Content;
- }
- public void setContent(String content) {
- Content = content;
- }
- }
響應圖文消息
- package com.ifp.weixin.entity.Message.resp;
- import java.util.List;
- /**
- * 多圖文消息,
- * 單圖文的時候 Articles 只放一個就行了
- * @author Caspar.chen
- */
- 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 com.ifp.weixin.entity.Message.resp;
- /**
- * 圖文消息
- *
- */
- public class Article {
- /**
- * 圖文消息名稱
- */
- private String Title;
- /**
- * 圖文消息描述
- */
- private String Description;
- /**
- * 圖片鏈接,支持JPG、PNG格式,<br>
- * 較好的效果為大圖640*320,小圖80*80
- */
- 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;
- }
- }
響應音樂消息
- package com.ifp.weixin.entity.Message.resp;
- /**
- * 音樂消息
- */
- public class MusicMessage extends BaseMessage {
- /**
- * 音樂
- */
- private Music Music;
- public Music getMusic() {
- return Music;
- }
- public void setMusic(Music music) {
- Music = music;
- }
- }
音樂消息的定義
- package com.ifp.weixin.entity.Message.resp;
- /**
- * 音樂消息
- */
- 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;
- }
- }
構建好之后的項目結構圖為
到這里,請求消息和響應消息的實體類都定義好了
解析請求消息
用戶向微信公眾平台發送消息后,微信公眾平台會通過post請求發送給我們。
上一章中WeixinController 類的post方法我們空着
現在我們要在這里處理用戶請求了。
因為微信的發送和接收都是用xml格式的,所以我們需要處理請求過來的xml格式。
發送的時候也需要轉化成xml格式再發送給微信,所以封裝了消息處理的工具類,用到dome4j和xstream兩個jar包
- package com.ifp.weixin.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 com.ifp.weixin.entity.Message.resp.Article;
- import com.ifp.weixin.entity.Message.resp.MusicMessage;
- import com.ifp.weixin.entity.Message.resp.NewsMessage;
- import com.ifp.weixin.entity.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 {
- /**
- * 解析微信發來的請求(XML)
- *
- * @param request
- * @return
- * @throws Exception
- */
- 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();
- // 得到根元素的所有子節點
- @SuppressWarnings("unchecked")
- 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塊
- *
- */
- private static XStream xstream = new XStream(new XppDriver() {
- public HierarchicalStreamWriter createWriter(Writer out) {
- return new PrettyPrintWriter(out) {
- // 對所有xml節點的轉換都增加CDATA標記
- boolean cdata = true;
- protected void writeText(QuickWriter writer, String text) {
- if (cdata) {
- writer.write("<![CDATA[");
- writer.write(text);
- writer.write("]]>");
- } else {
- writer.write(text);
- }
- }
- };
- }
- });
- }
接下來在處理業務邏輯,建立一個接收並響應消息的service類,並針對用戶輸入的1或2回復不同的信息給用戶
- package com.ifp.weixin.biz.core.impl;
- import java.util.Date;
- import java.util.Map;
- import javax.servlet.http.HttpServletRequest;
- import org.apache.log4j.Logger;
- import org.springframework.stereotype.Service;
- import com.ifp.weixin.biz.core.CoreService;
- import com.ifp.weixin.constant.Constant;
- import com.ifp.weixin.entity.Message.resp.TextMessage;
- import com.ifp.weixin.util.MessageUtil;
- @Service("coreService")
- public class CoreServiceImpl implements CoreService{
- public static Logger log = Logger.getLogger(CoreServiceImpl.class);
- @Override
- public String processRequest(HttpServletRequest request) {
- String respMessage = null;
- try {
- // xml請求解析
- Map<String, String> requestMap = MessageUtil.parseXml(request);
- // 發送方帳號(open_id)
- String fromUserName = requestMap.get("FromUserName");
- // 公眾帳號
- String toUserName = requestMap.get("ToUserName");
- // 消息類型
- String msgType = requestMap.get("MsgType");
- TextMessage textMessage = new TextMessage();
- textMessage.setToUserName(fromUserName);
- textMessage.setFromUserName(toUserName);
- textMessage.setCreateTime(new Date().getTime());
- textMessage.setMsgType(Constant.RESP_MESSAGE_TYPE_TEXT);
- textMessage.setFuncFlag(0);
- // 文本消息
- if (msgType.equals(Constant.REQ_MESSAGE_TYPE_TEXT)) {
- // 接收用戶發送的文本消息內容
- String content = requestMap.get("Content");
- if ("1".equals(content)) {
- textMessage.setContent("1是很好的");
- // 將文本消息對象轉換成xml字符串
- respMessage = MessageUtil.textMessageToXml(textMessage);
- }else if ("2".equals(content)) {
- textMessage.setContent("我不是2貨");
- // 將文本消息對象轉換成xml字符串
- respMessage = MessageUtil.textMessageToXml(textMessage);
- }
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- return respMessage;
- }
- }
接下來在controller里面的post方法里面調用即可
WeixinController類的完整代碼
- package com.ifp.weixin.controller;
- import java.io.IOException;
- import java.io.PrintWriter;
- import java.io.UnsupportedEncodingException;
- import javax.annotation.Resource;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.springframework.stereotype.Controller;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestMethod;
- import com.ifp.weixin.biz.core.CoreService;
- import com.ifp.weixin.util.SignUtil;
- @Controller
- @RequestMapping("/weixinCore")
- public class WeixinController {
- @Resource(name="coreService")
- private CoreService coreService;
- @RequestMapping(method = RequestMethod.GET)
- public void get(HttpServletRequest request, HttpServletResponse response) {
- // 微信加密簽名,signature結合了開發者填寫的token參數和請求中的timestamp參數、nonce參數。
- String signature = request.getParameter("signature");
- // 時間戳
- String timestamp = request.getParameter("timestamp");
- // 隨機數
- String nonce = request.getParameter("nonce");
- // 隨機字符串
- String echostr = request.getParameter("echostr");
- PrintWriter out = null;
- try {
- out = response.getWriter();
- // 通過檢驗signature對請求進行校驗,若校驗成功則原樣返回echostr,否則接入失敗
- if (SignUtil.checkSignature(signature, timestamp, nonce)) {
- out.print(echostr);
- }
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- out.close();
- out = null;
- }
- }
- @RequestMapping(method = RequestMethod.POST)
- public void post(HttpServletRequest request, HttpServletResponse response) {
- try {
- request.setCharacterEncoding("UTF-8");
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- response.setCharacterEncoding("UTF-8");
- // 調用核心業務類接收消息、處理消息
- String respMessage = coreService.processRequest(request);
- // 響應消息
- PrintWriter out = null;
- try {
- out = response.getWriter();
- out.print(respMessage);
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- out.close();
- out = null;
- }
- }
- }
效果如下:
ok,大功告成,消息的接收和發送就寫完了。