在微信公众号开发之公众号基础配置一文中,我们介绍了如何对微信公众号进行基础配置。
下面基于李森的博客的一个需求说明一下如何实现公众号用户的回复开发。
需求描述
用户给公众号发送消息时,我们查询博客的内容,然后回复给用户。
代码实现
其实用户回复的请求,跟微信公众号开发之公众号基础配置中配置的URL
是一致的。区别在于我们在微信公众号开发之公众号基础配置中配置的请求是GET
请求,用户回复的时候,微信会通过POST
请求到后台。
定义Controller相应微信请求
POST
请求定义其实没啥特别的,代码如下
@PostMapping("official")
public void post(HttpServletRequest request, HttpServletResponse response) {
try {
request.setCharacterEncoding("UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
response.setCharacterEncoding("UTF-8");
// 调用核心业务类接收消息、处理消息
// String respMessage = weixinPost(request);
String respMessage = messageService.newMessageRequest(request);
// 响应消息
PrintWriter out = null;
try {
out = response.getWriter();
out.print(respMessage);
} catch (IOException e) {
e.printStackTrace();
logger.error(e.getMessage());
} finally {
out.close();
out = null;
}
}
封装实体
封装消息基础实体BaseMessage.java
/**
* 微信自动回复消息封装
*/
@Data
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;
/**
* 位0x0001被标志时,星标刚收到的消息
*/
private int FuncFlag;
}
封装普通文本消息实体TextMessage.java
@EqualsAndHashCode(callSuper = true)
@Data
public class TextMessage extends BaseMessage{
// 消息内容
private String Content;
}
封装图文消息实体Article.java
@Data
public class Article {
/**
* 图文消息描述
*/
private String Description;
/**
* 图片链接,支持JPG、PNG格式,<br>
* 较好的效果为大图640*320,小图80*80
*/
private String PicUrl;
/**
* 图文消息名称
*/
private String Title;
/**
* 点击图文消息跳转链接
*/
private String Url;
}
封装多条图文消息实体'NewsMessage.java'
@EqualsAndHashCode(callSuper = true)
@Data
public class NewsMessage extends BaseMessage{
/**
* 图文消息个数,限制为10条以内
*/
private Integer ArticleCount;
/**
* 多条图文消息信息,默认第一个item为大图
*/
private List<Article> Articles;
}
封装工具类
我们需要将实体转换成xml
结构,微信消息都是通过xml
格式进行数据交互的。
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转换为map
*
* @param request
* @return
* @throws IOException
*/
public static Map<String, String> xmlToMap(HttpServletRequest request) throws IOException {
Map<String, String> map = new HashMap<String, String>();
SAXReader reader = new SAXReader();
InputStream ins = null;
try {
ins = request.getInputStream();
} catch (IOException e1) {
e1.printStackTrace();
}
Document doc = null;
try {
doc = reader.read(ins);
Element root = doc.getRootElement();
List<Element> list = root.elements();
for (Element e : list) {
map.put(e.getName(), e.getText());
}
return map;
} catch (DocumentException e1) {
e1.printStackTrace();
} finally {
ins.close();
}
return null;
}
/**
* @param @param request
* @param @return
* @param @throws Exception
* @Description: 解析微信发来的请求(XML)
*/
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;
}
// public static XStream xstream = new XStream();
/**
* 文本消息对象转换成xml
*
* @param textMessage 文本消息对象
* @return xml
*/
public static String textMessageToXml(TextMessage textMessage) {
// XStream xstream = new XStream();
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
/**
* @param @param newsMessage
* @param @return
* @Description: 图文消息对象转换成xml
*/
public static String newsMessageToXml(NewsMessage newsMessage) {
xstream.alias("xml", newsMessage.getClass());
xstream.alias("item", new Article().getClass());
return xstream.toXML(newsMessage);
}
/**
* 对象到xml的处理
*/
private static XStream xstream = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
@SuppressWarnings("rawtypes")
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);
}
}
};
}
});
}
封装服务层
/**
* 微信公众号处理
*
* @param request
* @return
*/
@Override
public String newMessageRequest(HttpServletRequest request) {
String respMessage = null;
try {
// xml请求解析
Map<String, String> requestMap = MessageUtil.xmlToMap(request);
// 发送方帐号(open_id)
String fromUserName = requestMap.get("FromUserName");
// 公众帐号
String toUserName = requestMap.get("ToUserName");
// 消息类型
String msgType = requestMap.get("MsgType");
// 用户发送的消息消息内容
String content = requestMap.get("Content");
logger.info("FromUserName is:" + fromUserName + ", ToUserName is:" + toUserName + ", MsgType is:" + msgType + ",content:" + content);
// 文本消息
if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) {
logger.info("进入方法内");
QueryWrapper<TypechoContents> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.like(TypechoContents::getTitle, content)
.or()
.like(TypechoContents::getText, content);
List<TypechoContents> typechoContentsList = typechoContentsMapper.selectTypechoContentsList(content);
logger.info("数据查询完成:" + typechoContentsList.size());
if (typechoContentsList.size() <= 0) {
//自动回复
TextMessage text = new TextMessage();
String string = "未找到要查询的内容,请换个关键字试试";
text.setContent(string);
text.setToUserName(fromUserName);
text.setFromUserName(toUserName);
text.setCreateTime(new Date().getTime());
text.setMsgType(msgType);
respMessage = MessageUtil.textMessageToXml(text);
return respMessage;
} else if (typechoContentsList.size() > 1) {
TextMessage text = new TextMessage();
StringBuilder stringBuilder = new StringBuilder("搜索到以下内容:\r\n");
for (TypechoContents typechoContents : typechoContentsList) {
stringBuilder.append("<a href='https://www.xiangcaowuyu.net/").append(typechoContents.getCategory()).append("/").append(typechoContents.getSlug()).append(".html'>").append(typechoContents.getTitle()).append("</a>");
stringBuilder.append("\r\n");
}
text.setContent(stringBuilder.toString());
text.setToUserName(fromUserName);
text.setFromUserName(toUserName);
text.setCreateTime(new Date().getTime());
text.setMsgType(msgType);
respMessage = MessageUtil.textMessageToXml(text);
return respMessage;
} else { //一条回复图文消息
NewsMessage newsMessage = new NewsMessage();
newsMessage.setArticles(new ArrayList<>());
newsMessage.setArticleCount(typechoContentsList.size());
newsMessage.setToUserName(fromUserName);
newsMessage.setFromUserName(toUserName);
newsMessage.setCreateTime(new Date().getTime());
newsMessage.setMsgType("news");
for (TypechoContents typechoContents : typechoContentsList) {
Article article = new Article();
article.setUrl("https://www.xiangcaowuyu.net/" + typechoContents.getCategory() + "/" + typechoContents.getSlug() + ".html");
article.setDescription(typechoContents.getTitle());
List<String> imageUrlList = MessageUtil.getMatchString(typechoContents.getText());
if (imageUrlList.size() <= 0) {
article.setPicUrl("https://www.xiangcaowuyu.net/usr/themes/xiangcaowuyu/assets/img/logo.png");
} else {
logger.info(imageUrlList.get(0));
article.setPicUrl(imageUrlList.get(0));
}
article.setTitle(typechoContents.getTitle());
newsMessage.getArticles().add(article);
}
respMessage = MessageUtil.newsMessageToXml(newsMessage);
return respMessage;
}
}
// 事件推送
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
String eventType = requestMap.get("Event");// 事件类型
// 订阅
if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {
//文本消息
TextMessage text = new TextMessage();
logger.info(XiangCaoWuYuConfig.getResp());
text.setContent(XiangCaoWuYuConfig.getResp().replace("<br/>","\n"));
text.setToUserName(fromUserName);
text.setFromUserName(toUserName);
text.setCreateTime(new Date().getTime());
text.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
respMessage = MessageUtil.textMessageToXml(text);
return respMessage;
}
// 取消订阅后用户再收不到公众号发送的消息,因此不需要回复消息
else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {// 取消订阅
}
}
} catch (Exception e) {
logger.error("error......");
}
return respMessage;
}
谢谢