掃碼關注公眾號登錄系統

霧裏看Java 2022-01-07 21:04:20 阅读数:591

前言

最近公司的客戶要求在登錄時加上一個流程,就是在輸入賬號密碼之後要求用戶關注他們的微信公眾號才能進入系統,作為職場新人的我從來沒有接觸過微信 API ,經曆3天的調研和2天的代碼終於把功能做了出來。在這裏記錄一下,順便和大家分享一下我踩過的坑。由於水平有限,如果有什麼錯誤歡迎指出,但請不要言語謾罵。如果我幫助到了同樣是第一次接觸微信API的你,希望不要吝嗇一個贊,謝謝!!!
開篇之前介紹下我設計的登錄流程
在這裏插入圖片描述

准備階段

在注册微信公眾平臺的開發者賬號之前,您需要准備如下工具

NATAPP

無論是公眾平臺的正式賬號,還是用來開發的測試賬號,微信都要求開發者提供一個公網可訪問的域名。如果您沒有域名並且不打算在功能完成之前購買域名,可以考慮使用NATAPP。這裏提供一個教程教您配置macOS下載及配置教程windows下載及配置教程

測試賬號

在項目上線之前,公司可能不會提供正式賬號。微信為我們提供了一個開通了所有接口權限(除了支付)的測試賬號,申請地址:戳我申請開發者測試賬號

工具代碼

在用到這些代碼之前,請先把代碼複制到工程中,不用管具體實現,都是一些死代碼沒必要理解。

  1. java 發送 http 請求代碼
public class NetWorkUtil {

public static String httpURLConnectionPOST(String postUrl, QrCodeParam param) {

try {

URL url = new URL(postUrl);
// 1. 將url 以 open方法返回的urlConnection
// 連接强轉為HttpURLConnection連接.此時cnnection只是為一個連接對象,待連接中
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// 2. 設置連接輸出流為true,默認false (post 請求是以流的方式隱式的傳遞參數)
connection.setDoOutput(true);
// 3. 設置連接輸入流為true
connection.setDoInput(true);
// 4.設置請求方式為post
connection.setRequestMethod("POST");
// 5.post請求緩存設為false
connection.setUseCaches(false);
/* * 6.設置請求頭裏面的各個屬性 (以下為設置內容的類型,設置為經過urlEncoded編碼過的from參數) * application/xml:xml數據 ,application/json:json對象 * text/html:錶單數據 */
connection.setRequestProperty("Content-Type", "application/json;charset=utf-8");
// 7.建立連接
// (請求未開始,直到connection.getInputStream()方法調用時才發起,以上各個參數設置需在此方法之前進行)
connection.connect();
// 8.創建輸入輸出流,用於往連接裏面輸出攜帶的參數,(輸出內容為?後面的內容)
DataOutputStream dataout = new DataOutputStream(connection.getOutputStream());
// 9.入參:json格式
String jsonStr = JSON.toJSONString(param);
// 10.將參數輸出到連接
dataout.writeBytes(jsonStr);
// 輸出完成後刷新並關閉流
dataout.flush();
dataout.close(); // 重要且易忽略步驟 (關閉流,切記!)
// System.out.println("響應code:"+connection.getResponseCode());
// 連接發起請求,處理服務器響應 (從連接獲取到輸入流並包裝為bufferedReader)
BufferedReader bf = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
String line;
StringBuilder sb = new StringBuilder(); // 用來存儲響應數據
// 循環讀取流,若不到結尾處
while ((line = bf.readLine()) != null) {

sb.append(line);//若要換行:sb.append(line).append(System.getProperty("line.separator"));
}
bf.close(); // 重要且易忽略步驟 (關閉流,切記!)
connection.disconnect(); // 銷毀連接
System.err.println(sb.toString());
return sb.toString();
} catch (Exception e) {

e.printStackTrace();
System.out.println("請求失敗:"+e.getMessage());
}
return "";
}
public static String httpURLConnectionGET(String getURL){

try{

URL url = new URL(getURL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true); // 設置該連接是可以輸出的
connection.setRequestMethod("GET"); // 設置請求方式
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
String line = null;
StringBuilder result = new StringBuilder();
while ((line = br.readLine()) != null) {
 // 讀取數據
result.append(line);
}
connection.disconnect();
return result.toString();
}catch (Exception e){

e.printStackTrace();
}
return null;
}
}
  1. 將微信服務器推送的XML形式的JSON轉換成Map(關於微信服務器推送的消息格式請查閱這個教程
public class MessageUtil {

public static Map<String, String> parseXml(HttpServletRequest req){

Map<String, String> map = new HashMap<>();
try(
InputStream inputStream = req.getInputStream();
){

// 初始化 Dom4j核心 對象
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
// 獲取根節點
Element rootElement = document.getRootElement();
// 獲取所有節點
List<Element> elements = rootElement.elements();
for (Element element : elements) {

// 節點名做為 key, 文本節點做為值
map.put(element.getName(), element.getText());
}
}catch (Exception e){

e.printStackTrace();
}
return map;
}
/** * 擴展xstream使其支持CDATA */
private static XStream xstream = new XStream(new XppDriver() {

public HierarchicalStreamWriter createWriter(Writer out) {

return new PrettyPrintWriter(out) {

// 對所有xml節點的轉換都增加CDATA標記
boolean cdata = true;
@SuppressWarnings("unchecked")
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);
}
}
};
}
});
/** * 文本消息對象轉換成xml * * @param textMessage 文本消息對象 * @return xml */
public static String messageToXml(TextMessage textMessage) {

xstream.alias("xml", textMessage.getClass());
return xstream.toXML(textMessage);
}
}
  1. 對微信發來的驗證參數進行排序和加密的代碼
