微信公眾號開發 (2) 消息處理

一、前言

本文將實現

  1. 接收消息
  2. 回覆消息

二、消息接收

消息接收POST和微信認證GET是同一個接口(開發者填寫的URL)

微信公眾號開發 (2) 消息處理

<code>@Slf4j
@RestController
@RequestMapping("/api/weixin/index")
@Api(tags = "微信 - 接口")
public class IndexController extends BaseController {

/**
* 解析請求消息,post請求
*/
@PostMapping
public void msgProcess(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 獲取請求的字節流
ServletInputStream inputStream = request.getInputStream();
// 轉換為字符流, 得到緩衝流
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
BufferedReader reader = new BufferedReader(inputStreamReader);
String content = null;
// 讀取消息內容
while ((content = reader.readLine()) != null) {
System.out.println(content);
}
}

}/<code>

接收到的文本消息格式如下

其它消息格式可查看微信開放文檔:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html

<code>
<tousername>
<fromusername>
<createtime>1578996487/<createtime>
<msgtype>
<content>
<msgid>22605792697428179/<msgid>
/<code>


三、消息處理

當用戶發送消息給公眾號時(或某些特定的用戶操作引發的事件推送時),會產生一個POST請求,開發者可以在響應包(Get)中返回特定XML結構,來對該消息進行響應(現支持回覆文本、圖片、圖文、語音、視頻、音樂)。

溫馨小提示:這裡的消息封裝可根據https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html中給出的消息格式自行封裝,完整代碼可參考文末給出的源碼哦~

1、封裝消息類型枚舉類

微信公眾號開發 (2) 消息處理

① 請求消息類型


<code>public enum EnumRequestMessageType {
// 請求消息類型
TEXT("text", "文本"),
IMAGE("image", "圖片"),
VOICE("voice", "語音"),
VIDEO("video", "視頻"),
SHORTVIDEO("shortvideo", "小視頻"),
LOCATION("location", "地理位置"),
LINK("link", "鏈接"),
// 事件推送
EVENT("event", "推送");
}/<code>


② 事件推送類型


<code>public enum EnumEventMessageType {
SUBSCRIBE("subscribe", "訂閱"),
UNSUBSCRIBE("unsubscribe", "取消訂閱"),
CLICK("CLICK", "自定義菜單點擊事件"),
SCAN("SCAN", "用戶已關注時的事件推送");
}/<code>


③ 響應消息類型


<code>public enum EnumResponseMessageType {
TEXT("text", "文本"),
IMAGE("image", "圖片"),
VOICE("voice", "語音"),
VIDEO("video", "視頻"),
MUSIC("music", "音樂"),
NEWS("news", "圖文");
}/<code>


2、封裝請求消息

微信公眾號開發 (2) 消息處理

① 請求消息基類


<code>@Data
@ApiModel(description = "請求消息-基類")
public class BaseMessageDTO {

@ApiModelProperty(value = "開發者微信號")
private String ToUserName;

@ApiModelProperty(value = "發送方帳號(一個OpenID)")
private String FromUserName;

@ApiModelProperty(value = "消息創建時間 (整型)")
private long CreateTime;

@ApiModelProperty(value = "消息id,64位整型")
private long MsgId;

}/<code>


② 事件推送


<code>@Data
@ApiModel(description = "事件推送")
public class EventMessageDTO extends BaseMessageDTO {

@ApiModelProperty(value = "消息類型", hidden = true)
private String MsgType = EnumRequestMessageType.EVENT.getType();

@ApiModelProperty(value = "文本消息內容")
private String Event;

}/<code>


③ 圖片消息


<code>@Data
@ApiModel(description = "圖片消息")
public class ImageMessageDTO extends BaseMessageDTO {

@ApiModelProperty(value = "消息類型", hidden = true)
private String MsgType = EnumRequestMessageType.IMAGE.getType();

@ApiModelProperty(value = "圖片鏈接(由系統生成)")
private String PicUrl;

@ApiModelProperty(value = "圖片消息媒體id,可以調用獲取臨時素材接口拉取數據")
private String MediaId;

}/<code>


