當(dāng)前位置:首頁(yè) > IT技術(shù) > 微信平臺(tái) > 正文

Spring Boot 如何給微信公眾號(hào)返回消息
2021-07-29 14:50:31

hello 各位小伙伴,今天我們來(lái)繼續(xù)學(xué)習(xí)如何通過(guò) Spring Boot 開(kāi)發(fā)微信公眾號(hào)。還沒(méi)閱讀過(guò)上篇文章的小伙伴建議先看看上文,有助于理解本文:

  • Spring Boot 開(kāi)發(fā)微信公眾號(hào)后臺(tái)

上篇文章中我們將微信服務(wù)器和我們自己的服務(wù)器對(duì)接起來(lái)了,并且在自己的服務(wù)器上也能收到微信服務(wù)器發(fā)來(lái)的消息,本文我們要看的就是如何給微信服務(wù)器回復(fù)消息。

消息分類(lèi)

在討論如何給微信服務(wù)器回復(fù)消息之前,我們需要先來(lái)了解下微信服務(wù)器發(fā)來(lái)的消息主要有哪些類(lèi)型以及我們回復(fù)給微信的消息都有哪些類(lèi)型。

在上文中大家了解到,微信發(fā)送來(lái)的 xml 消息中有一個(gè) MsgType 字段,這個(gè)字段就是用來(lái)標(biāo)記消息的類(lèi)型。這個(gè)類(lèi)型可以標(biāo)記出這條消息是普通消息還是事件消息還是圖文消息等。

普通消息主要是指:

  • 文本消息
  • 圖片消息
  • 語(yǔ)音消息
  • 視頻消息
  • 小視頻消息
  • 地址位置消息
  • 鏈接消息

不同的消息類(lèi)型,對(duì)應(yīng)不同的 MsgType,這里我還是以普通消息為例,如下:

消息類(lèi)型 MsgType
文本消息 text
圖片消息 image
語(yǔ)音消息 voice
視頻消息 video
小視頻消息 shortvideo
地址位置消息 location
鏈接消息 link

大家千萬(wàn)不要以為不同類(lèi)型消息的格式是一樣的,其實(shí)是不一樣的,也就是說(shuō),MsgType 為 text 的消息和 MsgType 為 image 的消息,微信服務(wù)器發(fā)給我們的消息內(nèi)容是不一樣的,這樣帶來(lái)一個(gè)問(wèn)題就是我無(wú)法使用一個(gè) Bean 去接收不同類(lèi)型的數(shù)據(jù),因此這里我們一般使用 Map 接收即可。

這是消息的接收,除了消息的接收之外,還有一個(gè)消息的回復(fù),我們回復(fù)的消息也有很多類(lèi)型,可以回復(fù)普通消息,也可以回復(fù)圖片消息,回復(fù)語(yǔ)音消息等,不同的回復(fù)消息我們可以進(jìn)行相應(yīng)的封裝。因?yàn)椴煌姆祷叵?shí)例也是有一些共同的屬性的,例如消息是誰(shuí)發(fā)來(lái)的,發(fā)給誰(shuí),消息類(lèi)型,消息 id 等,所以我們可以將這些共同的屬性定義成一個(gè)父類(lèi),然后不同的消息再去繼承這個(gè)父類(lèi)。

返回消息類(lèi)型定義

首先我們來(lái)定義一個(gè)公共的消息類(lèi)型:

public class BaseMessage {
    private String ToUserName;
    private String FromUserName;
    private long CreateTime;
    private String MsgType;
    private long MsgId;
    //省略 getter/setter
}

在這里:

  • ToUserName 表示開(kāi)發(fā)者的微信號(hào)
  • FromUserName 表示發(fā)送方賬號(hào)(用戶的 OpenID)
  • CreateTime 消息的創(chuàng)建時(shí)間
  • MsgType 表示消息的類(lèi)型
  • MsgId 表示消息 id

