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

Spring Boot 開發(fā)微信公眾號后臺
2021-07-29 15:04:16

Hello 各位小伙伴,松哥今天要和大家聊一個(gè)有意思的話題,就是使用 Spring Boot 開發(fā)微信公眾號后臺。

很多小伙伴可能注意到松哥的個(gè)人網(wǎng)站(http://www.javaboy.org)前一陣子上線了一個(gè)公眾號內(nèi)回復(fù)口令解鎖網(wǎng)站文章的功能,還有之前就有的公眾號內(nèi)回復(fù)口令獲取超 2TB 免費(fèi)視頻教程的功能(免費(fèi)視頻教程),這兩個(gè)都是松哥基于 Spring Boot 來做的,最近松哥打算通過一個(gè)系列的文章,來向小伙伴們介紹下如何通過 Spring Boot 來開發(fā)公眾號后臺。

1. 緣起

今年 5 月份的時(shí)候,我想把我自己之前收集到的一些視頻教程分享給公眾號上的小伙伴,可是這些視頻教程大太了,無法一次分享,單次分享分享鏈接立馬就失效了,為了把這些視頻分享給大家,我把視頻拆分成了很多份,然后設(shè)置了不同的口令,小伙伴們在公眾號后臺通過回復(fù)口令就可以獲取到這些視頻,口令前前后后有 100 多個(gè),我一個(gè)一個(gè)手動(dòng)的在微信后臺進(jìn)行配置。這么搞工作量很大,前前后后大概花了三個(gè)晚上才把這些東西搞定。

于是我就在想,該寫點(diǎn)代碼了。

上個(gè)月買了服務(wù)器,也備案了,該有的都有了,于是就打算把這些資源用代碼實(shí)現(xiàn)下,因?yàn)榇髮W(xué)時(shí)候搞過公眾號開發(fā),倒也沒什么難度,于是說干就干。

2. 實(shí)現(xiàn)思路

其實(shí)松哥這個(gè)回復(fù)口令獲取視頻鏈接的實(shí)現(xiàn)原理很簡單,說白了,就是一個(gè)數(shù)據(jù)查詢操作而已,回復(fù)的口令是查詢關(guān)鍵字,回復(fù)的內(nèi)容則是查詢結(jié)果。這個(gè)原理很簡單。

另一方面大家需要明白微信公眾號后臺開發(fā)消息發(fā)送的一個(gè)流程,大家看下面這張圖:

?

這是大家在公眾號后臺回復(fù)關(guān)鍵字的情況。那么這個(gè)消息是怎么樣一個(gè)傳遞流程呢?我們來看看下面這張圖:

Spring Boot 開發(fā)微信公眾號后臺_Spring Boot

這張圖,我給大家稍微解釋下:

  1. 首先 javaboy4096 這個(gè)字符從公眾號上發(fā)送到了微信服務(wù)器
  2. 接下來微信服務(wù)器會把 javaboy4096 轉(zhuǎn)發(fā)到我自己的服務(wù)器上
  3. 我收到 javaboy4096 這個(gè)字符之后,就去數(shù)據(jù)庫中查詢,將查詢的結(jié)果,按照騰訊要求的 XML 格式進(jìn)行返回
  4. 微信服務(wù)器把從我的服務(wù)器收到的信息,再發(fā)回到微信上,于是小伙伴們就看到了返回結(jié)果了

大致的流程就是這個(gè)樣子。

接下來我們就來看一下實(shí)現(xiàn)細(xì)節(jié)。

3. 公眾號后臺配置

開發(fā)的第一步,是微信服務(wù)器要驗(yàn)證我們自己的服務(wù)器是否有效。

首先我們登錄微信公眾平臺官網(wǎng)后,在公眾平臺官網(wǎng)的 開發(fā)-基本設(shè)置 頁面,勾選協(xié)議成為開發(fā)者,然后點(diǎn)擊“修改配置”按鈕,填寫:

  • 服務(wù)器地址(URL)
  • Token
  • EncodingAESKey

Spring Boot 開發(fā)微信公眾號后臺_Spring Boot_02

這里的 URL 配置好之后,我們需要針對這個(gè) URL 開發(fā)兩個(gè)接口,一個(gè)是 GET 請求的接口,這個(gè)接口用來做服務(wù)器有效性驗(yàn)證,另一個(gè)則是 POST 請求的接口,這個(gè)用來接收微信服務(wù)器發(fā)送來的消息。也就是說,微信服務(wù)器的消息都是通過 POST 請求發(fā)給我的。

Token 可由開發(fā)者可以任意填寫,用作生成簽名(該 Token 會和接口 URL 中包含的 Token 進(jìn)行比對,從而驗(yàn)證安全性)。

EncodingAESKey 由開發(fā)者手動(dòng)填寫或隨機(jī)生成,將用作消息體加解密密鑰。

同時(shí),開發(fā)者可選擇消息加解密方式:明文模式、兼容模式和安全模式。明文模式就是我們自己的服務(wù)器收到微信服務(wù)器發(fā)來的消息是明文字符串,直接就可以讀取并且解析,安全模式則是我們收到微信服務(wù)器發(fā)來的消息是加密的消息,需要我們手動(dòng)解析后才能使用。

4. 開發(fā)

公眾號后臺配置完成后,接下來我們就可以寫代碼了。

4.1 服務(wù)器有效性校驗(yàn)

我們首先來創(chuàng)建一個(gè)普通的 Spring Boot 項(xiàng)目,創(chuàng)建時(shí)引入 spring-boot-starter-web 依賴,項(xiàng)目創(chuàng)建成功后,我們創(chuàng)建一個(gè) Controller ,添加如下接口:

@GetMapping("/verify_wx_token")
public void login(HttpServletRequest request, HttpServletResponse response) throws UnsupportedEncodingException {
    request.setCharacterEncoding("UTF-8");
    String signature = request.getParameter("signature");
    String timestamp = request.getParameter("timestamp");
    String nonce = request.getParameter("nonce");
    String echostr = request.getParameter("echostr");
    PrintWriter out = null;
    try {
        out = response.getWriter();
        if (CheckUtil.checkSignature(signature, timestamp, nonce)) {
            out.write(echostr);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        out.close();
    }
}

關(guān)于這段代碼,我做如下解釋:

  1. 首先通過 request.getParameter 方法獲取到微信服務(wù)器發(fā)來的 signature、timestamp、nonce 以及 echostr 四個(gè)參數(shù),這四個(gè)參數(shù)中:signature 表示微信加密簽名,signature 結(jié)合了開發(fā)者填寫的 token 參數(shù)和請求中的timestamp參數(shù)、nonce參數(shù);timestamp 表示時(shí)間戳;nonce 表示隨機(jī)數(shù);echostr 則表示一個(gè)隨機(jī)字符串。
  2. 開發(fā)者通過檢驗(yàn) signature 對請求進(jìn)行校驗(yàn),如果確認(rèn)此次 GET 請求來自微信服務(wù)器,則原樣返回 echostr 參數(shù)內(nèi)容,則接入生效,成為開發(fā)者成功,否則接入失敗。
  3. 具體的校驗(yàn)就是松哥這里的 CheckUtil.checkSignature 方法,在這個(gè)方法中,首先將token、timestamp、nonce 三個(gè)參數(shù)進(jìn)行字典序排序,然后將三個(gè)參數(shù)字符串拼接成一個(gè)字符串進(jìn)行 sha1 加密,最后開發(fā)者獲得加密后的字符串可與 signature 對比,標(biāo)識該請求來源于微信。

校驗(yàn)代碼如下:

public class CheckUtil {
    private static final String token = "123456";
    public static boolean checkSignature(String signature, String timestamp, String nonce) {
        String[] str = new String[]{token, timestamp, nonce};
        //排序
        Arrays.sort(str);
        //拼接字符串
        StringBuffer buffer = new StringBuffer();
        for (int i = 0; i < str.length; i++) {
            buffer.append(str[i]);
        }
        //進(jìn)行sha1加密
        String temp = SHA1.encode(buffer.toString());
        //與微信提供的signature進(jìn)行匹對
        return signature.equals(temp);
    }
}
public class SHA1 {
    private static final char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5',
            '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    private static String getFormattedText(byte[] bytes) {
        int len = bytes.length;
        StringBuilder buf = new StringBuilder(len * 2);
        for (int j = 0; j < len; j++) {
            buf.append(HEX_DIGITS[(bytes[j] >> 4) & 0x0f]);
            buf.append(HEX_DIGITS[bytes[j] & 0x0f]);
        }
        return buf.toString();
    }
    public static String encode(String str) {
        if (str == null) {
            return null;
        }
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("SHA1");
            messageDigest.update(str.getBytes());
            return getFormattedText(messageDigest.digest());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

OK,完成之后,我們的校驗(yàn)接口就算是開發(fā)完成了。接下來就可以開發(fā)消息接收接口了。

4.2 消息接收接口

接下來我們來開發(fā)消息接收接口,消息接收接口和上面的服務(wù)器校驗(yàn)接口地址是一樣的,都是我們一開始在公眾號后臺配置的地址。只不過消息接收接口是一個(gè) POST 請求。

我在公眾號后臺配置的時(shí)候,消息加解密方式選擇了明文模式,這樣我在后臺收到的消息直接就可以處理了。微信服務(wù)器給我發(fā)來的普通文本消息格式如下:

<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>

這些參數(shù)含義如下:

參數(shù) 描述
ToUserName 開發(fā)者微信號
FromUserName 發(fā)送方帳號(一個(gè)OpenID)
CreateTime 消息創(chuàng)建時(shí)間 (整型)
MsgType 消息類型,文本為text
Content 文本消息內(nèi)容
MsgId 消息id,64位整型

看到這里,大家心里大概就有數(shù)了,當(dāng)我們收到微信服務(wù)器發(fā)來的消息之后,我們就進(jìn)行 XML 解析,提取出來我們需要的信息,去做相關(guān)的查詢操作,再將查到的結(jié)果返回給微信服務(wù)器。

這里我們先來個(gè)簡單的,我們將收到的消息解析并打印出來:

@PostMapping("/verify_wx_token")
public void handler(HttpServletRequest request, HttpServletResponse response) throws Exception {
    request.setCharacterEncoding("UTF-8");
    response.setCharacterEncoding("UTF-8");
    PrintWriter out = response.getWriter();
    Map<String, String> parseXml = MessageUtil.parseXml(request);
    String msgType = parseXml.get("MsgType");
    String content = parseXml.get("Content");
    String fromusername = parseXml.get("FromUserName");
    String tousername = parseXml.get("ToUserName");
    System.out.println(msgType);
    System.out.println(content);
    System.out.println(fromusername);
    System.out.println(tousername);
}
public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
    Map<String, String> map = new HashMap<String, String>();
    InputStream inputStream = request.getInputStream();
    SAXReader reader = new SAXReader();
    Document document = reader.read(inputStream);
    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;
}

大家看到其實(shí)都是一些常規(guī)代碼,沒有什么難度。

做完這些之后,我們將項(xiàng)目打成 jar 包在服務(wù)器上部署啟動(dòng)。啟動(dòng)成功之后,確認(rèn)微信的后臺配置也沒問題,我們就可以在公眾號上發(fā)一條消息了,這樣我們自己的服務(wù)端就會打印出來剛剛消息的信息。

好了,篇幅限制,今天就和大家先聊這么多,后面再聊不同消息類型的解析和消息的返回問題。

不知道小伙伴們看懂沒?有問題歡迎留言討論。

參考資料:微信開放文檔

?Spring Boot 開發(fā)微信公眾號后臺_Spring Boot_03

?

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

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