④ 鏈接消息


<code>@Data
@ApiModel(description = "鏈接消息")
public class LinkMessageDTO extends BaseMessageDTO {

@ApiModelProperty(value = "消息類型", hidden = true)
private String MsgType = EnumRequestMessageType.LINK.getType();

@ApiModelProperty(value = "消息標題")
private String Title;

@ApiModelProperty(value = "消息描述")
private String Description;

@ApiModelProperty(value = "消息鏈接")
private String Url;

}/<code>


⑤ 地理位置消息


<code>@Data
@ApiModel(description = "地理位置消息")
public class LocationMessageDTO extends BaseMessageDTO {

@ApiModelProperty(value = "消息類型", hidden = true)
private String MsgType = EnumRequestMessageType.LOCATION.getType();

@ApiModelProperty(value = "地理位置維度")
private String Location_X;

@ApiModelProperty(value = "地理位置經度")
private String Location_Y;

@ApiModelProperty(value = "地圖縮放大小")
private String Scale;

@ApiModelProperty(value = "地理位置信息")
private String Label;

}/<code>


⑥ 小視頻消息


<code>@Data
@ApiModel(description = "小視頻消息")
public class ShortVideoMessageDTO extends BaseMessageDTO {

@ApiModelProperty(value = "消息類型", hidden = true)
private String MsgType = EnumRequestMessageType.SHORTVIDEO.getType();

@ApiModelProperty(value = "視頻消息媒體id,可以調用獲取臨時素材接口拉取數據")

private String MediaId;

@ApiModelProperty(value = "視頻消息縮略圖的媒體id,可以調用獲取臨時素材接口拉取數據")
private String ThumbMediaId;

}/<code>


⑦ 文本消息


<code>@Data
@ApiModel(description = "文本消息")
public class TextMessageDTO extends BaseMessageDTO {

@ApiModelProperty(value = "消息類型", hidden = true)
private String MsgType = EnumRequestMessageType.TEXT.getType();

@ApiModelProperty(value = "文本消息內容")
private String Content;

}/<code>


⑧ 視頻消息


<code>@Data
@ApiModel(description = "視頻消息")
public class VideoMessageDTO extends BaseMessageDTO {

@ApiModelProperty(value = "消息類型", hidden = true)
private String MsgType = EnumRequestMessageType.VIDEO.getType();

@ApiModelProperty(value = "視頻消息媒體id,可以調用獲取臨時素材接口拉取數據")

private String MediaId;

@ApiModelProperty(value = "視頻消息縮略圖的媒體id,可以調用多媒體文件下載接口拉取數據")
private String ThumbMediaId;

}/<code>


⑨ 語音消息


<code>@Data
@ApiModel(description = "語音消息")
public class VoiceMessageDTO extends BaseMessageDTO {

@ApiModelProperty(value = "消息類型", hidden = true)
private String MsgType = EnumRequestMessageType.VOICE.getType();

@ApiModelProperty(value = "語音消息媒體id,可以調用獲取臨時素材接口拉取數據")
private String MediaId;

@ApiModelProperty(value = "語音格式,如amr,speex等")
private String Format;

/**
* 請注意,開通語音識別後,用戶每次發送語音給公眾號時,微信會在推送的語音消息XML數據包中,
* 增加一個Recognition字段(注:由於客戶端緩存,開發者開啟或者關閉語音識別功能,對新關注者立刻生效,對已關注用戶需要24小時生效。開發者可以重新關注此帳號進行測試)
*/

@ApiModelProperty(value = "語音識別結果,UTF8編碼")
private String Recognition;

}/<code>


3、封裝響應消息

微信公眾號開發 (2) 消息處理

① 響應消息基類


