Vue3專案中優雅實現微信授權登入的方法
目錄
- 前言
- 準備
- 實現思路
- 上程式碼
- 總結
前言
微信授權登入是做微信公眾號開發一直繞不開的話題,而且整個授權登入流程的實現,是需要前後端配合一起完成的。在過去前後端還未分離的年代,也許我們前端並不需要太過關心授權的具體實現。然而現在都2021年了,前後端分離的架構大行其道,如何在前後端分離的情況下實現微信授權登入就成了今天要探討的重點問題。
準備
首先,我們還是需要先梳理下微信授權整個流程是怎樣的,這裡我就直接將官方文件搬來:
如果使用者在微信客戶端中訪問第三方,公眾號可以通過微信網頁授權機制,來獲取使用者基本資訊,進而實現業務邏輯。
...
關於網頁授權的兩種scope的區別說明
1、以snsapi_base為scope發起的網頁授權,是用來獲取進入頁面的使用者的openid的,並且是靜默授權並自動跳轉到回撥頁的。使用者感知的就是直接進入了回撥頁(往往是業務頁面)
2、以snsapi_userinfo為scope發起的網頁授權,是用來獲取使用者的基本資訊的。但這種授權需要使用者手動同意,並且由於使用者同意過,所以無須關注,就可在授權後獲取該使用者的基本資訊。
...
具體而言,網頁授權流程分為四步:
1、引導使用者進入授權頁面同意授權,獲取code
2、通過code換取網頁授權access_token(與基礎支援中的access_token不同)
3、如果需要,開發者可以重新整理網頁授權access_token,避免過期
4、通過網頁授權access_token和openid獲取使用者基本資訊(支援UnionID機制)
這裡附上微信公眾號開發之微信授權的官方文件。
以上是筆者提煉出來的比較關鍵的幾點資訊,當然還有更多的說明,希望新手讀者們還是先認真看完官方文件。
這裡我再補充說明下,以上流程的4個步驟中,除了第一步以外,另外三步都是需要在伺服器端去完成的。前端要做的核心其實是怎麼進行使用者登入狀態的檢查判斷和登入狀態的維護。
實現思路
大家都知道,是前後端分離技術方案下的產物,它是一個純前端應用(服務端渲染除外)。通常我們是需要在使用者開啟頁面,執行到頁面的時,我們再非同步去請求服務端資料,再進行相關邏輯的處理和判斷。我們要實現微信授權登入的前提是,需要先判斷使用者是否需要登入(cookie或者token)。當用戶未登入時,才需要走授權登入流程,當授權登入成功後,我們也需要在前端記錄好登入狀態,以方便在頁面切換時,不用再次觸發授權登入。再通過分析可知,前端其實能做的就是獲取微信伺服器給我們的code,再將code給我們的後端,讓後端完成後續步驟拿到使用者資訊後生成使用者。那麼整個過程我再梳理如下:
- (前端)檢查使用者是否登入;
- (前端)如果未登入,引導使用者進入授權頁面同意授權,獲取code
- (前端)將獲取到的code提交給後端
- (後端)通過code換取使用者憑證openid
- (後端)通過openid檢查使用者是否存在,是否需要註冊新使用者,並獲取使用者id
- (後端)返回使用者資訊;
- (前端)記錄使用者登入狀態,跳回登入前頁面;
這個過程,我畫了個圖,如下:
上程式碼
根據以上思路,現在開始編碼環節。筆者採用的是Vue3,Vue2的開發者還請根據情況做適當調整。
為了方便喚起使用者授權登入邏輯,筆者打算將授權登入封住為一個login頁面,這樣做的好處是我們在任何判斷到需要登www.cppcns.com錄的地方直接通過Vue Router的push方法跳轉到登入頁面即可。
通常情況下,我們的應用並不是所有頁面都需要登入後才能訪問,只有在訪問特定頁面時候,才需要使用者登入,那麼我們就需要標識哪些頁面需要進行登入鑑權。這裡我們可以利用Vue Router的meta屬性來進行標識,官方文件對meta解釋如下:
有時,你可能希望將任意資訊附加到路由上,如過渡名稱、誰可以訪問路由等。這些事情可以通過接收屬性物件的meta屬性來實現,並且它可以在路由地址和導航守衛上都被訪問到。
剛好Vue Router官方就有示例,如下:
const routes = [ { path: '/posts',component: PostsLayout,children: [ { path: 'new',component: PostsNew,// 需要登入後才能訪問的頁面 meta: { requiresAuth: true } },{ path: ':id',component: PostsDetail,// 任何人都可訪問的頁面 meta: { requiresAuth: false } } ] } ]
接下來我們就可以在Vue Router的全域性守衛beforeEach中獲取到這個元資訊從而做登入跳轉了
router.beforeEach((to,from) => { // 而不是去檢查每條路由記錄 // to.matched.some(record => record.meta.requiresAuth) if (to.meta.requiresAuth && !userStore.isLogin) { // 此路由需要授權,請檢查是否已登入 // 如果沒有,則重定向到登入頁面 return { path: '/login',// 儲存我們所在的位置,以便以後再來 query: { redirect: to.fullPath },} } })
需要補充說明的是,userStore.isLogin的實現。這裡和我們實際採用的登入態維護方案有關,如果是採用token方式的話,那就是檢查token是否已經存在。筆者採用了vuex作為來儲存token,然後藉助外掛來將Store中的資料持久化到localStorage。
接下來我們來看具體的實現:
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,則已經授權 if (code) { getUserInfo(code as string).then((res: any) => { // 記錄token userStore.saveToken(res.access_token) const redirect = userStore.userState.landPageRoute || '/' // 跳轉到授權前訪問的頁面 redirectTo(redirect) }) } else { // 1.記錄上一個頁面的地址 const { redirect } = getRouteQuery() if http://www.cppcns.com(redirect) { userStore.setLandPage(redirect as string) } // 2.跳轉授權 const callbackUrl = window.location.origin + window.location.pathname jump2Auth(callbackUrl) } },}) </script>
可以看到,login頁面其實並沒有什麼內容,跳轉到該頁面後,我們會直接重定向到微信授權的頁面,授權回客棧調回來也會回到該頁面,此時我們再通過獲取路由引數的方式獲取code引數。
@/hooks/usePage.ts: 該檔案主要是封裝了router相關的常用方法
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引數 */ export function getRouteQuery() { const { currentRoute } = router const { query } = currentRoute.value return cloneDeep(query) }
@/hooks/useWechatAuth.ts:該檔案封裝了微信授權與後端互動的請求
import { useAxios } from '@/hooks/useAxios' /** * 獲取微信授權的跳轉地址 * @param callbackUrl 授權後回撥連結 * @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進行登入 * @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: 全域性狀態儲存,主要是記錄token和登入前訪問頁面
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設定為true時,表示建立動態模組,執行時將模組註冊到儲存中 // preserveState 如果資料有持久化,該變數為true時可以從storage中拿取初始值 @Module({ namespaced: true,name: NAME,dynamic: true,store,preserveState: Boolean(initialUnencryptedStorage[NAME]),}) export class User extends VuexModule { userState: UserState = { token: '',tUCrEZFizo /** 登入前訪問頁面 */ 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中資料儲存到了localStorage,這樣做的好處是使用者關閉頁面後,再次訪問,即不用重新觸發微信授權流程,大大優化了使用者體驗。
總結
不得不說,Vue3在程式碼抽象和複用這塊上,寫起來著實舒服很多,希望大家也也嘗試著按照官方實踐那樣,多將邏輯程式碼解耦抽離,生成一個個的hook,這樣程式碼就顯得優雅多啦。該方案經過筆者嘗試論證,不論是程式碼整潔優雅程度,還是業務需求的實現上,都幾乎完美(請容我裝一波b)。當然,這裡可能存在我沒發現的bug或者痛點,畢竟從來沒有十全十美的架構嘛,這裡也歡迎看官大佬們和我交流探討,提供更好的方案和想法。
到此這篇關於Vue3專案中優雅實現微信授權登入的文章就介紹到這了,更多相關Vue3微信授權登入內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!