成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

手把手教你用 SpringBoot 開發微信公眾號后臺

開發 項目管理
大家千萬不要以為不同類型消息的格式是一樣的,其實是不一樣的,也就是說,MsgType 為 text 的消息和 MsgType 為 image 的消息,微信服務器發給我們的消息內容是不一樣的,這樣帶來一個問題就是我無法使用一個 Bean 去接收不同類型的數據,因此這里我們一般使用 Map 接收即可。

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

很多小伙伴可能注意到松哥的微信公眾號后臺有一個回復關鍵字如 666? 或者 888 可以獲取學習資料的功能,這是松哥基于 Spring Boot 寫的一個簡單后臺,今天我們就來簡單聊聊這個如何實現。

1. 實現思路

其實松哥這個回復關鍵字獲取學習資料實現原理很簡單,說白了,就是一個數據查詢操作而已,回復的口令是查詢關鍵字,回復的內容則是查詢結果。這個原理很簡單。

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

圖片

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

圖片

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

  • 首先javaboy4096 這個字符從公眾號上發送到了微信服務器
  • 接下來微信服務器會把javaboy4096 轉發到我自己的服務器上
  • 我收到javaboy4096 這個字符之后,就去數據庫中查詢,將查詢的結果,按照騰訊要求的 XML 格式進行返回
  • 微信服務器把從我的服務器收到的信息,再發回到微信上,于是小伙伴們就看到了返回結果了

大致的流程就是這個樣子。

接下來我們就來看一下實現細節。

2. 公眾號后臺配置

開發的第一步,是微信服務器要驗證我們自己的服務器是否有效。

首先我們登錄微信公眾平臺官網后,在公眾平臺官網的 開發-基本設置 頁面,勾選協議成為開發者,然后點擊“修改配置”按鈕,填寫:

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

圖片

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

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

EncodingAESKey 由開發者手動填寫或隨機生成,將用作消息體加解密密鑰。

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

3. 開發

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

3.1 服務器有效性校驗

我們首先來創建一個普通的 Spring Boot 項目,創建時引入 spring-boot-starter-web 依賴,項目創建成功后,我們創建一個 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();
}
}

關于這段代碼,我做如下解釋:

首先通過 request.getParameter 方法獲取到微信服務器發來的 signature、timestamp、nonce 以及 echostr 四個參數,這四個參數中:signature 表示微信加密簽名,signature 結合了開發者填寫的 token 參數和請求中的timestamp參數、nonce參數;timestamp 表示時間戳;nonce 表示隨機數;echostr 則表示一個隨機字符串。

開發者通過檢驗 signature 對請求進行校驗,如果確認此次 GET 請求來自微信服務器,則原樣返回 echostr 參數內容,則接入生效,成為開發者成功,否則接入失敗。

具體的校驗就是松哥這里的 CheckUtil.checkSignature 方法,在這個方法中,首先將token、timestamp、nonce 三個參數進行字典序排序,然后將三個參數字符串拼接成一個字符串進行 sha1 加密,最后開發者獲得加密后的字符串可與 signature 對比,標識該請求來源于微信。

校驗代碼如下:

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,完成之后,我們的校驗接口就算是開發完成了。接下來就可以開發消息接收接口了。

3.2 消息接收接口

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

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

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

這些參數含義如下:

參數

描述

ToUserName

開發者微信號

FromUserName

發送方帳號(一個OpenID)

CreateTime

消息創建時間 (整型)

MsgType

消息類型,文本為text

Content

文本消息內容

MsgId

消息id,64位整型

看到這里,大家心里大概就有數了,當我們收到微信服務器發來的消息之后,我們就進行 XML 解析,提取出來我們需要的信息,去做相關的查詢操作,再將查到的結果返回給微信服務器。

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

@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;
}

大家看到其實都是一些常規代碼,沒有什么難度。

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

4. 消息分類

在討論如何給微信服務器回復消息之前,我們需要先來了解下微信服務器發來的消息主要有哪些類型以及我們回復給微信的消息都有哪些類型。

在前文中大家了解到,微信發送來的 xml 消息中有一個 MsgType 字段,這個字段就是用來標記消息的類型。這個類型可以標記出這條消息是普通消息還是事件消息還是圖文消息等。

普通消息主要是指:

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

不同的消息類型,對應不同的 MsgType,這里我還是以普通消息為例,如下:

消息類型

MsgType

文本消息

text

圖片消息

image

語音消息

voice

視頻消息

video

小視頻消息

shortvideo

地址位置消息

location

鏈接消息

link

大家千萬不要以為不同類型消息的格式是一樣的,其實是不一樣的,也就是說,MsgType 為 text 的消息和 MsgType 為 image 的消息,微信服務器發給我們的消息內容是不一樣的,這樣帶來一個問題就是我無法使用一個 Bean 去接收不同類型的數據,因此這里我們一般使用 Map 接收即可。

