• 【低代码】角色和权限的解决方案


    低代码的角色和权限

    基于Vue3 + UI 库,实现角色和权限的维护,依赖低代码的 JSON 而设计。

    普通项目也可以使用,只是由于没有现成的 JSON,显得稍微麻烦一些。

    角色和权限,我是一直都觉得挺简单的,甚至都不需要一开始就去设计角色。因为,开发项目的时候,按照“原子”级别设置功能模块,然后把权限设置到字段。这样,项目开发完毕再去设置角色也不迟,甚至可以让客户自行维护

    主要内容

    • 权限的分类
    • 权限、角色、用户的关系
    • 定义相关的接口
    • 实现角色的权限的维护方式

    权限的分类

    以前权限可以分为操作权限和资源权限,现在前后端分离,可以加上后端API的权限。

    • 操作权限:用户可以做哪些操作,比如打开某个模块,使用添加、修改、审批等操作。
    • 资源权限:用户可以使用哪些数据,比如只能维护自己的客户信息,不能看别人的客户信息等。
    • 后端API权限:用户可以使用哪些API。

    整理一下画了一个脑图:

    其中后端 API 可以和操作按钮相对应,比如拥有【添加按钮】的权限的话,那么就应该有对应的后端 API 的权限,否则如何提交数据呢?

    当然还有一些后端 API 和操作按钮没有明显的对应关系,这种情况需要单独设置,比如访问“字典”的权限。

    权限、角色和用户的关系

    一个角色可以有“一组”权限,一个用户可以有多个角色,一个角色也可以有多个用户。这样就建立了一个简单的关联关系。

    定义接口

    总体思路有了我们开始设计接口。好吧,其实以前是直接设计关系型数据库的,不过现在喜欢先设计接口。

    • 角色和权限
    • 角色里的用户
    • 权限的备选容器* 模块列表* 模块里的操作按钮* 模块的列* 模块的查询条件* 模块的资源权限* 模块的后端API

    IRole 角色和权限

    一个角色拥有哪些权限?我们来罗列一下:

    type idType = number | string
    
    /**
     * (一个)角色拥有的一组权限
     */
    export interface IRole {roleId: idType, // 角色编号roleName: string, // 角色名称rolePower: { // 角色拥有的权限集合moduleIds: Array, // 可以使用哪些模块buttonIds: {[moduleId: idType]: Array // 模块里面可以使用的操作按钮},gridIds: {[moduleId: idType]: Array // 模块里可以使用的列(字段)},findIds: {[moduleId: idType]: Array // 模块里可以使用的查询条件(字段)},gridIdsNot: {[moduleId: idType]: Array // 列表里面不可以用的列},findIdsNot: {[moduleId: idType]: Array // 列表里面不可以使用的查询字段},resources: {[moduleId: idType]: Array // 模块可以加载的资源权限},APIs: {[moduleId: idType]: Array // 模块里的特殊的后端API}}
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    IRoleUser 角色的用户

    /**
     * 角色和用户的关联,基于关系型数据库
     */
    export interface IRoleUser {roleUserId: idType,roleId: idType,userId: idType,
    }
    
    /**
     * 一个用户可以用多个角色
     */
    export interface IUserRole {userId: idType,roleIds: Array
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    IRoleUser 是按照关系型数据库设置的,IUserRole 是按照对象的思路做的,一个用户有哪些角色。

    IRoleData 权限的备选项容器

    维护权限,需要先准备好基础信息,比如模块信息、操作按钮信息等,我们来设计一个接口:

    /**
     * 维护角色的准备数据
     */
    export interface IRoleData {modules: IRoleModule[], // 模块信息,绑定 el-treebuttons: { // 模块里面的操作按钮,绑定 模块里的按钮[moduleId: idType]: Array},grids: { // 模块里面的列[moduleId: idType]: Array },finds: { // 模块里面的查询条件[moduleId: idType]: Array},resources: { // 模块里面可以选择的资源权限[moduleId: idType]: Array},APIs: { // 模块里面可以选择的(其他)后端API[moduleId: idType]: Array}
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    用于建立设置权限的界面,比如有哪些模块,模块里的按钮、列等备选信息。

    IRoleModule 模块信息

    主要用于绑定 el-tree,n 级分组。

    /**
     * 记录 功能模块 信息,用于绑定 el-tree
     */
    export interface IRoleModule {id: idType, // 模块IDlabel: string,children?: IRoleModule[]
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    IRoleButton 模块的操作按钮

    一个模块里面有哪些操作按钮?需要记录一下。总不能固定为添加、修改、删除吧。

    /**
     * 记录 操作按钮 信息
     */
    export interface IRoleButton {buttonId: idType, // valuemoduleId: idType,label: string, // labelkind: string | 'add' | 'update' | 'delete' | 'look' | 'detail' | 'list', // 按钮类型,增删改查等
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    IRoleColumn 权限到字段

    细粒度的权限,需要可以到“字段”这个级别,比如权限到列表的字段,权限到查询的字段,权限到表单的字段等。

    另外,由于资源权限和后端API,需要的结构和 IRoleColumn 其实是一样的,所以就不单独设置接口了。

    /**
     * 记录 模块的列表、查询字段、资源权限、后端API。
     * * 一个模块只有一个列表。
     * * 一个模块只有一组查询字段。
     * * 模块可以选择的资源权限
     * * 模块可以选择的后端API
     */
    export interface IRoleColumn {value: idType, // 字段IDlabel: string, // 字段名称/API 名称/资源权限名称
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    资源权限和后端API

    资源权限也可以做个目录,然后和模块关联,以备选择。

    /**
     * 模块可以选择的资源权限,或者后端API
     */
    export interface IRoleResourcesOrAPI {id: idType, // 资源权限的编号label: string, // 资源权限的名称
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5

    接口定义好了,然后我们看看编码的实现方式。

    实现角色和权限的维护

    角色的一组权限是一个整体,应该一同显示出来,所以需要我们先把各种“可选项”罗列出来,然后设置已经有的权限,便于让用户调整角色的权限。

    定义状态

    因为功能分散在多个组件里面实现,为了更好的共享数据,这里没有采用 props 的传递方式,而是采用了“局部状态”的方式,所以我们先定义一套状态:

    import type { InjectionKey } from 'vue'
    import { watch } from 'vue'
    
    import { defineStore, useStoreLocal } from '@naturefw/nf-state'
    import type { IState } from '@naturefw/nf-state/dist/type'
    
    import type {IRoleData
    } from '../types/10-role'
    
    const flag = Symbol('role') as InjectionKey
    
    /**
     * 注册局部状态
     * * 角色管理用的状态 : IRoleData & IState 
     * @returns
     */
    export const regRoleState = (): IRoleData & IState => {// 定义 角色用的状态const state = defineStore(flag, {state: (): IRoleData => {return {modules: [], // 模块信息,绑定 treebuttons: {}, // 模块里面的操作按钮,绑定 模块里的按钮grids: {}, // 模块里面的列finds: {}, // 模块里面的查询条件resources: {}, // 模块里面可以选择的资源权限APIs: {}, // 模块里面可以选择的(其他)后端APIhaveCols: {}, // 模块是否有列、资源权限等选项roleInfo: { // 当前的角色的信息roleId: 0,roleName: '默认' ,rolePower: { // 角色拥有的权限集合moduleIds: [], // 权限到【模块】buttonIds: {}, // 权限到【按钮】,按钮ID集合gridIds: {}, // 权限到【列表】字段,列表里的字段ID集合findIds: {}, // 权限到【查询】字段,查询里的字段ID集合gridIdsNot: {}, // 【列表】里的字段ID集合,不允许使用的findIdsNot: {}, // 【查询】里的字段ID集合,不允许使用的resources: {}, // 可以使用的【资源权限】APIs: {} // 可以使用的【API】}}}},actions: {/** * 加载数据, */async loadData () {// 加载数据}}},{ isLocal: true })
     return state
    }
    
    /**
     * 子组件获取状态
     */
    export const getRoleState = (): IRoleData & IState => {return useStoreLocal(flag)
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    制作菜单

    可以使用 el-tree 实现功能菜单的展示,自带一些关联选择等功能,还是比较方便的。

      
    
    • 1

    模块的操作按钮

    为了拆分代码(便于维护),我们做一个组件实现权限到操作按钮的功能,然后把这个组件放入 el-tree 的slot 里面。

     {{item.label}} 
    
    • 1

    这里还遇到一个小问题,如果使用 el-checkbox-group 的话,会报错,所以只好使用 el-checkbox 了。

     import { getRoleState } from '../state/state-role'const state = getRoleState()const list = state.buttons[props.moduleId]const roleButton = reactive({})// 如果有按钮,设置选项值if (list) {// 设置 check 的选项值list.forEach((item) => {roleButton[item.buttonId] = false})}const mychange = (e, buttonId) => { // 防止事件冒泡 e.stopPropagation()} 
    
    • 1

    这里需要使用 e.stopPropagation() 阻止事件冒泡。

    权限到字段

    思路和操作按钮一致,只是这里可以设定几个安全级别:

    1.宽松:不设置可以访问的列,表示可以使用模块里所有的列 —— 便于设置角色。
    2.严谨:必须设置可以使用的列,没有设置的话不可以访问 —— 提高安全性。
    3.预防:敏感列放入“不可访问”名单,可以在一定程度上避免误操作 —— 折中方案。

    宽松级别,便于设置角色的权限,因为大部分情况下,可以使用这个模块的话,那么就意味着模块里的列都是可以访问的,如果必须设置列,那么有点繁琐,还容易点错。

    严谨级别是对于要求严格的项目而设置,为了更安全,必须设置可以访问的列。虽然更安全,但是显然做设置的时候比较繁琐。

    预防级别,这是一个折中的方案,既然大部分字段都可以访问,只有个别的不能访问,那么我们把这几个敏感字段标注起来,列入“黑名单”。

     {{title[kind]}} 0"v-model="value":options="list"size="small":placeholder="placeholder"style="width: 240px;"multipleclearablecollapse-tagscollapse-tags-tooltip:height="300"@click="mychange($event)":teleported="false"/> 
    
    • 1

    一开始用的是 el-select ,发现报错了。还好 el-select-v2 没有报错,否则就麻烦了。

    资源权限

    资源权限,主要是后端的事情,因为前端不应该拿到没有权限的数据。

    简单的说呢,非常简单,我们首先看一下SQL

    select * from table1 where 【userId = xxx 】 and xxx = xxxx ... 
    
    • 1

    当然有个前提,项目使用关系型数据库。

    资源权限,最根本的就是上面 【】内部的部分,不管中间过程如何,最后都会归结为如何写SQL(where 后面的查询条件)

    好像有点跑题,维护的时候,只需要根据情况做选择即可,表现形式和权限到字段是一样的,所以就用了同一个组件,然后内部做一下判断,区分选项来源即可。

     const dic = {gridIds: 'grids',gridIdsNot: 'grids',findIds: 'finds',findIdsNot: 'finds',resources: 'resources',APIs: 'APIs'}const value = ref([])const { kind } = props// 获取状态const state = getRoleState()// 根据类型,获取下拉列表的备选项const list = state[dic[kind]][props.moduleId]?? reactive([])if (state.haveCols[props.moduleId]) {if (state.haveCols[props.moduleId] === false) {state.haveCols[props.moduleId] = (list.length > 0)}} else {state.haveCols[props.moduleId] = (list.length > 0)}// 监听选项值,设置角色watch(value, () => {if (value.value.length === 0) {delete state.roleInfo.rolePower[kind][props.moduleId]} else {state.roleInfo.rolePower[kind][props.moduleId] = value.value}}) 
    
    • 1

    根据用户的选择,设置角色可以拥有的权限。

    小结

    角色的权限的设置方面基本就是这样了。用Vue3 + UI 库实现功能,方便了很多。以前用jQuery,一些功能需要自己实现,现在UI库搞定了各种基础操作,我们整合一下即可。

    权限设置完毕,下面就是在项目里面如何使用的问题了。低代码的话比较容易,因为低代码是依赖JSON渲染的,而权限,其实说白了,就是规定可以加载哪些JSON。

    最后

    为大家准备了一个前端资料包。包含54本,2.57G的前端相关电子书,《前端面试宝典(附答案和解析)》,难点、重点知识视频教程(全套)。



    有需要的小伙伴,可以点击下方卡片领取,无偿分享

  • 相关阅读:
    【图像处理GIU】图像分割(Matlab代码实现)
    6.hadoop文件数据库系列讲解
    【华为机试真题 JAVA】最长子字符串的长度-100
    x64dbg 配置插件SDK开发环境
    尚好房 05_二手房管理
    Python:腾讯云-轻量应用服务器-实现自动快照
    【C++】类和对象 从入门到超神 (上)
    Polygon zkEVM Merkle tree的circom约束
    MySQL基础操作
    设计模式——策略模式
  • 原文地址:https://blog.csdn.net/qq_53225741/article/details/126991254