微信服務器將用戶發送的消息以 HTTP POST 方式推送到開發者配置的 URL。開發者需在 5 秒內響應(否則微信斷開並重試)。消息類型分為:
<xml>
<ToUserName><![CDATA[gh_xxxxx]]></ToUserName>
<FromUserName><![CDATA[openid_xxx]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[用戶發送的文本]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>
通用字段:ToUserName(公眾號)、FromUserName(用戶 OpenID)、CreateTime(時間戳)、MsgType(類型)、MsgId(消息 ID)。各類型另有專有字段(如圖片的 PicUrl、MediaId)。
<xml>
<ToUserName><![CDATA[openid_xxx]]></ToUserName>
<FromUserName><![CDATA[gh_xxxxx]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[回復給用戶的文本]]></Content>
</xml>
注意:回復的 ToUserName = 接收的 FromUserName,回復的 FromUserName = 接收的 ToUserName。
@WebServlet("/wechat")
public class WechatServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// 驗證服務器配置:signature、timestamp、nonce、echostr
if (verifySignature(req)) {
resp.getWriter().write(req.getParameter("echostr"));
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
try {
Map<String, String> msgMap = parseXml(req);
String respXml = buildResponse(msgMap);
resp.getWriter().write(respXml);
} catch (Exception e) {
log.error("處理微信消息異常", e);
}
}
private Map<String, String> parseXml(HttpServletRequest req) throws Exception {
Map<String, String> map = new HashMap<>();
InputStream is = req.getInputStream();
SAXReader reader = new SAXReader();
Document doc = reader.read(is);
Element root = doc.getRootElement();
for (Element e : root.elements()) {
map.put(e.getName(), e.getText());
}
is.close();
return map;
}
private String buildResponse(Map<String, String> msg) {
String msgType = msg.get("MsgType");
String from = msg.get("FromUserName");
String to = msg.get("ToUserName");
long now = System.currentTimeMillis() / 1000;
// 簡單邏輯:收到文本「文本」回復文本,收到「圖片」回復圖片...
if ("text".equals(msgType)) {
String content = msg.get("Content");
if ("文本".equals(content)) return buildTextMsg(from, to, now, "這是文本回復");
if ("圖片".equals(content)) return buildImageMsg(from, to, now, "media_id_xxx");
if ("音樂".equals(content)) return buildMusicMsg(from, to, now, "標題", "描述", "music_url", "hq_url");
if ("圖文".equals(content)) return buildNewsMsg(from, to, now, List.of(
new Article("標題1", "描述1", "pic_url", "url"),
new Article("標題2", "描述2", "pic_url", "url")
));
// 默認回復
return buildTextMsg(from, to, now, "收到: " + content);
}
// 其他類型消息默認回復
return buildTextMsg(from, to, now, "收到非文本消息");
}
// 各類構建方法省略...
private String buildTextMsg(String to, String from, long time, String content) {
return String.format(
"<xml><ToUserName><![CDATA[%s]]></ToUserName>" +
"<FromUserName><![CDATA[%s]]></FromUserName>" +
"<CreateTime>%d</CreateTime>" +
"<MsgType><![CDATA[text]]></MsgType>" +
"<Content><![CDATA[%s]]></Content></xml>",
to, from, time, content
);
}
}
SAXReader 直接讀取 InputStream,避免字符串轉換導致的 "Premature end of file" 錯誤。<![CDATA[...]]> 中,防止特殊字符破壞 XML 結構。doGet 階段驗證 signature = SHA1(sort(token, timestamp, nonce)),確保請求來自微信服務器。media_id,回復時引用。MsgType、Event、關鍵詞分發到不同 Handler,保持 Controller 輕量。@Async + 線程池,或接入消息隊列(RabbitMQ/Kafka)削峰填谷。微信公眾號開發核心是「XML 解析 → 業務邏輯 → XML 構建」。掌握消息類型對應關係、CDATA 使用、5秒響應限制、素材管理,配合成熟 SDK 可快速構建穩健的公眾號後端。