微信公众号开发(三)------消息的接收与回复
前言
现在,我们已经封装好了各种消息对应的javaBean,下面就可以实现消息的识别,以及对各类消息的回复.
正文
我们查看微信的官方文档可以看到:
当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL上。
请注意:
1、关于重试的消息排重,推荐使用msgid排重。
2、微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。假如服务器无法保证在五秒内处理并回复,
可以直接回复空串,微信服务器不会对此作任何处理,并且不会发起重试。详情请见“发送消息-被动回复消息”。
3、如果开发者需要对用户消息在5秒内立即做出回应,即使用“发送消息-被动回复消息”接口向用户被动回复消息时,可以在
公众平台官网的开发者中心处设置消息加密。开启加密后,用户发来的消息和开发者回复的消息都会被加密(但开发者通过客服
接口等API调用形式向用户发送消息,则不受影响)。
也就是说,用户在给我们的公众号发送消息的时候,微信服务器会将消息的内容封装成xml,且以POST请求发送到我们的服务器,因此,我们就可以在servlet
的doPost方法中实现我们的处理逻辑.
xml和java对象的相互转化
在这里,我们需要考虑一个问题,就是xml消息和我们封装的javaBean的转化,我们可以使用java原生的API来转化,但是这里我推荐使用开源的工具包dom4j
和xStream
.
关于这两个小工具的使用,百度即可,下面贴上工具包的MAVEN地址:
<!--引入dom4j解析xml-->
<!-- https://mvnrepository.com/artifact/dom4j/dom4j -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.thoughtworks.xstream/xstream -->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
</dependency>
我们现在自己定义一个小工具类,用来方便我们处理消息"
消息处理工具类
package com.xiaojian.utils;
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;
import com.xiaojian.bean.commons.Article;
import com.xiaojian.bean.response.*;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 消息处理工具类
*/
public class MessageUtil {
//请求消息的类型------------------------------------------
//文本
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_VOICE = "voice";
//视频
public static final String REQ_MESSAGE_TYPE_VIDEO = "video";
//小视频
public static final String REQ_MESSAGE_TYPE_SHORTVIDEO = "shortvideo";
//地理位置
public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
//链接
public static final String REQ_MESSAGE_TYPE_LINK = "link";
//事件推送
public static final String REQ_MESSAGE_TYPE_EVENT = "event";
//事件类型--订阅
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
//取消订阅
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
//已关注用户扫描带参数二维码
public static final String EVENT_TYPE_SCAN = "SCAN";
//上报地理位置
public static final String EVENT_TYPE_LOCATION = "LOCATION";
//自定义菜单事件
public static final String EVENT_TYPE_CLICK = "CLICK";
//响应消息类型
//文本
public static final String RESP_MESSAGE_TYPE_TEXT = "text";
//图片
public static final String RESP_MESSAGE_TYPE_IMAGE = "image";
//语音
public static final String RESP_MESSAGE_TYPE_VOICE = "voice";
//视频
public static final String RESP_MESSAGE_TYPE_VIDEO = "video";
//音乐
public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
//图文
public static final String RESP_MESSAGE_TYPE_ARTICLE = "news";
//---------------------------------------------------------------------
/**
* 解析xml请求
*/
public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
Map<String, String> map = new HashMap<>();
//取出输入流,使用dom4J获取文档对象
InputStream inputStream = request.getInputStream();
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
Element rootElement = document.getRootElement();
//获取根元素的所有节点
List<Element> elements = rootElement.elements();
//遍历节点,将数据存入map
for (Element e: elements) {
map.put(e.getName(), e.getText());
}
inputStream.close();
inputStream = null;
return map;
}
/**
* 扩展xstream,支持CDATA
*/
private static XStream xStream = new XStream(new XppDriver() {
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
boolean cdate = true;
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
protected void writeText(QuickWriter writer, String text) {
if (cdate) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
}else {
writer.write(text);
}
}
};
}
});
/**
* 文本消息转换成xml
*/
public static String messageToXml(TextMessageResponse textMessageResponse) {
xStream.alias("xml", textMessageResponse.getClass());
return xStream.toXML(textMessageResponse);
}
/**
* 图片消息对象转换xml
*/
public static String messageToXml(ImageMessageResponse imageMessageResponse) {
xStream.alias("xml", imageMessageResponse.getClass());
return xStream.toXML(imageMessageResponse);
}
/**
* 语音消息转换为xml
*/
public static String messageToXml(VoiceMessageResponse voiceMessageResponse) {
xStream.alias("xml", voiceMessageResponse.getClass());
return xStream.toXML(voiceMessageResponse);
}
/**
* 视频消息转换成xml
*/
public static String messageToXml(VideoMessageResponse videoMessageResponse) {
xStream.alias("xml", videoMessageResponse.getClass());
return xStream.toXML(videoMessageResponse);
}
/**
* 音乐消息转换xml
*/
public static String messageToXml(MusicMessageResponse musicMessageResponse) {
xStream.alias("xml", musicMessageResponse.getClass());
return xStream.toXML(musicMessageResponse);
}
/**
* 图文消息转换成xml
*/
public static String messageToXml(ArticleMessageResponse articleMessageResponse) {
xStream.alias("xml", articleMessageResponse.getClass());
xStream.alias("item", new Article().getClass());
return xStream.toXML(articleMessageResponse);
}
}
完成CoreServlet的POST方法
有了以上的工具类,我们就可以方便的处理微信发送的消息了,我们完善CoreServlet
:
package com.xiaojian.servlet;
import com.xiaojian.service.CoreService;
import com.xiaojian.utils.SignUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 微信核心servlet
*/
public class CoreServlet extends HttpServlet {
private static final Logger logger = LoggerFactory.getLogger(CoreServlet.class);
/**
* 确认请求来自微信服务器
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
logger.info("微信请求接入>>>");
//获取微信加密签名
String signature = req.getParameter("signature");
logger.info("微信签名是: {}", signature);
//获取时间戳
String timestamp = req.getParameter("timestamp");
//获取随机数
String nonce = req.getParameter("nonce");
//获取随机字符串
String echostr = req.getParameter("echostr");
PrintWriter out = resp.getWriter();
//判断加密后的字符串和签名是否一样.如果一样表示接入成功
if (SignUtil.checkSignature(signature, timestamp, nonce)) {
out.print(echostr);
}
out.close();
out = null;
}
/**
* 处理微信消息
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
logger.info("收到用户请求:{}", req);
//调用service处理请求
String respXml = CoreService.processRequest(req);
//响应
PrintWriter writer = resp.getWriter();
writer.print(respXml);
writer.close();
}
}
创建一个CoreService来处理消息
package com.xiaojian.service;
import com.xiaojian.bean.response.TextMessageResponse;
import com.xiaojian.utils.MessageUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Map;
/**
* 核心服务类
*/
public class CoreService {
private static final Logger logger = LoggerFactory.getLogger(CoreService.class);
/**
* 处理微信请求
*/
public static String processRequest(HttpServletRequest request) {
logger.info("CoreService开始处理请求>>>>>>>>>>");
String respXml = null;
//返回体
String respContent = "未知的消息类型!";
try {
Map<String, String> requestMap = MessageUtil.parseXml(request);
//取出基本参数
String fromUserName = requestMap.get("FromUserName");
String toUserName = requestMap.get("ToUserName");
String msgType = requestMap.get("MsgType");
//对所有的类型都回复文本消息
TextMessageResponse textMessageResponse = new TextMessageResponse();
textMessageResponse.setFromUserName(toUserName);
textMessageResponse.setToUserName(fromUserName);
textMessageResponse.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
textMessageResponse.setCreateTime(new Date().getTime());
//判断消息类型,获取对应的返回消息
respContent = setRespContent(requestMap);
textMessageResponse.setContent(respContent);
//转换为xml
respXml = MessageUtil.messageToXml(textMessageResponse);
logger.info("处理结束,返回参数为: {}", respXml);
} catch (Exception e) {
e.printStackTrace();
}
return respXml;
}
public static String setRespContent(Map<String, String> requestMap) {
String respContent = null;
String msgType = requestMap.get("MsgType");
//判断是事件还是消息
if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
//如果是事件,则多一个属性eventType
String eventType = requestMap.get("Event");
if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {
respContent = "谢谢您的关注!";
} else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {
//用户取消关注后不用回复消息.
} else if (eventType.equals(MessageUtil.EVENT_TYPE_SCAN)) {
//扫描带参数的二维码
} else if (eventType.equals(MessageUtil.EVENT_TYPE_LOCATION)) {
//上报地理位置事件
} else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {
//处理自定义菜单点击事件
}
} else {
if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
respContent = "文本消息~~~~~~!";
} else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) {
respContent = "图片消息~~~~~~!";
} else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) {
respContent = "语音消息~~~~~~!";
} else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_VIDEO)) {
respContent = "视频消息~~~~~~!";
} else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_SHORTVIDEO)) {
respContent = "小视频消息~~~~~~!";
} else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) {
respContent = "地理位置消息~~~~~~!";
} else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) {
respContent = "链接消息~~~~~~!";
}
}
return respContent;
}
}
OK,现在我们可以测试了~~~
测试成功!!!
总结
至此,我们公众号的简单消息处理已经完成了,下一篇博文将获取access_token,来调用微信提供的其他接口.