若依笔记(三):用户权限体系与数据隔离

本文分析了若依系统权限控制的实现,包括前端路由拦截、后端数据隔离以及角色、部门和岗位的细分。通过后端的DataScope注解和切面配合Mybatis的Mapper实现数据权限过滤,前端利用vue-element-admin动态路由和v-hasPermi指令进行权限判断,确保用户只能看到和操作与其角色和部门关联的数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

权限级别

用户/权限/部门/岗位

​数据隔离

mybatis的maaper写法

注解和切面

前端路由拦截


已知若依单体的前端采用vue-element-admin,在前端的专栏系列vue-element-admin的动态路由已详细拆解,其最大特点是使用后端返回数据控制前端菜单,每次接口请求前会有路由拦截来获取权限和菜单列表并重新渲染页面,配合若依后端自己实现的数据权限切面,能够实现很好地的数据隔离和权限隔离,那么现在就从代码层面分析其实现;

权限级别

若依系统的权限控制实体分为目录-->菜单-->按钮,最小到按钮级别,也就是针对某个表可以控制其是否能够删除或新增,普通用户只能查看筛选(查);

  • 目录:左侧菜单栏
  • 菜单:二级菜单
  • 按钮:数据的增删改查操作

用户/权限/部门/岗位

权限控制主体是用户,用户具有角色、部门、岗位的细分属性,

  • 角色:角色可以控制菜单,角色意味着该用户具备哪些数据的管理操作权限,可以在角色编辑栏针对角色来控制不同的菜单项;用户和角色是多对多的关系,用户的权限控制是通过角色权限控制实现的(菜单/按钮权限+数据权限),菜单/按钮是通过返回给前端的list动态路由实现的,数据权限是通过aop拦截修改SQL语句实现的;
  • 部门:部门不涉及权限,但涉及组织架构,当树形展开部门组织架构后,可以清晰明了地知道用户隶属于什么部门,及其岗位是啥,只是用户的某个属性;
  • 岗位:岗位也只是用户的某个属性,岗位与角色关联,比如测试组的负责人应该可以查看所有测试表的数据,那么可以给这个负责人用户1个test-admin角色,给其测试表的所有操作权限,同理,研发和营销人员都应该有1个对应的admin角色,通过角色控制对应部门的菜单列表;

刚才说可以通过定义角色权限来控制目录+菜单,实现不同的部门看不同的菜单栏,那么如果同一个部门内部,比如营销人员A和营销人员B,A是小组长可以新增任务,B只能查看和认领/修改,那么这就涉及同一个表的按钮级别权限,可以新建一个营销的comon角色,该角色只能查看按钮;

 数据隔离

上面从功能层面描述了权限管理体系,也就是实现从前端去控制用户的操作权限,那么后端的数据如何做到数据隔离呢?数据隔离就是不同部门看隶属于其部门的数据,部门A和部门B的common普通用户都有查看<施工监测>这个目录--->菜单的权限,并且均有查看权限,那么其查看的数据应该是跟部门绑定的,这就是通过后端切面来实现的;

mybatis的maaper写法

我们来看代码生成器生成的mapper怎么写的,sql的where循环条件最后一行有params.dataScope的这个字符串,它是入参ProjectJklc这个pojo类的某个属性,这里可以把他当作1个占位符;

    <select id="selectProjectJklcList" parameterType="ProjectJklc" resultMap="ProjectJklcResult">
        <include refid="selectProjectJklcVo"/>
        <where>  
            <if test="gdxc != null "> and gdxc = #{gdxc}</if>
            <if test="zbsl != null "> and zbsl = #{zbsl}</if>
            <if test="gjxc != null "> and gjxc = #{gjxc}</if>
            <if test="scyl != null "> and scyl = #{scyl}</if>
            <if test="cjyl != null "> and cjyl = #{cjyl}</if>
            <if test="gjnl != null "> and gjnl = #{gjnl}</if>
            <if test="tnl != null "> and tnl = #{tnl}</if>
            <if test="mgzl != null "> and mgzl = #{mgzl}</if>
            <if test="wynbwy != null "> and wynbwy = #{wynbwy}</if>
            <if test="xgjy != null  and xgjy != ''"> and xgjy = #{xgjy}</if>
            ${params.dataScope}
        </where>
            </select>

在 ProjectJklc这个实体定义中继承了BaseEntity,它有params属性,是个map默认是空;

public class ProjectJklc extends BaseEntity
private Map<String, Object> params;

 params在何时被填充的,这个sql何时被补充完整的?可以查看使用这个mapper的impl调用入口,有个注解@DataScope(deptAlias = "project_jklc"),这个注解中定义了deptAlias这个key,翻译过来是部门表别名,意味着这个value是个跨部门访问的表;

    /**
     * 查询监控量测列表
     * 
     * @param projectJklc 监控量测
     * @return 监控量测
     */
    @Override
    @DataScope(deptAlias = "project_jklc")
    public List<ProjectJklc> selectProjectJklcList(ProjectJklc projectJklc)
    {
        return projectJklcMapper.selectProjectJklcList(projectJklc);
    }
注解和切面

@DataScope注解定义有3个属性,部门表别名,用户表别名,权限字符,实现跨部门跨用户的数据隔离过滤条件;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope
{
    /**
     * 部门表的别名
     */
    public String deptAlias() default "";

    /**
     * 用户表的别名
     */
    public String userAlias() default "";

    /**
     * 权限字符(用于多个角色匹配符合要求的权限)默认根据权限注解@ss获取,多个权限用逗号分隔开来
     */
    public String permission() default "";
}

在DataScopeAspect切面中有一系列判断,其核心是首先看当前用户所属权限是否限制了本人操作过的数据只有本人能看,或部门内都可以使用,比如当前用户所属角色(营销人员B的营销common角色)若是仅仅本人数据权限就只能查user_id等于她自己的;

判断中角色如果是全部数据权限/all权限不拼接sql,正常情况是本部门可以访问,也就是只做部门间的数据隔离,那么就只拼接dept_id的SQL语句,如下:

            if (DATA_SCOPE_ALL.equals(dataScope))
            {
                sqlString = new StringBuilder();
                conditions.add(dataScope);
                break;
            }
            else if (DATA_SCOPE_DEPT.equals(dataScope))
            {
                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
            }

该切面在方法调用前填充了实体中的params属性,在mybaits的mapper使用时候这个实体已经有值了,这样就实现了部门/用户层面的数据隔离。

前端路由拦截

那么代码生成器生成的前端代码如何配合后端来完成按钮级别的控制的呢?这个就涉及到前端代码入侵,在前端<el-button>按钮组件上有个v-hasPermi属性

      <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          icon="el-icon-delete"
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['system:jklc:remove']"
        >删除</el-button>
      </el-col>

v-hasPermi是自定义指令,用于处理操作权限。它通过获取store中的permissions来判断用户是否有操作权限,如果没有则移除该元素,[system:jklc:query]将被解析成system,jklc,query然后比对permission获取的后端权限list,如果没有比对上,就是fasle,就不会选自然这个el-button组件。

import store from '@/store'

export default {
  inserted(el, binding, vnode) {
    const { value } = binding
    const all_permission = "*:*:*";
    const permissions = store.getters && store.getters.permissions

    if (value && value instanceof Array && value.length > 0) {
      const permissionFlag = value

      const hasPermissions = permissions.some(permission => {
        return all_permission === permission || permissionFlag.includes(permission)
      })

      if (!hasPermissions) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    } else {
      throw new Error(`请设置操作权限标签值`)
    }
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A叶子叶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值