public class EncodingUtil {

public static String sort(String token, String timestamp, String nonce){

String[] str = {
token, timestamp, nonce};
Arrays.sort(str);
StringBuilder builder = new StringBuilder();
for (String s : str) {

builder.append(s);
}
return builder.toString();
}
public static String shal(String str) {

try {

MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(str.getBytes());
byte messageDigest[] = digest.digest();
StringBuffer hexString = new StringBuffer();
// 字節數組轉換為 十六進制 數
for (int i = 0; i < messageDigest.length; i++) {

String shaHex = Integer.toHexString(messageDigest[i] & 0xFF);
if (shaHex.length() < 2) {

hexString.append(0);
}
hexString.append(shaHex);
}
return hexString.toString();
} catch (NoSuchAlgorithmException e) {

e.printStackTrace();
}
return "";
}
}

微信 API 調研階段

步驟1:注册賬號(如果使用測試賬號可跳過)

第一 步當然是在微信公眾平臺注册一個賬號啦,但是在注册之前,希望你了解一下訂閱號服務號接口權限說明,以免自己注册的賬號不能滿足功能要求。
如果已經擁有公眾平臺的賬號了,一定要注意下您注册的是訂閱號/服務號而不是小程序,本人就是因為半年之前注册了一個小程序的開發者賬號(只是注册一個玩玩,從來沒用過),然後第一天登錄公眾平臺後就只顯示小程序的接口不顯示訂閱號/服務號的接口。導致我按照微信官方文檔公眾號文檔中的說明找半天也找不到對應的接口,氣得我問候老馬的mother(無能咆哮)。浪費我一天的時間(真是蠢哭了)。

步驟2:了解微信掃碼機制

經過本人實驗,對於已關注的用戶,微信只會在該用戶掃描帶參數二維碼時才會推送給服務器消息。對於未關注用戶,在掃碼後並不會直接推送給服務器事件消息,只有點擊了“關注公眾號”才會推送關注事件消息

步驟3:了解微信消息類型

微信的消息類型有如下幾種格式

  1. 文本消息
  2. 圖片消息
  3. 語音消息
  4. 視頻消息
  5. 小視頻消息
  6. 地理比特置消息
  7. 鏈接消息
  8. 關注/取消關注事件
  9. 掃描帶參數二維碼事件
  10. ······

我們實現的是登錄後掃碼關注公眾號進入系統,最應該關心的就是8和9這兩種消息類型

帶參數二維碼

何為帶參數二維碼?可以看下微信官方的解釋,微信開發文檔/生成帶參數二維碼

