node實現后台權限管理系統


本文面向的是node初學者,目標是搭建一個基礎的后台權限系統。使用的node框架是上手最簡單的express,模板是ejs,這些在node入門的書籍中都有介紹說明,所以應該是難度較低的。

對於node初學者來說,可以先嘗試搭建一個blog,單用戶的或者多用戶的都可以。cnodejs論壇是我學的第一個源碼,還是很經典的。但是由於開發比較早,基於nodev4版本,很多后面新加的特性都未使用,比如class,async/await語法等。隨着node版本的大幅升級,目前至少是基於nodev8,穩定的是nodev10版本開發。所以本系列教程node版本至少是8版本,推薦裝LTS 10版本。

ide強烈推薦速度最快,使用最流暢的Visual Studio Code。它也是基於node的electron實現的,一級棒。

數據庫用過MS SQLServer、MySQL、mongodb,其中mongodb好多node教程推薦使用才開始流行,但它並非關系型數據庫,所以綜合考慮還是選用MySQL。數據庫客戶端推薦Navicat Premium。

以上各環境的安裝准備工作大家可以自行教程,不在展開說明了。

說下此項目的目標是搭建一個后台權限管理系統。對於實際項目的業務開發,后台的基礎權限框架必不可少。本教程將會帶你如何設計用戶、角色、菜單、及權限控制,並通過代碼示例實現。

如何定義一個好的框架,沒有一個標准。主要看每個人的水平及項目的適用性。以前從事.net開發,學設計模式、封裝、Ioc注入等,好固然是好,但也提高了入門的門檻,想要讓一個初學者快速上手進行業務開發卻很難。因為封裝后的代碼可讀性變差,如果沒有一定水平框架也很難維護。還有就是如果一個項目比較小,那么在設計框架時分層分個5、6層就有點過頭了,一般3層MVC就夠用了。當然如果你在大型互聯網公司,接觸到的用戶數量是幾萬甚至幾十萬,業務也很多,那框架就勢必要考慮到很多方面的問題,那架構設計就不是那么簡簡單單的了,可能會涉及到分布式、微服務等。

我們可以先從基礎的簡單的框架入手,等經驗豐富了后再不斷的重構升級以應對日益增長的業務需求。

下面我們來開始設計並搭建框架。

最基礎的權限是用戶的登錄,通過用戶名和密碼跟數據庫匹配來判斷是否登錄成功。

用戶登錄並存入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賬號新增用戶報無權限演示:

項目源碼地址:https://github.com/ciey/NodeExpressAdmin


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM