學(xué)習(xí)總結(jié)vue后臺管理系統(tǒng)
后臺頁面的權(quán)限驗證與安全性是非常重要的,可以說是一個后臺項目一開始就必須考慮和搭建的基礎(chǔ)核心功能
我們前端所要做的是:? ?不同的權(quán)限對應(yīng)著不同的路由,同時側(cè)邊欄也需要根據(jù)不同的權(quán)限 ,? 異步生成.
技術(shù)棧主要有: vue,vue-router,vuex,axios,vue-cli 3.x(沒有webpack.config.js配置文件,取而代之的是vue.config.js文件), fiddle.php,nodejs(express框架配合myspl搭建過一個簡單的后臺系統(tǒng)框架,cookie.session配合使用,驗證登錄狀態(tài),但是我們這個項目使用的是token來驗證)
一. 登入界面
1.? 登錄: 當(dāng)用戶填寫完賬號和密碼后向服務(wù)端驗證是否正確, 服務(wù)端返回一個token,? 拿到token之后(我會將這個token存儲到cookie中,保證刷新頁面后能記住用戶登錄), 前端會根據(jù)token在去拉取一個user_info的接口來獲取用戶的詳細信息(如用戶權(quán)限,用戶名等等信息)
2.??權(quán)限驗證:? 通過token獲取用戶對應(yīng)的role,? 動態(tài)根據(jù)用戶的role算出其對應(yīng)有權(quán)限的路由, 通過router.addRoutes動態(tài)掛載這些路由.
這些都是通過VUEX全局管理控制的(補充說明: 刷新頁面后vuex的內(nèi)容也會丟失)
具體實施:?
????????首先做一個靜態(tài)登入頁面,兩個input的框, 一個登錄賬號,一個登錄密碼,在放置一個登錄按鈕,綁定click事件,點擊登錄之后向服務(wù)端提交賬號和密碼進行驗證,在向服務(wù)端提交賬號和密碼之前我們前端還可以進行一次簡單的校驗,減輕服務(wù)器壓力,優(yōu)化前端代碼
? ? ? ? click綁定登錄按鈕,當(dāng)點擊按鈕,提交賬號密碼,登錄成功之后在這里推薦是用第三方登錄平臺不重定向到首頁, this.showDialog = true //彈出選擇第三方平臺的dialog,利用this.$store.dispatch提交username信息到vuex中的異步action,并將token儲存在cookie之中,這樣下次打開頁面的時候能記住用戶的登錄狀態(tài),不用在登錄頁面重新登錄了.
注意: 為了安全性,我司在后臺所有token有效期都是seeion,就是瀏覽器關(guān)閉了就丟失了,重新打開瀏覽器都需要重新登錄一次,確保用戶不會因為電腦遺失或者其他原因被人隨意使用賬號
1.1. 獲取用戶信息
用戶登錄成功之后,我們在全局鉤子`router.beforeEach`中攔截路由,判斷是否已獲得token,在獲取token之后我們就要去獲取用戶的基本信息了
(同時要注意一點的是: 我們之后存儲一個用戶token,并沒有存儲別的用戶信息{用戶名,用戶頭像等})
(假設(shè)我把用戶權(quán)限和用戶名存在本地,如果我在這時候有另一臺電腦登錄并修改了自己的用戶名,那再用之前的電腦登錄,,那么他會默認(rèn)去讀取本地cookie中的名字,并不會去拉取新的用戶信息)
所以現(xiàn)在的策略:
頁面會從cookie中查看是否存在token,沒有,就走一遍上部分的流程重新登錄,如果有token,就會把這個token返給后端去拉取user_info,保證用戶信息是最新的.(如果做了單點登錄功能的話, 用戶信息存儲在本地也是可以得,當(dāng)你一臺電腦登錄時谣殊,另一臺會被提下線楚殿,所以總會重新登錄獲取最新的內(nèi)容)
而且從代碼層面我建議還是把?login和get_user_info兩件事分開比較好,在這個后端全面微服務(wù)的年代,后端同學(xué)也想寫優(yōu)雅的代碼~
二. 權(quán)限篇
在工作中,前端會有一個路由表,他表示了每一個路由可訪問的權(quán)限.
1. 用戶登錄之后,通過token獲取用戶的role
2. 動態(tài)根據(jù)用戶的role 算出其對應(yīng)應(yīng)有權(quán)限的路由
3. 再通過router.addRouetes 動態(tài)掛載路由(這些都只是路由級的,后端的權(quán)限是逃不掉的)
????????現(xiàn)在,就是前端來控制頁面級的權(quán)限,不同權(quán)限的用戶顯示不同的側(cè)邊欄和限制其所能進入的頁面(還有少許的按鈕級別的權(quán)限控制),后端會驗證每一個涉及請求的操作,驗證其是否有該操作的權(quán)限,每一個后臺的請求不管是get還是post都會讓前端在請求header里面攜帶用戶token , 后端會根據(jù)改token來驗證在token是否有權(quán)限執(zhí)行該操作,如果沒有權(quán)限就會拋出一個對應(yīng)的狀態(tài)碼,前端測到狀態(tài)碼,做出相應(yīng)的操作
三. 具體實現(xiàn)
1. 創(chuàng)建vue實例的時候?qū)ue-router掛載 , 但這個時候vue-router掛載一些登錄或者不用權(quán)限的公用的頁面
2. 當(dāng)用戶登錄后, 獲取用role, 將role和路由表每個頁面需要的權(quán)限作比較,? 生成最終用戶可訪問的路由表
3. 調(diào)用router.addRoutes(store.getters.addRouters)添加用戶可訪問的路由
4. 使用vuex管理路由表,? 根據(jù)vuex中可訪問的路由渲染側(cè)邊欄組件
router.js中書寫實現(xiàn)路由表:
1. 首先我們要實現(xiàn)如首頁和登錄頁和一些不用權(quán)限的公用頁面vue-router如登錄頁和首頁
2. 之后實例化vue的時候只掛載上面不用權(quán)限的路由export default new Router({routers: 上面的路由})
3. 異步掛載路由:? ?動態(tài)需要根據(jù)權(quán)限加載路由表,在這里我們根據(jù)vue-router官方推薦的方法meta路線元字段(可以`meta`在定義路徑時包含字段,寫在你children里面)標(biāo)簽來標(biāo)示頁面訪問的權(quán)限有哪些 `meta: {role: ['admin' , 'super_editor']}` 表示該頁面只有admin個超級編輯才能有資格進入
注意事項:? 這里有一個需要非常注意的地方就是404 頁面一定要最后加載 , 如果放在constantRouterMap 一同聲明了 404 , 后面的所有頁面都會攔截404
main.js數(shù)據(jù)入口文檔(關(guān)鍵的main.js)
在main.js中主要用的是router.beforeEach(to,from,next) => { if (store.getters.token) {if (to.path === '/login') {next ({path: '/'}))}}}也就是登錄之后,判斷是否有token,如果沒有,那就在免登錄白名單中查找,如果有就是直接進入,如果沒有那么就跳轉(zhuǎn)到登錄頁
????????如果有,并且入口路徑to是從/login登錄頁中進入的,那么就redirect重定向跳轉(zhuǎn)到首頁,
????????否則先判斷當(dāng)前用戶是否已拉取完user_info信息? if(store.getters.roles.length === 0) , 如果是,那么user_info 拉取info`store.dispatch('GetInfo方法名').then`在從這個異步操作中獲取所有的值,const roles = res.data.role,生成可訪問的路由表store.dispatch('GenerateRoutes', {roles}),在獲取到可訪問的路由表后,我們在動態(tài)添加可訪問的路由表router.addRoutes(store.getters.addRouters)? ,在通過next({ ...to , replace: true})hack方法 確保addRouters 已完成 , set the replace: true
如果沒有拉取到info信息就返回err .catch(err => {console.log(err)})
如果當(dāng)有用戶權(quán)限的時候,說明所有可訪問路由已生成 , 如果沒權(quán)限的頁面會自動進入404頁面
如果頁面沒有token時,如果在面登入的白名單中,就直接進入if(whiteList.indexOf(to.path) !== -1){next()} , 否則全部重定向到登入頁面
下面是store/permission.js
這里就是干一件是,通過用戶權(quán)限和之前在router.js里面asyncRouterMap的每一個頁面所需要的權(quán)限做匹配 , 最后放回一個該用戶能夠訪問路由有哪些
這是一個vuex狀態(tài)管理模式,vuex的狀態(tài)管理是響應(yīng)式式的,當(dāng)vue組件從store好讀取狀態(tài)的時候,若store中的狀態(tài)發(fā)生改變 , 那么相應(yīng)的組件也會發(fā)生改變
但是,你不能直接改變store中的狀態(tài).改變store中的狀態(tài)唯一的途徑就是,顯示的提交(commit ) mutation . 在vue組件中獲取vuex狀態(tài)
封裝hasPermission函數(shù),判斷進入頁面是否需要權(quán)限,還有封裝vuex中mobule模塊
側(cè)邊欄
基于element-ui的NavMenu側(cè)邊欄來實現(xiàn)的
遍歷之前算出來的permission_routers , 通過vuex拿到之后動態(tài)v-for渲染(這里因為一些業(yè)務(wù)需要要加很多判斷,比如我們定義路由的時候會加很多參數(shù))
hidden:true? 是否顯示,默認(rèn)為flase? ?
redirect: noredirect如果重定向為conredirect那么重定向不會再面包屑中顯示?
name: 'router-name'? ?名稱由<keep-alive>(必須設(shè)置!!)使用
meta: {role? title? ?icon? ?noCache}??
role: ['admin' , 'editor']??將控制頁面角色(您可以設(shè)置多個角色)
title: 'title'? ?子菜單和面包屑中顯示的名稱(推薦集)
icon: 'svg-name'? 側(cè)邊欄中顯示的圖標(biāo)
noCache: true??如果fasle,頁面將不會被緩存(默認(rèn)為false)
側(cè)邊欄高亮問題: element-ui官方給了default-active
:default-active="$route.path" 將default-active一直指向當(dāng)前路由就可以了,就是這么簡單??
按鈕級別權(quán)限控制
現(xiàn)在是通過獲取到用戶的role之后,在前端用v-if手動判斷來區(qū)分不同權(quán)限對應(yīng)的按鈕的。
理由前面也說了混萝,我司顆粒度的權(quán)限判斷是交給后端來做的,每個操作后端都會進行權(quán)限判斷萍恕。而且我覺得其實前端真正需要按鈕級別判斷的地方不是很多逸嘀,如果一個頁面有很多種不同權(quán)限的按鈕,我覺得更多的應(yīng)該是考慮產(chǎn)品層面是否設(shè)計合理允粤。
axios攔截器
首先我們通過request攔截器在每個請求頭里面塞入token崭倘,好讓后端對請求進行權(quán)限驗證。并創(chuàng)建一個resques攔截器类垫,當(dāng)服務(wù)端返回特殊的狀態(tài)碼司光,我們統(tǒng)一做處理,如沒權(quán)限或者token失效等操作悉患。
兩步驗證
考慮到安全性,簡簡單單一個賬號+密碼的方式很難保證安裝性,推薦借助騰訊的微信或qq作為第三方綁定
賬號和密碼驗證成功之后還需要綁定一個第三方平臺驗證,只需要在原有登錄的邏輯上改造一下就好,登錄成功之后,不直接跳到首頁而是讓用戶兩步登錄,選擇登錄平臺,第三方平臺登錄一樣要通過OAuth2.0授權(quán)
如微信還必須是你授權(quán)賬號的一級域名残家。所以你授權(quán)的域名是vue-element-admin.com,你就必須重定向到vue-element-admin.com/xxx/下面,所以你需要寫一個重定向的服務(wù)售躁,如vue-element-admin.com/auth/redirect?a.com 跳到該頁面時會再次重定向給a.com坞淮。
所以我們后臺也需要開一個authredirect頁面:代碼茴晋。他的作用是第三方登錄成功之后會默認(rèn)跳到授權(quán)的頁面,授權(quán)的頁面會再次重定向回我們的后臺回窘,由于是spa诺擅,改變路由的體驗不好,我們通過window.opener.location.href的方式改變hash毫玖,在login.js里面再監(jiān)聽hash的變化。當(dāng)hash變化時凌盯,獲取之前第三方登錄成功返回的code與第一步賬號密碼登錄之后返回的uid一同發(fā)送給服務(wù)端驗證是否正確付枫,如果正確,這時候就是真正的登錄成功驰怎。
外賣后臺管理系統(tǒng)
后臺界面的首頁:
是由element的? 時間區(qū)間插件 , 省市區(qū)級聯(lián)插件,? 樹形插件選擇不同的角色;這幾個條件, 來篩選對應(yīng)條件的數(shù)據(jù)
????????我們把后臺返回的數(shù)據(jù)放到Echarts的折線圖,餅狀圖里面,每次登錄系統(tǒng)每個角色看到的這個統(tǒng)計數(shù)據(jù)是不一的,這取決于我們前端利用token拉取的user_info接口中所獲取的信息,參數(shù)是不一樣的 ,?這樣做到了有公司管理者對公司整體的運營情況的一個把握
? ? ? ? 我們的有一些系統(tǒng)會給入駐的商家時候,他們可以添加商店 , 我們審核 ,給予相應(yīng)的權(quán)限,我們前端在通過token獲取roel,根據(jù)用戶的roel動態(tài)算出其擁有權(quán)限的路由,之后通過router.addRouters動態(tài)掛載這些路由
? ? ? ? 商戶可以上傳一些菜品 , 圖片 , 價格等參數(shù) , 我們后臺人員會檢測到每個商家的經(jīng)營情況 , 動態(tài)的給他們推送一些活動 , 首頁置頂之類的;
? ? ? ? 這里我們使用了 element的Upload上傳插件;? Transfer 穿梭框插件,? Form插件,? Table表格插件,? Pagination 分頁插件
? ? ? ? 在利用flex布局,布局: 頭部固定,左邊固定,右邊自適應(yīng)? display: flex;??
flex布局中分為主軸方向和交叉軸:
主軸方向: 我們在彈性容器上通過flex-direction修改主軸方向;如果主軸方向改變那么交叉軸也會變它有幾個屬性,分為row左->右 , column上->下 , row-reverse左<-右 , column-reverse上<-下
彈性布局永遠沿著主軸排列,當(dāng)主軸排列不下,我們可以通過flex-wrap進行換行 , nowrap,不換行 一行顯示 , wrap換行下一行顯示 , wrap-reverse 反向換行
復(fù)合屬性: flex-flow = flex-drection + flex-wrap
復(fù)合屬性flex = flex-grow + flex-shrink + flex-basis? ??
flex-grow: 放大比例? ? ? flex-shrink生效前的尺寸? ? ? ? ?flex-basis設(shè)置的是元素在主軸上的初始尺寸
主軸上元素的對齊方式: justify-content: flex-start默認(rèn)左->右 , flex-end左<-右 , center居中 ,? ?space-between左右對齊中間自適應(yīng) , space-around平分剩余空間
交叉軸上的對齊方式: align-items
stretch: 默認(rèn)值,會在交叉軸方向撐滿? ? ? ? flex-start沿前端? ? ? ? flex-end沿交叉軸終點對齊? ? ? ? center沿交叉軸中點對齊? ? ? ? baseline沿第一行文字的基線對齊...