為了滿足用戶渠道推廣分析和用戶帳號綁定等場景的需要,公眾平臺提供了生成帶參數二維碼的接口。使用該接口可以獲得多個帶不同場景值的二維碼,用戶掃描後,公眾號可以接收到事件推送。

舉個例子,商家為了推廣自己的產品,經常會實行獎勵機制:誰推送的我就給誰個回扣。商家如何知道一個新的用戶是誰推薦的呢?在微信中,商戶就是通過帶參數二維碼來獲知這個新的用戶是誰推薦來的了。

帶參數二維碼可以推送以下兩種事件

  1. 如果用戶還未關注公眾號,則用戶可以關注公眾號,關注後微信會將帶場景值關注事件推送給開發者。
  2. 果用戶已經關注公眾號,則微信會將帶場景值掃描事件推送給開發者。

實現登錄功能我們不需要關心二維碼的參數,我們使用帶參數二維碼的主要目的是為了接收已經關注公眾號用戶的掃碼事件,加上關注,我們就能接收兩種用戶(關注的用戶和未關注的用戶)的掃碼事件了。

編碼對接微信服務器階段

實現思路

在用戶輸入完賬號密碼之後跳轉到我們的帶參數二維碼界面,跳轉到該界面時,開啟一個定時任務,不斷的向後端某個接口詢問該用戶是否已經掃碼登錄(ajax輪詢)。由於每個二維碼的ticket都是唯一的,所以可以對應一個客戶端用戶。被輪詢的接口會返回3種狀態:

  1. 用戶已經掃碼並關注,請跳轉到系統主頁(跳轉頁面)
  2. 用戶還沒有進行掃碼,請繼續等待(無為而坐)
  3. 二維碼過期,請重新獲取(刷新當前頁面)

創建項目

