最近開發某應用的PC端后臺管理時,突然對登錄頁面的賬號密碼還有圖片識字驗證碼感到厭煩了,不僅填寫麻煩,要記賬號密碼也麻煩。為什么不嘗試用微信掃碼登錄呢?功能實現后,我整理出來分享給大家(友情提示:閱讀本文需要比較熟悉微信公眾號的相關開發,前端框架是基于vue3的element plus,后端是.net6.0 的c# )。本文是針對PC上web應用的登錄場景下,實際上我以前做過桌面端wpf程序的微信掃碼登錄,大致流程和代碼也是一樣的,也就是說所有客戶端顯示二維碼,讓用戶掃碼登錄,處理流程大體是一樣,差異只是因為客戶端不同,掃碼成功后服務端發送登錄信息給客戶端所采用的推送方式也不同。
一 功能設計描述
在PC上打開后臺登陸頁,無賬號密碼錄入框,只顯示一個二維碼。拿出手機微信掃碼,手機端顯示登錄成功的提示,然后pc端的登錄頁面自動跳轉到登錄后的下個頁面。
二開發前提條件:
作為管理后臺,要用微信掃碼登錄,就表示登錄用戶在數據庫里必須已記錄了在自家公眾號下的openid。簡單說,得有公眾號,用戶表里得有用戶的openid(如何獲取可以參考微信公眾號開發文檔),因為如何建立后臺管理用戶大家都各有各的做法,我這里就很簡單,發個鏈接給用戶微信,讓他點開獲取微信身份授權后,再填入他在管理后臺的賬號密碼,連同微信授權code一起發到服務端即可。接口地址必須在公眾號的授權回調域中配置好。更詳細內容請參考微信公眾號開發的網頁授權部分。
三 程序流程設計
登陸頁面初始化,生成隨機字符串sessionid, 二維碼鏈接帶上sessionid ,當用微信掃二維碼時,服務端獲得用戶微信openid進而通過openid找到用戶,核查合法后把用戶信息和sessionid一起寫入緩存。anxIOS則用sessionid作為參數查詢服務端的登錄結果,服務端則從緩存讀取該sessionid的登錄信息返回給客戶端,客戶端獲得登錄信息后存在本地并跳轉主頁,這是大致的流程。
由于掃碼成功后,登錄頁面需要自動完成登錄過程,這里就涉及了一個web開發常見的問題-推送,即掃碼后服務端需要主動把登錄結果發送給登錄頁面。本著用最簡單方式實現的目的,我這里就是直接就用axios發一個正常請求,然后把 connect timeout參數設置大一點,我打算通過服務端hold住線程輪詢數據來實現。當然更完美一點的方案,就是用settimeout之類讓請求輪詢執行,或者長輪詢,或者使用websocket等方法,因篇幅限制,不展開細講。
1 客戶端 登錄頁面vue代碼,我過濾了ui樣式以及細節處理的代碼,就是一個顯示二維碼的vue組件vue-qr,頁面初始化生成一個sessionid,拼在一個服務端接口地址上,形成二維碼組件的鏈接。頁面初始化同時也用sessionid發起查詢登錄結果請求。
<template>
<div class="loginTis">請使用微信掃碼登錄</div>
<vue-qr
:text="qrUrl" >
</vue-qr>
</div>
</template>
<script>
import vueQr from 'vue-qr';
import { checkLogin, baseURL } from "@/api/api";
export default {
components: {
vueQr
},
data () {
const sessionId = random_string(); //生成隨時字符串,作為客戶端sessionid
return {
sessionId,
qrUrl: baseURL + "/admin/login?sessionId=" + sessionId, //二維碼指向地址
};
},
mounted () {
checkLogin({ sessionId: this.sessionId }) //詢問服務端我的登錄是否成功
.then(res => {
if (res.data) { //登錄成功
this.$router.push({
path: "../index"
})
} else//登錄失敗,簡單刷新
{
alert('登錄失敗');
location.href=location.href
}
});
}
};
</script>
2 服務端查詢登錄結果接口代碼,因為邏輯相對簡單,所以我先講這個。簡單描述就是收到客戶端請求后,用sessionid查詢緩存,用每隔2秒輪詢查詢,一直到查到有結果才返回客戶端。客戶端收到登錄結果就進行下一步處理。
/// <summary>
/// 返回登錄結果
/// </summary>
/// <param name="sessionId">客戶端sessionid</param>
/// <returns></returns>
[EnableCors]
[HttpGet]
[AllowAnonymous]
public object CheckLogin(string sessionId)
{
var loginResult = redis_Utility.Get<object>($"login_{sessionId}");
while (loginResult == null)
{
Thread.Sleep(2000);
loginResult = Redis_Utility.Get<object>($"login_{sessionId}");
}
return loginResult;
}
3 二維碼對應服務端代碼。這個接口地址也就是二維碼的鏈接,即用戶微信掃碼后打開的地址,此時code參數為空,服務端會把當前頁面重定向到微信的授權頁面,獲取到微信授權code后再跳轉回當前接口地址,此時code不為空了,服務端拿到code,結合公眾號的Appid accesstoken之類就可以從微信得到用戶的openid,拿到openid就可以查詢用戶表,剩下的不用多介紹了吧。實際開發時 為了使得程序更簡單易維護,也可以把接口拆分開,一個是掃碼接口,一個是微信授權后跳轉回來的接口,我為了省事,兩個接口合并在一起,通過檢查code是否為空,識別出是掃碼還是微信授權后跳轉回來。登錄成功后 輸出一段html代碼在手機上顯示。
/// <summary>
/// 登錄接口
/// </summary>
/// <param name="sessionId">客戶端sessionid</param>
/// <param name="code">公眾號授權code</param>
/// <returns>{userInfo:xxxx, token:xxxx}</returns>
[HttpGet]
[AllowAnonymous]
public async Task Login(string sessionId, string? code = null)
{
if (string.IsNullOrEmpty(code))
{
string redirect_uri = HttpUtility.UrlEncode(Request.GetDisplayUrl());//授權后跳轉回來
Response.Redirect($"https://open.weixin.qq.com/connect/oauth2/authorize?appid={APPID}&redirect_uri={redirect_uri}&response_type=code&scope=SCOPE&state=STATE#wechat_redirect");
}
else
{
string openId = await WechatServices.GetOpenId(code);
var result = adminServices.Login(openId);
if (result.Code == 0)
{
Redis_Utility.Set($"expert_admin_login_{sessionId}", result.Data, 1);
string html = htmlTemp.Replace("$content", "登錄成功");
await Response.WriteAsync(html);
}
else
{
await Response.WriteAsync(result.Message);
}
}
}
碼字辛苦,如果覺得有參考價值請點個贊,如果有看不懂的地方歡迎提問,如果有更好更簡單的辦法歡迎指教!