乐趣区

从设计页面逻辑走一走权限管理的完整流程

本文主要想对前端权限管理功能实现做一个分享,所以并不会对后台管理的框架结构做太详细介绍,如果有朋友对其他有兴趣可以留言。

基本设计和分析

  • 前端 vue + elementui
  • 服务端: node + mysql + nginx

主要功能

打开思否页面,根据页面的功能点,设计出相关的数据表,和管理系统需要的相关页面。
计划后台管理需要完成的功能:

  • 权限管理(菜单权限到数据权限)— 已完成
  • 工作流(问答和文章在某个条件内,提交需要走流程)– 未完成
  • socket(对用户点赞,评论,系统通知等消息进行实时推送)– 未完成
  • 文件管理(将页面需要用到的文件上传管理,其他页面都统一访问文件库资源)– 已完成
  • 基本业务(业务页面)– 部分完成

模块相关介绍

模块 功能 页面编码 描述
登录 登录 login 菜单中不显示
401 401 401 角色无访问权限时进入这个页面
404 404 404 访问菜单不存在时进入这个页面
首页 首页 home
运维中心 opsCenter
问答管理 questionMan
专栏管理 blogMan
文章管理 articleMan
讲堂管理 liveMan
活动管理 activityMan
广告位 advertising
工作流 workflow
流程设计 processDesign
业务管理 businessMan
已办事项 finishedItems
未办事项 unfinishedItems
文件库 library
图片管理 imgMan
文件管理 fileMan
论坛配置 bbsConfig
轮播 carousel
技术频道 techSquare
通知 notices
标签类型管理 tagTypeMan
标签管理 tagMan
系统管理 sysMan
用户管理 userMan
角色管理 roleMan
菜单管理 menuMan
区域管理 areaMan
图表配置 chartConfig
系统日志 log

代码结构

├── admin                      // 打包产出文件
├── node_module                // npm 加载所需的项目依赖模块
├── public                     // 静态入口
├── src                        // 源代码
│   ├── api                    // 所有请求
│   ├── assets                 // 主题 字体 图片等静态资源
│   ├── common                 // 全局公用配置
│   │   ├── config             // 配置全局路由权限和错误捕获
│   │   ├── mixin              // 一些 vue 公用的 mixin
│   │   ├── js                 // 编写公有的方法
│   │   └── style              // 编写公有的样式
│   ├── components             // 全局公用组件
│   ├── directive              // 自定义指令
│   ├── router                 // 路由
│   ├── store                  // 全局 store 管理
│   ├── views                  // view
│   ├── App.vue                // 入口页面
│   └── main.js                // 入口 加载组件 初始化等
├── static                     // 第三方不打包资源
├── .babelrc                   // babel-loader 配置
├── eslintrc.js                // eslint 配置项
├── .gitignore                 // git 忽略项
├── vue.config.js              // vue-cli@3.0+ 配置文件
└── package.json               // package.json

权限设计

进入正文,关于权限设计,围绕的是前端页面,但是会将前端和后端的逻辑都讲出来。

用户管理

创建

前端页面

看图中圈起来的地方,前端看到的逻辑是这样的:

  • 当前用户为admin
  • 树用右键操作 admin创建的用户
  • 树用右键操作 创建的用户 admin 可以管理

就是创建了一个用户,这个用户创建的用户以及创建用户创建的用户,都可以被当前创建者管理。

接口逻辑
  • 查询到数据库中所有的用户 ID
  • 通过用户 ID 和创建人 ID 的关系,通过建立树状数据,得到当前用户创建的用户树
  • 递归从用户树中得到所有属于当前用户子集的用户 ID
  • select * from table where id in (子集用户 id)

通过这个逻辑,可以得到所有当前用户创建的子集,但是第一步有很大的问题,一旦用户数量巨大,这样查询会很慢。母目前只是为了功能实现,暂未考虑到性能方面,如果有好的方法,希望指点。

删除

前端页面
  1. 删除用户,调用接口判断用户是否有子集,存在 ->3,不存在 ->2
  2. 不存在直接删除
  3. 存在需要先将当前创建的用户转移给其他用户(其他用户不可为他的子集)
  4. 将用户转移成功, 则此时子集为空 ->2
接口逻辑
  1. 查询到数据库中是否存在创建人 ID 为当前要删除的用户 ID
  2. 存在则无法删除当前用户
  3. 前端调用户转移接口,将当前用户创建的用户转移给其他人后,此时可删除该用户

菜单管理

菜单设计的时候分为三个类型,管理平台,论坛,移动端,但是不一定会写完,感觉一个人写好累呀~~~~
通过菜单又分还有默认布局组件和页面组件的区分,布局组件为 layout,页面组件则为他的子路由,通过嵌套的形式,组成一个完整的页面。

页面


目前页面上都是通过右键点击树组件,进入操作,如图所示,可以对菜单进行增删改查操作。

菜单字段的定义和相关用处
字段定义是这样的:
看到图中有这些字段,对主要字段说明:

  • 菜单编码(对应前端页面的文件名,比如 userMan, 渲染时就会找到 */userMan/index 去 resolve)
  • 菜单组件 (指的是 layout 等,后面如果需要做多布局,通过这个设置页面即可有不同布局)

-- ----------------------------
-- bbs_menu
-- ----------------------------
DROP TABLE IF EXISTS `bbs_menu`;
CREATE TABLE `bbs_menu` (`id` INT(11) NOT NULL AUTO_INCREMENT,
  `pid` INT(11) DEFAULT '0',
  `type` tinyint(4) NOT NULL DEFAULT '1' COMMENT '菜单类型: 1. 管理平台菜单 2. BBS 菜单 3. 移动端菜单',
  `code` VARCHAR(48) NOT NULL COMMENT '菜单编码',
  `name` VARCHAR(48) NOT NULL COMMENT '菜单名称',
  `component` tinyint(4) NOT NULL COMMENT '对应组件: -1. 根节点 1. 页面组件 2. 默认布局 3456... 扩展布局',
  `icon` VARCHAR(128) DEFAULT NULL COMMENT '菜单图标',
  `alias` VARCHAR(128) DEFAULT NULL COMMENT '别名',
  `redirect` VARCHAR(128) DEFAULT NULL COMMENT '重定向路径: 配置菜单编码或 URL',
  `sort` INT(11) NOT NULL,
  `desc` VARCHAR(128) DEFAULT NULL,
  `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态: 0:停用,1:启用(默认为 1)',
  `create_user` INT(11) DEFAULT NULL,
  `create_time` datetime DEFAULT NULL,
  `update_user` INT(11) DEFAULT NULL,
  `update_time` datetime DEFAULT NULL,
  `delete_user` INT(11) DEFAULT NULL,
  `delete_time` datetime DEFAULT NULL,
  `flag` tinyint(4) NOT NULL DEFAULT '1' COMMENT '状态: 0:删除,1:可用(默认为 1)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='菜单表';
    id: '', // * 唯一 ID
    pid: '', // * 父 ID
    type: '', // * 菜单类型
    code: '', // * 菜单编码
    name: '', // * 菜单名称
    component: '', // * 菜单组件
    icon: '', // 菜单图标
    redirect: '', // 重定向路径
    sort: '', // * 排序
    desc: '', // 描述
    status: 1 // * 状态: 0:停用,1:启用(默认为 1)'

有什么用处呢和好处呢,就个人而言,就是觉得把路由表放在数据库,让项目更易于维护,在页面中通过一个匹配逻辑,可以将所有字段组装成为可以使用的路由表:

// 得到页面路径
function getPath (arr, child, code) {const pItem = arr.find(item => child.pid === item.id)
  // 当前元素还存在父节点, 且父节点不为根节点
  if (arr.find(item => pItem.pid === item.id && item.pid > -1)) {getPath(arr, pItem, `${pItem.code}/${code}`)
  } else {return `${pItem.code}/${code}`
  }
}
// 对基础数据的处理
              item.meta = {}
              item.meta.title = item.name
              item.meta.icon = item.icon
              item.meta.id = item.id
              // 使路由名字具有唯一性
              item.name = item.name + index
              // 设置对应的页面路径
              item.path = '/' + item.code
// 设置页面对应的组件 对应组件: -1. 根节点 1. 页面组件 2. 默认布局 3456... 扩展布局
              switch (item.component) {
                case -1:
                  console.log('根节点,已经过滤掉了')
                  break
                case 1:
                  item.component = resolve => require([`@/views/${getPath(menu, item, item.code)}/index`], resolve)
                  break
                case 2:
                  item.component = Layout
                  break
                default:
                  item.component = resolve => require(['@/views/errorPage/401'], resolve)
                  break
              }

通过这种方式,在设置页面权限的时候,只需要接口设置当前角色对应的菜单,用户查询的时候能获取到的就是当前分配给他的权限,将这个权限组装成路由表,即可。

数据权限

上面说的是菜单的配置,以及生成。然后和每个页面相关的数据权限,需要点击到页面级别的菜单才可以访问到,如图:

选中一个菜单之后,可以对这个菜单添加数据权限的控制,比如添加,编辑,删除等操作。

数据权限的实现

主要是字段设计,所以对图中字段(开发人员录入)详细说明:

  • 功能编码(页面编码: 功能编码,主要用于前端控制显隐)
  • 功能 api(接口编码,后端通过判断用户是否存在这个编码,来判断是否存在操作权限)
  • 请求方式 (restfulApi 情况下,因为 api 编码相同,需要根据请求方式来判断用户的操作权限)

前端实现

分配完权限之后,前端页面在对应的按钮或要操作的 dom 上,通过 v -if 功能编码是否存在来设置操作权限的显示隐藏。
但是前端的显隐一旦用户绕过页面去访问接口即可,所以数据权限前端只是操作显隐,具体实现还在后端。

后端实现
  • 做一个数据权限中间层,用户访问时中间层判断当前访问的接口用户是否拥有权限
  • 怎么判断,通过前端设置的功能 api 和请求方式,去表中查询当前用户角色是否可访问
  • 可访问继续往下走,不能访问就拒绝了

角色管理

用户存在了,菜单和数据权限也配置好了,但是需要角色去将他们关联到一起。

绑定用户

这里设置的逻辑是一个用户只能绑定一个角色。
角色管理页面,还是右键树组件,可以看到绑定用户的选项

分配权限

同样是右键,可以开始对角色进行分配权限的操作

左边是页面的权限分配,选中页面之后,右边会出现数据权限的分配:

继承式的分配权限
  • 总共有 100 个权限
  • a 有 50 个,a 给 b 分配时,只能分配 50 个
  • 假设 a 给 b 分配了 30 个,c 为 b 的下级,d 为 c 的下级
  • c 此时无权限,a 或 b 能分配 30 个给 c,但由于 c 无权限,a 或 b 分配给 d 时,分配的列表为空

总结

创建用户
创建菜单
创建角色
用户绑定角色,角色分配权限
完成

最后

案例地址

node 服务

退出移动版