Vue3項(xiàng)目中優(yōu)雅實(shí)現(xiàn)微信授權(quán)登錄的方法
前言
微信授權(quán)登錄是做微信公眾號(hào)開發(fā)一直繞不開的話題,而且整個(gè)授權(quán)登錄流程的實(shí)現(xiàn),是需要前后端配合一起完成的。在過去前后端還未分離的年代,也許我們前端并不需要太過關(guān)心授權(quán)的具體實(shí)現(xiàn)。然而現(xiàn)在都2021年了,前后端分離的架構(gòu)大行其道,如何在前后端分離的情況下實(shí)現(xiàn)微信授權(quán)登錄就成了今天要探討的重點(diǎn)問題。
準(zhǔn)備
首先,我們還是需要先梳理下微信授權(quán)整個(gè)流程是怎樣的,這里我就直接將官方文檔搬來(lái):
如果用戶在微信客戶端中訪問第三方網(wǎng)頁(yè),公眾號(hào)可以通過微信網(wǎng)頁(yè)授權(quán)機(jī)制,來(lái)獲取用戶基本信息,進(jìn)而實(shí)現(xiàn)業(yè)務(wù)邏輯。
...
關(guān)于網(wǎng)頁(yè)授權(quán)的兩種scope的區(qū)別說(shuō)明
1、以snsapi_base為scope發(fā)起的網(wǎng)頁(yè)授權(quán),是用來(lái)獲取進(jìn)入頁(yè)面的用戶的openid的,并且是靜默授權(quán)并自動(dòng)跳轉(zhuǎn)到回調(diào)頁(yè)的。用戶感知的就是直接進(jìn)入了回調(diào)頁(yè)(往往是業(yè)務(wù)頁(yè)面)
2、以snsapi_userinfo為scope發(fā)起的網(wǎng)頁(yè)授權(quán),是用來(lái)獲取用戶的基本信息的。但這種授權(quán)需要用戶手動(dòng)同意,并且由于用戶同意過,所以無(wú)須關(guān)注,就可在授權(quán)后獲取該用戶的基本信息。
...
具體而言,網(wǎng)頁(yè)授權(quán)流程分為四步:
1、引導(dǎo)用戶進(jìn)入授權(quán)頁(yè)面同意授權(quán),獲取code
2、通過code換取網(wǎng)頁(yè)授權(quán)access_token(與基礎(chǔ)支持中的access_token不同)
3、如果需要,開發(fā)者可以刷新網(wǎng)頁(yè)授權(quán)access_token,避免過期
4、通過網(wǎng)頁(yè)授權(quán)access_token和openid獲取用戶基本信息(支持UnionID機(jī)制)
這里附上微信公眾號(hào)開發(fā)之微信授權(quán)的官方文檔。
以上是筆者提煉出來(lái)的比較關(guān)鍵的幾點(diǎn)信息,當(dāng)然還有更多的說(shuō)明,希望新手讀者們還是先認(rèn)真看完官方文檔。
這里我再補(bǔ)充說(shuō)明下,以上流程的4個(gè)步驟中,除了第一步以外,另外三步都是需要在服務(wù)器端去完成的。前端要做的核心其實(shí)是怎么進(jìn)行用戶登錄狀態(tài)的檢查判斷和登錄狀態(tài)的維護(hù)。
實(shí)現(xiàn)思路
大家都知道,Vue是前后端分離技術(shù)方案下的產(chǎn)物,它是一個(gè)純前端應(yīng)用(服務(wù)端渲染除外)。通常我們是需要在用戶打開頁(yè)面,執(zhí)行到頁(yè)面的js腳本時(shí),我們?cè)佼惒饺フ?qǐng)求服務(wù)端數(shù)據(jù),再進(jìn)行相關(guān)邏輯的處理和判斷。我們要實(shí)現(xiàn)微信授權(quán)登錄的前提是,需要先判斷用戶是否需要登錄(cookie或者token)。當(dāng)用戶未登錄時(shí),才需要走授權(quán)登錄流程,當(dāng)授權(quán)登錄成功后,我們也需要在前端記錄好登錄狀態(tài),以方便在頁(yè)面切換時(shí),不用再次觸發(fā)授權(quán)登錄。再通過分析可知,前端其實(shí)能做的就是獲取微信服務(wù)器給我們的code,再將code給我們的后端,讓后端完成后續(xù)步驟拿到用戶信息后生成用戶。那么整個(gè)過程我再梳理如下:
- (前端)檢查用戶是否登錄;
- (前端)如果未登錄,引導(dǎo)用戶進(jìn)入授權(quán)頁(yè)面同意授權(quán),獲取code
- (前端)將獲取到的code提交給后端
- (后端)通過code換取用戶憑證openid
- (后端)通過openid檢查用戶是否存在,是否需要注冊(cè)新用戶,并獲取用戶id
- (后端)返回用戶信息;
- (前端)記錄用戶登錄狀態(tài),跳回登錄前頁(yè)面;
這個(gè)過程,我畫了個(gè)圖,如下:

上代碼
根據(jù)以上思路,現(xiàn)在開始編碼環(huán)節(jié)。筆者采用的是Vue3,Vue2的開發(fā)者還請(qǐng)根據(jù)情況做適當(dāng)調(diào)整。
為了方便喚起用戶授權(quán)登錄邏輯,筆者打算將授權(quán)登錄封住為一個(gè)login頁(yè)面,這樣做的好處是我們?cè)谌魏闻袛嗟叫枰卿浀牡胤街苯油ㄟ^Vue Router的push方法跳轉(zhuǎn)到登錄頁(yè)面即可。
通常情況下,我們的應(yīng)用并不是所有頁(yè)面都需要登錄后才能訪問,只有在訪問特定頁(yè)面時(shí)候,才需要用戶登錄,那么我們就需要標(biāo)識(shí)哪些頁(yè)面需要進(jìn)行登錄鑒權(quán)。這里我們可以利用Vue Router的meta屬性來(lái)進(jìn)行標(biāo)識(shí),官方文檔對(duì)meta解釋如下:
有時(shí),你可能希望將任意信息附加到路由上,如過渡名稱、誰(shuí)可以訪問路由等。這些事情可以通過接收屬性對(duì)象的meta屬性來(lái)實(shí)現(xiàn),并且它可以在路由地址和導(dǎo)航守衛(wèi)上都被訪問到。
剛好Vue Router官方就有示例,如下:
const routes = [
{
path: '/posts',
component: PostsLayout,
children: [
{
path: 'new',
component: PostsNew,
// 需要登錄后才能訪問的頁(yè)面
meta: { requiresAuth: true }
},
{
path: ':id',
component: PostsDetail,
// 任何人都可訪問的頁(yè)面
meta: { requiresAuth: false }
}
]
}
]
接下來(lái)我們就可以在Vue Router的全局守衛(wèi)beforeEach中獲取到這個(gè)元信息從而做登錄跳轉(zhuǎn)了
router.beforeEach((to, from) => {
// 而不是去檢查每條路由記錄
// to.matched.some(record => record.meta.requiresAuth)
if (to.meta.requiresAuth && !userStore.isLogin) {
// 此路由需要授權(quán),請(qǐng)檢查是否已登錄
// 如果沒有,則重定向到登錄頁(yè)面
return {
path: '/login',
// 保存我們所在的位置,以便以后再來(lái)
query: { redirect: to.fullPath },
}
}
})
需要補(bǔ)充說(shuō)明的是,userStore.isLogin的實(shí)現(xiàn)。這里和我們實(shí)際采用的登錄態(tài)維護(hù)方案有關(guān),如果是采用token方式的話,那就是檢查token是否已經(jīng)存在。筆者采用了vuex作為來(lái)保存token,然后借助插件來(lái)將Store中的數(shù)據(jù)持久化到localStorage。
接下來(lái)我們來(lái)看具體的實(shí)現(xiàn):
login.vue: 登錄組件
<template>
<div class="login"></div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { jump2Auth, getUserInfo } from '@/hooks/useWechatAuth'
import { userStore } from '@/store/modules/user'
import { redirectTo, getRouteQuery } from '@/hooks/usePage'
export default defineComponent({
name: 'Login',
setup() {
let code = getRouteQuery().code as string
// 3.如果有code,則已經(jīng)授權(quán)
if (code) {
getUserInfo(code as string).then((res: any) => {
// 記錄token
userStore.saveToken(res.access_token)
const redirect = userStore.userState.landPageRoute || '/'
// 跳轉(zhuǎn)到授權(quán)前訪問的頁(yè)面
redirectTo(redirect)
})
} else {
// 1.記錄上一個(gè)頁(yè)面的地址
const { redirect } = getRouteQuery()
if (redirect) {
userStore.setLandPage(redirect as string)
}
// 2.跳轉(zhuǎn)授權(quán)
const callbackUrl = window.location.origin + window.location.pathname
jump2Auth(callbackUrl)
}
},
})
</script>
可以看到,login頁(yè)面其實(shí)并沒有什么內(nèi)容,跳轉(zhuǎn)到該頁(yè)面后,我們會(huì)直接重定向到微信授權(quán)的頁(yè)面,授權(quán)回調(diào)回來(lái)也會(huì)回到該頁(yè)面,此時(shí)我們?cè)偻ㄟ^獲取路由參數(shù)的方式獲取code參數(shù)。
@/hooks/usePage.ts: 該文件主要是封裝了router相關(guān)的常用方法
import router from '@/router'
import { cloneDeep } from 'lodash'
import { toRaw } from 'vue'
/**
* 重定向
* @param path 路徑
*/
export function redirectTo(path: string) {
const { replace } = router
replace({
path,
})
}
/**
* 獲取路由上query參數(shù)
*/
export function getRouteQuery() {
const { currentRoute } = router
const { query } = currentRoute.value
return cloneDeep(query)
}
@/hooks/useWechatAuth.ts:該文件封裝了微信授權(quán)與后端交互的請(qǐng)求
import { useAxios } from '@/hooks/useAxios'
/**
* 獲取微信授權(quán)的跳轉(zhuǎn)地址
* @param callbackUrl 授權(quán)后回調(diào)鏈接
* @returns
*/
export function jump2Auth(callbackUrl: string) {
useAxios({
url: '/api/wechat/auth',
params: {
redirect_url: callbackUrl,
},
}).then((authUrl: any) => {
if (process.env.NODE_ENV === 'development') {
window.location.href = callbackUrl + '?code=test'
} else {
window.location.href = authUrl
}
})
}
/**
* 提交code進(jìn)行登錄
* @param code
* @returns
*/
export async function getUserInfo(code: string) {
const userInfo = await useAxios({
method: 'POST',
url: '/api/wechat/auth',
params: {
code,
},
})
return userInfo
}
@/store/modules/user.ts: 全局狀態(tài)存儲(chǔ),主要是記錄token和登錄前訪問頁(yè)面
import { Module, VuexModule, Mutation, getModule, Action } from 'vuex-module-decorators'
import store from '@/store'
import { initialUnencryptedStorage } from '../globals'
interface UserState {
token: string
landPageRoute: string
}
const NAME = 'user'
// name: 模塊名字
// namespaced 表示開啟命名空間
// dynamic設(shè)置為true時(shí),表示創(chuàng)建動(dòng)態(tài)模塊,運(yùn)行時(shí)將模塊注冊(cè)到存儲(chǔ)中
// preserveState 如果數(shù)據(jù)有持久化,該變量為true時(shí)可以從storage中拿取初始值
@Module({
namespaced: true,
name: NAME,
dynamic: true,
store,
preserveState: Boolean(initialUnencryptedStorage[NAME]),
})
export class User extends VuexModule {
userState: UserState = {
token: '',
/** 登錄前訪問頁(yè)面 */
landPageRoute: '',
}
get isLogin(): boolean {
return !!this.userState.token
}
@Mutation
saveToken(token: string): void {
this.userState.token = token
}
@Mutation
setLandPage(route: string): void {
this.userState.landPageRoute = route
}
}
export const userStore = getModule<User>(User)
筆者借助vuex-persistedstate插件將store中數(shù)據(jù)存儲(chǔ)到了localStorage,這樣做的好處是用戶關(guān)閉頁(yè)面后,再次訪問,即不用重新觸發(fā)微信授權(quán)流程,大大優(yōu)化了用戶體驗(yàn)。
總結(jié)
不得不說(shuō),Vue3在代碼抽象和復(fù)用這塊上,寫起來(lái)著實(shí)舒服很多,希望大家也也嘗試著按照官方實(shí)踐那樣,多將邏輯代碼解耦抽離,生成一個(gè)個(gè)的hook,這樣代碼就顯得優(yōu)雅多啦。該方案經(jīng)過筆者嘗試論證,不論是代碼整潔優(yōu)雅程度,還是業(yè)務(wù)需求的實(shí)現(xiàn)上,都幾乎完美(請(qǐng)容我裝一波b)。當(dāng)然,這里可能存在我沒發(fā)現(xiàn)的bug或者痛點(diǎn),畢竟從來(lái)沒有十全十美的架構(gòu)嘛,這里也歡迎看官大佬們和我交流探討,提供更好的方案和想法。
到此這篇關(guān)于Vue3項(xiàng)目中優(yōu)雅實(shí)現(xiàn)微信授權(quán)登錄的文章就介紹到這了,更多相關(guān)Vue3微信授權(quán)登錄內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
利用vue重構(gòu)有贊商城的思路以及總結(jié)整理
這篇文章主要介紹了利用vue重構(gòu)有贊商城的思路以及總結(jié)整理,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2019-02-02
在vue項(xiàng)目中使用codemirror插件實(shí)現(xiàn)代碼編輯器功能
這篇文章主要介紹了在vue項(xiàng)目中使用codemirror插件實(shí)現(xiàn)代碼編輯器功能(代碼高亮顯示及自動(dòng)提示),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08
vue實(shí)現(xiàn)鼠標(biāo)經(jīng)過顯示懸浮框效果
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)鼠標(biāo)經(jīng)過顯示懸浮框效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03
web前端vue之vuex單獨(dú)一文件使用方式實(shí)例詳解
Vuex 是一個(gè)專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式。這篇文章主要介紹了web前端vue:vuex單獨(dú)一文件使用方式,需要的朋友可以參考下2018-01-01
vue頁(yè)面使用阿里oss上傳功能的實(shí)例(一)
本篇文章主要介紹了vue頁(yè)面使用阿里oss上傳功能的實(shí)例(一),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來(lái)看看吧2017-08-08
VUE中Echarts的resize事件報(bào)錯(cuò)和移除windows的事件問題
這篇文章主要介紹了VUE中Echarts的resize事件報(bào)錯(cuò)和移除windows的事件問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07

