国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

關于前后端分離權限控制,元素細粒度(iview-admin實現)

YorkChen / 3647人閱讀

摘要:按鈕方面按鈕通過自定義指令綁定其特定的操作接口信息如產品上傳按鈕,需要擁有產品上傳的信息,才可以繼續執行按鈕的業務邏輯。

開篇啰嗦幾句

在傳統單體項目中,通常會有一些框架用來管理熟知的權限。如耳濡目染的 Shiro 或者 Spring Security 。然而,到了現在這個時代,新開始的項目會更多的才用后端微服務 + 前端 mvvm 的架構開始書寫項目。權限控制方面將變得有些許晦澀。當然,得益于 Java 后端的 Spring 項目的可插拔式接口以及前端大部分 SPA 的架構,權限控制也并沒有變得那么的難以實現。相反,在我看來只要找到一個合適的插拔插件的入口,權限也可以做的很簡單,跟業務只要少許的耦合即可實現我們熟悉的權限模塊。

示例代碼 iview-admin-permission

實現思路 路由方面

后端提供當前登錄用戶可訪問的所有可訪問資源接口信息,前端項目根據當前用戶所擁有的接口信息,判斷是否擁有菜單權限,如果擁有主要訪問權限則加入路由,讓用戶可以訪問其當前的頁面。

由于 iview-admin 中,路由信息與菜單是同一份資料,所以在加入路由的時候,即可實現菜單的隱藏與現實。

按鈕方面

按鈕通過 vue 自定義指令綁定其特定的操作接口信息(如:產品上傳按鈕,需要擁有產品上傳的信息,才可以繼續執行按鈕的業務邏輯)。在按鈕或者 a 標簽被渲染的時候,判斷是否擁有權限,如若沒有,可以隱藏,或者劫持原來綁定的點擊事件,使其變成無權限的彈窗提示。

該指令支持 render 生成的按鈕或者 a 標簽。

數據表格方面

數據的隱藏與否應該不會放在前端做吧...不然就是沙雕網友了。

這方面可以提供思路,在微服務架構中,如若前端項目擁有 NodeJS 做數據轉換層,那是極好的。如果沒有,則需要在微服務路由層做一些細微的改變。即用戶無權限的數據列(屬性)給他模糊掉,變成 * 或者 輸出。這方面與前端無關,那么并不會在這里出現。

優缺點

優點:

后端不再關注頁面上的事情,基于權限代碼讓前端自己隨心所欲編排

靈活性較高,可以控制頁面上所有的元素,可以隱藏彈窗等等喜歡的功能

缺點:

編排路由的時候會顯得繁瑣,權限代碼比較零散,開發完業務頁面可能會忘記加上

代碼實現

公司實現并不能成為開源,所以我重新下載一份 iview-admin ,然后加入關鍵代碼,來實現上面所需要的功能。

常規操作:

npm install
npm run dev

正常打開窗口即可進行修改。

接下來就小步前進加入權限的功能。

新增權限模塊的store

權限提供了一個用戶所擁有的所有權限列表以及一個 getters 后面會用到。

export default {
  state: {
    // { operatorCode: "ProductEndpoint#upload", name: "產品上傳" }
    operatorList: []
  },
  getters: {
    hasPermission: (state) => (queryOpcode) => {
      if (!state.operatorList || !state.operatorList.length) {
        return false
      }
      return state.operatorList.map(operatInfo => operatInfo.operatorCode).indexOf(queryOpcode) > -1
    }
  },
  mutations: {
    setPermissionList (state, opList) {
      state.operatorList = opList
    }
  }
}

在這里我定義簡單的后端返回值:一個 operatorCode 代表某個操作的代號,一個 name 用于分配權限的時候給用戶查看。

operatorCode 有很多中生成方式,我這里采用 SpringMVCController 名 + # + 方法名 的形式, Controller 名用于分割不同資源的資源空間。使用 Spring 的生命周期函數,在項目啟動的時候掃描,寫入數據庫,相當于所有的資源。后面的權限模型,以前該怎樣就還是怎樣,比如用戶 —> 角色 —> 資源的模型。然后根據登陸的用戶 ID 查詢,返回給前端。至于中文名字,我們項目搭配 Swagger 使用,如果沒有使用 Swagger 則需要使用自定義開發的注解指定。

注冊到 store 中:

