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è)傳遞流程呢?我們來看看下面這張圖:
這張圖,我給大家稍微解釋下:
- 首先
javaboy4096
這個(gè)字符從公眾號上發(fā)送到了微信服務(wù)器 - 接下來微信服務(wù)器會把
javaboy4096
轉(zhuǎn)發(fā)到我自己的服務(wù)器上 - 我收到
javaboy4096
這個(gè)字符之后,就去數(shù)據(jù)庫中查詢,將查詢的結(jié)果,按照騰訊要求的 XML 格式進(jìn)行返回 - 微信服務(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
這里的 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)于這段代碼,我做如下解釋:
- 首先通過 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ī)字符串。
- 開發(fā)者通過檢驗(yàn) signature 對請求進(jìn)行校驗(yàn),如果確認(rèn)此次 GET 請求來自微信服務(wù)器,則原樣返回 echostr 參數(shù)內(nèi)容,則接入生效,成為開發(fā)者成功,否則接入失敗。
- 具體的校驗(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ù)端就會打印出來剛剛消息的信息。
好了,篇幅限制,今天就和大家先聊這么多,后面再聊不同消息類型的解析和消息的返回問題。
不知道小伙伴們看懂沒?有問題歡迎留言討論。
參考資料:微信開放文檔
?
?
本文摘自 :https://blog.51cto.com/u