這是消息的接收,除了消息的接收之外,還有一個消息的回復,我們回復的消息也有很多類型,可以回復普通消息,也可以回復圖片消息,回復語音消息等,不同的回復消息我們可以進行相應的封裝。因為不同的返回消息實例也是有一些共同的屬性的,例如消息是誰發來的,發給誰,消息類型,消息 id 等,所以我們可以將這些共同的屬性定義成一個父類,然后不同的消息再去繼承這個父類。

5. 返回消息類型定義

首先我們來定義一個公共的消息類型:

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

在這里:

  • ToUserName 表示開發者的微信號
  • FromUserName 表示發送方賬號(用戶的 OpenID)
  • CreateTime 消息的創建時間
  • MsgType 表示消息的類型
  • MsgId 表示消息 id

這是我們的基本消息類型,就是說,我們返回給用戶的消息,無論是什么類型的消息,都有這幾個基本屬性。然后在此基礎上,我們再去擴展出文本消息、圖片消息 等。

我們來看下文本消息的定義:

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

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

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

6. 返回消息生成

消息類型的 Bean 定義完成之后,接下來就是將實體類生成 XML。

首先我們定義一個消息工具類,將常見的消息類型枚舉出來:

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

大家注意這里消息類型的定義,以 RESP 開頭的表示返回的消息類型,以 REQ 表示微信服務器發來的消息類型。然后在這個工具類中再定義兩個方法,用來將返回的對象轉換成 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 方法用來將 TextMessage 對象轉成 XML 返回給微信服務器,類似的方法我們還需要定義 imageMessageToXml、voiceMessageToXml 等,不過定義的方式都基本類似,我就不一一列出來了。

7. 返回消息分發

由于用戶發來的消息可能存在多種情況,我們需要分類進行處理,這個就涉及到返回消息的分發問題。因此我在這里再定義一個返回消息分發的工具類,如下:

public class MessageDispatcher {
public static String processMessage(Map<String, String> map){
String openid = map.get("FromUserName"); //用戶 openid
String mpid = map.get("ToUserName"); //公眾號原始 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){
//在這里處理事件
}
}

這里我們還可以多加幾個 elseif 去判斷不同的消息類型,我這里因為只有普通文本消息,所以一個 if 就夠用了。

在這里返回值我寫死了,實際上這里需要根據微信服務端傳來的 Content 去數據中查詢,將查詢結果返回,數據庫查詢這一套相信大家都能搞定,我這里就不重復介紹了。

最后在消息接收 Controller 中調用該方法,如下:

@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 中,我們首先判斷消息是否是事件,如果是事件,進入到事件處理通道,如果不是事件,則進入到消息處理通道。

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

如此之后,我們的服務器就可以給公眾號返回消息了。

好了,本文我們就先說到這里。

責任編輯:武曉燕 來源: 江南一點雨
相關推薦

2022-08-04 10:39:23

Jenkins集成CD

2015-10-26 09:24:30

微信公眾號數據分析

2022-10-19 14:30:59

2021-08-09 13:31:25

PythonExcel代碼

2011-03-28 16:14:38

jQuery

2021-02-04 09:00:57

SQLDjango原生

2021-02-06 14:55:05

大數據pandas數據分析

2009-04-22 09:17:19

LINQSQL基礎

2020-03-08 22:06:16

Python數據IP

2012-01-11 13:40:35

移動應用云服務

2021-08-02 23:15:20

Pandas數據采集

2021-02-02 13:31:35

Pycharm系統技巧Python

2021-12-11 20:20:19

Python算法線性

2021-05-10 06:48:11

Python騰訊招聘

2021-01-21 09:10:29

ECharts柱狀圖大數據

2021-01-08 10:32:24

Charts折線圖數據可視化

2014-11-17 11:13:17

易維

2021-01-30 10:37:18

ScrapyGerapy網絡爬蟲

2021-05-17 21:30:06

Python求均值中值

2017-10-29 21:43:25

人臉識別
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 日韩视频区| 日韩一区二区在线视频 | 一区二区三区在线播放 | 一本色道精品久久一区二区三区 | 秋霞在线一区 | 久草中文在线 | 免费成人高清在线视频 | 狠狠综合网 | 亚洲综合一区二区三区 | 久久精品久久精品久久精品 | 久久日本| 人人性人人性碰国产 | 精品欧美一区二区三区久久久 | 亚洲一区在线播放 | 国产高清在线视频 | 亚洲国产精品99久久久久久久久 | 国产在线精品一区二区三区 | 午夜久久| 国产成人精品一区二区三区四区 | 中文字幕一区二区三区在线观看 | 亚洲一区精品视频 | 中文字幕 在线观看 | 久久久99国产精品免费 | 亚洲视频免费在线观看 | 看片国产 | 国产三级电影网站 | 国产高清久久久 | 91看片视频 | 国产精品视频不卡 | 黄网址在线观看 | 精品国产乱码久久久久久88av | 国产高清视频 | 日韩一区二区三区视频 | 欧美日韩国产精品一区 | 99精品国产一区二区三区 | 伊人伊人伊人 | 色综合天天天天做夜夜夜夜做 | 99re热精品视频 | 在线观看精品视频网站 | 99精品免费久久久久久久久日本 | 久久亚洲视频 |