• MybatisPlus拦截器+注解实现数据权限


    数据权限注解

    最好放在Mapper下,以免Service/Controller里有不需要增加数据权限的查询出现问题

    1. @Target(ElementType.METHOD)
    2. @Retention(RetentionPolicy.RUNTIME)
    3. @Documented
    4. public @interface DataScope {
    5. /**
    6. * 关联用户表的主表别名
    7. */
    8. String mainTableAlias() default "";
    9. /**
    10. * 用户表的别名
    11. */
    12. String userAlias() default "";
    13. /**
    14. * mainTableAlias表里的用户ID字段名
    15. */
    16. String userField() default "";
    17. /**
    18. * 关联用户表字段,默认用户表ID
    19. */
    20. String joinField() default "id";
    21. }

    数据权限拦截器 

    1. @Slf4j
    2. public class DataScopePermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {
    3. private DataScopePermissionHandler dataScopePermissionHandler;
    4. public void setDataScopePermissionHandler(DataScopePermissionHandler dataScopePermissionHandler) {
    5. this.dataScopePermissionHandler = dataScopePermissionHandler;
    6. }
    7. @Override
    8. public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    9. if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) {
    10. return;
    11. }
    12. PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
    13. mpBs.sql(this.parserSingle(mpBs.sql(), ms.getId()));
    14. }
    15. /**
    16. * 通过API构建出新的条件,并将新的条件和之前的条件拼接在一起
    17. * @param select
    18. * @param index
    19. * @param sql
    20. * @param obj
    21. */
    22. @Override
    23. protected void processSelect(Select select, int index, String sql, Object obj) {
    24. // 解析SQL
    25. SelectBody selectBody = select.getSelectBody();
    26. try {
    27. if (selectBody instanceof PlainSelect) {
    28. // 单个sql
    29. dataScopePermissionHandler.buildDataPermission((PlainSelect) selectBody);
    30. } else if (selectBody instanceof SetOperationList) {
    31. //多个sql,用;号隔开,一般不会用到。例如:select * from user;select * from role;
    32. SetOperationList setOperationList = (SetOperationList) selectBody;
    33. List selectBodyList = setOperationList.getSelects();
    34. for (SelectBody s : selectBodyList) {
    35. dataScopePermissionHandler.buildDataPermission((PlainSelect) s);
    36. }
    37. }
    38. } catch (Exception e) {
    39. log.error("sql添加数据权限失败", e);
    40. }
    41. }
    42. }

    注解切面,并处理数据权限SQL拼接

    原本是想在拦截器里通过方法名直接获取注解,但是分页用的是PageHelper插件,导致分页会封装一个_COUNT方法查询数量,这个方法就获取不到注解,会导致分页total数量没有做数据权限。不知道MybatisPlus自己的分页方法有没有这个问题。所以只能增加切面用ThreadLocal来存储注解数据。拼接数据权限方法中也有对计算数量sql可能会形成子查询别名为table_count做处理。

    1. @Slf4j
    2. @Aspect
    3. @Component
    4. public class DataScopePermissionHandler {
    5. /**
    6. * 全部数据权限
    7. */
    8. public static final int DATA_SCOPE_ALL = 1;
    9. /**
    10. * 本部门(及下级部门)
    11. */
    12. public static final int DATA_SCOPE_DEPT_AND_CHILD = 2;
    13. /**
    14. * 仅本人数据权限
    15. */
    16. public static final int DATA_SCOPE_SELF = 3;
    17. /**
    18. * 指定部门
    19. */
    20. public static final int DATA_SCOPE_CUSTOM = 4;
    21. /**
    22. * 用户表、组织表、角色组织关系表
    23. */
    24. public static final String TABLE_USER = "sys_user";
    25. public static final String TABLE_ORG = "sys_org";
    26. public static final String TABLE_ROLE_ORG = "sys_role_org";
    27. /**
    28. * 用户表:组织ID字段
    29. */
    30. public static final String FIELD_ORG_ID = "org_id";
    31. /**
    32. * pagehelper分页计算数量别名
    33. */
    34. public static final String TABLE_COUNT = "table_count";
    35. /**
    36. * 通过ThreadLocal记录权限相关的属性值
    37. */
    38. ThreadLocal threadLocal = new ThreadLocal<>();
    39. @Before("@annotation(dataScope)")
    40. public void doBefore(JoinPoint point, DataScope dataScope) {
    41. SecurityUser currentUser = SecurityUserUtil.getCurrentUser();
    42. if (currentUser != null) {
    43. DataScopeParam dataScopeParam = new DataScopeParam(dataScope.mainTableAlias(), dataScope.userAlias(), dataScope.userField(), dataScope.joinField(), SecurityUserUtil.isAdmin(), currentUser);
    44. threadLocal.set(dataScopeParam);
    45. }
    46. }
    47. /**
    48. * 清空当前线程上次保存的权限信息
    49. * @param dataScope
    50. */
    51. @After("@annotation(dataScope)")
    52. public void clearThreadLocal(DataScope dataScope) {
    53. threadLocal.remove();
    54. }
    55. /**
    56. * 拼接数据权限SQL
    57. * @param plainSelect
    58. * @throws JSQLParserException
    59. */
    60. public void buildDataPermission(PlainSelect plainSelect) throws JSQLParserException {
    61. DataScopeParam dataScopeParam = threadLocal.get();
    62. if (dataScopeParam == null || dataScopeParam.isAdmin()) {
    63. return;
    64. }
    65. log.info("原始SQL:[{}]", plainSelect.toString());
    66. SecurityUser currentUser = dataScopeParam.currentUser;
    67. Set dataRanges = currentUser.getRoles().stream().map(SysRole::getDataRange).collect(Collectors.toSet());
    68. if (CollectionUtils.isEmpty(dataRanges)) {
    69. //数据权限为空,不查询数据
    70. plainSelect.setWhere(new HexValue(" 1 = 0 "));
    71. return;
    72. }
    73. if (dataRanges.stream().anyMatch(t -> t == DATA_SCOPE_ALL)) {
    74. //有全部数据权限,不加限制
    75. return;
    76. }
    77. //未被pagehelper count封装过的
    78. PlainSelect realSelect = plainSelect;
    79. List expressions = new ArrayList<>();
    80. for (SysRole role : currentUser.getRoles()) {
    81. int dataRange = role.getDataRange();
    82. if (plainSelect.getFromItem() instanceof SubSelect) {
    83. SubSelect fromSubSelect = (SubSelect) plainSelect.getFromItem();
    84. //子查询,判断是否为pagehelper封装的获取数量的
    85. if (TABLE_COUNT.equals(fromSubSelect.getAlias().getName()) && CollectionUtils.isEmpty(plainSelect.getJoins())) {
    86. realSelect = (PlainSelect) fromSubSelect.getSelectBody();
    87. }
    88. }
    89. List joins = realSelect.getJoins();
    90. String sql;
    91. if (DATA_SCOPE_CUSTOM == dataRange || DATA_SCOPE_DEPT_AND_CHILD == dataRange) {
    92. //都要关联user表
    93. boolean hasUserTable = false;
    94. if (!dataScopeParam.getUserAlias().equalsIgnoreCase(realSelect.getFromItem().getAlias().getName())) {
    95. //主表不是user表
    96. if (!CollectionUtils.isEmpty(joins)) {
    97. //判断join的表里有没有user表
    98. for (Join join : joins) {
    99. // 判断join里面是否存在user表,不存在则新增
    100. FromItem rightItem = join.getRightItem();
    101. if (rightItem instanceof Table) {
    102. Table table = (Table) rightItem;
    103. if (table.getName().equalsIgnoreCase(TABLE_USER)) {
    104. hasUserTable = true;
    105. }
    106. }
    107. }
    108. }
    109. } else {
    110. hasUserTable = true;
    111. }
    112. if (!hasUserTable) {
    113. //没有user表,增加关联用户表
    114. Table innerTable = new Table(TABLE_USER).withAlias(new Alias(dataScopeParam.getUserAlias(), false));
    115. Join join = new Join();
    116. join.withRightItem(innerTable);
    117. EqualsTo equalsTo = new EqualsTo();
    118. equalsTo.setLeftExpression(new StringValue(StrUtil.format("{}.{}", dataScopeParam.getMainTableAlias(), dataScopeParam.getUserField())));
    119. equalsTo.setRightExpression(new Column(innerTable, dataScopeParam.getJoinField()));
    120. join.withOnExpression(equalsTo);
    121. realSelect.addJoins(join);
    122. }
    123. if (DATA_SCOPE_CUSTOM == dataRange) {
    124. //指定部门
    125. sql = StrUtil.format(" {}.{} IN ( SELECT `value` FROM {} WHERE role_id = {} ) ",
    126. dataScopeParam.getUserAlias(), FIELD_ORG_ID, TABLE_ROLE_ORG, role.getId());
    127. } else {
    128. //本部门(及下级部门)
    129. sql = StrUtil.format(" {}.{} IN ( SELECT id FROM {} WHERE id = {} or find_in_set( {} , ancestors ) ) ",
    130. dataScopeParam.getUserAlias(), FIELD_ORG_ID, TABLE_ORG, currentUser.getOrgId(), currentUser.getOrgId());
    131. }
    132. } else if (DATA_SCOPE_SELF == dataRange) {
    133. //仅本人数据权限
    134. sql = StrUtil.format(" {}.{} = {} ", dataScopeParam.getMainTableAlias(), dataScopeParam.getUserField(), currentUser.getId());
    135. } else {
    136. sql = " 1 = 0";
    137. }
    138. Expression expression = CCJSqlParserUtil.parseCondExpression(sql);
    139. expressions.add(expression);
    140. }
    141. Expression dataExpression;
    142. if (expressions.size() > 1) {
    143. //数据权限大于1个,之间用或
    144. OrExpression orExpression = new OrExpression(expressions.get(0), expressions.get(1));
    145. for (int i = 2; i < expressions.size(); i++) {
    146. orExpression = new OrExpression(orExpression, expressions.get(i));
    147. }
    148. // 数据权限使用单独的括号 防止与其他条件冲突
    149. dataExpression = new Parenthesis(orExpression);
    150. } else {
    151. dataExpression = expressions.get(0);
    152. }
    153. Expression where = realSelect.getWhere();
    154. if (where != null) {
    155. where = new AndExpression(where, dataExpression);
    156. } else {
    157. where = dataExpression;
    158. }
    159. realSelect.setWhere(where);
    160. }
    161. @Data
    162. @AllArgsConstructor
    163. static class DataScopeParam {
    164. /**
    165. * 关联用户表的主表别名
    166. */
    167. private String mainTableAlias;
    168. /**
    169. * 用户表的别名
    170. */
    171. private String userAlias;
    172. /**
    173. * 用户字段名
    174. */
    175. private String userField;
    176. /**
    177. * 关联用户表字段,默认用户表ID
    178. */
    179. private String joinField;
    180. /**
    181. * 是否管理员
    182. */
    183. private boolean isAdmin;
    184. /**
    185. * 当前登录用户
    186. */
    187. private SecurityUser currentUser;
    188. }
    189. }

    MybatisPlus插件配置

    1. @Configuration
    2. public class MybatisPlusConfig {
    3. @Autowired
    4. private DataScopePermissionHandler dataScopePermissionHandler;
    5. @Bean
    6. public MybatisPlusInterceptor mybatisPlusInterceptor() {
    7. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    8. // 数据权限
    9. DataScopePermissionInterceptor dataScopePermissionInterceptor = new DataScopePermissionInterceptor();
    10. dataScopePermissionInterceptor.setDataScopePermissionHandler(dataScopePermissionHandler);
    11. interceptor.addInnerInterceptor(dataScopePermissionInterceptor);
    12. return interceptor;
    13. }
    14. }

    参考:SpringCloud微服务实战——搭建企业级开发框架(二十八):扩展MybatisPlus插件DataPermissionInterceptor实现数据权限控制
    Mybatis-Plus通过注解形式实现数据权限过滤 

    MyBatis Plus 拦截器实现数据权限控制(完整版)

  • 相关阅读:
    mybatis 数据库字段为空or为空串 忽略条件过滤, 不为空且不为空串时才需nameParam过滤条件
    mysql索引和事务
    受控组件和非受控组件
    程序员宝藏
    Pytest----当fixture重名时如何调用
    苹果iPhone 15/Pro新机发布,毫米波5G仍然只限美国版
    (多级缓存)缓存同步
    【影刀演示_发送邮件的格式化HTML留存】
    Hive怎么调整优化Tez引擎的查询?在Tez上优化Hive查询的指南
    实时矢量搜索如何彻底改变各行各业?
  • 原文地址:https://blog.csdn.net/huang369509940/article/details/134074626