MybatisPlus interceptor + annotation to implement data permissions

Data permission notes

It is best to place it under Mapper to avoid problems with queries in Service/Controller that do not require increased data permissions.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
    /**
     * Main table alias associated with the user table
     */
    String mainTableAlias() default "";
    /**
     * Alias of user table
     */
    String userAlias() default "";
    /**
     * The user ID field name in the mainTableAlias table
     */
    String userField() default "";
    /**
     * Associated user table fields, default user table ID
     */
    String joinField() default "id";
}

Data Permission Blocker

@Slf4j
public class DataScopePermissionInterceptor extends JsqlParserSupport implements InnerInterceptor {

    private DataScopePermissionHandler dataScopePermissionHandler;

    public void setDataScopePermissionHandler(DataScopePermissionHandler dataScopePermissionHandler) {
        this.dataScopePermissionHandler = dataScopePermissionHandler;
    }

    @Override
    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) {
            return;
        }
        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);
        mpBs.sql(this.parserSingle(mpBs.sql(), ms.getId()));
    }

    /**
     * Construct new conditions through API and splice new conditions with previous conditions together
     * @param select
     * @param index
     * @param sql
     * @param obj
     */
    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
        // Parse SQL
        SelectBody selectBody = select.getSelectBody();
        try {
            if (selectBody instanceof PlainSelect) {
                // single sql
                dataScopePermissionHandler.buildDataPermission((PlainSelect) selectBody);
            } else if (selectBody instanceof SetOperationList) {
                //Multiple sql, separated by ; sign, generally not used. For example: select * from user;select * from role;
                SetOperationList setOperationList = (SetOperationList) selectBody;
                List<SelectBody> selectBodyList = setOperationList.getSelects();
                for (SelectBody s : selectBodyList) {
                    dataScopePermissionHandler.buildDataPermission((PlainSelect) s);
                }
            }
        } catch (Exception e) {
            log.error("sql failed to add data permission", e);
        }
    }
}

Annotate aspects and handle data permission SQL splicing

Originally, I wanted to get the annotations directly through the method name in the interceptor, but the PageHelper plug-in is used for paging, which causes the paging to encapsulate a _COUNT method to query the quantity. This method cannot obtain the annotations, which will result in the paging total number not having data permissions. . I don’t know if MybatisPlus’s own paging method has this problem. Therefore, we can only add aspects and use ThreadLocal to store annotation data. In the splicing data permission method, the calculation quantity SQL may form a subquery alias table_count for processing.

@Slf4j
@Aspect
@Component
public class DataScopePermissionHandler {
    /**
     * Full data access
     */
    public static final int DATA_SCOPE_ALL = 1;
    /**
     * This department (and subordinate departments)
     */
    public static final int DATA_SCOPE_DEPT_AND_CHILD = 2;
    /**
     *Only personal data permissions
     */
    public static final int DATA_SCOPE_SELF = 3;
    /**
     *Designated department
     */
    public static final int DATA_SCOPE_CUSTOM = 4;

    /**
     * User table, organization table, role organization relationship table
     */
    public static final String TABLE_USER = "sys_user";
    public static final String TABLE_ORG = "sys_org";
    public static final String TABLE_ROLE_ORG = "sys_role_org";
    /**
     * User table: Organization ID field
     */
    public static final String FIELD_ORG_ID = "org_id";

    /**
     * pagehelper paging calculation quantity alias
     */
    public static final String TABLE_COUNT = "table_count";

    /**
     * Record permission-related attribute values through ThreadLocal
     */
    ThreadLocal<DataScopeParam> threadLocal = new ThreadLocal<>();

    @Before("@annotation(dataScope)")
    public void doBefore(JoinPoint point, DataScope dataScope) {
        SecurityUser currentUser = SecurityUserUtil.getCurrentUser();
        if (currentUser != null) {
            DataScopeParam dataScopeParam = new DataScopeParam(dataScope.mainTableAlias(), dataScope.userAlias(), dataScope.userField(), dataScope.joinField(), SecurityUserUtil.isAdmin(), currentUser);
            threadLocal.set(dataScopeParam);
        }
    }

    /**
     * Clear the last saved permission information of the current thread
     * @param dataScope
     */
    @After("@annotation(dataScope)")
    public void clearThreadLocal(DataScope dataScope) {
        threadLocal.remove();
    }

