目录
AOP简介
面向切面编程(AOP)的概念
面向切面编程(Aspect-Oriented Programming, AOP)是一种编程范式,旨在通过将横切关注点(cross-cutting concerns)从核心业务逻辑中分离出来,以提高代码的模块化和可维护性。横切关注点是指那些影响多个模块但又不属于任何单一模块的核心功能,如日志记录、事务管理、权限控制等。
在传统的面向对象编程(OOP)中,这些横切关注点通常会散布在各个业务方法中,导致代码重复和难以维护。AOP通过引入“切面”(Aspect)、“切点”(Pointcut)、“通知”(Advice)等概念,允许开发者将这些横切关注点集中处理,从而使得业务逻辑更加清晰和专注。
切面(Aspect):包含横切关注点的模块化组件。
切点(Pointcut):定义了在哪些连接点(Join Point)上应用通知的规则。
通知(Advice):在特定的连接点执行的动作,如前置通知(Before Advice)、后置通知(After Advice)、环绕通知(Around Advice)等。
连接点(Join Point):程序执行过程中的某个点,如方法调用、异常抛出等。
织入(Weaving):将切面应用到目标对象的过程,可以在编译时、类加载时或运行时进行。
AOP在RuoYi框架中的应用
在RuoYi框架中,AOP被广泛用于实现各种横切关注点,如权限控制、日志记录、性能监控等。特别是DataScopeAspect类,它利用AOP机制来动态调整SQL查询语句,确保用户只能访问其权限范围内的数据。这种设计不仅提高了系统的安全性和可控性,还简化了权限管理和数据隔离的实现。
DataScopeAspect类的作用
类的功能
DataScopeAspect类的主要功能是根据用户的权限级别动态调整SQL查询语句,以实现精确的数据范围控制。具体来说,它会在SQL查询中添加额外的条件,确保查询结果仅限于用户有权限查看的数据。这有助于保护敏感信息,防止越权访问。DataScopeAspect类中的代码如下:
/**
* 数据过滤处理
*
* @author ruoyi
*/
@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, "");
}
}
}
切点选择
DataScopeAspect通过Spring AOP的切点机制介入到业务逻辑中。它使用@before
注解来定义一个切点,该切点会在带有@DataScope
注解的方法执行之前触发。具体实现如下:
权限检查
DataScopeAspect通过SecurityUtils.getLoginUser()
方法获取当前登录的用户信息,并根据用户的权限级别进行相应的处理。以下是具体的步骤:
如果用户是超级管理员,则不过滤数据,因为超级管理员拥有全部权限。否则,调用dataScopeFilter
方法进行数据范围过滤。dataScopeFilter
方法遍历用户的每个角色,根据角色的权限类型生成相应的SQL片段。
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 ");
}
}
}
在前端如何给不同的用户设置不同的权限
为不同的角色分配不同的权限
通过赋予不同用户对应的角色来实现不同的用户拥有其对应的权限
实际代码示例
控制层
服务层
Mapper层
params通过parameterTypr中的SysUser继承的对象类中获取
DataScopeAspect类
DataScopeAspect类的作用在前面已经说明了,接下里通过不同权限的所对应的sql代码进行解读
1.全部权限
if (DATA_SCOPE_ALL.equals(dataScope))
{
sqlString = new StringBuilder();
break;
}
当用户拥有全部权限时,DataScopeAspect
不会对SQL语句进行任何修改。
例如:
SELECT u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar,
u.phonenumber, u.password, u.sex, u.status, u.del_flag,
u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
d.dept_name, d.leader
FROM sys_user u
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
WHERE u.del_flag = '0'
2.自定义权限
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()));
}
DataScopeAspect会读取自定义权限配置,并将相应的条件(如特定部门或项目)添加到SQL查询中。例如,如果用户的某个角色设置了特定的部门ID,则会在SQL语句中添加AND u.dept_id IN (SELECT dept_id FROM sys_role_dept WHERE role_id = {role_id})这样的条件。
例如:
SELECT u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar,
u.phonenumber, u.password, u.sex, u.status, u.del_flag,
u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
d.dept_name, d.leader
FROM sys_user u
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
WHERE u.del_flag = '0'
AND u.dept_id IN (SELECT dept_id FROM sys_role_dept WHERE role_id = 1)
3. 本部门及以下权限
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()));
}
DataScopeAspect会在SQL语句中添加条件,使得查询结果仅限于用户所在部门及其下属部门的数据。具体来说,它会添加一个递归条件,如AND u.dept_id IN (SELECT dept_id FROM sys_dept WHERE dept_id = {user.dept_id} OR find_in_set({user.dept_id}, ancestors)),以确保查询结果包括用户所在部门及其所有子部门的数据。
例如:
SELECT u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar,
u.phonenumber, u.password, u.sex, u.status, u.del_flag,
u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
d.dept_name, d.leader
FROM sys_user u
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
WHERE u.del_flag = '0'
AND (u.dept_id = 1001 OR u.dept_id IN (SELECT dept_id FROM sys_dept WHERE find_in_set(1001, ancestors)))
4.仅本人权限
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 ");
}
}
DataScopeAspect会在SQL语句中添加严格的过滤条件,使查询结果只包含与当前用户直接相关的记录。例如,它会添加AND u.user_id = {current_user_id}这样的条件,确保查询结果只返回当前用户自己的数据。
例如:
SELECT u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar,
u.phonenumber, u.password, u.sex, u.status, u.del_flag,
u.login_ip, u.login_date, u.create_by, u.create_time, u.remark,
d.dept_name, d.leader
FROM sys_user u
LEFT JOIN sys_dept d ON u.dept_id = d.dept_id
WHERE u.del_flag = '0'
AND u.user_id = 10001
总结
DataScopeAspect类中dataScopeFilter方法生成的SQL片段会被存储在baseEntity.getParams()中。 在Mapper层的SQL映射文件中,使用${params.dataScope}占位符来插入这个SQL片段。从而实现不同权限下使用相应的sql语句来进行权限管理。
标签:DataScopeAspect,RuoYi,dept,user,SQL,SCOPE,权限,DATA,id From: https://blog.csdn.net/dggdg1249/article/details/144648665