美文网首页
使用JSqlParser 通用数据权限的方案

使用JSqlParser 通用数据权限的方案

作者: jackcooper | 来源:发表于2021-09-06 18:00 被阅读0次

    理论知识:

    Mybatis Interceptor 拦截器

    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;
            }
        }
    

    摘自: https://juejin.cn/post/6981280887216275492
    相似文章: https://blog.csdn.net/hzh1234565/article/details/70226248
    JSqlParser官网: https://github.com/JSQLParser/JSqlParser

    相关文章

      网友评论

          本文标题:使用JSqlParser 通用数据权限的方案

          本文链接:https://www.haomeiwen.com/subject/yrcywltx.html