微信公众号开发文章目录
1.微信公众号开发 - 环境搭建
2.微信公众号开发 - 配置表设计以及接入公众号接口开发
3.微信公众号开发 - token获取(保证同一时间段内只请求一次)
4.微信公众号开发 - 菜单按钮bean封装
5.微信公众号开发 - 创建菜单
6.微信公众号开发 - 事件处理和回复消息
7.微信公众号开发 - 发送Emoji表情
项目完整代码请访问github:https://github.com/liaozq0426/wx.git
上篇文章实现了微信公众号菜单创建,现在实现事件的处理和回复用户消息。
微信服务器会将所有的用户事件和消息通过公众号后台配置的服务器地址
推送给我们,就是下图红框的URL
编写代码
因为接收微信消息和返回消息都需要以xml格式数据进行传输,因此我们先封装一个工具类BeanXmlUtil,来实现xml和对象之间的转换,这里用的是dom4j框架来处理xml
package com.gavin.util;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.naming.NoNameCoder;
import com.thoughtworks.xstream.io.xml.DomDriver;
/**
* @title Map对象 和 xml字符串之间相互转换
* @author gavin
* @date 2019年4月21日
*/
public class BeanXmlUtil {
/**
* @title 从HttpServletRequest中获取xml数据流,并转换为String对象
* @author gavin
* @date 2019年4月22日
* @param request
* @return xml字符串
*/
public static String xmltoString(HttpServletRequest request) {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(request.getInputStream(), "utf-8"));
StringBuffer sb = new StringBuffer();
String line = null;
while((line = br.readLine()) != null) {
sb.append(line+"\n");
}
br.close();
return sb.toString();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
/**
* @title 从HttpServletRequest中获取xml数据流,并转换为map对象
* @author gavin
* @date 2019年4月21日
* @param request
* @return map对象
*/
public static Map<String, String> xmlToMap(HttpServletRequest request) {
Map<String, String> map = new HashMap<String, String>();
SAXReader reader = new SAXReader();
try {
InputStream ins = request.getInputStream();
Document doc = reader.read(ins);
Element root = doc.getRootElement();
@SuppressWarnings("unchecked")
List<Element> list = root.elements();
for (Element e : list) {
map.put(e.getName(), e.getText());
}
ins.close();
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
/**
* @title 将xml字符串转换为map对象
* @author gavin
* @date 2019年4月21日
* @param request
* @return map对象
*/
public static Map<String, String> xmlToMap(String xml) {
Map<String, String> map = new HashMap<String, String>();
try {
// 将字符串转为xml
Document doc = DocumentHelper.parseText(xml);
Element root = doc.getRootElement();
@SuppressWarnings("unchecked")
List<Element> list = root.elements();
for (Element e : list) {
map.put(e.getName(), e.getText());
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
/**
* @title 将object对象转换为xml字符串
* @desc 不进行 双下划线、CDATA标记处理
* @author gavin
* @date 2019年4月21日
* @param object
* @return xml字符串
*/
public static String beanToXmlDefault(Object object) {
XStream xStream = new XStream(new DomDriver());
xStream.alias("xml", object.getClass());
return xStream.toXML(object);
}
/**
* @title 将object对象转换为xml字符串
* @desc 对 双下划线 问题进行处理,并进行CDATA标记处理,根节点自动设置名字为xml
* @author gavin
* @date 2019年4月21日
* @param object
* @return xml字符串
*/
public static String beanToXmlCommon(Object object) {
XStream xStream = new CommonXStream(new CommonXppDriver(new NoNameCoder()) ,object.getClass());
return xStream.toXML(object);
}
}
要注意的是,最后一个方法beanToXmlCommon()中使用的不是默认的XStream和DomDriver,而是自定义的CommonXStream和CommonXppDriver,这是因为我们要对返回给微信的dom元素内容增加CDATA标记
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>12345678</CreateTime>
<MsgType><![CDATA[image]]></MsgType>
<Image>
<MediaId><![CDATA[media_id]]></MediaId>
</Image>
</xml>
CommonXppDriver类代码如下,该类继承了XppDriver,实现对dom元素增加CDATA标记
package com.gavin.util;
import java.io.Writer;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.naming.NoNameCoder;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
/**
* @title 自定义XappDriver , 对dom元素增加CDATA标记
* @author gavin
* @date 2019年11月29日
*/
public class CommonXppDriver extends XppDriver {
public CommonXppDriver() {
super();
}
public CommonXppDriver(NoNameCoder noNameCoder) {
super(noNameCoder);
}
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
@Override
@SuppressWarnings("rawtypes")
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
@Override
public String encodeNode(String name) {
return name;
}
@Override
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
}
CommonXStream类代码如下,该类继承了XStream,构造函数接收一个CommonXppDriver对象。
package com.gavin.util;
import com.thoughtworks.xstream.XStream;
/**
* @title 自定义XStream
* @author gavin
* @date 2019年11月29日
*/
public class CommonXStream extends XStream {
public CommonXStream(CommonXppDriver driver) {
super(driver);
}
public CommonXStream(CommonXppDriver driver , Class<?> rootClassType) {
super(driver);
// 将根元素重命名为xml
this.alias("xml", rootClassType);
}
}
工具类封装完成后,开始编写消息处理的代码,我们定义一个TextMessage类来封装微信文本消息字段
package com.gavin.pojo;
/**
* @title 文本消息bean
* @author gavin
* @date 2019年11月29日
*/
public class TextMessage {
private String ToUserName; // 消息接收方
private String FromUserName; // 消息发送方
private long CreateTime;
private String MsgType; // 消息类型
private String Content; // 消息内容
public long getCreateTime() {
return CreateTime;
}
public void setCreateTime(long createTime) {
CreateTime = createTime;
}
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 String getMsgType() {
return MsgType;
}
public void setMsgType(String msgType) {
MsgType = msgType;
}
public String getContent() {
return Content;
}
public void setContent(String content) {
Content = content;
}
}
再编写一个微信消息类型定义bean,避免出现硬代码
package com.gavin.pojo;
/**
* @title 微信消息类型定义bean
* @author gavin
* @date 2019年11月29日
*/
public class WxMessageConst {
public static String MSG_TYPE = "MsgType"; // 消息类型 包含text:文本消息、event:事件类型等
public static String MSG_TYPE_TEXT = "text"; // 本文消息
public static String MSG_TYPE_EVENT = "event"; // 事件消息
public static String EVENT = "Event"; // 值可以为subscribe、unsubscribe等
public static String EVENT_TYPE_SUBSCRIBE = "subscribe"; //订阅
public static String EVENT_TYPE_UN_SUBSCRIBE = "unsubscribe"; //取消订阅
public static String FROM_USER_NAME = "FromUserName";
public static String TO_USER_NAME = "ToUserName";
public static String CONTENT = "Content"; // 消息内容
public static String DEFAULT_MSG = "success"; // 如果不返回自定义消息,则返回success
}
接下来定义WxMsgService接口来处理事件消息
package com.gavin.service;
import javax.servlet.http.HttpServletRequest;
public interface WxMsgService {
String processWxRequest(HttpServletRequest request) throws Exception;
}
实现类WxMsgServiceImpl代码如下
package com.gavin.service.impl;
import java.util.Date;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;
import com.gavin.pojo.TextMessage;
import com.gavin.pojo.WxMessageConst;
import com.gavin.service.WxMsgService;
import com.gavin.util.BeanXmlUtil;
/**
* @title 微信消息处理
* @author gavin
* @date 2019年11月29日
*/
@Service
public class WxMsgServiceImpl implements WxMsgService{
private Logger logger = Logger.getLogger(this.getClass());
@Override
public String processWxRequest(HttpServletRequest request) throws Exception {
// xml请求解析成map
Map<String, String> requestMap = BeanXmlUtil.xmlToMap(request);
logger.info(requestMap.toString());
// 发送方帐号
String fromUserName = requestMap.get(WxMessageConst.FROM_USER_NAME);
// 接受方帐号
String toUserName = requestMap.get(WxMessageConst.TO_USER_NAME);
// 消息类型
String msgType = requestMap.get(WxMessageConst.MSG_TYPE);
if (WxMessageConst.MSG_TYPE_EVENT.equals(msgType)) {
// 事件消息类型
// 订阅
if (WxMessageConst.EVENT_TYPE_SUBSCRIBE.equals(requestMap.get(WxMessageConst.EVENT))) {
// 关注时自动回复消息
TextMessage textMessage = new TextMessage();
textMessage.setContent("感谢您关注,我会继续努力!");
textMessage.setCreateTime(new Date().getTime());
textMessage.setFromUserName(toUserName);
textMessage.setToUserName(fromUserName);
textMessage.setMsgType(WxMessageConst.MSG_TYPE_TEXT);
String response = BeanXmlUtil.beanToXmlCommon(textMessage);
if(response != null)
return response;
} else if (WxMessageConst.EVENT_TYPE_UN_SUBSCRIBE.equals(requestMap.get(WxMessageConst.EVENT))) {
// 取消订阅
logger.info("用户" + fromUserName + "取消关注了");
}
}else if(WxMessageConst.MSG_TYPE_TEXT.equals(msgType)) {
// 文本消息类型
// 定义自动回复文本消息
TextMessage textMessage = new TextMessage();
if (!StringUtils.isBlank(msgType)) {
textMessage.setFromUserName(toUserName);
textMessage.setToUserName(fromUserName);
textMessage.setCreateTime(new Date().getTime());
textMessage.setMsgType(WxMessageConst.MSG_TYPE_TEXT);
// 用户发送过来的消息
String content = requestMap.get(WxMessageConst.CONTENT);
logger.info(content);
// 回复消息
String rspContent = "您好,有什么问题可以问我!";
textMessage.setContent(rspContent);
if(!StringUtils.isBlank(textMessage.getContent())) {
String response = BeanXmlUtil.beanToXmlCommon(textMessage);
logger.info(response);
if(response != null)
return response;
}
}
}else {
// 其他消息
}
// 默认回复
return WxMessageConst.DEFAULT_MSG;
}
}
实现类中处理了关注/取消关注事件
和回复用户文本消息
,当有用户关注时会返回感谢您关注,我会继续努力!
给客户端,当接收到用户的文本消息时,会返回您好,有什么问题可以问我!
给客户端。
另外要注意的是,如果不想返回任何内容给客户端,需要返回success
字符串,否则微信公众号会提示故障异常。
最后在WxController中编写wxIn方法,注意一定要定义为post请求,因为微信服务器是以post方式推送消息给我们的。
@PostMapping("wxIn")
public void wxMsgReply(HttpServletRequest request, HttpServletResponse response){
try {
request.setCharacterEncoding("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
response.setCharacterEncoding("UTF-8");
try {
// String respMsg = "success";
String respMsg = wxMsgService.processWxRequest(request);
PrintWriter out = response.getWriter();
out.print(respMsg);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
到这里,消息处理的代码就编写完成了
测试
现在我们来测试一下,重新关注一下微信公众号,公众自动回复了感谢您关注,我会继续努力!
,如下图所示
向公众号发送一条文本消息,公众号回复了您好,有什么问题可以问我!
,如下图所示