zhcc - 基于 vue+ ssm + shiro 的权限框架


MIT
跨平台
Java

软件简介

zhcc

基于vue(element ui) + ssm + shiro 的权限框架

随着前后端分离项目的热潮,前端各大框架的,前后端沟通部分也成了问题,之前服务端渲染的页面生成到前端来,现在前后端可能是两个服务器,一些技术的迁移,本框架的权限部分的设计思想,借鉴了前端大牛的想法,也有传统后端的设计方案,抛砖引玉,做个桥梁,实现前后端分离的权限的设计,代码仅供参考,思路仅供参考,相信优秀的你写自己的代码,用自己的思想会更为贴切,方便。
最终即具有统一响应结构、
前后台数据流转机制(HTTP消息与Java对象的互相转化机制)、统一的异常处理机制、参数验证机制、Cors跨域请求机制以及鉴权机制。

前端设计:采用 Vue 的 element ui ,对于前端设计者来说,应该很好理解源码。

后端设计:shiro + ssm + redis 存储 jwt

交互方式:前端存储jwt,在访问后端时携带,这也是唯一交互验证方式。

前期工作:设计符合需求的vue模板,路由,资源,角色,用户其中对应关系也可从数据表中体现出来。

设计思路

基本想法就是,用到Vuex 和 Vue Router
前者用来做状态控制,后者绑定路由,这样权限可以直接对应到组件上,某个用于只能访问某个组件,而不用将每个组件都加上权限控制,重要的是还有单点登录。
所以抛砖引玉,写一个通用框架,(至少是通用想法)具体可以模块化来可插拔就ok 了。
非动态路由的问题是只能在拿到权限之后初始化Vue实例,因此必须把登陆页从SPA中剥离出来做成一个独立的页面,用户登录/退出操作需要在两个url之间跳转,体验略差。

另一种做法是直接用所有路由实例化应用,当用户登录拿到权限后再通过元素操作隐藏越权菜单,这时用户还可以手动输入地址访问越权页面,因此还需要给路由加beforeEach钩子来限制路由访问,路由钩子本身会增加一定的性能压力,而且实例化即注册所有路由也会使前端加载冗余的路由组件。
本系统采用的在初始路由注册首页和登录页,并在拿到token后得到权限,然后在实例化Vue实例。路由代码如下:

const router = new Router({
  routes: [
    {
      path: '/login',
      name: "login",
      component: LoginView,
      meta: { requiresAuth: false }
    },{
      path: '/index',
      redirect: '/',
      meta: { requiresAuth: true }
    }
  ]
});
generateIndexRouter();

// 验证token,存在才跳转
router.beforeEach((to, from, next) => {
  let token = sessionStorage.getItem('token')
  if(to.path === '/') {
    if(!token) {
      next({
                path: '/login',
                query: { redirect: to.fullPath }
      })
      return
    }
  }

    if(to.meta.requiresAuth) {
        if(token) {
            next()
        } else {
            next({
                path: '/login',
                query: { redirect: to.fullPath }
            })
        }
    } else {
        next()
    }
});

router.afterEach((to, from) => {
  // 设置面包屑
  let breadCrumbItems = []
  let homePageCrumb = {
    title: '首页',
    to: '/'
  }
  breadCrumbItems.push(homePageCrumb)
  if(to.meta.nameFullPath) {
    let fullPathSplit = to.meta.nameFullPath.split('/')
    fullPathSplit.forEach(( item, index ) => {
      let routerBreadCrumb = {
        title: item,
        to: (index == fullPathSplit.length - 1 ? to.path : '')
      }
      breadCrumbItems.push(routerBreadCrumb)
    });
  }
  // 更新到state
  router.app.$store.dispatch('setBreadcurmbItems', breadCrumbItems)
})

// 生成首页路由
function generateIndexRouter() {
  if (!sessionStorage.routers) {
    return
  }
  let indexRouter = {
    path: "/",
    name: "/",
    component: resolve => require(['@/views/home/index'], resolve),
    children: [
      ...generateChildRouters()
    ]
  }
  router.addRoutes([indexRouter])
}

// 生成嵌套路由(子路由)
function generateChildRouters() {
  let routers = JSON.parse(sessionStorage.routers)
  let childRouters = []
  for(let router of routers) {
    if(router.code != null) {
      let routerProps = JSON.parse(router.properties)
      let childRouter = {
        path: router.url,
        name: router.code,
        component: resolve => require(['@/views/' + router.code + '/index'], resolve),
        meta: { routerId: router.id, requiresAuth: routerProps.meta.requiresAuth, nameFullPath: routerProps.nameFullPath }
      }
      childRouters.push(childRouter)
    }
  }
  return childRouters
}

export default router;

前后端数据格式约定

既然是restful风格,必然有通用返回状态的类,遵循网上开源原则,一类继承hashmap这样达到可以返回任意的数据,通用的数据就是code.msg.data这些,如果有分页会另外加分页,还有一种是,data.msg.state(code).token
+ 分页类型的数据如:

"data": {
    "list": null,
    "pagebar": {
      "page": 1,
      "total": 2,
      "limit": 10
    }
  },
 "msg": "error",
  "state": 0,
  "is_redirect": true,
  "redirect_url": "http://qq.com",
  "token": null

本项目考虑到后期的扩展性,用到了第一类,其中实现了常用的失败和成功的状态码及其响应,类名设计为Result,位于zhcc-
common下面,一般性地是封装到ResponseEntity中返回。

前后端数据接口约定

分别对应http协议的get/put/post/delete方法,后端权限是:read/:update/:create/:delete

case "get":
    permissionString += ":read";
    break;
case "put":
    permissionString += ":update";
    break;
case "post":
    permissionString += ":create";
    break;
case "delete":
    permissionString += ":delete";

验证部分

用的是com.baidu.unbiz.fluentvalidator.ValidationError 而不是hibernateValidator
减轻服务端编程等的压力。直接在controller里面验证,最后封装到Result的fail方法里面返回。

权限的设计

权限的控制主要分为4大类,主要是基于RBAC原理。 路由,资源,角色,用户 路由级别和组件级别可控制

过程设计

1.权限设计 2.异常设计 3.字典和其他接口设计 4.前后的通讯设计==