消息的发送和处理是在doPost方法中完成的
消息发送处理

一:微信公众平台的通讯过程

当微信用户向你的公众平台发送一条消息,实际上这条消息首先发送到微信服务器,由微信服务器向网站服务器发起另外一个请求,网站服务器返回这个请求的结果,再由微信服务器发送到微信客户端。

整个消息通讯流程如下图:
通讯过程
上述5个步骤中,作为开发者我们主要精力都集中在步骤3上,这个步骤主实际上要有3项任务:

接收来自2的XML信息
服务器内部逻辑执行
组织并返回用于4的XML信息
上述三项任务我会在后面做详细说明,并提供一整套简单、高效的处理方法。

二:解析微信服务器传来的消息

因为微信服务器发送过来的是xml格式的消息,所以我们可以采用 开源框架dom4j去解析xml 。

1
2
3
4
5
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>

三:将响应消息转换成xml返回给微信服务器

如何将响应消息转换成xml返回的问题,这里我们将 采用开源框架xstream来实现Java类到xml的转换

1
2
3
4
5
6
<!-- 采用开源框架xstream来实现Java类到xml的转换 -->
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
</dependency>

1.封装消息处理工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package com.wyj.wechart.utils;
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.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.wyj.wechart.message.resp.Article;
import com.wyj.wechart.message.resp.ImageMessage;
import com.wyj.wechart.message.resp.MusicMessage;
import com.wyj.wechart.message.resp.NewsMessage;
import com.wyj.wechart.message.resp.TextMessage;
import com.wyj.wechart.message.resp.VideoMessage;
import com.wyj.wechart.message.resp.VoiceMessage;
/**
* 消息处理工具类
*
*
* @author:WangYuanJun
* @date:2018年1月23日 下午2:09:54
*/
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";
// 事件类型:subscribe(订阅)
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
// 事件类型:unsubscribe(取消订阅)
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
// 事件类型:scan(用户已关注时的扫描带参数二维码)
public static final String EVENT_TYPE_SCAN = "scan";
// 事件类型:LOCATION(上报地理位置)
public static final String EVENT_TYPE_LOCATION = "LOCATION";
// 事件类型:CLICK(自定义菜单)
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_NEWS = "news";
/**
* 解析微信发来的请求(XML)
*
* @param request
* @return Map<String, String>
* @throws Exception
*/
@SuppressWarnings("unchecked")
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;
}
/**
* 扩展xstream使其支持CDATA
*/
private static XStream xstream = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 对所有xml节点的转换都增加CDATA标记
boolean cdata = true;
@SuppressWarnings("unchecked")
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);
}
}
};
}
});
/**
* 文本消息对象转换成xml
*
* @param textMessage
* 文本消息对象
* @return xml
*/
public static String messageToXml(TextMessage textMessage) {
xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
/**
* 图片消息对象转换成xml
*
* @param imageMessage
* 图片消息对象
* @return xml
*/
public static String messageToXml(ImageMessage imageMessage) {
xstream.alias("xml", imageMessage.getClass());
return xstream.toXML(imageMessage);
}
/**
* 语音消息对象转换成xml
*
* @param voiceMessage
* 语音消息对象
* @return xml
*/
public static String messageToXml(VoiceMessage voiceMessage) {
xstream.alias("xml", voiceMessage.getClass());
return xstream.toXML(voiceMessage);
}
/**
* 视频消息对象转换成xml
*
* @param videoMessage
* 视频消息对象
* @return xml
*/
public static String messageToXml(VideoMessage videoMessage) {
xstream.alias("xml", videoMessage.getClass());
return xstream.toXML(videoMessage);
}
/**
* 音乐消息对象转换成xml
*
* @param musicMessage
* 音乐消息对象
* @return xml
*/
public static String messageToXml(MusicMessage musicMessage) {
xstream.alias("xml", musicMessage.getClass());
return xstream.toXML(musicMessage);
}
/**
* 图文消息对象转换成xml
*
* @param newsMessage
* 图文消息对象
* @return xml
*/
public static String messageToXml(NewsMessage newsMessage) {
xstream.alias("xml", newsMessage.getClass());
xstream.alias("item", new Article().getClass());
return xstream.toXML(newsMessage);
}
}

