微信公众平台开发教程Java版(三) 消息接收和发送
前面两章已经介绍了如何接入微信公众平台,这一章说说消息的接收和发送
可以先了解公众平台的消息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,大功告成,消息的接收和发送就写完了。
可加我的微信公众号一起讨论
微信公众号:andedaohang
或扫描二维码
我所有的博客都搬到csdn了,以后所有的博客都会在csdn上更新,
CSDN博客地址:http://blog.csdn.net/tuposky