推薦使用 springboot 創建項目,這能够省去配置項目的時間(如何創建 springboot 項目請自行百度)。注意,項目啟動端口請設置成 80(http 默認端口) 或者 443(https 默認端口),這是微信官方要求的,如果想用其他端口可以使用Nginx 反向代理(同樣的,Nginx使用方法請自行百度
需要引入如下依賴

 <!-- 我的項目使用了 thymeleaf 模板,如果你用的是 jsp 或者其他模板,在頁面中的取值方式注意更換一下 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 用來解析微信服務器推送的事件和用戶發來的消息 -->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<!-- springboot 基礎配置項-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

access_token

請先移步到微信官方文檔/獲取AccessToken了解其重要性
如果您仔細閱讀文檔,一定會發現這麼一句話:access_token的有效期目前為2個小時,需定時刷新。這個定時刷新,我是通過一個線程,每隔 2小時 - 20 秒刷新一次 access_token,提前20秒防止意外情况發生阻塞 access_token的刷新。並且該線程在 springboot 項目啟動後立即啟動。具體代碼實現如下


@SpringBootApplication
//開啟緩存
@EnableCaching
public class DemoApplication {

@Autowired
private WeChatBasicInfo basicInfo;
// 自己的測試號
private static String appID = "你的appID";
private static String appsecret = "你的appsecret";
// 這個類的定義在下面
public static AccessToken accessToken = null;
// 請求 access_token 的接口
private static String uri = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
public static void main(String[] args) {

SpringApplication.run(DemoApplication.class, args);
poll();
}
// 開啟獲取 access_token 的線程
private static void poll() {

new Thread(() -> {

synchronized (DemoApplication.class) {

try {

System.out.println("=----------------- 獲取accesstoken");
while (true) {

uri = String.format(uri, appID, appsecret);
URL url = new URL(uri);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setDoOutput(true); // 設置該連接是可以輸出的
connection.setRequestMethod("GET"); // 設置請求方式
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
BufferedReader br = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8"));
String line = null;
StringBuilder result = new StringBuilder();
while ((line = br.readLine()) != null) {
 // 讀取數據
result.append(line);
}
connection.disconnect();
String jsonStr = result.toString();
accessToken = JSON.parseObject(jsonStr, AccessToken.class);
// 判斷請求是否成功
if (accessToken.getErrcode() == null || accessToken.getErrcode().isEmpty()) {

System.out.println("獲取 token 成功 token = " + accessToken.getAccess_token());
// 請求成功等待兩小時
Thread.sleep(7000 * 1000);
} else {

System.out.printf("獲取 token 失敗,請重新獲取。錯誤碼 : %s\t請查閱微信公眾平臺返回碼(https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html)", accessToken.getErrcode());
// 3秒後重試一次
Thread.sleep(1000 * 3);
}
}
} catch (Exception e) {

e.printStackTrace();
}
}
}).start();
}
}
@Data
public class AccessToken {

private String access_token;
private String expires_in; // 過期時間
private String errcode; // 如果請求出錯才會有值
private String errmsg;// 如果請求出錯才會有值
}

測試賬號

如果您注册好測試賬號後,進入測試賬號首頁就會看到測試號信息了。
在這裏插入圖片描述
其中 appID相當於用戶名,appsecret相當於密碼。這兩個屬性用來換區**access_token**,非常重要,推薦在項目中已常量或者放在配置文件中。

配置接口配置信息

在這裏插入圖片描述
接口配置信息有兩個用途

  1. 微信服務器校驗簽名( GET 請求)
  2. 接收微信服務器推送的事件和用戶發來的消息( POST請求)(這是官方文檔沒有詳細說明的,坑的我兩天都無法接收關注/取消事件和掃碼事件

解釋下參數

  1. URL 是你的項目接口,用來校驗簽名和接收用戶信息/事件推送請求的地址
  2. 隨便填,用來校驗簽名

    PS : URL就填你用NATAPP進行內網穿透顯示的網址 + 你的項目地址即可
在點擊提交按鈕後,會向我們填寫的 URL 發送一個請求,請求中一共有四個參數,官方文檔解釋傳送門
在這裏插入圖片描述
前三個沒什麼好說的,主要是最後一個。如果通過簽名校驗通過,原樣返回 echostr 。如果沒通過什麼也不用幹。

正式賬號

前提條件:首先要確定你注册的賬號是服務號 才能查看到我下面介紹的配置項,同時,你必須是通過微信認證的服務號才能生成帶參數的二維碼(據說300RMB)。

進行配置:鼠標滾到最下面,可以在左側導航欄看見開發選項,點擊基本配置
可以看到正式號也有 AppID 和 AppSecret
在這裏插入圖片描述
不同的是正式號多了一個 IP 白名單,這個 IP 白名單需要配置本機 公網IP 。(注意,在終端/cmd獲取的 IP 不是公網IP)打開百度搜索本機IP 即可獲得本機的 公網IP。

服務器配置

服務器配置和測試號的接口配置信息一樣,GET請求用來校驗簽名,POST請求用來處理用戶信息/事件推送。
在這裏插入圖片描述

創建 GET 接口驗證簽名

 private static final String TOKEN = "你填寫的TOKEN";
/** * 微信服務器校驗簽名 * @param req * @param resp * @throws Exception */
@RequestMapping(value = "/wechatLogin/test",method = RequestMethod.GET)
public void login(HttpServletRequest req, HttpServletResponse resp) throws Exception {

System.out.println("-----開始校驗簽名-----");
// 接收微信服務器發送請求時傳遞過來的參數
String signature = req.getParameter("signature");
String timestamp = req.getParameter("timestamp");
String nonce = req.getParameter("nonce"); //隨機數
String echostr = req.getParameter("echostr");//隨機字符串
// 將token、timestamp、nonce三個參數進行字典序排序並拼接為一個字符串
String sortStr = EncodingUtil.sort(TOKEN,timestamp, nonce);
// 字符串進行shal加密
String mySignature = EncodingUtil.shal(sortStr);
// 校驗微信服務器傳遞過來的簽名 和 加密後的字符串是否一致, 若一致則簽名通過
if (!"".equals(signature) && !"".equals(mySignature) && signature.equals(mySignature)) {

System.out.println("-----簽名校驗通過-----");
resp.getWriter().write(echostr);
} else {

System.out.println("-----校驗簽名失敗-----");
}
}

啟動項目,點擊提交按鈕,如果一切順利你將會看到
在這裏插入圖片描述
如果配置失敗請打斷點查看四個參數和你進行加密之後的結果。
下面的其他配置,例如JS接口安全域名,我也不知道它是幹嘛的,就不亂寫坑大家了,反正我沒用上它。

獲取ticket並跳轉至二維碼界面

前面在啟動類中開啟了一個線程,每隔2小時重新獲取一次 access_token 並賦值給一個 static變量,這樣我們就可以在其他類中獲取這個 access_token 了。

@Autowired
private RedisService redisService;
/** * 二維碼參數 */
@Autowired
private QrCodeParam param;
/** * 獲取二維碼 ticket * @param model * @param session * @return */
@RequestMapping(value = "/testToken", method = RequestMethod.GET)
public String testQrCode(Model model, HttpSession session) {

// 用token換取帶參數二維碼的 ticket
String access_token = DemoApplication.accessToken.getAccess_token();
String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + access_token;
// 獲取 ticket 的JSON格式
String result = NetWorkUtil.httpURLConnectionPOST(url, param);
// 將JSON轉換成Map
Map map = JSON.parseObject(result, Map.class);
// 獲取 ticket
String ticket = map.get("ticket").toString();
model.addAttribute("ticket", result);
Long aLong = Long.valueOf(param.getExpire_seconds());
// 用 ticket 做為一個客戶端的key(redis 的 key),值為 null ,只有當用戶掃碼並關注後才存值
redisService.set(ticket, null, aLong);
return "qrTest.html";
}

下面是 qrTest.html中的定義,ajax 輪詢的代碼也在其中

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h3 >token = </h3>
<img alt="場景參數二維碼" id="qrCode">
<script src="https://cdn.bootcss.com/jquery/2.1.2/jquery.js"></script>
<script th:inline="javascript"> // 獲取 ticket 並用來獲取二維碼,這是 thymeleaf 的取值方式,如果你用的 jsp 或者其他模板請注意更換 var ticket = JSON.parse([[${
ticket}]]); $('#qrCode').attr('src','https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket='+ticket.ticket); setInterval(function(){
 console.log('輪詢一次'); $.ajax({
 type : 'post', dataType : 'json', data : {
 "ticket":ticket.ticket, "expireSeconds" : ticket.expire_seconds }, url : '/qwxLogin/validQrCode', success : function(response){
 console.log('請求成功',response); if(response.wait){
 // 無為而坐 }else if(response.reload){
 alert('刷新'); // 重新獲取二維碼 window.location.reload(); }else if(response.scaned){
 alert('掃描完畢'); // 一個測試頁面,可以理解為系統主頁 window.location.href = "/focus/hello"; } }, error : function(error){
 console.log('請求失敗',error); } }) // 每隔一秒輪詢一次 },1000 * 1) </script>
</body>
</html>
@Component
@Data
@PropertySource("classpath:wechat.properties")
public class QrCodeParam {

/** * 該二維碼有效時間,以秒為單比特。 最大不超過2592000(即30天),此字段如果不填,則默認有效期為30秒。 */
@Value("${qr.expire_seconds}")
private String expire_seconds;
/** * 二維碼類型, * QR_SCENE為臨時的整型參數值, * QR_STR_SCENE為臨時的字符串參數值, * QR_LIMIT_SCENE為永久的整型參數值, * QR_LIMIT_STR_SCENE為永久的字符串參數值 */
@Value("${qr.action_name}")
private String action_name;
/** * 二維碼場景參數 */
private Map<String,Object> action_info;
/** * sessionID,用來區分二維碼是哪個客戶端的 */
private String sessionID;
}

這裏我把帶參數的二維碼需要設置的一些參數定義到了配置文件wechat.properties中,方便以後更改。(如果你不太了解這些字段,請一定要仔細看一下 官方文檔

創建 POST 接口接收消息/事件推送

在測試賬號的接口配置信息 的URL或者正式號的服務器配置中的URL既是接收消息/事件推送的接口地址,在Controller中創建一個POST請求即可。
微信服務器會將用戶信息/事件推送以 XML 格式的字符串傳遞到我們配置的接口中,查看所有格式請移步微信公眾平臺->消息管理。這裏只介紹登錄需要的 關注/取關、掃描帶參數的二維碼事件推送的消息。

  • 關注/取關事件推送 XML 格式
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
</xml>
  • 掃描帶參數二維碼
    • 用戶未關注時,進行關注後的事件推送
<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
<EventKey><![CDATA[qrscene_123123]]></EventKey>
<Ticket><![CDATA[TICKET]]></Ticket>
</xml>

在這裏插入圖片描述
* 用戶已關注時的事件推送

<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[SCAN]]></Event>
<EventKey><![CDATA[SCENE_VALUE]]></EventKey>
<Ticket><![CDATA[TICKET]]></Ticket>
</xml>

在這裏插入圖片描述
對於 XML 形式的字符串,可以使用上面提供的MessageUtil中的parseXML方法進行解析,返回一個Map。

接收微信服務器推送的 事件/用戶信息 的POST接口如下

/** * 當公眾號接收消息時,調用該接口,和上面的驗證簽名的 mapping 相同,請求方式不同 * @param req * @param res * @throws Exception */
@RequestMapping(path = "/wechatLogin/test", method = RequestMethod.POST)
public void receivesMessage(HttpServletRequest req, HttpServletResponse res) throws Exception{

// 設置編碼,否則返回給用戶信息時可能會亂碼
res.setCharacterEncoding("UTF-8");
// 解析微信服務器傳來的 XML 格式字符串
Map<String, String> stringMap = MessageUtil.parseXml(req);
// 獲取消息類型
String msgType = stringMap.get("MsgType");
// 如果是事件推送
if(MessageTypeEnum.REQ_MESSAGE_TYPE_EVENT.getTypeName().equals(msgType)){

// 如果用戶已經關注過公眾號,就是 SCAN 事件,或者是用戶的關注事件
if(EventTypeEnum.AFTER_FOCUS_ON.getEvent().equals(stringMap.get("Event")) || EventTypeEnum.BEFORE_FOCUS_ON.getEvent().equals(stringMap.get("Event"))){

System.err.println("關注或者掃碼事件。 xml = " + req.getSession().getId());
// 注意,如果要回複用戶消息,FromUserName 和 ToUserName 要調換順序
String toUser = stringMap.get("FromUserName");
String fromUser = stringMap.get("ToUserName");
// 獲取 ticket
String ticket = stringMap.get("Ticket");
// 將用戶掃描的二維碼的 Ticket作為 key,值為掃碼用戶的openId. redisService 代碼會在後面貼出。
redisService.set(ticket,toUser);
// 臨時返回信息 xml 字符串 這裏有個%s 這裏有個%s 
String reply = "<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>12345678</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[哈嘍!!!]]></Content</xml>";
String format = String.format(reply, toUser, fromUser);
res.getWriter().write(format);
}
// 如果用戶發來的文本事件,當然還可能有其他事件。請自行添加其他事件
}else {

System.err.println("文本事件");
String toUser = stringMap.get("FromUserName");
String fromUser = stringMap.get("ToUserName");
String reply = "<xml><ToUserName><![CDATA[%s]]></ToUserName><FromUserName><![CDATA[%s]]></FromUserName><CreateTime>12345678</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[text!!!]]></Content</xml>";
String format = String.format(reply, toUser, fromUser);
res.getWriter().write(format);
}
}

可以看到接收用戶的消息/事件推送後給用戶返回了一條提示消息,和接收消息一樣回複消息也要是微信指定的 XML 格式,具體格式請移步微信公眾平臺/被動回複用戶消息。具體的消息封裝成 XML 格式並返回給微信服務器的方法,請查看 這篇教程

總結

作為一名剛剛參加工作的初級開發攻城獅,當學習了新的知識之後做一個總結是個很好的習慣。記錄的同時也能發現之前自己犯的錯有多傻。還有,以前百度別人的教程,從來都是 command + ccommand+vcommand + w完事,從來沒想過要寫這麼久。

版权声明:本文为[霧裏看Java]所创,转载请带上原文链接,感谢。 https://gsmany.com/2022/01/202201072104198399.html