本文最后更新于 229 天前,如有失效请评论区留言。
权限拦截器
DataPermissionInterceptor 是 MyBatis Plus 的一个数据权限插件,支持动态拦截执行 SQl
然后拼接权限部分 SQL片段
。
怎么使用呢? 拦截器 PlusDataPermissionInterceptor,这里在查询、修改、删除的操作时做了拦截处理,核心的方式实现了 dataPermissionHandler
public class PlusDataPermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
@Override
protected void processSelect(Select select, int index, String sql, Object obj) {
SelectBody selectBody = select.getSelectBody();
if (selectBody instanceof PlainSelect plainSelect) {
this.setWhere(plainSelect, (String) obj);
} else if (selectBody instanceof SetOperationList setOperationList) {
List<SelectBody> selectBodyList = setOperationList.getSelects();
selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj));
}
}
@Override
protected void processUpdate(Update update, int index, String sql, Object obj) {
Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false);
if (null != sqlSegment) {
update.setWhere(sqlSegment);
}
}
@Override
protected void processDelete(Delete delete, int index, String sql, Object obj) {
Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false);
if (null != sqlSegment) {
delete.setWhere(sqlSegment);
}
}
/**
* 设置 where 条件
*
* @param plainSelect 查询对象
* @param mappedStatementId 执行方法id
*/protected void setWhere(PlainSelect plainSelect, String mappedStatementId) {
Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true);
if (null != sqlSegment) {
plainSelect.setWhere(sqlSegment);
}
}
}
PlusDataPermissionHandler 权限实现类
public class PlusDataPermissionHandler {
/**
* 方法或类(名称) 与 注解的映射关系缓存
*/
private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
/**
* spel 解析器
*/
private final ExpressionParser parser = new SpelExpressionParser();
private final ParserContext parserContext = new TemplateParserContext();
/**
* bean解析器 用于处理 spel 表达式中对 bean 的调用
*/
private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) {
DataColumn[] dataColumns = findAnnotation(mappedStatementId);
LoginUser currentUser = DataPermissionHelper.getVariable("user");
if (ObjectUtil.isNull(currentUser)) {
currentUser = LoginHelper.getLoginUser();
DataPermissionHelper.setVariable("user", currentUser);
}
// 如果是超级管理员或租户管理员,则不过滤数据
if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
return where;
}
String dataFilterSql = buildDataFilter(dataColumns, isSelect);
if (StringUtils.isBlank(dataFilterSql)) {
return where;
}
try {
Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql);
// 数据权限使用单独的括号 防止与其他条件冲突
Parenthesis parenthesis = new Parenthesis(expression);
if (ObjectUtil.isNotNull(where)) {
return new AndExpression(where, parenthesis);
} else {
return parenthesis;
}
} catch (JSQLParserException e) {
throw new ServiceException("数据权限解析异常 => " + e.getMessage());
}
}
/**
* 构造数据过滤sql
*/private String buildDataFilter(DataColumn[] dataColumns, boolean isSelect) {
// 更新或删除需满足所有条件
String joinStr = isSelect ? " OR " : " AND ";
LoginUser user = DataPermissionHelper.getVariable("user");
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(beanResolver);
DataPermissionHelper.getContext().forEach(context::setVariable);
Set<String> conditions = new HashSet<>();
for (RoleDTO role : user.getRoles()) {
user.setRoleId(role.getRoleId());
// 获取角色权限泛型
DataScopeType type = DataScopeType.findCode(role.getDataScope());
if (ObjectUtil.isNull(type)) {
throw new ServiceException("角色数据范围异常 => " + role.getDataScope());
}
// 全部数据权限直接返回
if (type == DataScopeType.ALL) {
return "";
}
boolean isSuccess = false;
for (DataColumn dataColumn : dataColumns) {
if (dataColumn.key().length != dataColumn.value().length) {
throw new ServiceException("角色数据范围异常 => key与value长度不匹配");
}
// 不包含 key 变量 则不处理
if (!StringUtils.containsAny(type.getSqlTemplate(),
Arrays.stream(dataColumn.key()).map(key -> "#" + key).toArray(String[]::new)
)) {
continue;
}
// 设置注解变量 key 为表达式变量 value 为变量值
for (int i = 0; i < dataColumn.key().length; i++) {
context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
}
// 解析sql模板并填充
String sql = parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class);
conditions.add(joinStr + sql);
isSuccess = true;
}
// 未处理成功则填充兜底方案
if (!isSuccess && StringUtils.isNotBlank(type.getElseSql())) {
conditions.add(joinStr + type.getElseSql());
}
}
if (CollUtil.isNotEmpty(conditions)) {
String sql = StreamUtils.join(conditions, Function.identity(), "");
return sql.substring(joinStr.length());
}
return "";
}
public DataColumn[] findAnnotation(String mappedStatementId) {
StringBuilder sb = new StringBuilder(mappedStatementId);
int index = sb.lastIndexOf(".");
String clazzName = sb.substring(0, index);
String methodName = sb.substring(index + 1, sb.length());
Class<?> clazz;
try {
clazz = ClassUtil.loadClass(clazzName);
} catch (Exception e) {
return null;
}
List<Method> methods = Arrays.stream(ClassUtil.getDeclaredMethods(clazz))
.filter(method -> method.getName().equals(methodName)).toList();
DataPermission dataPermission;
// 获取方法注解
for (Method method : methods) {
dataPermission = dataPermissionCacheMap.get(mappedStatementId);
if (ObjectUtil.isNotNull(dataPermission)) {
return dataPermission.value();
}
if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) {
dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class);
dataPermissionCacheMap.put(mappedStatementId, dataPermission);
return dataPermission.value();
}
}
dataPermission = dataPermissionCacheMap.get(clazz.getName());
if (ObjectUtil.isNotNull(dataPermission)) {
return dataPermission.value();
}
// 获取类注解
if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) {
dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class);
dataPermissionCacheMap.put(clazz.getName(), dataPermission);
return dataPermission.value();
}
return null;
}
}
@DataPermission 权限注解
SysUserMapper.java
/**
* 根据条件分页查询用户列表
*
* @param queryWrapper 查询条件
* @return 用户信息集合信息
*/
@DataPermission({
@DataColumn(key = "deptName", value = "d.dept_id"),
@DataColumn(key = "userName", value = "u.user_id")
})
List<SysUserVo> selectUserList(@Param(Constants.WRAPPER) Wrapper<SysUser> queryWrapper);
/**
* 通过用户ID查询用户
*
* @param userId 用户ID
* @return 用户对象信息
*/
@DataPermission({
@DataColumn(key = "deptName", value = "d.dept_id"),
@DataColumn(key = "userName", value = "u.user_id")
})
SysUserVo selectUserById(Long userId);
1、当进行 SQL 操作时,将@DataPermission 注解的方法的class 对象用 dataPermissionCacheMap 缓存起来,例如上面 SysUserMapper 中的查询方法、更新方法都有权限注解
2、buildDataFilter 数据过滤,默认管理员和租户管理员不过滤数据,普通用户按照角色进行过滤,使用自定数据权限
public enum DataScopeType {
/**
* 全部数据权限
*/
ALL("1", "", ""),
/**
* 自定数据权限
*/
CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "),
/**
* 部门数据权限
*/
DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "),
/**
* 部门及以下数据权限
*/
DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "),
/**
* 仅本人数据权限
*/
SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 ");
private final String code;
/**
* 语法 采用 spel 模板表达式
*/
private final String sqlTemplate;
/**
* 不满足 sqlTemplate 则填充
*/
private final String elseSql;
public static DataScopeType findCode(String code) {
if (StringUtils.isBlank(code)) {
return null;
}
for (DataScopeType type : values()) {
if (type.getCode().equals(code)) {
return type;
}
}
return null;
}
}
自定义权限注解中 #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} )
对应会被解析成 d.dept_id IN ( 100,101,105 )
这样经过SQL拦截处理后,就可以根据角色定义的规则进行数据过滤了
关于ruoyi-vue升级MybatisPlus,数据权限、分页这些有没有完整的代码可以供参考的呢?