怎么用Springboot+mybatis-plus+注解实现数据权限隔离

风敏同学_1065

风敏同学_1065

2023-05-14

3091人浏览

转载

SpringBoot升级-BW
SpringBoot升级-BW

将 Spring Boot 2.7 项目升级到 Spring Boot 3.5 的实战流程,覆盖版本基线、依赖坐标替换、Jakarta 迁移、配置兼容、异步上下文传递改造与验证门禁。用于企业多模块 Maven 项目升级与排障。

下载

1.创建注解

当此注解打在类上,不需要传参,该类下所有查询接口开启数据隔离;打在方法上默认开启数据隔离,传参为false则该方法关闭验证

/**
 * 数据权限验证注解
 * @author xiaohua
 * @date 2021/6/23
 */
@Documented
@Target({METHOD, ANNOTATION_TYPE, TYPE})
@Retention(RUNTIME)
public @interface DataPermission {
    /**
     * 是否要进行数据权限隔离
     */
    boolean isPermi() default true;
}

2. 具体实现

@Component
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class DataPermissionInterceptor implements Interceptor {
    private static final Logger logger = LoggerFactory.getLogger(DataPermissionInterceptor.class);

    @Autowired
    private TokenService tokenService;

    //扫描的包路径(根据自己的项目路径来),这里是取的配置里的包路径
    @Value("${permission.package-path}")
    private String packagePath;

    private final static String DEPT_ID = "dept_id";

    private final static String USER_ID = "create_user";

    private static List<string> classNames;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            LoginInfo user = tokenService.getLoginInfo();
            if (user == null){
                return invocation.proceed();
            }

            List<long> deptIds = (List<long>) Convert.toList(user.getDataScope());
            if (deptIds == null){
                deptIds = new ArrayList();
            }
            //反射扫包会比较慢,这里做了个懒加载
            if (classNames == null) {
                synchronized (LazyInit.class){
                    if (classNames == null){
                        //扫描指定包路径下所有包含指定注解的类
                        Set<class>> classSet = ClassUtil.scanPackageByAnnotation(packagePath, DataPermission.class);
                        if (classSet == null && classSet.size() == 0){
                            classNames = new ArrayList();
                        } else {
                            //取得类全名
                            classNames =  classSet.stream().map(Class::getName).collect(Collectors.toList());
                        }
                    }
                }
            }

            // 拿到mybatis的一些对象
            StatementHandler statementHandler = PluginUtils.realTarget(invocation.getTarget());
            MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
            MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");

            // mappedStatement.getId()为执行的mapper方法的全路径名,newId为执行的mapper方法的类全名
            String newId = mappedStatement.getId().substring(0, mappedStatement.getId().lastIndexOf("."));
            // 如果不是指定的方法,直接结束拦截
            if (!classNames.contains(newId)) {
                return invocation.proceed();
            }
            String newName = mappedStatement.getId().substring(mappedStatement.getId().lastIndexOf(".") + 1, mappedStatement.getId().length());
            //是否开启数据权限
            boolean isPermi = true;
            Class> clazz = Class.forName(newId);
            //遍历方法
            for (Method method : clazz.getDeclaredMethods()) {
                //方法是否含有DataPermission注解,如果含有注解则将数据结果过滤
                if (method.isAnnotationPresent(DataPermission.class) && newName.equals(method.getName())) {
                    DataPermission dataPermission =  method.getAnnotation(DataPermission.class);
                    if (dataPermission != null) {
                        //不验证
                        if (!dataPermission.isPermi()) {
                            isPermi = false;
                        } else { //开启验证
                            isPermi = true;
                        }
                    }
                }
            }

            if (isPermi){
                // 获取到原始sql语句
                String sql = statementHandler.getBoundSql().getSql();

                // 解析并返回新的SQL语句,只处理查询sql
                if (mappedStatement.getSqlCommandType().toString().equals("SELECT")) {
    //                    String newSql = getNewSql(sql,deptIds,user.getUserId());
                    sql = getSql(sql,deptIds,user.getUserId());
                }
                // 修改sql
                metaObject.setValue("delegate.boundSql.sql", sql);
            }
            return invocation.proceed();
        } catch (Exception e){
            logger.error("数据权限隔离异常:", e);
            return invocation.proceed();
        }

    }
    
    
    /**
     * 解析SQL语句,并返回新的SQL语句
     * 注意,该方法使用了JSqlParser来操作SQL,该依赖包Mybatis-plus已经集成了。如果要单独使用,请先自行导入依赖
     *
     * @param sql 原SQL
     * @return 新SQL
     */
    private String getSql(String sql,List<long> deptIds,Long userId) {

        try {
            String condition = "";
            String permissionSql = "(";
            if (deptIds.size() > 0){
                for (Long deptId : deptIds) {
                    if ("(".equals(permissionSql)){
                        permissionSql = permissionSql + deptId;
                    } else {
                        permissionSql = permissionSql + "," + deptId;
                    }
                }
                permissionSql = permissionSql + ")";
                // 修改原语句
                condition = DEPT_ID +" in " + permissionSql;
            } else {
                condition = USER_ID +" = " + userId;
            }

            if (StringUtils.isBlank(condition)){
                return sql;
            }
            Select select = (Select)CCJSqlParserUtil.parse(sql);
            PlainSelect plainSelect = (PlainSelect)select.getSelectBody();
            //取得原SQL的where条件
            final Expression expression = plainSelect.getWhere();
            //增加新的where条件
            final Expression envCondition = CCJSqlParserUtil.parseCondExpression(condition);
            if (expression == null) {
                plainSelect.setWhere(envCondition);
            } else {
                AndExpression andExpression = new AndExpression(expression, envCondition);
                plainSelect.setWhere(andExpression);
            }
            return plainSelect.toString();
        } catch (JSQLParserException e) {
            logger.error("解析原SQL并构建新SQL错误:" + e);
            return sql;
        }
    }</long></class></long></long></string>

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

