最近開(kāi)發(fā)某應(yīng)用的PC端后臺(tái)管理時(shí),突然對(duì)登錄頁(yè)面的賬號(hào)密碼還有圖片識(shí)字驗(yàn)證碼感到厭煩了,不僅填寫(xiě)麻煩,要記賬號(hào)密碼也麻煩。為什么不嘗試用微信掃碼登錄呢?功能實(shí)現(xiàn)后,我整理出來(lái)分享給大家(友情提示:閱讀本文需要比較熟悉微信公眾號(hào)的相關(guān)開(kāi)發(fā),前端框架是基于vue3的element plus,后端是.net6.0 的c# )。本文是針對(duì)PC上web應(yīng)用的登錄場(chǎng)景下,實(shí)際上我以前做過(guò)桌面端wpf程序的微信掃碼登錄,大致流程和代碼也是一樣的,也就是說(shuō)所有客戶端顯示二維碼,讓用戶掃碼登錄,處理流程大體是一樣,差異只是因?yàn)榭蛻舳瞬煌瑨叽a成功后服務(wù)端發(fā)送登錄信息給客戶端所采用的推送方式也不同。
一 功能設(shè)計(jì)描述
在PC上打開(kāi)后臺(tái)登陸頁(yè),無(wú)賬號(hào)密碼錄入框,只顯示一個(gè)二維碼。拿出手機(jī)微信掃碼,手機(jī)端顯示登錄成功的提示,然后pc端的登錄頁(yè)面自動(dòng)跳轉(zhuǎn)到登錄后的下個(gè)頁(yè)面。
二開(kāi)發(fā)前提條件:
作為管理后臺(tái),要用微信掃碼登錄,就表示登錄用戶在數(shù)據(jù)庫(kù)里必須已記錄了在自家公眾號(hào)下的openid。簡(jiǎn)單說(shuō),得有公眾號(hào),用戶表里得有用戶的openid(如何獲取可以參考微信公眾號(hào)開(kāi)發(fā)文檔),因?yàn)槿绾谓⒑笈_(tái)管理用戶大家都各有各的做法,我這里就很簡(jiǎn)單,發(fā)個(gè)鏈接給用戶微信,讓他點(diǎn)開(kāi)獲取微信身份授權(quán)后,再填入他在管理后臺(tái)的賬號(hào)密碼,連同微信授權(quán)code一起發(fā)到服務(wù)端即可。接口地址必須在公眾號(hào)的授權(quán)回調(diào)域中配置好。更詳細(xì)內(nèi)容請(qǐng)參考微信公眾號(hào)開(kāi)發(fā)的網(wǎng)頁(yè)授權(quán)部分。
三 程序流程設(shè)計(jì)
登陸頁(yè)面初始化,生成隨機(jī)字符串sessionid, 二維碼鏈接帶上sessionid ,當(dāng)用微信掃二維碼時(shí),服務(wù)端獲得用戶微信openid進(jìn)而通過(guò)openid找到用戶,核查合法后把用戶信息和sessionid一起寫(xiě)入緩存。anxIOS則用sessionid作為參數(shù)查詢服務(wù)端的登錄結(jié)果,服務(wù)端則從緩存讀取該sessionid的登錄信息返回給客戶端,客戶端獲得登錄信息后存在本地并跳轉(zhuǎn)主頁(yè),這是大致的流程。
由于掃碼成功后,登錄頁(yè)面需要自動(dòng)完成登錄過(guò)程,這里就涉及了一個(gè)web開(kāi)發(fā)常見(jiàn)的問(wèn)題-推送,即掃碼后服務(wù)端需要主動(dòng)把登錄結(jié)果發(fā)送給登錄頁(yè)面。本著用最簡(jiǎn)單方式實(shí)現(xiàn)的目的,我這里就是直接就用axios發(fā)一個(gè)正常請(qǐng)求,然后把 connect timeout參數(shù)設(shè)置大一點(diǎn),我打算通過(guò)服務(wù)端hold住線程輪詢數(shù)據(jù)來(lái)實(shí)現(xiàn)。當(dāng)然更完美一點(diǎn)的方案,就是用settimeout之類讓請(qǐng)求輪詢執(zhí)行,或者長(zhǎng)輪詢,或者使用websocket等方法,因篇幅限制,不展開(kāi)細(xì)講。
1 客戶端 登錄頁(yè)面vue代碼,我過(guò)濾了ui樣式以及細(xì)節(jié)處理的代碼,就是一個(gè)顯示二維碼的vue組件vue-qr,頁(yè)面初始化生成一個(gè)sessionid,拼在一個(gè)服務(wù)端接口地址上,形成二維碼組件的鏈接。頁(yè)面初始化同時(shí)也用sessionid發(fā)起查詢登錄結(jié)果請(qǐng)求。
<template>
<div class="loginTis">請(qǐng)使用微信掃碼登錄</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(); //生成隨時(shí)字符串,作為客戶端sessionid
return {
sessionId,
qrUrl: baseURL + "/admin/login?sessionId=" + sessionId, //二維碼指向地址
};
},
mounted () {
checkLogin({ sessionId: this.sessionId }) //詢問(wèn)服務(wù)端我的登錄是否成功
.then(res => {
if (res.data) { //登錄成功
this.$router.push({
path: "../index"
})
} else//登錄失敗,簡(jiǎn)單刷新
{
alert('登錄失敗');
location.href=location.href
}
});
}
};
</script>
2 服務(wù)端查詢登錄結(jié)果接口代碼,因?yàn)檫壿嬒鄬?duì)簡(jiǎn)單,所以我先講這個(gè)。簡(jiǎn)單描述就是收到客戶端請(qǐng)求后,用sessionid查詢緩存,用每隔2秒輪詢查詢,一直到查到有結(jié)果才返回客戶端。客戶端收到登錄結(jié)果就進(jìn)行下一步處理。
/// <summary>
/// 返回登錄結(jié)果
/// </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 二維碼對(duì)應(yīng)服務(wù)端代碼。這個(gè)接口地址也就是二維碼的鏈接,即用戶微信掃碼后打開(kāi)的地址,此時(shí)code參數(shù)為空,服務(wù)端會(huì)把當(dāng)前頁(yè)面重定向到微信的授權(quán)頁(yè)面,獲取到微信授權(quán)code后再跳轉(zhuǎn)回當(dāng)前接口地址,此時(shí)code不為空了,服務(wù)端拿到code,結(jié)合公眾號(hào)的Appid accesstoken之類就可以從微信得到用戶的openid,拿到openid就可以查詢用戶表,剩下的不用多介紹了吧。實(shí)際開(kāi)發(fā)時(shí) 為了使得程序更簡(jiǎn)單易維護(hù),也可以把接口拆分開(kāi),一個(gè)是掃碼接口,一個(gè)是微信授權(quán)后跳轉(zhuǎn)回來(lái)的接口,我為了省事,兩個(gè)接口合并在一起,通過(guò)檢查code是否為空,識(shí)別出是掃碼還是微信授權(quán)后跳轉(zhuǎn)回來(lái)。登錄成功后 輸出一段html代碼在手機(jī)上顯示。
/// <summary>
/// 登錄接口
/// </summary>
/// <param name="sessionId">客戶端sessionid</param>
/// <param name="code">公眾號(hào)授權(quán)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());//授權(quán)后跳轉(zhuǎn)回來(lái)
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);
}
}
}
碼字辛苦,如果覺(jué)得有參考價(jià)值請(qǐng)點(diǎn)個(gè)贊,如果有看不懂的地方歡迎提問(wèn),如果有更好更簡(jiǎn)單的辦法歡迎指教!