這是我們的基本消息類(lèi)型,就是說(shuō),我們返回給用戶的消息,無(wú)論是什么類(lèi)型的消息,都有這幾個(gè)基本屬性。然后在此基礎(chǔ)上,我們?cè)偃U(kuò)展出文本消息、圖片消息 等。

我們來(lái)看下文本消息的定義:

public class TextMessage extends BaseMessage {
    private String Content;
    //省略 getter/setter
}

文本消息在前面消息的基礎(chǔ)上多了一個(gè) Content 屬性,因此文本消息繼承自 BaseMessage ,再額外添加一個(gè) Content 屬性即可。

其他的消息類(lèi)型也是類(lèi)似的定義,我就不一一列舉了,至于其他消息的格式,大家可以參考微信開(kāi)放文檔(http://1t.click/aPXK)。

返回消息生成

消息類(lèi)型的 Bean 定義完成之后,接下來(lái)就是將實(shí)體類(lèi)生成 XML。

首先我們定義一個(gè)消息工具類(lèi),將常見(jiàn)的消息類(lèi)型枚舉出來(lái):

/**
 * 返回消息類(lèi)型:文本
 */
public static final String RESP_MESSAGE_TYPE_TEXT = "text";
/**
 * 返回消息類(lèi)型:音樂(lè)
 */
public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
/**
 * 返回消息類(lèi)型:圖文
 */
public static final String RESP_MESSAGE_TYPE_NEWS = "news";
/**
 * 返回消息類(lèi)型:圖片
 */
public static final String RESP_MESSAGE_TYPE_Image = "image";
/**
 * 返回消息類(lèi)型:語(yǔ)音
 */
public static final String RESP_MESSAGE_TYPE_Voice = "voice";
/**
 * 返回消息類(lèi)型:視頻
 */
public static final String RESP_MESSAGE_TYPE_Video = "video";
/**
 * 請(qǐng)求消息類(lèi)型:文本
 */
public static final String REQ_MESSAGE_TYPE_TEXT = "text";
/**
 * 請(qǐng)求消息類(lèi)型:圖片
 */
public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
/**
 * 請(qǐng)求消息類(lèi)型:鏈接
 */
public static final String REQ_MESSAGE_TYPE_LINK = "link";
/**
 * 請(qǐng)求消息類(lèi)型:地理位置
 */
public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
/**
 * 請(qǐng)求消息類(lèi)型:音頻
 */
public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
/**
 * 請(qǐng)求消息類(lèi)型:視頻
 */
public static final String REQ_MESSAGE_TYPE_VIDEO = "video";
/**
 * 請(qǐng)求消息類(lèi)型:推送
 */
public static final String REQ_MESSAGE_TYPE_EVENT = "event";
/**
 * 事件類(lèi)型:subscribe(訂閱)
 */
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
/**
 * 事件類(lèi)型:unsubscribe(取消訂閱)
 */
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
/**
 * 事件類(lèi)型:CLICK(自定義菜單點(diǎn)擊事件)
 */
public static final String EVENT_TYPE_CLICK = "CLICK";
/**
 * 事件類(lèi)型:VIEW(自定義菜單 URl 視圖)
 */
public static final String EVENT_TYPE_VIEW = "VIEW";
/**
 * 事件類(lèi)型:LOCATION(上報(bào)地理位置事件)
 */
public static final String EVENT_TYPE_LOCATION = "LOCATION";
/**
 * 事件類(lèi)型:LOCATION(上報(bào)地理位置事件)
 */
public static final String EVENT_TYPE_SCAN = "SCAN";

大家注意這里消息類(lèi)型的定義,以 RESP 開(kāi)頭的表示返回的消息類(lèi)型,以 REQ 表示微信服務(wù)器發(fā)來(lái)的消息類(lèi)型。然后在這個(gè)工具類(lèi)中再定義兩個(gè)方法,用來(lái)將返回的對(duì)象轉(zhuǎn)換成 XML:

public static String textMessageToXml(TextMessage textMessage) {
    xstream.alias("xml", textMessage.getClass());
    return xstream.toXML(textMessage);
}
private static XStream xstream = new XStream(new XppDriver() {
    public HierarchicalStreamWriter createWriter(Writer out) {
        return new PrettyPrintWriter(out) {
            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);
                }
            }
        };
    }
});