springboot

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
Vite 项目如何打包和部署
Vite 项目如何打包和部署

Vite项目打包部署专题汇集生产构建、产物预览、输出目录、资源路径、Nginx与GitHub Pages部署及常见白屏和路由404问题的已审核文章,帮助读者完成上线流程。

2026.10.28

40

16

Vite 如何搭配 Vue.js
Vite 如何搭配 Vue.js

Vite搭配Vue.js专题汇集创建Vue 3项目、配置开发环境、路径别名、Vue插件、代理、多环境构建与调试的已审核文章,帮助读者完成从初始化到日常开发的配置。

2026.10.28

20

15

Vite 项目目录怎么看
Vite 项目目录怎么看

Vite项目目录专题汇集入口文件、src源码、public静态资源、路径别名、项目根目录和构建输出目录相关的已审核文章,帮助读者理解各文件夹的用途与引用方式。

2026.10.28

0

10

Gradle插件开发入门教程
Gradle插件开发入门教程

本专题介绍Gradle插件开发的基本流程,涵盖插件类编写、自定义任务注册、扩展参数设计和插件应用方法,帮助你理解插件如何组织构建逻辑,减少多个项目之间的重复配置。

2026.10.22

120

11

Gradle Wrapper配置教程
Gradle Wrapper配置教程

本专题介绍Gradle Wrapper的基本作用,涵盖文件组成、生成方法、版本配置和任务调用,帮助你理解项目如何使用指定的Gradle版本,减少不同开发环境带来的构建差异。

2026.10.22

80

10

Golangci-lint检查器使用教程
Golangci-lint检查器使用教程

本专题整理Golangci-lint常用Linters的用途与配置方法,覆盖错误处理、无效代码、安全、复杂度、命名和依赖检查,帮助项目合理选规则。

2026.10.22

60

15

Gradle怎么构建Java项目
Gradle怎么构建Java项目

本专题介绍Gradle Java项目构建方法,涵盖Java插件配置、依赖声明、源码编译、测试执行和打包检查,帮助你理解各阶段的任务关系,逐步完成项目构建。

2026.10.22

40

10

PHP时间戳漏洞修复教程
PHP时间戳漏洞修复教程

本专题汇总PHP常见时间戳漏洞,包括2038年溢出、时区漂移、时间戳伪造、重放攻击、时间盲注与日志审计失效等,并给出升级64位环境、使用 DateTimeImmutable、统一UTC、严格超时校验、预处理绑定、hash_equals 比对等修复方案,帮助开发者快速排查和加固时间相关代码。

2026.10.22

60

25

Golangci-lint配置文件与规则设置
Golangci-lint配置文件与规则设置

本专题聚焦Golangci-lint YAML配置文件,讲清运行参数、规则启停、目录排除、问题过滤及严重级别设置,便于团队统一检查标准。

2026.10.22

40

15

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Redis6入门到精通超详细教程
Redis6入门到精通超详细教程

共47课时 | 7.4万人学习

前端基础到实战(HTML5+CSS3+ES6+NPM)
前端基础到实战(HTML5+CSS3+ES6+NPM)

共162课时 | 28.9万人学习

第二十四期_综合实战
第二十四期_综合实战

共133课时 | 7.3万人学习