import Vue from "vue"
import Vuex from "vuex"

import user from "./module/user"
import app from "./module/app"
import permission from "./module/permission"

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    //
  },
  mutations: {
    //
  },
  actions: {
    //
  },
  modules: {
    user,
    app,
    permission
  }
})
用戶登陸時初始化擁有的資源操作

靜態操作數據:

// src/mock/data/permission-data.js
export const getPermissionCodeList = (userName) => {
  if (String(userName) === "1") {
    return [{ operatorCode: "ProductManageEndpoint#createProduct", name: "產品上傳" },
      { operatorCode: "ProductManageEndpoint#putawayProduct", name: "產品上架" },
      { operatorCode: "ProductManageEndpoint#delistProduct", name: "產品下架" }]
  } else {
    return [{ operatorCode: "ProductManageEndpoint#createProduct", name: "產品上傳" },
      { operatorCode: "ProductManageEndpoint#delistProduct", name: "產品下架" },
      { operatorCode: "ProductManageEndpoint#deleteByUuid", name: "產品刪除" }]
  }
}

登陸操作:

// src/view/login/login.vue
...
handleSubmit ({ userName, password }) {
    this.handleLogin({ userName, password }).then(res => {
        this.getUserInfo().then(res => {
            // 用戶登陸成功時候加入操作Code
            this.$store.commit("setPermissionList", getPermissionCodeList(userName))
            this.$router.push({
                name: this.$config.homeName
            })
        })
    })
}

加入用戶列表的信息,不然不能登錄,因為作者默認只放了兩個,當然生產項目肯定不管這里啦~~

// src/mock/login.js
const USER_MAP = {
  super_admin: {
    name: "super_admin",
    user_id: "1",
    access: ["super_admin", "admin"],
    token: "super_admin",
    avator: "https://file.iviewui.com/dist/a0e88e83800f138b94d2414621bd9704.png"
  },
  admin: {
    name: "admin",
    user_id: "2",
    access: ["admin"],
    token: "admin",
    avator: "https://avatars0.githubusercontent.com/u/20942571?s=460&v=4"
  },
  zhangsan: {
    name: "zhangsan",
    user_id: "3",
    access: ["super_admin", "admin"],
    token: "zhangsan",
    avator: "https://avatars0.githubusercontent.com/u/20942571?s=460&v=4"
  },
  lisi: {
    name: "lisi",
    user_id: "4",
    access: ["super_admin", "admin"],
    token: "lisi",
    avator: "https://avatars0.githubusercontent.com/u/20942571?s=460&v=4"
  }
}
...

加入權限數據:

// src/mock/data/permission-data.js
export const getPermissionCodeList = (userName) => {
  if (userName === "zhangsan") {
    return [{ operatorCode: "ProductManageEndpoint#list", name: "產品列表" },
      { operatorCode: "ProductManageEndpoint#createProduct", name: "產品上傳" },
      { operatorCode: "ProductManageEndpoint#putawayProduct", name: "產品上架" },
      { operatorCode: "ProductManageEndpoint#delistProduct", name: "產品下架" }]
  } else {
    return [{ operatorCode: "ProductManageEndpoint#createProduct", name: "產品上傳" },
      { operatorCode: "ProductManageEndpoint#delistProduct", name: "產品下架" },
      { operatorCode: "ProductManageEndpoint#deleteByUuid", name: "產品刪除" }]
  }
}
開始加載菜單

剛開始我們先設立一個口進入,由于項目的 menuList 掛載在 storeapp.js 里面,所以我們需要在這里動刀。

這里修改造成兩個事情:1. 清掉項目原來的 router 的菜單,但是還可以訪問(因為內存加載了);2. 我加入了需要權限管理的菜單,但是還不能訪問,因為路由還沒有。

// src/store/module/app.js
...
getters: {
    menuList: (state, getters, rootState) => {
      const allMenus = []
      const menuWithPermission = getMenuWithPermissionByRouter(routersWithPermission, rootState.permission.operatorList)
      allMenus.push(...menuWithPermission)
      return allMenus
    }
}
...

那么這里用到一個函數 getMenuWithPermissionByRouter,我決定放在 util.js 即可

// src/libs/util.js
...
export const getMenuWithPermissionByRouter = (routerWithPermissionList, userOperatorList) => {
  return []
}

