微信公眾號官方文檔 --> 入門指引:https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html
一、消息管理
1、消息接收
在上篇中,我們實現了應用服務器和微信騰訊服務器之間的對接操作,本篇主要實現java后台對微信公眾號消息的接收。
當我們在完成了服務器驗證之后,此后用戶每次向公眾號發送消息、或者產生自定義菜單點擊事件時,開發者填寫的服務器配置URL將得到微信服務器推送過來的消息和事件,然后開發者可以依據自身業務邏輯進行響應,例如回復消息等!通過這句話我們能知道后面所有的微信服務器和我們應用服務器之間的溝通都是通過post消息體來完成的,那么我們這里將講述如何接受微信post的消息體!
1. 官方提供的消息類型和格式
當普通微信用戶向公眾賬號發消息時,微信服務器將POST消息的XML數據包到開發者填寫的URL上。(與上篇中校驗是同一個路徑,但是請求方法不同)
- 普通消息類型:文本消息、圖片消息、語音消息、視頻消息、小視頻消息、地理位置消息、鏈接消息
- 事件消息類型:關注/取消關注事件、掃描帶參數二維碼事件、上報地理位置事件、自定義菜單事件、點擊菜單拉取消息時的事件推送、點擊菜單跳轉鏈接時的事件推送
(鏈接-link /地理位置-location /小視頻-shortvideo/視頻-video /語音-voice /圖片-image /文本-text)
請注意:
- 關於重試的消息排重,推薦使用MsgId排重。
- 微信服務器在五秒內收不到響應會斷掉連接,並且重新發起請求,總共重試三次。假如服務器無法保證在五秒內處理並回復,可以直接回復空串,微信服務器不會對此作任何處理,並且不會發起重試。詳情請見“發送消息-被動回復消息”。
- 如果開發者需要對用戶消息在5秒內立即做出回應,即使用“發送消息-被動回復消息”接口向用戶被動回復消息時,可以在
公眾平台官網的開發者中心處設置消息加密。開啟加密后,用戶發來的消息和開發者回復的消息都會被加密(但開發者通過客服接口等API調用形式向用戶發送消息,則不受影響)
舉例:官方返回的信息值 以文本消息為例
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[fromUser]]></FromUserName>
<CreateTime>1348831860</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[this is a test]]></Content>
<MsgId>1234567890123456</MsgId>
</xml>
參數解釋:
參數 | 描述 |
---|---|
ToUserName | 開發者微信號 |
FromUserName | 發送方帳號(一個OpenID) |
CreateTime | 消息創建時間 (整型) |
MsgType | 消息類型,文本為text |
Content | 文本消息內容 |
MsgId | 消息id,64位整型 |
2、消息回復
在前面我們已經完成了對消息的分類和回復消息實體的建立,這里回復文本消息需要用到的就是我們的TextMessage,我們把回復文本消息在【文本消息】類型中給出回復!在我們做消息回復的時候需要設置消息的接收人ToUserName(openid)、消息的發送方FromUserName、消息類型MsgType、創建時間CreateTime以及消息體Content,由於我們我們的消息回復格式是需要為xml,所以最終我們需要將其裝換成xml再做返回輸出!
二、代碼實現
1、根據官方文檔創建相關實體類
1. 基類(公共參數)
import lombok.Data;
/**
* @author yh
* @date 2020/8/21 10:13
* @description: 微信請求消息基本類
*/
@Data
public class BaseMessage {
// 開發者微信號
private String ToUserName;
// 發送方帳號(一個OpenID)
private String FromUserName;
// 消息創建時間 (整型)
private Long CreateTime;
// 消息類型(鏈接-link /地理位置-location /小視頻-shortvideo/視頻-video /語音-voice /圖片-image /文本-text)
private String MsgType;
// 消息id,64位整型
private Long MsgId;
}
2. 鏈接消息類
import lombok.Data;
/**
* @author yh
* @date 2020/8/21 10:14
* @description: 鏈接消息
*/
@Data
public class LinkMessage extends BaseMessage{
// 消息標題
private String Title;
// 消息描述
private String Description;
// 消息鏈接
private String Url;
}
3. 圖片消息類
import lombok.Data;
/**
* @author yh
* @date 2020/8/21 10:14
* @description: 圖片消息
*/
@Data
public class ImageMessage extends BaseMessage {
// 圖片鏈接
private String PicUrl;
//圖片消息媒體id,可以調用獲取臨時素材接口拉取數據。
private String MediaId;
}
4. 地理位置消息類
import lombok.Data;
/**
* @author yh
* @date 2020/8/21 10:15
* @description: 地理位置消息
*/
@Data
public class LocationMessage extends BaseMessage {
// 地理位置維度
private String Location_X;
// 地理位置經度
private String Location_Y;
// 地圖縮放大小
private String Scale;
// 地理位置信息
private String Label;
}
5. 文本消息類
import lombok.Data;
/**
* @author yh
* @date 2020/8/21 10:16
* @description: 文本消息
*/
@Data
public class TextMessage extends BaseMessage{
// 消息內容
private String Content;
}
6. 語音消息類
import lombok.Data;
/**
* @author yh
* @date 2020/8/21 10:17
* @description: 語音消息
*/
@Data
public class VoiceMessage extends BaseMessage {
// 語音消息媒體id,可以調用獲取臨時素材+接口拉取數據。
private String MediaId;
// 語音格式
private String Format;
}
7. 視頻/小視頻消息類
import lombok.Data;
/**
* @author yh
* @date 2020/8/21 10:16
* @description: 視頻/小視頻消息
*/
@Data
public class VideoMessage extends BaseMessage{
// 視頻消息媒體id,可以調用多媒體文件下載接口拉取數據
private String MediaId;
// 視頻消息縮略圖的媒體id,可以調用多媒體文件下載接口拉取數據
private String ThumbMediaId;
}
2、POM依賴
<!-- xml -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
</dependency>
3、控制層
1.api
/**
* @author: yh
* @description: token驗證
* @date: 2020/8/20
* @param request
* @return void
**/
@GetMapping("/wxRequest")
String doGet(HttpServletRequest request, HttpServletResponse response);
/**
* @author: yh
* @description: 消息的接收和處理
* @date: 2020/8/21
* @param request
* @param response
* @return void
**/
@PostMapping("/wxRequest")
void doPost(HttpServletRequest request, HttpServletResponse response);
2.controller
/**
* @author: yh
* @description: token驗證
* @date: 2020/8/20
* @param request
* @return void
**/
@Override
public String doGet(HttpServletRequest request,HttpServletResponse response) {
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
log.info("signature[{}], timestamp[{}], nonce[{}], echostr[{}]", signature, timestamp, nonce, echostr);
if(SignUtil.checkSignature(signature, timestamp, nonce)){
log.info("數據源為微信后台,將echostr[{}]返回!", echostr);
return echostr;
}
return "";
}
/**
* @author: yh
* @description: 消息/事件的接收和處理
* @date: 2020/8/21
* @param request
* @param response
* @return void
**/
@Override
public void doPost(HttpServletRequest request, HttpServletResponse response) {
try {
Map<String, String> messageMap = MessageUtil.parseXml(request);
System.out.println("消息:"+messageMap.get("Content"));
String type = messageMap.get("MsgType");
if(MessageUtil.REQ_MESSAGE_TYPE_EVENT.equals(type)){
//事件處理
EventDispatcher.processEvent(messageMap);
}else {
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
//消息處理
String data = MsgDispatcher.processMessage(messageMap);
PrintWriter out = response.getWriter();
out.print(data);
out.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
4、util- MessageUtil
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 org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.io.Writer;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author yh
* @date 2020/8/20 17:14
* @description: 消息工具類
*/
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_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";
/**
* 事件類型:CLICK(自定義菜單點擊事件)
*/
public static final String EVENT_TYPE_CLICK = "CLICK";
/**
* 事件類型:VIEW(掃描二維碼事件)
*/
public static final String EVENT_TYPE_SCAN = "SCAN";
/**
* 事件類型:LOCATION(位置上報事件)
*/
public static final String EVENT_TYPE_LOCATION = "LOCATION";
/**
* 事件類型:VIEW(自定義菜單View事件)
*/
public static final String EVENT_TYPE_VIEW = "VIEW";
/**
* @author: yh
* @description: 解析微信發來的請求(XML)
* @date: 2020/8/21
* @param request
* @return java.util.Map<java.lang.String,java.lang.String>
**/
@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;
}
/**
* @author: yh
* @description: 對象到xml的處理
* @date: 2020/8/21
* @param null
* @return
**/
@SuppressWarnings("unused")
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);
}
}
};
}
});
/**
* @author: yh
* @description: 文本消息對象轉換成xml
* @date: 2020/8/21
* @param textMessageRes
* @return java.lang.String
**/
public static String textMessageToXml(TextMessageRes textMessageRes) {
xstream.alias("xml", textMessageRes.getClass());
return xstream.toXML(textMessageRes);
}
/**
* @author: yh
* @description: 圖文消息對象轉換成xml
* @date: 2020/8/21
* @param articlesMessageRes
* @return java.lang.String
**/
public static String newsMessageToXml(ArticlesMessageRes articlesMessageRes) {
xstream.alias("xml", articlesMessageRes.getClass());
xstream.alias("item", new Article().getClass());
return xstream.toXML(articlesMessageRes);
}
/**
* @author: yh
* @description: 圖片消息對象轉換成xml
* @date: 2020/8/21
* @param imageMessageRes
* @return java.lang.String
**/
public static String imageMessageToXml(ImageMessageRes imageMessageRes) {
xstream.alias("xml", imageMessageRes.getClass());
return xstream.toXML(imageMessageRes);
}
/**
* @author: yh
* @description: 語音消息對象轉換成xml
* @date: 2020/8/21
* @param voiceMessageRes
* @return java.lang.String
**/
public static String voiceMessageToXml(VoiceMessageRes voiceMessageRes) {
xstream.alias("xml", voiceMessageRes.getClass());
return xstream.toXML(voiceMessageRes);
}
/**
* @author: yh
* @description: 視頻消息對象轉換成xml
* @date: 2020/8/21
* @param videoMessageRes
* @return java.lang.String
**/
public static String videoMessageToXml(VideoMessageRes videoMessageRes) {
xstream.alias("xml", videoMessageRes.getClass());
return xstream.toXML(videoMessageRes);
}
/**
* @author: yh
* @description: 音樂消息對象轉換成xml
* @date: 2020/8/21
* @param musicMessageRes
* @return java.lang.String
**/
public static String musicMessageToXml(MusicMessageRes musicMessageRes) {
xstream.alias("xml", musicMessageRes.getClass());
return xstream.toXML(musicMessageRes);
}
}
5、業務分發處理
1. 消息分類
import java.util.Date;
import java.util.Map;
/**
* @author yh
* @date 2020/8/21 10:19
* @description: 微信消息業務處理分發器
*/
public class MsgDispatcher {
public static String processMessage(Map<String, String> map) {
String openid=map.get("FromUserName"); //用戶openid
String mpid=map.get("ToUserName"); //公眾號原始ID
TextMessageRes textMessageRes = new TextMessageRes();
textMessageRes.setToUserName(openid);
textMessageRes.setFromUserName(mpid);
textMessageRes.setCreateTime(new Date().getTime());
textMessageRes.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) { // 文本消息
System.out.println("==============這是文本消息!");
String jsonString = JSONObject.toJSONString(map);
TextMessageReq textMessage = JSONObject.parseObject(jsonString, TextMessageReq.class);
textMessageRes.setContent("你好,這里是測試回復");
return MessageUtil.textMessageToXml(textMessageRes);
}
if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_IMAGE)) { // 圖片消息
System.out.println("==============這是圖片消息!");
String jsonString = JSONObject.toJSONString(map);
ImageMessageReq imageMessage = JSONObject.parseObject(jsonString, ImageMessageReq.class);
}
if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_LINK)) { // 鏈接消息
System.out.println("==============這是鏈接消息!");
String jsonString = JSONObject.toJSONString(map);
LinkMessageReq linkMessage = JSONObject.parseObject(jsonString, LinkMessageReq.class);
}
if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_LOCATION)) { // 位置消息
System.out.println("==============這是位置消息!");
String jsonString = JSONObject.toJSONString(map);
LocationMessageReq locationMessage = JSONObject.parseObject(jsonString, LocationMessageReq.class);
}
if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_VIDEO)) { // 視頻/小視頻消息
System.out.println("==============這是視頻消息!");
String jsonString = JSONObject.toJSONString(map);
VideoMessageReq videoMessage = JSONObject.parseObject(jsonString, VideoMessageReq.class);
}
if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_VOICE)) { // 語音消息
System.out.println("==============這是語音消息!");
String jsonString = JSONObject.toJSONString(map);
VoiceMessageReq voiceMessage = JSONObject.parseObject(jsonString, VoiceMessageReq.class);
}
return "";
}
}
2. 事件分類
/**
* @author yh
* @date 2020/8/21 10:19
* @description: 事件消息業務分發器
*/
public class EventDispatcher {
public static void processEvent(Map<String, String> map) {
if (map.get("Event").equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)) { //關注事件
System.out.println("==============這是關注事件!");
}
if (map.get("Event").equals(MessageUtil.EVENT_TYPE_UNSUBSCRIBE)) { //取消關注事件
System.out.println("==============這是取消關注事件!");
}
if (map.get("Event").equals(MessageUtil.EVENT_TYPE_SCAN)) { //掃描二維碼事件
System.out.println("==============這是掃描二維碼事件!");
}
if (map.get("Event").equals(MessageUtil.EVENT_TYPE_LOCATION)) { //位置上報事件
System.out.println("==============這是位置上報事件!");
}
if (map.get("Event").equals(MessageUtil.EVENT_TYPE_CLICK)) { //自定義菜單點擊事件
System.out.println("==============這是自定義菜單點擊事件!");
}
if (map.get("Event").equals(MessageUtil.EVENT_TYPE_VIEW)) { //自定義菜單View事件
System.out.println("==============這是自定義菜單View事件!");
}
}
}