    /**
     * Splicing data permissions SQL
     * @param plainSelect
     * @throws JSQLParserException
     */
    public void buildDataPermission(PlainSelect plainSelect) throws JSQLParserException {
        DataScopeParam dataScopeParam = threadLocal.get();
        if (dataScopeParam == null || dataScopeParam.isAdmin()) {
            return;
        }
        log.info("Original SQL:[{}]", plainSelect.toString());
        SecurityUser currentUser = dataScopeParam.currentUser;
        Set<Integer> dataRanges = currentUser.getRoles().stream().map(SysRole::getDataRange).collect(Collectors.toSet());
        if (CollectionUtils.isEmpty(dataRanges)) {
            //Data permission is empty, no data is queried
            plainSelect.setWhere(new HexValue(" 1 = 0 "));
            return;
        }
        if (dataRanges.stream().anyMatch(t -> t == DATA_SCOPE_ALL)) {
            //Have all data permissions without restrictions
            return;
        }
        //Not encapsulated by pagehelper count
        PlainSelect realSelect = plainSelect;
        List<Expression> expressions = new ArrayList<>();
        for (SysRole role : currentUser.getRoles()) {
            int dataRange = role.getDataRange();
            if (plainSelect.getFromItem() instanceof SubSelect) {
                SubSelect fromSubSelect = (SubSelect) plainSelect.getFromItem();
                //Subquery to determine whether it is the number of acquisitions encapsulated by pagehelper
                if (TABLE_COUNT.equals(fromSubSelect.getAlias().getName()) & amp; & amp; CollectionUtils.isEmpty(plainSelect.getJoins())) {
                    realSelect = (PlainSelect) fromSubSelect.getSelectBody();
                }
            }
            List<Join> joins = realSelect.getJoins();
            String sql;
            if (DATA_SCOPE_CUSTOM == dataRange || DATA_SCOPE_DEPT_AND_CHILD == dataRange) {
                //All must be associated with the user table
                boolean hasUserTable = false;
                if (!dataScopeParam.getUserAlias().equalsIgnoreCase(realSelect.getFromItem().getAlias().getName())) {
                    //The main table is not the user table
                    if (!CollectionUtils.isEmpty(joins)) {
                        //Determine whether there is a user table in the join table
                        for (Join join : joins) {
                            // Determine whether the user table exists in the join, if it does not exist, add it
                            FromItem rightItem = join.getRightItem();
                            if (rightItem instanceof Table) {
                                Table table = (Table) rightItem;
                                if (table.getName().equalsIgnoreCase(TABLE_USER)) {
                                    hasUserTable = true;
                                }
                            }
                        }
                    }
                } else {
                    hasUserTable = true;
                }
                if (!hasUserTable) {
                    //There is no user table, add associated user table
                    Table innerTable = new Table(TABLE_USER).withAlias(new Alias(dataScopeParam.getUserAlias(), false));
                    Join join = new Join();
                    join.withRightItem(innerTable);
                    EqualsTo equalsTo = new EqualsTo();
                    equalsTo.setLeftExpression(new StringValue(StrUtil.format("{}.{}", dataScopeParam.getMainTableAlias(), dataScopeParam.getUserField())));
                    equalsTo.setRightExpression(new Column(innerTable, dataScopeParam.getJoinField()));
                    join.withOnExpression(equalsTo);
                    realSelect.addJoins(join);
                }
                if (DATA_SCOPE_CUSTOM == dataRange) {
                    //Specify department
                    sql = StrUtil.format(" {}.{} IN ( SELECT `value` FROM {} WHERE role_id = {} ) ",
                            dataScopeParam.getUserAlias(), FIELD_ORG_ID, TABLE_ROLE_ORG, role.getId());
                } else {
                    //This department (and subordinate departments)
                    sql = StrUtil.format(" {}.{} IN ( SELECT id FROM {} WHERE id = {} or find_in_set( {} , ancestors ) ) ",
                            dataScopeParam.getUserAlias(), FIELD_ORG_ID, TABLE_ORG, currentUser.getOrgId(), currentUser.getOrgId());
                }

            } else if (DATA_SCOPE_SELF == dataRange) {
                //Only personal data permissions
                sql = StrUtil.format(" {}.{} = {} ", dataScopeParam.getMainTableAlias(), dataScopeParam.getUserField(), currentUser.getId());
            } else {
                sql = " 1 = 0";
            }
            Expression expression = CCJSqlParserUtil.parseCondExpression(sql);
            expressions.add(expression);
        }
        Expression dataExpression;
        if (expressions.size() > 1) {
            //Data permission is greater than 1, use or between
            OrExpression orExpression = new OrExpression(expressions.get(0), expressions.get(1));
            for (int i = 2; i < expressions.size(); i + + ) {
                orExpression = new OrExpression(orExpression, expressions.get(i));
            }
            // Use separate brackets for data permissions to prevent conflicts with other conditions
            dataExpression = new Parenthesis(orExpression);
        } else {
            dataExpression = expressions.get(0);
        }
        Expression where = realSelect.getWhere();
        if (where != null) {
            where = new AndExpression(where, dataExpression);
        } else {
            where = dataExpression;
        }
        realSelect.setWhere(where);
    }

    @Data
    @AllArgsConstructor
    static class DataScopeParam {
        /**
         * Main table alias associated with the user table
         */
        private String mainTableAlias;
        /**
         * Alias of user table
         */
        private String userAlias;
        /**
         * User field name
         */
        private String userField;
        /**
         * Associated user table fields, default user table ID
         */
        private String joinField;
        /**
         * Whether administrator
         */
        private boolean isAdmin;
        /**
         *Current logged in user
         */
        private SecurityUser currentUser;
    }
}

MybatisPlus plug-in configuration

@Configuration
public class MybatisPlusConfig {
    @Autowired
    private DataScopePermissionHandler dataScopePermissionHandler;

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // data permission
        DataScopePermissionInterceptor dataScopePermissionInterceptor = new DataScopePermissionInterceptor();
        dataScopePermissionInterceptor.setDataScopePermissionHandler(dataScopePermissionHandler);
        interceptor.addInnerInterceptor(dataScopePermissionInterceptor);
        return interceptor;
    }
}

Reference: Spring Cloud microservices in action – building an enterprise-level development framework (Twenty-eight): Extending the MybatisPlus plug-in DataPermissionInterceptor to implement data permission control
Mybatis-Plus implements data permission filtering through annotations

MyBatis Plus interceptor implements data permission control (full version)

The knowledge points of the article match the official knowledge files, and you can further learn related knowledge. Java skill treeNotesBasic syntax 138353 people are learning the system