node实现后台权限管理系统

本文面向的是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帐号新增用户报无权限演示:

项目源码地址:https://github.com/ciey/NodeExpressAdmin

相关文章
相关标签/搜索