mybatis plus数据权限插件在项目中的使用方式
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>前言</li><li>数据权限插件</li><li>使用DataPermissionHandler</li><li>自定义的dataScopeFilter方法</li><li>CCJSqlParserManager类解析</li><li>总结</li></ul></div><p class="maodian"></p><h2>前言</h2><p>平时开发中遇到根据当前用户的角色,只能查看数据权限范围的数据需求。实现方案有两种,一是在开发初期就做好判断,但如果这个需求是中途加的,或不希望每个接口都加一遍,就可以方案二加拦截器的方式。在mybatis执行sql前修改语句,限定where范围。</p>
<p>当然拦截器生效后是全局性的,如何保证只对需要的接口进行拦截和转化,就可以应用注解进行识别</p>
<p>因此具体需要哪些步骤就明确了:</p>
<ol><li>创建注解类</li><li>创建处理类,获取数据权限 SQL 片段,设置条件</li><li>将拦截器加到MyBatis-Plus插件中</li></ol>
<p class="maodian"></p><h2>数据权限插件</h2>
<p>DataPermissionInterceptor</p>
<p>插件原理和租户插件类似动态拦截执行 SQl 然后拼接权限部分 SQL片段 , 该插件一直是免费开源的,企业高级特性-数据范围功能也是基于该原理实现,只不过添加了注解支持。</p>
<p class="maodian"></p><h2>使用DataPermissionHandler</h2>
<div class="jb51code"><pre class="brush:java;">/**
* 数据权限处理器
*
* @author hubin
* @since 3.4.1 +
*/
public interface DataPermissionHandler {
/**
* 获取数据权限 SQL 片段
*
* @param where 待执行 SQL Where 条件表达式
* @param mappedStatementId Mybatis MappedStatement Id 根据该参数可以判断具体执行方法
* @return JSqlParser 条件表达式
*/
Expression getSqlSegment(Expression where, String mappedStatementId);
}
</pre></div>
<p>先定义一个注解:</p>
<div class="jb51code"><pre class="brush:java;">@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScope {
/**
* 表前缀
*/
String tableAlis() default "";
/**
* 机构前缀
*/
String orgAlis() default "org_id";
/**
* 是否生效
*/
boolean enabled() default true;
}
</pre></div>
<p>新建一个OrgScopeHandler,通过实现以上接口:</p>
<div class="jb51code"><pre class="brush:java;"> @Override
public Expression getSqlSegment(Expression where, String mappedStatementId) {
try {
Class<?> clazz = Class.forName(mappedStatementId.substring(0, mappedStatementId.lastIndexOf(".")));
String methodName = mappedStatementId.substring(mappedStatementId.lastIndexOf(".") + 1);
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
DataScope annotation = method.getAnnotation(DataScope.class);
if (ObjectUtils.isNotEmpty(annotation)
&& (method.getName().equals(methodName) || (method.getName() + "_COUNT").equals(methodName))) {
if (annotation.enabled()) {
return dataScopeFilter(annotation, where);
}
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return where;
}
</pre></div>
<p>在项目中的MybatisPlusConfig中加上自定义的OrgScopeHandler:</p>
<div class="jb51code"><pre class="brush:java;"> MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 数据权限
interceptor.addInnerInterceptor(new DataPermissionInterceptor(new OrgScopeHandler()));
</pre></div>
<p>getSqlSegment中的实现是遍历Mybatis MappedStatement类的所有方法,对每一个方法执行以下操作:</p>
<p>获取该方法上的 DataScope 注解。</p>
<p>检查注解是否存在且不为空(ObjectUtils.isNotEmpty(annotation))。</p>
<p>检查方法名是否与之前获取的 methodName 相同,或者是方法名后加上 _COUNT 后缀的情况。</p>
<p>如果以上条件都满足,再检查 DataScope 注解的 enabled() 属性是否为 true。</p>
<p>如果找到了匹配的方法并且注解启用,那么调用 dataScopeFilter 方法,传入该方法上的 DataScope 注解和 where 参数,进行数据范围过滤。</p>
<p class="maodian"></p><h2>自定义的dataScopeFilter方法</h2>
<div class="jb51code"><pre class="brush:java;"> /**
* 根据注解中的内容构造 拼接sql,用于数据权限过滤
*
* @param dataPermission 数据权限注解,用于指定表和组织别名等信息
* @param where 原始sql的where条件表达式
* @return 返回拼接后的sql表达式,添加了数据权限的过滤条件
*/
private Expression dataScopeFilter(DataScope dataPermission, Expression where) {
// 获取当前登录的用户
NutUser user = SecurityUtil.getNutUser();
String tableAlias = dataPermission.tableAlis(); // 表别名
String orgAlias = dataPermission.orgAlis(); // 组织别名
// 构造数据过滤的in表达式
InExpression inExpression = new InExpression();
// 设置左边的字段表达式,根据表和组织别名构建
inExpression.setLeftExpression(buildColumn(tableAlias, orgAlias));
// 使用JSQLParser解析组织ID的查询语句
CCJSqlParserManager parserManager = new CCJSqlParserManager();
Statement statement;
try {
// 构造查询当前用户所属组织及其子组织ID的sql语句
StringReader statementReader = new StringReader(
"select id from sys_org where path like '%" + user.getOrgId() + "%'");
statement = parserManager.parse(statementReader);
} catch (JSQLParserException e) {
// 处理解析异常
throw new RuntimeException(e);
}
Select selectStatement = (Select) statement;
SubSelect subSelect = new SubSelect();
subSelect.setSelectBody(selectStatement.getSelectBody()); // 设置子查询的查询体
inExpression.setRightExpression(subSelect); // 设置右边的子查询表达式
// 如果原有where条件不为空,则将数据权限过滤条件和原有条件用and连接
return ObjectUtils.isNotEmpty(where) ? new AndExpression(where, new Parenthesis(inExpression)) : inExpression;
}
</pre></div>
<p>buildColumn方法:</p>
<div class="jb51code"><pre class="brush:java;"> /**
* 构建Column
*
* @param tableAlias 表别名
* @param columnName 字段名称
* @return 带表别名字段
*/
public static Column buildColumn(String tableAlias, String columnName) {
if (StringUtils.isNotEmpty(tableAlias)) {
columnName = tableAlias + "." + columnName;
}
return new Column(columnName);
}
</pre></div>
<p>使用:</p>
<div class="jb51code"><pre class="brush:java;">/**
* 分页查询
*
* @param pagepage
* @param query query
*/
@DataScope(tableAlis = "item")
Page<InfoBO> selectPageQuery(Page page, @Param("query") HandleQuery query);
</pre></div>
<p>结果是实际的sql上将拼接上:</p>
<div class="jb51code"><pre class="brush:sql;">and (org_id IN (SELECT id FROM sys_org WHERE path LIKE concat('%', 1, '%')))</pre></div>
<p class="maodian"></p><h2>CCJSqlParserManager类解析</h2>
<p>CCJSqlParserManager 类是 JSqlParser 库中的一个关键类,它主要用于管理和执行 SQL 语句的解析操作。</p>
<p>JSqlParser 是一个 Java 库,用于解析 SQL 语言,并将其转化为可遍历、操作和修改的 Java 对象模型。</p>
<p>CCJSqlParserManager 主要功能如下:</p>
<ol><li>初始化解析器资源:该类负责初始化和管理 JSqlParser 解析器所需的资源。</li><li>解析 SQL 语句:它可以接受字符串形式的 SQL 查询,并通过 parse() 方法将其转换成 Statement 对象,这是一个抽象类,代表了 SQL 语句的不同类型(如 Select, Update, Delete, Insert 等)。</li><li>处理并发请求:CCJSqlParserManager 可能会实现资源池或其他机制来更有效地处理多个并发的 SQL 解析请求。<br />例如,在上述代码片段中:</li></ol>
<div class="jb51code"><pre class="brush:java;">CCJSqlParserManager parserManager = new CCJSqlParserManager();
Statement statement;
try {
StringReader statementReader = new StringReader("select id from sys_org where path like '%" + user.getOrgId() + "%'");
statement = parserManager.parse(statementReader);
} catch (JSQLParserException e) {
throw new RuntimeException(e);
}
</pre></div>
<p>这段代码就是创建了一个 CCJSqlParserManager 实例,然后使用它来解析一个 SQL 语句,这个 SQL 语句是为了查询当前用户及其子组织的 ID。</p>
<p>通过解析得到的 statement 对象可以进一步被分析或修改,以便进行动态 SQL 生成、权限检查、SQL 优化等各种操作。</p>
<p class="maodian"></p><h2>总结</h2>
<p>以上为个人经验,希望能给大家一个参考,也希望大家多多支持琼殿技术社区。</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>MyBatisPlus数据权限控制实现的三种方式</li><li>MybatisPlus实现数据权限隔离的示例详解</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]