2.使用CoreServlet 类完成消息的接受与响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package com.wyj.wechart.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.wyj.wechart.service.CoreService;
import com.wyj.wechart.utils.SignUtil;
/**
* 来接收微信服务器传来信息
*
*
* @author:WangYuanJun
* @date:2018年1月23日 下午2:17:39
*/
@WebServlet(urlPatterns = "/wechat", description = "wechat")
public class CoreServlet extends HttpServlet {
private static final long serialVersionUID = -8685285401859800066L;
/**
* 确认请求来自微信服务器
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(">>>>>>>>>>doGet()<<<<<<<<<<<");
// 微信加密签名
String signature = req.getParameter("signature");
// 时间戳
String timestamp = req.getParameter("timestamp");
// 随机数
String nonce = req.getParameter("nonce");
// 随机字符串
String echostr = req.getParameter("echostr");
PrintWriter out = resp.getWriter();
// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,表示接入成功,否则接入失败
if (SignUtil.checkSignature(signature, timestamp, nonce)) {
out.print(echostr);
}
out.close();
out = null;
}
/**
* 处理微信服务器发来的消息
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(">>>>>>>>>>doPost()<<<<<<<<<<<");
// 消息的接收、处理、响应
// 将请求、响应的编码均设置为UTF-8(防止中文乱码)
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
// 调用核心业务类接收消息、处理消息
String respXml = CoreService.processRequest(req);
// 响应消息
PrintWriter out = resp.getWriter();
out.print(respXml);
out.close();
}
}

3.使用CoreService类完成消息的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package com.wyj.wechart.service;
import java.util.Date;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import com.wyj.wechart.message.resp.TextMessage;
import com.wyj.wechart.utils.MessageUtil;
/**
*
* 核心服务类
*
* @author:WangYuanJun
* @date:2018年1月23日 下午2:21:24
*/
public class CoreService {
/**
* 处理微信发来的请求
* @param request
* @return xml
*/
public static String processRequest(HttpServletRequest request) {
// xml格式的消息数据
String respXml = null;
// 默认返回的文本消息内容
String respContent = "未知的消息类型!";
try {
// 调用parseXml方法解析请求消息
Map<String, String> requestMap = MessageUtil.parseXml(request);
// 发送方帐号
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(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
// 文本消息
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 = "您发送的是链接消息!";
}
// 事件推送
else if (msgType.equals(MessageUtil.REQ_MESSAGE_TYPE_EVENT)) {
// 事件类型
String eventType = requestMap.get("Event");
// 关注
if (eventType.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) {
respContent = "谢谢您的关注!";
}
// 取消关注
else if (eventType.equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) {
// TODO 取消订阅后用户不会再收到公众账号发送的消息,因此不需要回复
}
// 扫描带参数二维码
else if (eventType.equals(MessageUtil.EVENT_TYPE_SCAN)) {
// TODO 处理扫描带参数二维码事件
}
// 上报地理位置
else if (eventType.equals(MessageUtil.EVENT_TYPE_LOCATION)) {
// TODO 处理上报地理位置事件
}
// 自定义菜单
else if (eventType.equals(MessageUtil.EVENT_TYPE_CLICK)) {
// TODO 处理菜单点击事件
}
}
// 设置文本消息的内容
textMessage.setContent(respContent);
// 将文本消息对象转换成xml
respXml = MessageUtil.messageToXml(textMessage);
} catch (Exception e) {
e.printStackTrace();
}
return respXml;
}
}

本地测试效果如下
message

注:github项目地址:微信公共号开发用例