(五)Vue实用框架-Ruoyi(权限控制和页面渲染)

路由的组成

前端token获取那一步中有一块内容,只是简单提了一下,但其实实际涉及到的内容很多:

image.png


用户信息的获取

第一步的GetInfo后端接口不讲了,因为接口都比较简单,就根据获取得到的数据展开下:

前端权限控制粒度


image.png

依旧挑重点讲,user对象的无非就是包含了 部门dept 角色roles 信息,但有个 permissions : 就拿 system:user:resetPwd 来说,system代表一级路由的关键词(也是路由地址),user是二级路由的关键词,resetPwd是对这个页面中某些操作的描述关键词,具体看下图:

image.png
image.png

这套权限控制前端有按钮粒度级别的程度,把握得死死的,那么前端代码是如何根据此 permissions 控制的,继续看代码,随便拉一个页面:

image.png

有个自定义方法 hasPermi ,顺藤摸瓜看下有个 hasPerm.js ,说明我已经写在注释里了:

image.png

这个钩子函数需要全局定义:

//全局定义语法参考
Vue.directive('hello', {
    inserted(el) {
        console.log(el);

如果是在html中定义,那么可以这么写:

Vue.directive("hello",function(el,binding,vnode){
  el.style["color"]= binding.value;

因为这个 hasPermi 很多地方都会用到,所以肯定采用第一种方式:

image.png


后端权限控制粒度

如果你认为仅仅只是前端通过 控制按钮的显隐 限制操作权限 那就太天真了,不信的话我们试一下,把前端按钮保留不刷新,但同时在其他浏览器把该角色的某个按钮权限去除(修改数据库没用,因为取的是redis缓存),那么即使有编辑入口,接口也不会允许通过

image.png

看后端代码:

image.png

@PreAuthorize是security中用来进行权限控制的注解, @ss.hasPermi('system:notice:edit') 其中的表达式,返回boolean类型:

/**
 * 验证用户是否具备某权限
 * @param permission 权限字符串
 * @return 用户是否具备某权限
public boolean hasPermi(String permission) {
    if (StringUtils.isEmpty(permission)) {
      return false;
    //通过request头部中的token获取redis中的User信息
    LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
    if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {
      //如果缓存中无用户信息或者该用户也没权限信息,那么直接返回false
      return false;
    //判断是否包含该权限
    return hasPermissions(loginUser.getPermissions(), permission);

如果是false则会直接返回403错误,前段会根据返回code弹出对应的错误:

export default {
  '401': '认证失败,无法访问系统资源',
  '403': '当前操作没有权限',
  '404': '访问资源不存在',
  'default': '系统未知错误,请反馈给管理员'

后端接口的权限判断都是基于redis缓存中的数据,所以 测试权限这一块的时候修改数据库的值是没用 的,好在权限的功能模块已经相对完善,能够满足复杂的权限管理,基本上不需要去修改什么。

权限数据解析

再看下第二步的生成动态路由部分:


看下这个路由内部是个什么玩样儿,输出看一下:

image.png

相信其他都好理解,就是有个 component 怎么会有这么多内容:

// 遍历后台传来的路由字符串,转换为组件对象
function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) {
  return asyncRouterMap.filter(route => {
    if (type && route.children) {
      route.children = filterChildren(route.children)
    if (route.component) {
      // Layout ParentView 组件特殊处理
      if (route.component === 'Layout') {
        route.component = Layout
      } else if (route.component === 'ParentView') {
        route.component = ParentView
      } else {
        //这里的component是一个url,所以直接调用路由懒加载方法注册路由
        route.component = loadView(route.component)
    if (route.children != null && route.children && route.children.length) {
      route.children = filterAsyncRouter(route.children, route, type)
    } else {
      delete route['children']