<p id="m2nkj"><option id="m2nkj"><big id="m2nkj"></big></option></p>
    <strong id="m2nkj"></strong>
    <ruby id="m2nkj"></ruby>

    <var id="m2nkj"></var>
  • zhcc基于 vue+ ssm + shiro 的權(quán)限框架

    聯(lián)合創(chuàng)作 · 2023-09-30 08:53

    zhcc

    基于vue(element ui) + ssm + shiro 的權(quán)限框架

    隨著前后端分離項目的熱潮,前端各大框架的,前后端溝通部分也成了問題,之前服務(wù)端渲染的頁面生成到前端來,現(xiàn)在前后端可能是兩個服務(wù)器,一些技術(shù)的遷移,本框架的權(quán)限部分的設(shè)計思想,借鑒了前端大牛的想法,也有傳統(tǒng)后端的設(shè)計方案,拋磚引玉,做個橋梁,實現(xiàn)前后端分離的權(quán)限的設(shè)計,代碼僅供參考,思路僅供參考,相信優(yōu)秀的你寫自己的代碼,用自己的思想會更為貼切,方便。 最終即具有統(tǒng)一響應(yīng)結(jié)構(gòu)、 前后臺數(shù)據(jù)流轉(zhuǎn)機制(HTTP消息與Java對象的互相轉(zhuǎn)化機制)、統(tǒng)一的異常處理機制、參數(shù)驗證機制、Cors跨域請求機制以及鑒權(quán)機制。

    前端設(shè)計:采用 Vue 的 element ui ,對于前端設(shè)計者來說,應(yīng)該很好理解源碼。

    后端設(shè)計:shiro + ssm + redis 存儲 jwt

    交互方式:前端存儲jwt,在訪問后端時攜帶,這也是唯一交互驗證方式。

    前期工作:設(shè)計符合需求的vue模板,路由,資源,角色,用戶其中對應(yīng)關(guān)系也可從數(shù)據(jù)表中體現(xiàn)出來。

    設(shè)計思路

    基本想法就是,用到Vuex 和 Vue Router 前者用來做狀態(tài)控制,后者綁定路由,這樣權(quán)限可以直接對應(yīng)到組件上,某個用于只能訪問某個組件,而不用將每個組件都加上權(quán)限控制,重要的是還有單點登錄。 所以拋磚引玉,寫一個通用框架,(至少是通用想法)具體可以模塊化來可插拔就ok 了。 非動態(tài)路由的問題是只能在拿到權(quán)限之后初始化Vue實例,因此必須把登陸頁從SPA中剝離出來做成一個獨立的頁面,用戶登錄/退出操作需要在兩個url之間跳轉(zhuǎn),體驗略差。

    另一種做法是直接用所有路由實例化應(yīng)用,當(dāng)用戶登錄拿到權(quán)限后再通過元素操作隱藏越權(quán)菜單,這時用戶還可以手動輸入地址訪問越權(quán)頁面,因此還需要給路由加beforeEach鉤子來限制路由訪問,路由鉤子本身會增加一定的性能壓力,而且實例化即注冊所有路由也會使前端加載冗余的路由組件。 本系統(tǒng)采用的在初始路由注冊首頁和登錄頁,并在拿到token后得到權(quán)限,然后在實例化Vue實例。路由代碼如下:

    const router = new Router({
      routes: [
        {
          path: '/login',
          name: "login",
          component: LoginView,
          meta: { requiresAuth: false }
        },{
          path: '/index',
          redirect: '/',
          meta: { requiresAuth: true }
        }
      ]
    });
    generateIndexRouter();
    
    // 驗證token,存在才跳轉(zhuǎn)
    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) => {
      // 設(shè)置面包屑
      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;

    前后端數(shù)據(jù)格式約定

    既然是restful風(fēng)格,必然有通用返回狀態(tài)的類,遵循網(wǎng)上開源原則,一類繼承hashmap這樣達到可以返回任意的數(shù)據(jù),通用的數(shù)據(jù)就是code.msg.data這些,如果有分頁會另外加分頁,還有一種是,data.msg.state(code).token + 分頁類型的數(shù)據(jù)如:

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

    本項目考慮到后期的擴展性,用到了第一類,其中實現(xiàn)了常用的失敗和成功的狀態(tài)碼及其響應(yīng),類名設(shè)計為Result,位于zhcc-common下面,一般性地是封裝到ResponseEntity中返回。

    前后端數(shù)據(jù)接口約定

    分別對應(yīng)http協(xié)議的get/put/post/delete方法,后端權(quán)限是: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 減輕服務(wù)端編程等的壓力。直接在controller里面驗證,最后封裝到Result的fail方法里面返回。

    權(quán)限的設(shè)計

    權(quán)限的控制主要分為4大類,主要是基于RBAC原理。 路由,資源,角色,用戶 路由級別和組件級別可控制

    過程設(shè)計

    1.權(quán)限設(shè)計 2.異常設(shè)計 3.字典和其他接口設(shè)計 4.前后的通訊設(shè)計==

    瀏覽 26
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

    編輯 分享
    舉報
    評論
    圖片
    表情
    推薦
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

    編輯 分享
    舉報
    <p id="m2nkj"><option id="m2nkj"><big id="m2nkj"></big></option></p>
    <strong id="m2nkj"></strong>
    <ruby id="m2nkj"></ruby>

    <var id="m2nkj"></var>
  • 久久一区二区三区在线 | 插插插色欲 | 日本一区视频在线观看 | 插吧插吧网 | 免费看毛片网站 | 欧美AAAAAAAAAA特级 | 乱伦视频91 | 亚洲天堂一区二区三区在线观看 | 天天日天天干天天草 | 人妻无码中文专区久久5566 |