本节先记录一下原理,代码写的诸多不妥帖。开源项目fastweixin 有封装的很不错的方法。可以直接用。
之前接入开发的时候,配置的那个URL,就是接收公众号消息的接口。但是接入的时候,发送的是GET请求。而接收消息的时候发送的是POST请求,所以写一个Controller接口,配置method 一个GET一个POST区分开。
下面是一个接收普通文本消息,返回文本消息的简单demo
消息体是明文模式。无论收到什么消息,都返回”接收成功“的文本消息。
/**
* 接入开发走这个GET
*/
@RequestMapping(value = "/joinDev", method = RequestMethod.GET)
public Object delMsg(HttpServletRequest request) {
// 微信加密签名
String signature = request.getParameter("signature");
// 时间戳
String timestamp = request.getParameter("timestamp");
// 随机数
String nonce = request.getParameter("nonce");
// 随机字符串
String echostr = request.getParameter("echostr");
//如果验证消息是来自微信,返回echostr
boolean check = checkSignature(signature, timestamp, nonce);
if (check) {
return echostr;
}
return null;
}
/**
* 配置RequestMethod.POST,用于接收处理消息
*/
@RequestMapping(value = "/joinDev", method = RequestMethod.POST)
public Object delMsg(HttpServletRequest request, @RequestBody InMsgEntity msg) {
return delMsg(msg);
}
private OutMsgEntity delMsg(InMsgEntity msg) {
log.info("接收消息为:{}", msg);
//创建消息响应对象
OutMsgEntity out = new OutMsgEntity();
//把原来的发送方设置为接收方
out.setToUserName(msg.getFromUserName());
//把原来的接收方设置为发送方
out.setFromUserName(msg.getToUserName());
//设置消息的响应类型
out.setMsgType("text");
//设置消息创建时间
out.setCreateTime(new Date().getTime());
out.setContent("接收成功");
return out;
}
如下
普通消息
通过微信中转过来的消息都是xml格式的。例如文本消息的格式为:官网文档 https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140453
<?xml version="1.0" encoding="utf-8"?>
<xml>
<ToUserName>toUser</ToUserName>
<FromUserName>fromUser</FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType>text</MsgType>
<Content>this is a test</Content>
<MsgId>1234567890123456</MsgId>
</xml>
我直接用了xml解析的注解,所以接收的xml可以直接转为对象。返回的对象也会转为xml。不建议这么做,对于不同类型的消息不具备通用性。可以使用后面介绍的fastweixin. 消息的解析,加解密都已经做好了。直接使用即可。
@Data
@ToString
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
public class InMsgEntity {
// 开发者微信号
protected String FromUserName;
// 发送方帐号(一个OpenID)
protected String ToUserName;
// 消息创建时间
protected Long CreateTime;
// 消息类型 text 文本消息 image 图片消息 voice 语音消息 video 视频消息 event 事件消息
protected String MsgType;
// 消息id
protected Long MsgId;
// 文本内容
private String Content;
// 图片链接(由系统生成)
private String PicUrl;
// 图片消息媒体id,可以调用多媒体文件下载接口拉取数据
private String MediaId;
/**
* 事件类型
* 自定义菜单事件 CLICK 拉取消息 VIEW 跳转链接
* 关注/取消关注事件 subscribe 订阅 unsubscribe 取关
* 扫描带参数二维码事件 用户扫描带场景值二维码时,可能推送以下两种事件:
* 如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。
* 如果用户已经关注公众号,则微信会将带场景值扫描事件推送给开发者。
* 上报地理位置事件 LOCATION 上报位置
*/
private String Event;
// 事件KEY值
private String EventKey;
}
普通消息中的图片消息、语音消息、视频消息等都是通过MsgType 来区分的。text 文本消息 image 图片消息 voice 语音消息 video 视频消息 。
事件消息
当MsgType 是 event 的时候表示事件消息。事件消息报文中会一个节点Event 表明事件的类型。例如下面 Event 节点的 subscribe 表示这是个订阅消息。其他还有unsubscribe(取消订阅) ,SCAN 扫描带参数二维码事件,CLICK 自定义菜单事件等等。
<xml>
<ToUserName>< ![CDATA[toUser] ]></ToUserName>
<FromUserName>< ![CDATA[FromUser] ]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType>< ![CDATA[event] ]></MsgType>
<Event>< ![CDATA[subscribe] ]></Event>
</xml>
接收到事件消息根据事件类型和需求处理就行了。如下,当然这么写不好。我只是说明一下原理就是这么搞得。fastweixin 项目中已经封装的很好。可以直接引入jar包用。
@RequestMapping(value = "/joinDev", method = RequestMethod.POST)
public Object delMsg(HttpServletRequest request) {
Map<String, String> requestMap = delMsgFromRequest(request); // 从流中读取消息体
// 发送方帐号(open_id)
String fromUserName = requestMap.get("FromUserName");
// 公众帐号
String toUserName = requestMap.get("ToUserName");
// 消息类型
String msgType = requestMap.get("MsgType");
if (MESSAGE_TYPE_TEXT.equals(msgType)) {
return delTextMsg(requestMap, fromUserName, toUserName);
} else if (MESSAGE_TYPE_EVENT.equals(msgType)) {
Object textMessage = delEventMst(requestMap, fromUserName, toUserName);
if (textMessage != null) return textMessage;
}
return errMsg(fromUserName, toUserName);
}
private Object errMsg(String fromUserName, String toUserName) {
TextMessage textMessage = new TextMessage();
textMessage.setToUserName(fromUserName);
textMessage.setFromUserName(toUserName);
textMessage.setCreateTime(new Date().getTime());
textMessage.setMsgType(MESSAGE_TYPE_TEXT);
textMessage.setContent("消息有误");
return textMessage;
}
private Object delEventMst(Map<String, String> requestMap, String fromUserName, String toUserName) {
// 事件类型
String eventType = requestMap.get("Event");
// 订阅
if (EVENT_TYPE_SUBSCRIBE.equals(eventType)) {
String respContent = "谢谢您的关注!";
// 回复关注消息
TextMessage textMessage = new TextMessage();
textMessage.setToUserName(fromUserName);
textMessage.setFromUserName(toUserName);
textMessage.setCreateTime(new Date().getTime());
textMessage.setMsgType(MESSAGE_TYPE_TEXT);
textMessage.setContent(respContent);
return textMessage;
}
// 自定义菜单点击事件
else if (EVENT_TYPE_CLICK.equals(eventType)) {
// 事件类型
String eventKey = requestMap.get("EventKey");
if ("USER_INFO_CLICK".equals(eventKey)) { // USER_INFO_CLICK
ResImageMessage imageMessage = new ResImageMessage();
imageMessage.setMediaId(new String[]{"PSUYMe5hdzqT-JrvKg-FEId9iGBj-648kuMLqDQPGr1ffA0yCCF4HxlmFuzrIfAL"});
imageMessage.setCreateTime(new Date().getTime());
imageMessage.setFromUserName(toUserName);
imageMessage.setToUserName(fromUserName);
imageMessage.setMsgType("image");
return imageMessage;
} else if ("V1001_GOOD".equals(eventKey)) {
// ...
}
}
return null;
}
private Map<String, String> delMsgFromRequest(HttpServletRequest request) {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<>();
// 从request中取得输入流
InputStream inputStream = null;
try {
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());
}
} catch (IOException | DocumentException e) {
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return map;
}