<code>@Data
@ApiModel(description = "響應消息-基類")
public class BaseMessageVO {


@ApiModelProperty(value = "接收方帳號(收到的OpenID)")
private String ToUserName;

@ApiModelProperty(value = "開發者微信號")
private String FromUserName;

@ApiModelProperty(value = "消息創建時間 (整型)")
private long CreateTime;

@ApiModelProperty(value = "位0x0001被標誌時,星標剛收到的消息")
private int FuncFlag = 0;

}/<code>


② 圖片消息


<code>@Data
@ApiModel(description = "圖片消息")
public class ImageMessageVO extends BaseMessageVO {

@ApiModelProperty(value = "消息類型", hidden = true)
private String MsgType = EnumResponseMessageType.IMAGE.getType();

@ApiModelProperty(value = "圖片")
private Image Image;

@Data
@AllArgsConstructor
@ApiModel(description = "圖片消息中Image類的定義")
public static class Image {

@ApiModelProperty(value = "通過素材管理中的接口上傳多媒體文件,得到的id")
private String MediaId;

}


}/<code>


③ 音樂消息


<code>@Data
@ApiModel(description = "音樂消息")
public class MusicMessageVO extends BaseMessageVO {

@ApiModelProperty(value = "消息類型", hidden = true)
private String MsgType = EnumResponseMessageType.MUSIC.getType();

@ApiModelProperty(value = "音樂")
private Music Music;

@Data
@AllArgsConstructor
@ApiModel(description = "音樂消息中Music類的定義")
public static class Music {

@ApiModelProperty(value = "音樂標題")
private String Title;

@ApiModelProperty(value = "音樂描述")
private String Description;

@ApiModelProperty(value = "音樂鏈接")
private String MusicUrl;

@ApiModelProperty(value = "高質量音樂鏈接,WIFI環境優先使用該鏈接播放音樂")
private String HQMusicUrl;

@ApiModelProperty(value = "縮略圖的媒體id,通過素材管理中的接口上傳多媒體文件,得到的id")
private String ThumbMediaId;

}


}/<code>


④ 圖文消息


<code>@Data
@ApiModel(description = "圖文消息")
public class NewsMessageVO extends BaseMessageVO {

@ApiModelProperty(value = "消息類型", hidden = true)
private String MsgType = EnumResponseMessageType.NEWS.getType();

@ApiModelProperty(value = "圖文消息個數;當用戶發送文本、圖片、視頻、圖文、地理位置這五種消息時,開發者只能回覆1條圖文消息;其餘場景最多可回覆8條圖文消息")
private int ArticleCount = 0;

@ApiModelProperty(value = "圖文消息信息,注意,如果圖文數超過限制,則將只發限制內的條數")
private List<article> Articles;

@Data
@ApiModel(description = "圖文消息中Article類的定義")
public static class Article {

@ApiModelProperty(value = "圖文消息標題")
private String Title;

@ApiModelProperty(value = "圖文消息描述")
private String Description = "";

@ApiModelProperty(value = "圖片鏈接,支持JPG、PNG格式,較好的效果為大圖360*200,小圖200*200")
private String PicUrl = "";


@ApiModelProperty(value = "點擊圖文消息跳轉鏈接")
private String Url = "";

}

public void addArticle(Article article) {
if (Articles == null) {
Articles = Lists.newLinkedList();
}
Articles.add(article);
ArticleCount++;
}

}/<article>/<code>


⑤ 文本消息


<code>@Data
@ApiModel(description = "文本消息")
public class TextMessageVO extends BaseMessageVO {

@ApiModelProperty(value = "消息類型", hidden = true)
private String MsgType = EnumResponseMessageType.TEXT.getType();

@ApiModelProperty(value = "回覆的消息內容(換行:在content中能夠換行,微信客戶端就支持換行顯示)")
private String Content;

}/<code>


⑥ 視頻消息


<code>@Data 

