本文面向的是node初學者,目標是搭建一個基礎的後臺權限系統。使用的node框架是上手最簡單的express,模板是ejs,這些在node入門的書籍中都有介紹說明,因此應該是難度較低的。前端
對於node初學者來講,能夠先嚐試搭建一個blog,單用戶的或者多用戶的均可以。cnodejs論壇是我學的第一個源碼,仍是很經典的。可是因爲開發比較早,基於nodev4版本,不少後面新加的特性都未使用,好比class,async/await語法等。隨着node版本的大幅升級,目前至少是基於nodev8,穩定的是nodev10版本開發。因此本系列教程node版本至少是8版本,推薦裝LTS 10版本。node
ide強烈推薦速度最快,使用最流暢的Visual Studio Code。它也是基於node的electron實現的,一級棒。git
數據庫用過MS SQLServer、MySQL、mongodb,其中mongodb好多node教程推薦使用纔開始流行,但它並不是關係型數據庫,因此綜合考慮仍是選用MySQL。數據庫客戶端推薦Navicat Premium。github
以上各環境的安裝準備工做你們能夠自行教程,不在展開說明了。ajax
說下此項目的目標是搭建一個後臺權限管理系統。對於實際項目的業務開發,後臺的基礎權限框架必不可少。本教程將會帶你如何設計用戶、角色、菜單、及權限控制,並經過代碼示例實現。mongodb
如何定義一個好的框架,沒有一個標準。主要看每一個人的水平及項目的適用性。之前從事.net開發,學設計模式、封裝、Ioc注入等,好當然是好,但也提升了入門的門檻,想要讓一個初學者快速上手進行業務開發卻很難。由於封裝後的代碼可讀性變差,若是沒有必定水平框架也很難維護。還有就是若是一個項目比較小,那麼在設計框架時分層分個五、6層就有點過頭了,通常3層MVC就夠用了。固然若是你在大型互聯網公司,接觸到的用戶數量是幾萬甚至幾十萬,業務也不少,那框架就勢必要考慮到不少方面的問題,那架構設計就不是那麼簡簡單單的了,可能會涉及到分佈式、微服務等。數據庫
咱們能夠先從基礎的簡單的框架入手,等經驗豐富了後再不斷的重構升級以應對日益增加的業務需求。express
下面咱們來開始設計並搭建框架。json
最基礎的權限是用戶的登陸,經過用戶名和密碼跟數據庫匹配來判斷是否登陸成功。bootstrap
用戶登陸並存入session後,下次只需判斷session中用戶是否存在,能夠寫在express中間件中。
/** 權限判斷中間件*/ class authMiddleware { /** 須要用戶登陸*/ async loginRequired(req, res, next) { if (!req.session || !req.session.user || !req.session.user.id) { return res.redirect('/login'); } await next(); } } module.exports = new authMiddleware();
而後在每次請求的路由中,先判斷下用戶是否已登陸,而後再執行相應controller。
const router = require('express').Router(), auth = require('./middleware/auth'), login = require('./controller/login'), main = require('./controller/main'); router.get('/login', login.showLogin); router.post('/login', login.login); router.get('/main', auth.loginRequired, main.showMain);
其中登陸頁面及登陸post提交是不須要檢查session中有無用戶的,由於用戶這時候還沒登陸成功。可是像主頁/main設定的是須要用戶登陸才能查看的。
登陸後的後臺管理系統佈局通常都是左側是菜單樹,右側爲內容。
很顯然左側的菜單須要權限控制,用於區分哪些菜單(頁面)用戶能夠訪問,哪些菜單(頁面)用戶不能夠訪問。通常不能訪問的菜單(頁面)不顯示給用戶,這樣不一樣的用戶登陸後顯示的左側菜單是不相同的。
能夠從圖上看到,在原來的登陸的基礎上增長了菜單表和用戶菜單表,一個用戶能夠對應有多個菜單。那麼他在登陸後就得到了相應的菜單集合,在頁面左側加載便可。
對於那些在界面上沒顯示的菜單在後臺路由中也是要檢查下權限的,要否則像用戶管理/userList這條路由雖然沒有配給某個測試帳號,但他能夠直接在地址欄輸入/userList進行訪問。因此在Middleware中須要增長判斷用戶是否有此page_url的訪問權限。
/** 須要用戶菜單權限*/ async userPermission(req, res, next) { //先判斷用戶session if (!req.session || !req.session.user || !req.session.user.id) { return res.redirect('/login'); } let hasPower = false; //userMenu指當前用戶擁有的菜單集合,請自行db查詢 userMenu.forEach(el => { if (el.page_url == req.route.path) { hasPower = true; } }); if (!hasPower) { if (req.xhr) { return res.json({ state: false, msg: "抱歉,您無此權限!請聯繫管理員" }); } return res.send('抱歉,您無此權限!請聯繫管理員'); } next(); }
路由中增長中間件的執行
router.get('/userList', auth.userPermission, main.showUser);
這樣當瀏覽器請求/userList路由時,先執行中間件userPermission方法,判斷用戶是否擁有此url權限,若是hasPower爲false,即沒有權限,直接返回輸出。若是有權限,再執行相應controller中方法。
至此,基本的用戶登陸,及用戶菜單權限已設計完成。但若是須要進一步權限控制到頁面中的按鈕、對用戶進行分組設置權限等,還得再進行補充完善。
上面權限系統控制到了頁面,若是頁面中不一樣用戶操做按鈕(控件)權限須要區分,好比常見的增、刪、改操做,還須要進一步權限設計。
頁面列表數據查看、新增、修改、刪除等各類操做,都須要經過ajax將數據或參數提交給對應的接口,而後接口再將結果返回,因此咱們能夠經過限制接口的訪問來作到對頁面按鍵功能的控制。
能夠把各類按鈕也當作是菜單項,對前面的菜單表進行擴充,增長(控件地址、是否顯示)兩個字段便可。
其中控件地址這個字段,就是咱們訪問接口數據的路由地址,好比說獲取單個訂單數據是經過指定主鍵id,它的路由接口通常設計成'/api/user/:id'。這些路由地址是咱們本身設計且在整個項目中都是惟一的。因此在開發時,咱們能夠在後臺作箇中間件攔截,經過用戶是否有這個菜單項對應的接口路由地址,來判斷用戶是否有訪問該接口的權限。
是否顯示字段,是爲了在輸出菜單列表時將這些按鈕菜單項隱藏,不在界面中顯示出來。
當系統中用戶增多,對每個用戶都須要單獨設置權限這種重複勞動工做量增大的時候,勢必要考慮用戶組了,在咱們windows系統中早就有用戶組的概念了。有了用戶組概念,就能夠先設置好某個用戶組的權限,而後對於新加進來的用戶只需加入以前配置好的用戶組中,即新加進來的用戶就擁有了用戶組的權限。
考慮到現實狀況,一個公司或企業的組織架構一般是有不少個部門組成,部門下面會有相應的職位,好比部門經理、部門員工等。用職位代替角色或用戶組更讓人可以容易理解。不一樣的職位有不一樣的工做職責,也有不一樣的操做權限。而部門主要是爲了方便對職位進行分組管理,若是職位多時沒有對應的分組,查詢不方便,若是有類似的職位,也容易混淆。
對於企業來講,人員因爲流動關係可能會常常變化,而職位則相對來講是比較固定的,因此權限綁定職位更合適,而不權限直接綁定用戶。當一位員工更換崗位時,只須要更改他所綁定的職位,對於新入職的員工,也只須要綁定他所入職崗位,他們就能夠擁有該職位的全部權限。
固然也會存在一些特殊的須要,好比說某人與同事都隸屬於同一個職位,但他是老員工能夠擁有更多的權限,這時能夠增長一個新職位(經過制定職位級別)來區分他們的權限。
又好比說,若是權限須要限制部門訪問權限,而該部門內的職位只能設置當前部門對應的權限,若是有員工須要跨部門擁有其餘權限時,能夠經過更改用戶帳號綁定多職位的方式來實現,也就是說一個員工他只能夠綁定一個主部門,但他能夠同時擁有多個職位,這樣它的權限就是多個職位權限的集合。
這樣在數據庫表設計中須要調整的是新加入職位表(職位id、職位名稱、部門id、菜單權限)和部門表(部門id、部門名稱、部門編碼、上一級部門id),並在用戶表中增長職位id、部門id。
職位表是綁定在部門下的權限角色,菜單權限字段直接與菜單項進行關聯,不一樣職位能夠設置不一樣的權限(設置可查看與操做的菜單項)
職位表還須要存儲與部門表的關聯項:部門表id。若是爲了避免關聯查詢,也能夠直接冗餘存儲部門編碼、和部門名稱字段(直接存儲這個冗餘字段,是爲在須要顯示職位所屬部門時,不須要從部門表中關聯查詢,部門名稱設置後更改的機會不大,但查詢是每次都固定須要查詢),因此冗餘字段的設置減小了查詢表次數,不過要在程序中確保兩邊表的數據更新一致。
部門表它至關於權限分組,能夠根據企業的部門結構,建立對應的結構記錄,這樣也方便企業對系統權限關係更加容易理解。固然也能夠根據須要設置虛擬部門出來管理。
爲了之後擴展須要,須要添加部門編碼字段,編碼從01開始一直累加到99,固然若是部門超過99個的話,要麼增長到3位數,要麼當前框架已不能支持業務的發展須要思考新的架構了。
編碼每增長一級,在01後面自動增長」0x「,編碼的長度跟部門分級深度相關。
綜合以上權限設計思路,最終整理出的數據表關係圖爲:
整個權限控制就4張表,若是現實狀況不太用獲得組織部門,還能夠把部門這張表去掉,並把職位表改爲角色表,這樣最精簡的權限控制數據庫表就設計完成了。
下面根據4張表來設計界面,主要有用戶管理頁面、菜單管理頁面、部門管理頁面、職位管理頁面。
前端採用inspinia模板,有些插件略有增減,對話框採用layer,樹菜單採用ztree,表格採用bootstrap-table,具體能夠看源碼。
express中採用ejs-mate模板,是ejs擴展版本,支持layout。
編輯用戶中主要選擇用戶所屬部門和職位,其中職位是能夠多個,採用樹型結構勾選便可。
菜單列表採用bootstrap-table,並使用tree-grid插件顯示菜單層級關係。
編輯菜單主要是設置上下級菜單關係,頁面地址和控件地址,若是這個菜單隻有控件地址,不在左側樹菜單顯示的,須要將是否顯示設爲隱藏。
左側能夠經過點擊部門樹,來篩選該部門下的職位列表,方便顯示。
編輯職位主要設置該職位名稱及該職位所擁有的菜單項,菜單項是一個樹型結構,打勾即表示該職位擁護此菜單項權限。
以上是最終所實現的界面效果。大部分都屬於簡單的列表和增刪改頁面,稍微複雜點是有些頁面會涉及到樹形菜單加載。
用戶登陸後,就得到了用戶所屬職位的菜單項集合,在用戶每次請求路由中需增長權限判斷,咱們寫在中間件中。
/** 用戶鑑權*/ async authUserPermission(req, res, next) { if (!req.session || !req.session.user || !req.session.user.id) { return res.redirect('/login'); } if (!req.session || !req.session.menu || req.session.menu.length == 0) { return res.send('抱歉,您無此權限!請聯繫管理員'); } let targetUrl = req.route.path; let hasPower = false; req.session.menu.forEach(el => { if (el.page_url == targetUrl || el.control_url == targetUrl) { hasPower = true; } }); if (!hasPower) { if (req.xhr) { return res.json({ state: false, msg: "抱歉,您無此權限!請聯繫管理員" }); } return res.send('抱歉,您無此權限!請聯繫管理員'); } next(); }
在每次路由請求中先判斷用戶權限,若有權限則往下執行正常邏輯,如無權限直接返回。
router.get('/system/userList', auth.authUserPermission, system.showUserList); router.get('/system/userEdit/:id', auth.authUserPermission, system.showUserEdit);
test帳號新增用戶報無權限演示: