跳至主要內容
若依源码解析:DataScopeAspect实现数据范围的控制

源代码

@Aspect
@Component
public class DataScopeAspect
{
    /**
     * 全部数据权限
     */
    public static final String DATA_SCOPE_ALL = "1";

    /**
     * 自定数据权限
     */
    public static final String DATA_SCOPE_CUSTOM = "2";

    /**
     * 部门数据权限
     */
    public static final String DATA_SCOPE_DEPT = "3";

    /**
     * 部门及以下数据权限
     */
    public static final String DATA_SCOPE_DEPT_AND_CHILD = "4";

    /**
     * 仅本人数据权限
     */
    public static final String DATA_SCOPE_SELF = "5";

    /**
     * 数据权限过滤关键字
     */
    public static final String DATA_SCOPE = "dataScope";

    @Before("@annotation(controllerDataScope)")
    public void doBefore(JoinPoint point, DataScope controllerDataScope) throws Throwable
    {
        clearDataScope(point);
        handleDataScope(point, controllerDataScope);
    }

    protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope)
    {
        // 获取当前的用户
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (StringUtils.isNotNull(loginUser))
        {
            SysUser currentUser = loginUser.getUser();
            // 如果是超级管理员,则不过滤数据
            if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin())
            {
                dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
                        controllerDataScope.userAlias());
            }
        }
    }

    /**
     * 数据范围过滤
     *
     * @param joinPoint 切点
     * @param user 用户
     * @param userAlias 别名
     */
    public static void dataScopeFilter(JoinPoint joinPoint, SysUser user, String deptAlias, String userAlias)
    {
        StringBuilder sqlString = new StringBuilder();

        for (SysRole role : user.getRoles())
        {
            String dataScope = role.getDataScope();
            if (DATA_SCOPE_ALL.equals(dataScope))
            {
                sqlString = new StringBuilder();
                break;
            }
            else if (DATA_SCOPE_CUSTOM.equals(dataScope))
            {
                sqlString.append(StringUtils.format(
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_role_dept WHERE role_id = {} ) ", deptAlias,
                        role.getRoleId()));
            }
            else if (DATA_SCOPE_DEPT.equals(dataScope))
            {
                sqlString.append(StringUtils.format(" OR {}.dept_id = {} ", deptAlias, user.getDeptId()));
            }
            else if (DATA_SCOPE_DEPT_AND_CHILD.equals(dataScope))
            {
                sqlString.append(StringUtils.format(
                        " OR {}.dept_id IN ( SELECT dept_id FROM sys_dept WHERE dept_id = {} or find_in_set( {} , ancestors ) )",
                        deptAlias, user.getDeptId(), user.getDeptId()));
            }
            else if (DATA_SCOPE_SELF.equals(dataScope))
            {
                if (StringUtils.isNotBlank(userAlias))
                {
                    sqlString.append(StringUtils.format(" OR {}.user_id = {} ", userAlias, user.getUserId()));
                }
                else
                {
                    // 数据权限为仅本人且没有userAlias别名不查询任何数据
                    sqlString.append(" OR 1=0 ");
                }
            }
        }

        if (StringUtils.isNotBlank(sqlString.toString()))
        {
            Object params = joinPoint.getArgs()[0];
            if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
            {
                BaseEntity baseEntity = (BaseEntity) params;
                baseEntity.getParams().put(DATA_SCOPE, " AND (" + sqlString.substring(4) + ")");
            }
        }
    }

    /**
     * 拼接权限sql前先清空params.dataScope参数防止注入
     */
    private void clearDataScope(final JoinPoint joinPoint)
    {
        Object params = joinPoint.getArgs()[0];
        if (StringUtils.isNotNull(params) && params instanceof BaseEntity)
        {
            BaseEntity baseEntity = (BaseEntity) params;
            baseEntity.getParams().put(DATA_SCOPE, "");
        }
    }
}

程序员诚哥大约 5 分钟若依若依后端源码解析
若依源码解析:pagehelper和mybaties进行分页

若依分页使用示例

@PreAuthorize("@ss.hasPermi('system:dict:list')")
    @GetMapping("/list")
    public TableDataInfo list(SysDictData dictData)
    {
        startPage();
        List<SysDictData> list = dictDataService.selectDictDataList(dictData);
        return getDataTable(list);
    }

程序员诚哥大约 5 分钟若依若依后端源码解析
若依源码解析:RuoYi-Vue权限系统设计

摘要

若依(RuoYi)是一款基于Spring Boot和Vue.js开发的快速开发平台,它的权限管理是通过RBAC(Role-based Access Control 基于角色的访问控制)模型来设计的。

RBAC模型将权限控制分为角色管理和权限管理两个部分。在若依中,角色是指对系统的一类用户或操作者的定义,而权限是指对系统中某个资源或操作的访问控制。通过为每个角色分配相应的权限,可以实现对系统的全面管理和控制。

具体来说,在若依中,权限管理包括以下几个方面:

  1. 菜单管理:通过对系统菜单进行管理,可以控制用户在系统中能够访问的页面和功能。

  2. 按钮权限:在系统中,某些操作需要特定的权限才能进行,例如删除、修改等操作。通过对按钮权限的控制,可以限制用户对系统的访问和操作。

  3. 数据权限:在某些情况下,需要根据用户的角色或部门来限制其对数据的访问。通过数据权限的设置,可以实现对数据的细粒度控制。

  4. API接口权限:在若依中,API也可以通过权限的方式进行控制。通过对API的权限进行管理,可以限制用户对API的访问和使用。


程序员诚哥大约 9 分钟若依若依后端源码解析
若依源码解析:RuoYi-Vue登录和鉴权的实现

摘要

若依(RuoYi)是一款基于 Spring Boot 和 Vue.js 的开源权限管理系统,若依登录和鉴权的实现还包含验证码的生成与校验,这是为了增加系统的安全性,防止恶意攻击和暴力破解等行为。其登录和鉴权实现主要包括以下几个步骤:

  1. 验证码生成
  • 前端页面:登录页面中的验证码显示区域,通常位于 Vue.js 的 src/views 目录下。
  • 验证码 API:后端应用的验证码接口。该接口接收请求并生成验证码图片,并将验证码字符保存在 Redis 或者内存中。
  1. 用户登录:用户在登录页面输入用户名和密码,点击登录按钮,将表单数据发送给后端 Spring Boot 应用。

程序员诚哥大约 16 分钟若依若依后端源码解析
若依源码解析:图片验证码生成

摘要

若依通过合理的验证码生成流程和相应的代码实现,为应用程序提供了生成图片验证码和基于数学运算的验证码文本的功能,以增加系统的安全性和防护能力。
本文讨论了若依(Ruoyi)生成图片验证码的过程以及相关代码。首先,我们了解了生成图片验证码的基本步骤,包括生成随机字符串、创建图片对象、绘制背景和文本、添加干扰线等。
接下来,我们分析了若依中的验证码生成控制器,它根据配置的验证码类型,在后台生成对应类型的验证码,并将验证码图片以Base64编码的形式返回给前端页面。
最后,我们解释了一个自定义的验证码文本生成器,它生成基于数学运算的验证码文本,要求用户进行计算并输入结果进行验证。这种验证码形式提高了安全性和防止自动化攻击的能力。


程序员诚哥大约 7 分钟若依若依后端源码解析
若依源码解析:用LoggingAspect进行日志处理

摘要

本文将深入探讨若依框架中的LoggingAspect切面的作用和重要性。LoggingAspect是若依框架中用于实现日志记录功能的切面组件,它通过切点和切面的概念,将日志记录逻辑与业务逻辑解耦,实现非侵入式的日志记录。文章首先介绍了切面编程和AOP的概念,然后重点讨论了LoggingAspect的作用,包括捕获方法调用、记录日志信息以及提供代码重用和可维护性等优势。此外,文章还解释了@Pointcut注解的作用,它用于定义切点表达式,精确定位目标方法。最后,文章强调了在logback.xml配置文件中为LoggingAspect设置日志记录级别的重要性,以便控制日志输出的详细程度。通过深入理解LoggingAspect,开发人员可以优化日志记录和追踪功能,提高应用程序的可维护性和可读性,从而更好地监测和排查问题。


程序员诚哥大约 6 分钟若依若依后端源码解析
若依源码解析:防止表单重复提交@RepeatSubmit、RepeatableFilter、RepeatedlyRequestWrapper和RepeatSubmitInterceptor

摘要

若依(Ruoyi)是一款基于Spring Boot和MyBatis的开源后台管理系统,它提供了一系列的拦截器(Interceptor)用于处理请求。其中,RepeatSubmitInterceptor(重复提交拦截器)是若依系统中的一个关键拦截器,用于防止用户重复提交表单请求。

在Web应用程序中,用户可能会重复提交表单,例如在点击提交按钮后多次点击或者网络延迟造成用户误以为提交未成功而再次提交。这可能导致一些问题,例如重复的数据插入或重复的业务逻辑处理。

RepeatSubmitInterceptor 的主要作用是在用户提交表单请求时,对请求进行拦截和处理,防止重复提交。判断该url是否有RepeatSubmit注解,如果有的话,就里面取到了:【参数,url,用户】然后和RepeatSubmit里的过期时间一起放到了redis。


程序员诚哥大约 5 分钟若依若依后端源码解析
解决MyBatis-Plus updateById方法更新不了空字符串或null

概要

在用mybatis-plus封装的updateById方法来更新数据时,想把一个字段设置为null值,但是发现更新后数据没有为null还是原来的值,这是因为mybatis-plus在更新的时候做了null判断,默认不更新为null的传参。可以在配置文件中设置update-strategy为ignored来全局处理,也可以在字段上设置注解来单个处理:@TableField(updateStrategy = FieldStrategy.IGNORED)。在低版本的mybatis-plus中可以全局设置field-strategy为ignored,或单个字段设置注解
@TableField(strategy = FieldStrategy.IGNORED)来处理。


程序员诚哥大约 3 分钟若依若依后端源码解析
若依源码解析:代码生成ruoyi-generator

摘要

若依的代码生成器模块(ruoyi-generator)可以根据数据库表的设计信息和配置的模板,自动生成相应的Java代码文件。代码生成器使用Velocity作为模板引擎,根据模板文件中的占位符和变量替换规则,将元数据信息嵌入到生成的代码中,生成具体的代码文件。通过导入表结构和生成代码两个后端接口,实现了快速导入数据库表结构和生成代码的功能。导入表结构会从information_schema数据库的tables和columns表中查询表和列的信息,并插入到ruoyi数据库的gen_table和gen_table_column表中。生成代码时,会根据查询到的表和列信息,初始化Velocity模板引擎,并准备上下文信息,包括变量值信息。然后,读取模板文件,渲染模板,并将渲染后的内容添加到压缩流中生成zip压缩文件,供前端下载使用。ruoyi-vue代码生成器大大提高了开发效率,使得开发人员能够快速生成符合规范的代码文件。


程序员诚哥大约 12 分钟若依若依后端代码自动生成源码解析