@ApiModel(description = "視頻消息")
public class VideoMessageVO extends BaseMessageVO {

@ApiModelProperty(value = "消息類型", hidden = true)
private String MsgType = EnumResponseMessageType.VIDEO.getType();

@ApiModelProperty(value = "視頻")
private Video Video;

@Data
@AllArgsConstructor
@ApiModel(description = "視頻消息中Voice類的定義")
public static class Video {

@ApiModelProperty(value = "通過素材管理中的接口上傳多媒體文件,得到的id")
private String MediaId;

@ApiModelProperty(value = "視頻消息的標題")
private String Title;

@ApiModelProperty(value = "視頻消息的描述")
private String Description;

}

}/<code>


⑦ 語音消息


<code>@Data
@ApiModel(description = "語音消息")
public class VoiceMessageVO extends BaseMessageVO {

@ApiModelProperty(value = "消息類型", hidden = true)
private String MsgType = EnumResponseMessageType.VOICE.getType();

@ApiModelProperty(value = "語音")
private Voice Voice;


@Data
@AllArgsConstructor
@ApiModel(description = "語音消息中Voice類的定義")
public static class Voice {

@ApiModelProperty(value = "通過素材管理中的接口上傳多媒體文件,得到的id")
private String MediaId;

}

}/<code>


4、消息處理工具類


① pom.xml中引入所需依賴


<code>

<dependency>
<groupid>org.dom4j/<groupid>
<artifactid>dom4j/<artifactid>
<version>2.1.1/<version>
/<dependency>

<dependency>
<groupid>jaxen/<groupid>
<artifactid>jaxen/<artifactid>
<version>1.1.6/<version>
/<dependency>



<dependency>
<groupid>com.thoughtworks.xstream/<groupid>
<artifactid>xstream/<artifactid>
<version>1.4.11.1/<version>
/<dependency>/<code>


② 工具類


  1. 解析微信發來的請求(xml)
  2. 將響應消息的Java對象轉換成xml
