Hello 各位小伙伴,松哥今天要和大家聊一個有意思的話題,就是使用 Spring Boot 開發(fā)微信公眾號后臺。
很多小伙伴可能注意到松哥的個人網(wǎng)站(http://www.javaboy.org)前一陣子上線了一個公眾號內(nèi)回復(fù)口令解鎖網(wǎng)站文章的功能,還有之前就有的公眾號內(nèi)回復(fù)口令獲取超 2TB 免費視頻教程的功能(免費視頻教程),這兩個都是松哥基于 Spring Boot 來做的,最近松哥打算通過一個系列的文章,來向小伙伴們介紹下如何通過 Spring Boot 來開發(fā)公眾號后臺。
1. 緣起
今年 5 月份的時候,我想把我自己之前收集到的一些視頻教程分享給公眾號上的小伙伴,可是這些視頻教程大太了,無法一次分享,單次分享分享鏈接立馬就失效了,為了把這些視頻分享給大家,我把視頻拆分成了很多份,然后設(shè)置了不同的口令,小伙伴們在公眾號后臺通過回復(fù)口令就可以獲取到這些視頻,口令前前后后有 100 多個,我一個一個手動的在微信后臺進行配置。這么搞工作量很大,前前后后大概花了三個晚上才把這些東西搞定。
于是我就在想,該寫點代碼了。
上個月買了服務(wù)器,也備案了,該有的都有了,于是就打算把這些資源用代碼實現(xiàn)下,因為大學(xué)時候搞過公眾號開發(fā),倒也沒什么難度,于是說干就干。
2. 實現(xiàn)思路
其實松哥這個回復(fù)口令獲取視頻鏈接的實現(xiàn)原理很簡單,說白了,就是一個數(shù)據(jù)查詢操作而已,回復(fù)的口令是查詢關(guān)鍵字,回復(fù)的內(nèi)容則是查詢結(jié)果。這個原理很簡單。
另一方面大家需要明白微信公眾號后臺開發(fā)消息發(fā)送的一個流程,大家看下面這張圖:
?
這是大家在公眾號后臺回復(fù)關(guān)鍵字的情況。那么這個消息是怎么樣一個傳遞流程呢?我們來看看下面這張圖:
這張圖,我給大家稍微解釋下:
- 首先
javaboy4096
這個字符從公眾號上發(fā)送到了微信服務(wù)器 - 接下來微信服務(wù)器會把
javaboy4096
轉(zhuǎn)發(fā)到我自己的服務(wù)器上 - 我收到
javaboy4096
這個字符之后,就去數(shù)據(jù)庫中查詢,將查詢的結(jié)果,按照騰訊要求的 XML 格式進行返回 - 微信服務(wù)器把從我的服務(wù)器收到的信息,再發(fā)回到微信上,于是小伙伴們就看到了返回結(jié)果了
大致的流程就是這個樣子。
接下來我們就來看一下實現(xiàn)細節(jié)。
3. 公眾號后臺配置
開發(fā)的第一步,是微信服務(wù)器要驗證我們自己的服務(wù)器是否有效。
首先我們登錄微信公眾平臺官網(wǎng)后,在公眾平臺官網(wǎng)的 開發(fā)-基本設(shè)置 頁面,勾選協(xié)議成為開發(fā)者,然后點擊“修改配置”按鈕,填寫:
- 服務(wù)器地址(URL)
- Token
- EncodingAESKey
這里的 URL 配置好之后,我們需要針對這個 URL 開發(fā)兩個接口,一個是 GET 請求的接口,這個接口用來做服務(wù)器有效性驗證,另一個則是 POST 請求的接口,這個用來接收微信服務(wù)器發(fā)送來的消息。也就是說,微信服務(wù)器的消息都是通過 POST 請求發(fā)給我的。
Token 可由開發(fā)者可以任意填寫,用作生成簽名(該 Token 會和接口 URL 中包含的 Token 進行比對,從而驗證安全性)。
EncodingAESKey 由開發(fā)者手動填寫或隨機生成,將用作消息體加解密密鑰。
同時,開發(fā)者可選擇消息加解密方式:明文模式、兼容模式和安全模式。明文模式就是我們自己的服務(wù)器收到微信服務(wù)器發(fā)來的消息是明文字符串,直接就可以讀取并且解析,安全模式則是我們收到微信服務(wù)器發(fā)來的消息是加密的消息,需要我們手動解析后才能使用。
4. 開發(fā)
公眾號后臺配置完成后,接下來我們就可以寫代碼了。
4.1 服務(wù)器有效性校驗
我們首先來創(chuàng)建一個普通的 Spring Boot 項目,創(chuàng)建時引入 spring-boot-starter-web
依賴,項目創(chuàng)建成功后,我們創(chuàng)建一個 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)于這段代碼,我做如下解釋:
- 首先通過 request.getParameter 方法獲取到微信服務(wù)器發(fā)來的 signature、timestamp、nonce 以及 echostr 四個參數(shù),這四個參數(shù)中:signature 表示微信加密簽名,signature 結(jié)合了開發(fā)者填寫的 token 參數(shù)和請求中的timestamp參數(shù)、nonce參數(shù);timestamp 表示時間戳;nonce 表示隨機數(shù);echostr 則表示一個隨機字符串。
- 開發(fā)者通過檢驗 signature 對請求進行校驗,如果確認此次 GET 請求來自微信服務(wù)器,則原樣返回 echostr 參數(shù)內(nèi)容,則接入生效,成為開發(fā)者成功,否則接入失敗。
- 具體的校驗就是松哥這里的 CheckUtil.checkSignature 方法,在這個方法中,首先將token、timestamp、nonce 三個參數(shù)進行字典序排序,然后將三個參數(shù)字符串拼接成一個字符串進行 sha1 加密,最后開發(fā)者獲得加密后的字符串可與 signature 對比,標(biāo)識該請求來源于微信。
校驗代碼如下:
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]);
}
//進行sha1加密
String temp = SHA1.encode(buffer.toString());
//與微信提供的signature進行匹對
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,完成之后,我們的校驗接口就算是開發(fā)完成了。接下來就可以開發(fā)消息接收接口了。
4.2 消息接收接口
接下來我們來開發(fā)消息接收接口,消息接收接口和上面的服務(wù)器校驗接口地址是一樣的,都是我們一開始在公眾號后臺配置的地址。只不過消息接收接口是一個 POST 請求。
我在公眾號后臺配置的時候,消息加解密方式選擇了明文模式,這樣我在后臺收到的消息直接就可以處理了。微信服務(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ā)送方帳號(一個OpenID) |
CreateTime | 消息創(chuàng)建時間 (整型) |
MsgType | 消息類型,文本為text |
Content | 文本消息內(nèi)容 |
MsgId | 消息id,64位整型 |
看到這里,大家心里大概就有數(shù)了,當(dāng)我們收到微信服務(wù)器發(fā)來的消息之后,我們就進行 XML 解析,提取出來我們需要的信息,去做相關(guān)的查詢操作,再將查到的結(jié)果返回給微信服務(wù)器。
這里我們先來個簡單的,我們將收到的消息解析并打印出來:
@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;
}
大家看到其實都是一些常規(guī)代碼,沒有什么難度。
做完這些之后,我們將項目打成 jar 包在服務(wù)器上部署啟動。啟動成功之后,確認微信的后臺配置也沒問題,我們就可以在公眾號上發(fā)一條消息了,這樣我們自己的服務(wù)端就會打印出來剛剛消息的信息。
好了,篇幅限制,今天就和大家先聊這么多,后面再聊不同消息類型的解析和消息的返回問題。
不知道小伙伴們看懂沒?有問題歡迎留言討論。
參考資料:微信開放文檔
?
?
本文摘自 :https://blog.51cto.com/u