OK,運行項目。很好,沒有出現錯誤,繼續前行。

我的需求是這樣的,當一個路由,在 meta 里面的 requireCode 用戶擁有第一項操作權限的時候,是可以進入的,也就是菜單需要加載出來。

export default [
  {
    path: "/product",
    name: "product",
    component: Main,
    meta: {
      hideInBread: true
    },
    children: [
      {
        path: "list",
        name: "list",
        meta: {
          title: "product-list",
          requireCode: ["ProductManageEndpoint#list", "ProductManageEndpoint#createProduct", "ProductManageEndpoint#putawayProduct", "ProductManageEndpoint#delistProduct", "ProductManageEndpoint#deleteByUuid"]
        },
        component: () => import("@/view/product/product-list.vue")
      },
      {
        path: "add",
        name: "add",
        meta: {
          title: "product-add",
          requireCode: ["ProductManageEndpoint#createProduct"]
        },
        component: () => import("@/view/product/product-form.vue")
      }
    ]
  }
]

當這個頁面需要同時滿足兩個要求的資源操作的時候,第一項使用數組裝載。即:

requireCode: [["ProductManageEndpoint#list", "ProductManageEndpoint#createProduct"], "ProductManageEndpoint#putawayProduct", "ProductManageEndpoint#delistProduct", "ProductManageEndpoint#deleteByUuid"]

那么這個頁面需要同時滿足前面兩個的時候,才能加載出來。

那么開始編寫 getMenuWithPermissionByRouter 函數。呃,怎么說呢,先把 Aresn 的代碼拷過來改改滿足我上面的需求即可:

/**
 * 根據需要控制菜單的路由,獲取菜單列表
 * @param routerWithPermissionList 需要權限管理的路由
 * @param userOperatorList 用戶擁有的所有操作
 * @returns {Array}
 */
export const getMenuWithPermissionByRouter = (routerWithPermissionList, userOperatorList) => {
  // debugger
  let res = []
  const allOpCodeArr = userOperatorList.map(opCodeObj => opCodeObj.operatorCode)
  forEach(routerWithPermissionList, item => {
    if (!item.meta || (item.meta && !item.meta.hideInMenu)) {
      let obj = {
        icon: (item.meta && item.meta.icon) || "",
        name: item.name,
        meta: item.meta
      }
      if ((hasChild(item) || (item.meta && item.meta.showAlways))) {
        obj.children = getMenuWithPermissionByRouter(item.children, userOperatorList)
      }
      if (hasThisMenuPermission(item, allOpCodeArr) || hasChild(item)) res.push(obj)
    }
  })
  return res
}

const hasThisMenuPermission = (routerInfo, allOpCodeArr) => {
  if (routerInfo.meta && routerInfo.meta.requireCode && routerInfo.meta.requireCode.length) {
    const requireCode = routerInfo.meta.requireCode[0]
    if (Array.isArray(requireCode)) {
      let hasPermission = true
      for (let i = 0; i < requireCode.length; i++) {
        hasPermission = hasPermission && allOpCodeArr.indexOf(requireCode[i]) > -1
      }
      return hasPermission
    } else {
      return allOpCodeArr.indexOf(requireCode) > -1
    }
  } else return false
}

OK,如圖所示,已經把菜單加載出來了。但是現在出現一個問題,就是刷新頁面的時候,什么都沒了。因為刷新頁面的時候,內存中的 store 被置空了。

頁面加載的時候自動加載操作
// src/view/single-page/home/home.vue
...
mounted () {
    // 需要在這里加載權限信息
    const userName = this.$store.state.user.userName
    this.$store.commit("setPermissionList", getPermissionCodeList(userName))
}
...

可喜可賀,刷新的時候,菜單已經出來了。但是有個報錯,報 sideMenuundefined 異常。是由于剛開始刷新頁面的時候,菜單還沒有出來,而代碼寫死了讀第0個元素,菜單是空數組,所以理所當然導致了異常。

// src/components/main/components/side-menu/side-menu.vue 第 20 行,去掉最后屬性的或者即可

OK,我感覺最難的菜單搞定了。接下來要搞定路由。

動態新增有權限的路由

有了前面的鋪墊,我感覺路由要好做很多了。

動態路由有一個調用的函數是:

router.addRoutes(routes: Array)

那么我們需要做的只是,獲取有權限的路由,然后加入到 Vue 中。

但是這段代碼有點問題就是每次進入都會調用一次= =

// src/view/single-page/home/home.vue
mounted () {
    // 需要在這里加載權限信息
    const userName = this.$store.state.user.userName
    this.$store.commit("setPermissionList", getPermissionCodeList(userName))
    const routers = getRouterWithPermission(routersWithPermission, this.$store.state.permission.operatorList)
    const originRouteNames = this.$router.options.routes.map(r => r.name)
    // 需要解決重復加入問題
    if (routers && routers.length && originRouteNames.indexOf(routers[0].name) < 0) {
        this.$router.addRoutes(routers)
    }
}

好了,菜單有了,路由有了,點擊測試能否進入。

然后被作者的路由攔截到了-,-

// src/router/index.js
const turnTo = (to, access, next) => {
  next() // 直接過。
}
測試路由和菜單

分別登陸 zhangsanlisi 從上面的測試權限數據可以看到,lisi 是進入不了產品列表的。

登陸lisi

強行進入:

OK,正確了,如果沒有權限,肯定就沒有頁面嘛,直接404.

登陸zhangsan

按鈕的控制

頁面的跳轉搞定了,接下來就要考慮按鈕的問題了。那么上面設置的 VuexhasPermission 在這里派上用場。關于按鈕的綁定,思路來源于 Vue 的自定義指令,只需要在主頁面注冊自定義指令,每次頁面加載的時候,加載到指定指令的組件,開始讀取這個組件的 operaCode 是否在當前登陸的用戶中,如果存在,則繼續執行,如果不存在,則拿到了節點的 Vnode 開始設置我們想要的東西,比如鎖定按鈕啊,劫持點擊事件彈出提示,或者隱藏都可以。

// src/main.js
/**
 * 注冊權限控制指令
 */
Vue.directive("opcode", {
  bind: function (el, opcode, vnode, oldVNode) {
    const requireOpCode = opcode.value
    // 如果用戶沒有這個操作Code的權限,那么劫持click事件,賦予彈出無權限彈窗的事件
    console.log(requireOpCode);
    if (vnode.componentInstance === undefined || vnode.componentInstance === null) {
      if (!vnode.context.$store.getters.hasPermission(requireOpCode)) {
        vnode.data.on.click.fns = function () {
          Modal.warning({
            title: "無權限",
            content: "很抱歉,您沒有這項操作的權限"
          })
        }
      }
    } else {
      if (!vnode.componentInstance.$store.getters.hasPermission(requireOpCode)) {
        vnode.componentInstance.$off("click")
        vnode.componentInstance.$on("click", function () {
          Modal.warning({
            title: "無權限",
            content: "很抱歉,您沒有這項操作的權限"
          })
        })
      }
    }
  }
})

常規按鈕使用:


Render 函數中使用:

// src/view/product/product-list.vue
prodColumns: [
        {
          title: "商品名稱",
          key: "pname",
          align: "center",
          width: 300
        },
        {
          title: "操作",
          key: "operator",
          align: "center",
          render: (h, params) => {
            return h("a", {
              attrs: {
                href: "javascript:;"
              },
              on: {
                click: () => {
                  this.handleEditProduct(params.row)
                }
              },
              // 在這里注冊自定義指令
              directives: [
                {
                  name: "opcode",
                  value: "ProductManageEndpoint#updateByUuid"
                }
              ]
            }, "編輯")
          }
        }
      ],

這種方式即使分頁,改變表格數據,也可以被主頁面上的判斷監聽到。

PS:在這里就可以看到了按鈕的編排的問題,即我們新增產品的頁面接口,是在填寫完 Form 表單的時候才開始請求的,但是我們需要在兩個地方做設置,一個是跳轉頁面(路由+菜單),一個是新增產品的按鈕,即需要把這個權限攔截提前到進入頁面之前。這方面現在確實想不到更好的解決辦法了。

演示

回顧一下權限設置數據:

export const getPermissionCodeList = (userName) => {
  if (userName === "zhangsan") {
    return [{ operatorCode: "ProductManageEndpoint#list", name: "產品列表" },
      { operatorCode: "ProductManageEndpoint#createProduct", name: "產品上傳" },
      { operatorCode: "ProductManageEndpoint#putawayProduct", name: "產品上架" },
      { operatorCode: "ProductManageEndpoint#delistProduct", name: "產品下架" }]
  } else {
    return [{ operatorCode: "ProductManageEndpoint#createProduct", name: "產品上傳" },
      { operatorCode: "ProductManageEndpoint#delistProduct", name: "產品下架" },
      { operatorCode: "ProductManageEndpoint#deleteByUuid", name: "產品刪除" }]
  }
}

我們知道,用戶名為 zhangsan 是沒有刪除產品權限以及編輯產品的,但是前面的都有。而 lisi 用戶,沒有產品列表功能,所以列表都不可以進去。

zhangsan 用戶界面:

lisi 用戶界面:

權限編排頁面建議

我們知道,經典權限模型是用戶、角色、資源的編排。那么我們可以設置一系列的角色,當然記得給自己預留一個流氓角色可以讀取所有資源和角色的,利于管理。每個角色可以綁定不同的資源,也可以綁定不同的用戶。這樣用戶一旦登陸,即可讀取所有角色中的所有資源操作(記得去重)。

我們公司的綁定界面,通過讀取用戶所擁有的所有資源,再把 router 中綁定的讀取出來(當然過濾掉當前綁定用戶無權限的),然后通過加載勾選,最后發送請求給服務器做修改。這樣設置可以無限極下限,只要賦予用戶授權的權限,用戶即可一直創建然后綁定他想綁定的。

權限模塊建議

當前所做的都是前端需要做的工作,后端也需要配合完成接口的調用,即使前端做了設置,但是有些用戶可以繞過前端,這時候所有的權限操作就需要后端來攔截了。

作者:WeidanLi
我的博客

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/102387.html

相關文章

  • sSpring Boot多模塊+ Shiro + Vue:前后分離登陸整合,權限認證(一)

    摘要:前言本文主要使用來實現前后端分離的認證登陸和權限管理,適合和我一樣剛開始接觸前后端完全分離項目的同學,但是你必須自己搭建過前端項目和后端項目,本文主要是介紹他們之間的互通,如果不知道這么搭建前端項目的同學可以先找別的看一下。 前言 本文主要使用spring boot + shiro + vue來實現前后端分離的認證登陸和權限管理,適合和我一樣剛開始接觸前后端完全分離項目的同學,但是你必...

    macg0406 評論0 收藏0
  • [ 好文分享 ] 美團酒店Node全棧開發實踐

    摘要:我所在的美團酒店事業部去年月份成立,新的業務新的開發團隊,這一切使得我們的前后端分離推進的很徹底。日志監控平臺日志監控平臺是美團內部的一個日志收集系統,目前美團統一使用收集日志,具有接收格式日志的能力,而日志監控平臺也是以格式日志來收集。 轉自:美團技術團隊 作者:美團技術團隊 分享理由:很好的分享,可見,基于Node的前后端分離的架構是越顯流行和重要,前端攻城獅們,No...

    wangdai 評論0 收藏0
  • 從NNVM看2016年深度學習框架發展趨勢

    摘要:兩者取長補短,所以深度學習框架在年,迎來了前后端開發的黃金時代。陳天奇在今年的中,總結了計算圖優化的三個點依賴性剪枝分為前向傳播剪枝,例已知,,求反向傳播剪枝例,,求,根據用戶的求解需求,可以剪掉沒有求解的圖分支。 虛擬框架殺入從發現問題到解決問題半年前的這時候,暑假,我在SIAT MMLAB實習??粗乱粫号躎orch,一會兒跑MXNet,一會兒跑Theano。SIAT的服務器一般是不...

    ThinkSNS 評論0 收藏0
  • 如何從零設計結構清晰、操作友好的權限管理模塊

    摘要:同時將用戶關聯到用戶組,從而可以在不斷變動權限的情況下,配置一次對應關系,將用戶權限限制到單個上。這樣在返回數據的時候,所有記錄均為對其具有操作權限的對象。當然是天生的組件化設計理念。 前言 在開講之前,先列舉幾個場景:場景一Hi,今天那個銷售總監說要設立幾個銷售經理的職位,然后每個經理管理自己小組的銷售員,我們把用戶的銷售數據按組分開來吧。場景二Mario,今天那個市場部的說要分立幾...

    kel 評論0 收藏0

發表評論

0條評論

最新活動
閱讀需要支付1元查看
<