<code>public class MessageUtil {

/**
* 解析微信發來的請求(XML)
*
* @param request
* @return
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static Map<string> parseXml(HttpServletRequest request) throws Exception {
// 將解析結果存儲在HashMap中
Map<string> map = new HashMap<>(10);

// 從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;
}

/**
* 文本消息對象轉換成xml
*
* @param textMessageVO 文本消息對象 XStream是一個Java對象和XML相互轉換的工具
* @return xml
*/
public static String textMessageToXml(TextMessageVO textMessageVO) {
xstream.alias("xml", textMessageVO.getClass());
return xstream.toXML(textMessageVO);
}

/**
* 圖片消息對象轉換成xml
*
* @param imageMessageVO 圖片消息對象
* @return xml
*/
public static String imageMessageToXml(ImageMessageVO imageMessageVO) {
xstream.alias("xml", imageMessageVO.getClass());
return xstream.toXML(imageMessageVO);
}

/**
* 語音消息對象轉換成xml
*
* @param voiceMessageVO 語音消息對象
* @return xml
*/
public static String voiceMessageToXml(VoiceMessageVO voiceMessageVO) {
xstream.alias("xml", voiceMessageVO.getClass());
return xstream.toXML(voiceMessageVO);

}

/**
* 視頻消息對象轉換成xml
*
* @param videoMessageVO 視頻消息對象
* @return xml
*/
public static String videoMessageToXml(VideoMessageVO videoMessageVO) {
xstream.alias("xml", videoMessageVO.getClass());
return xstream.toXML(videoMessageVO);
}

/**
* 音樂消息對象轉換成xml
*
* @param musicMessage 音樂消息對象
* @return xml
*/
public static String musicMessageToXml(MusicMessageVO musicMessage) {
xstream.alias("xml", musicMessage.getClass());
return xstream.toXML(musicMessage);
}

/**
* 圖文消息對象轉換成xml
*
* @param newsMessage 圖文消息對象
* @return xml
*/
public static String newsMessageToXml(NewsMessageVO newsMessage) {
xstream.alias("xml", newsMessage.getClass());
xstream.alias("item", NewsMessageVO.Article.class);
return xstream.toXML(newsMessage);
}

/**
* 擴展xstream,使其支持CDATA塊
*/
private static XStream xstream = new XStream(new XppDriver() {
@Override
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 對所有xml節點的轉換都增加CDATA標記

boolean cdata = true;

@Override
@SuppressWarnings("unchecked")
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}

@Override
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write(" writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});

}/<element>/<string>/<string>/<code>


四、回覆消息


1、回覆文本消息


<code>@PostMapping
public void msgProcess(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 將請求、響應的編碼均設置為UTF-8(防止中文亂碼)
request.setCharacterEncoding("UTF-8");
response.setCharacterEncoding("UTF-8");
// 調用核心業務類接收消息、處理消息
String respMessage = msgService.processRequest(request);
// 響應消息
PrintWriter out = response.getWriter();

out.print(respMessage);
out.close();
}/<code>

其中的processRequest消息處理方法邏輯如下

<code>@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class MsgServiceImpl implements IMsgService {

@Override
public String processRequest(HttpServletRequest request) {
String respMessage = null;
try {
// xml請求解析
Map<string> requestMap = MessageUtil.parseXml(request);

// 發送方帳號(open_id)
String fromUserName = requestMap.get("FromUserName");
// 公眾帳號
String toUserName = requestMap.get("ToUserName");
// 消息類型
String msgType = requestMap.get("MsgType");

// 回覆文本消息
TextMessageVO textMessage = new TextMessageVO();
textMessage.setToUserName(fromUserName);
textMessage.setFromUserName(toUserName);
textMessage.setCreateTime(System.currentTimeMillis());
textMessage.setMsgType(EnumResponseMessageType.TEXT.getType());

// 默認返回的文本消息內容
String respContent = "請求處理異常,請稍候重試!";

// 事件推送
if (EnumRequestMessageType.getEnum(msgType) == EnumRequestMessageType.EVENT) {
// 事件類型
String eventType = requestMap.get("Event");
switch (EnumEventMessageType.getEnum(eventType)) {
// 訂閱
case SUBSCRIBE:
respContent = "親,感謝您的關注!";

break;
// 取消訂閱
case UNSUBSCRIBE:
// TODO 取消訂閱後用戶再收不到公眾號發送的消息,因此不需要回復消息
break;
// 自定義菜單點擊事件
case CLICK:
// TODO 自定義菜單未實現,暫不處理該類消息
break;
default:
break;
}
} else {
respContent = EnumRequestMessageType.getEnum(msgType).getTypeValue();
}
textMessage.setContent(respContent);
respMessage = MessageUtil.textMessageToXml(textMessage);
} catch (Exception e) {
e.printStackTrace();
}
return respMessage;
}

}/<string>/<code>

測試

微信公眾號開發 (2) 消息處理

2、 原樣返回消息內容

原樣返回消息內容,可調用IMsgService中的processRequestReturnSameMsg方法

<code>public String processRequestReturnSameMsg(HttpServletRequest request) {
String respMessage = null;
try {
// xml請求解析
Map<string> requestMap = MessageUtil.parseXml(request);

// 發送方帳號(open_id)
String fromUserName = requestMap.get("FromUserName");
// 公眾帳號
String toUserName = requestMap.get("ToUserName");
// 消息類型
String msgType = requestMap.get("MsgType");

// 回覆消息
BaseMessageVO baseMessageVO = new BaseMessageVO();
baseMessageVO.setToUserName(fromUserName);
baseMessageVO.setFromUserName(toUserName);
baseMessageVO.setCreateTime(System.currentTimeMillis());

switch (EnumRequestMessageType.getEnum(msgType)) {
// 文本消息
case TEXT:
TextMessageVO textMessageVO = BeanUtil.copyProperties(baseMessageVO, TextMessageVO.class);
textMessageVO.setContent(requestMap.get("Content"));
respMessage = MessageUtil.textMessageToXml(textMessageVO);
break;
// 圖片消息
case IMAGE:
ImageMessageVO imageMessageVO = BeanUtil.copyProperties(baseMessageVO, ImageMessageVO.class);
ImageMessageVO.Image image = new ImageMessageVO.Image(requestMap.get("MediaId"));
imageMessageVO.setImage(image);
respMessage = MessageUtil.imageMessageToXml(imageMessageVO);
break;
// 語音消息
case VOICE:
VoiceMessageVO voiceMessageVO = BeanUtil.copyProperties(baseMessageVO, VoiceMessageVO.class);
VoiceMessageVO.Voice voice = new VoiceMessageVO.Voice(requestMap.get("MediaId"));
voiceMessageVO.setVoice(voice);

respMessage = MessageUtil.voiceMessageToXml(voiceMessageVO);
break;
// 事件推送
case EVENT:
// 事件類型
String eventType = requestMap.get("Event");
switch (EnumEventMessageType.getEnum(eventType)) {
// 訂閱
case SUBSCRIBE:
NewsMessageVO newsMessage = BeanUtil.copyProperties(baseMessageVO, NewsMessageVO.class);
NewsMessageVO.Article article = new NewsMessageVO.Article();
article.setTitle("感謝您的關注,這是一條圖文消息!");
article.setDescription("mysql轉oracle筆記");
article.setPicUrl("https://mmbiz.qpic.cn/mmbiz_png/iaUVVC0premhqE0TrtLzM6ABMIKKjnu81hraZvXia52byYMqADCyqXKwbs2wJ6jiadWc7MypLKL4EC5mUzXZKH2Rg/640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1");
article.setUrl("https://mp.weixin.qq.com/s?__biz=Mzg2NzEwMjc3Ng==&mid=2247483813&idx=1&sn=7a18081426d014ddccd203d33011f526&chksm=ce41f8f2f93671e4fc5d93292360fd24da4cf1434415befc37f25be2d40780b4a485653861d8&mpshare=1&scene=1&srcid=&sharer_sharetime=1579140222251&sharer_shareid=936076bf8d5bee83e89fd7e769b5c6db&key=c62bc26d01d4cb91d588b8abdeaca0fbba6d713fbc52e3b4c4a9f0377e231e0fe6b4ce07f287f509e37cefa17a0346475f12d85e21bcdbb8e953d0685018a874fbd80005417e94836ad9b0ff7559b334&ascene=1&uin=MTg4MzA0MzMxNA%3D%3D&devicetype=Windows+7&version=62070158&lang=zh_CN&exportkey=AYq%2FJJZv5hRr7YLluyVInZk%3D&pass_ticket=vCPgwidZSOs1xBfcd5SrzkCdVlApSWF7Xc%2BOzjYf8GlJ9%2BLQco9XYzTHe9yWHqc1");
newsMessage.addArticle(article);
respMessage = MessageUtil.newsMessageToXml(newsMessage);
break;
// 取消訂閱
case UNSUBSCRIBE:
// TODO 取消訂閱後用戶再收不到公眾號發送的消息,因此不需要回復消息
break;
// 自定義菜單點擊事件
case CLICK:
// TODO 自定義菜單未實現,暫不處理該類消息
break;
default:
break;
}
break;
default:
break;
}
} catch (Exception e) {
e.printStackTrace();
}
return respMessage;
}/<string>/<code>

測試

微信公眾號開發 (2) 消息處理

就這樣一個簡單的接受消息與回覆消息就完成了,關於消息處理我們可以根據微信提供的官方文檔去學習即可~

本文案例demo源碼

https://gitee.com/zhengqingya/java-workspace


分享到:


相關文章: