用戶發送 → 微信後台 → POST XML 到開發者 URL → 開發者解析 → 回覆 XML → 微信轉發給用戶
POSTXML(req.getInputStream() 讀取)@PostMapping("/wechat")
public void handleRequest(HttpServletRequest req, HttpServletResponse resp) throws Exception {
req.setCharacterEncoding("UTF-8");
resp.setCharacterEncoding("UTF-8");
// 1. 讀取輸入流
InputStream is = req.getInputStream();
// 2. DOM4J 解析 XML 轉 Map
SAXReader reader = new SAXReader();
Document doc = reader.read(is);
Element root = doc.getRootElement();
Map<String, String> msgMap = new HashMap<>();
for (Element e : root.elements()) {
msgMap.put(e.getName(), e.getTextTrim());
}
// 3. 核心欄位
String msgType = msgMap.get("MsgType"); // text, image, voice, video, event...
String fromUser = msgMap.get("FromUserName");
String toUser = msgMap.get("ToUserName");
long createTime = System.currentTimeMillis();
// 4. 根據類型分發處理
String replyXml = buildReply(msgType, msgMap, fromUser, toUser, createTime);
// 5. 輸出回覆
resp.getWriter().write(replyXml);
}
依賴:com.thoughtworks.xstream:xstream:1.4.18
// 文字訊息
@XStreamAlias("xml")
public class TextMsg {
String ToUserName, FromUserName, CreateTime, MsgType, Content;
// getter/setter
}
// 圖文訊息
@XStreamAlias("xml")
public class NewsMsg {
String ToUserName, FromUserName, CreateTime, MsgType = "news";
int ArticleCount;
List<Article> Articles;
}
public class Article {
String Title, Description, PicUrl, Url;
}
private String toXml(Object msg) {
XStream xstream = new XStream(new DomDriver("UTF-8", new NoNameCoder()));
xstream.processAnnotations(msg.getClass());
// 子元素別名
if (msg instanceof NewsMsg) xstream.alias("item", Article.class);
return xstream.toXML(msg);
}
TextMsg text = new TextMsg();
text.setToUserName(fromUser);
text.setFromUserName(toUser);
text.setCreateTime(createTime);
text.setMsgType("text");
text.setContent("收到:" + msgMap.get("Content"));
return toXml(text);
List<Article> articles = new ArrayList<>();
Article a = new Article();
a.setTitle("SpringBoot 實戰");
a.setDescription("從入門到精通");
a.setPicUrl("https://example.com/cover.jpg");
a.setUrl("https://example.com/detail");
articles.add(a);
NewsMsg news = new NewsMsg();
news.setToUserName(fromUser);
news.setFromUserName(toUser);
news.setCreateTime(createTime);
news.setArticleCount(articles.size());
news.setArticles(articles);
return toXml(news);
// 圖片
ImageMsg img = new ImageMsg();
img.setToUserName(fromUser);
img.setFromUserName(toUser);
img.setCreateTime(createTime);
img.setMsgType("image");
img.setMediaId(mediaId); // 素材管理介面上傳獲得
return toXml(img);
| Event | 場景 | 關鍵欄位 |
|---|---|---|
| subscribe | 關注公眾號 | EventKey (qrscene_ 開頭為帶參數二維碼) |
| unsubscribe | 取消關注 | 無法回覆 |
| CLICK | 自訂選單點擊 | EventKey (選單 key) |
| VIEW | 跳轉鏈接 | 無回覆 |
| SCAN | 掃帶參數二維碼 | EventKey, Ticket |
| LOCATION | 上報地理位置 | Latitude, Longitude, Precision |
SHA1(sort(token, timestamp, nonce)) == signatureEncrypt 欄位Encrypt、MsgSignature、TimeStamp、Nonceweixin-java-mp / WxMpService 封裝庫自動處理@WebServlet("/wechat")
public class WechatServlet extends HttpServlet {
private static final String TOKEN = "your_token";
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
// 驗證簽名
String signature = req.getParameter("signature");
String timestamp = req.getParameter("timestamp");
String nonce = req.getParameter("nonce");
String echostr = req.getParameter("echostr");
String[] arr = {TOKEN, timestamp, nonce};
Arrays.sort(arr);
String sha1 = SHA1Util.encode(String.join("", arr));
if (sha1.equals(signature)) resp.getWriter().print(echostr);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
// 解析、構建回覆、輸出(同上邏輯)
}
}