textMessageToXML 方法用來(lái)將 TextMessage 對(duì)象轉(zhuǎn)成 XML 返回給微信服務(wù)器,類(lèi)似的方法我們還需要定義 imageMessageToXml、voiceMessageToXml 等,不過(guò)定義的方式都基本類(lèi)似,我就不一一列出來(lái)了。

返回消息分發(fā)

由于用戶發(fā)來(lái)的消息可能存在多種情況,我們需要分類(lèi)進(jìn)行處理,這個(gè)就涉及到返回消息的分發(fā)問(wèn)題。因此我在這里再定義一個(gè)返回消息分發(fā)的工具類(lèi),如下:

public class MessageDispatcher {
    public static String processMessage(Map<String, String> map) {
        String openid = map.get("FromUserName"); //用戶 openid
        String mpid = map.get("ToUserName");   //公眾號(hào)原始 ID
        if (map.get("MsgType").equals(MessageUtil.REQ_MESSAGE_TYPE_TEXT)) { 
            //普通文本消息
            TextMessage txtmsg = new TextMessage();
            txtmsg.setToUserName(openid);
            txtmsg.setFromUserName(mpid);
            txtmsg.setCreateTime(new Date().getTime());
            txtmsg.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
            txtmsg.setContent("這是返回消息");
            return MessageUtil.textMessageToXml(txtmsg);
        }
        return null;
    }
    public String processEvent(Map<String, String> map) {
        //在這里處理事件
    }
}

這里我們還可以多加幾個(gè) elseif 去判斷不同的消息類(lèi)型,我這里因?yàn)橹挥衅胀ㄎ谋鞠?,所以一個(gè) if 就夠用了。

在這里返回值我寫(xiě)死了,實(shí)際上這里需要根據(jù)微信服務(wù)端傳來(lái)的 Content 去數(shù)據(jù)中查詢,將查詢結(jié)果返回,數(shù)據(jù)庫(kù)查詢這一套相信大家都能搞定,我這里就不重復(fù)介紹了。

最后在消息接收 Controller 中調(diào)用該方法,如下:

@PostMapping(value = "/verify_wx_token",produces = "application/xml;charset=utf-8")
public String handler(HttpServletRequest request, HttpServletResponse response) throws Exception {
    request.setCharacterEncoding("UTF-8");
    Map<String, String> map = MessageUtil.parseXml(request);
    String msgType = map.get("MsgType");
    if (MessageUtil.REQ_MESSAGE_TYPE_EVENT.equals(msgType)) {
        return messageDispatcher.processEvent(map);
    }else{
        return messageDispatcher.processMessage(map);
    }
}

在 Controller 中,我們首先判斷消息是否是事件,如果是事件,進(jìn)入到事件處理通道,如果不是事件,則進(jìn)入到消息處理通道。

注意,這里需要配置一下返回消息的編碼,否則可能會(huì)出現(xiàn)中文亂碼。

如此之后,我們的服務(wù)器就可以給公眾號(hào)返回消息了。

上篇文章發(fā)出后,有小伙伴問(wèn)松哥這個(gè)會(huì)不會(huì)開(kāi)源,我可以負(fù)責(zé)任的告訴大家,肯定會(huì)開(kāi)源,這個(gè)系列截稿后,我把代碼處理下就上傳到 GitHub。

好了,本文我們就先說(shuō)到這里。

?
Spring Boot 如何給微信公眾號(hào)返回消息_Java

?

本文摘自 :https://blog.51cto.com/u

開(kāi)通會(huì)員,享受整站包年服務(wù)立即開(kāi)通 >