在芋道自定义数据权限

在芋道自定义数据权限

一、前言

最近在研究芋道的自定义数据权限规则,他的文档上写的又相对简单,折腾了好久,这里记录一下。

芋道版本

二、查询文档

可到这里查看芋道的数据权限文档,因为要加入芋道的社区,不是所以人都可以看,这里把图截下来了

三、过程

1、确定数据权限字段

确定自己要做数据权限的字段,这里通过 ent_id (企业id)和 receive_ent_id (接单企业id),用来筛选后面运单表(Waybill)数据,可在数据权限范围枚举类中新增

完整代码如下:

java 复制代码
package cn.iocoder.yudao.module.system.enums.permission;

import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.Arrays;

/**
 * 数据范围枚举类
 *
 * 用于实现数据级别的权限
 *
 * @author 芋道源码
 */
@Getter
@AllArgsConstructor
public enum DataScopeEnum implements ArrayValuable<Integer> {

    ALL(1), // 全部数据权限

    DEPT_CUSTOM(2), // 指定部门数据权限
    DEPT_ONLY(3), // 部门数据权限
    DEPT_AND_CHILD(4), // 部门及以下数据权限

    SELF(5), // 仅本人数据权限

    ENT_CUSTOM(6), // 指定企业数据权限
    ENT_ONLY(7); // 企业数据权限

    /**
     * 范围
     */
    private final Integer scope;

    public static final Integer[] ARRAYS = Arrays.stream(values()).map(DataScopeEnum::getScope).toArray(Integer[]::new);

    @Override
    public Integer[] array() {
        return ARRAYS;
    }

}

2、仿写 PermissionService

然后在 PermissionService 这里仿照芋道的部门权限方法,写一个运单的数据权限方法

在写方法体之前先仿照芋道 DeptDataPermissionRespDTO 写一个数据权限 WaybillDataPermissionRespDTO

仿写 WaybillDataPermissionRespDTO 代码如下:

java 复制代码
package cn.iocoder.yudao.framework.common.biz.waybill.permission.dto;

import lombok.Data;

import java.util.HashSet;
import java.util.Set;

/**
 * 运单的数据权限 Response DTO
 *
 * @author: gan
 * @date: 2026-03-03 10:03
 */
@Data
public class WaybillDataPermissionRespDTO {

    /**
     * 是否可查看全部数据
     */
    private Boolean all;
    /**
     * 是否可查看自己的数据
     */
    private Boolean self;

    /**
     * 可查看的租户数组
     */
    private Set<Long> tenantIds;

    /**
     * 可查看的企业数组
     */
    private Set<Long> entIds;

    public WaybillDataPermissionRespDTO() {
        this.all = false;
        this.self = false;
        this.tenantIds = new HashSet<>();
        this.entIds = new HashSet<>();
    }
}

接口代码如下:

java 复制代码
package cn.iocoder.yudao.module.system.service.permission;

import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.framework.common.biz.waybill.permission.dto.WaybillDataPermissionRespDTO;

import java.util.Collection;
import java.util.Set;

import static java.util.Collections.singleton;

/**
 * 权限 Service 接口
 * <p>
 * 提供用户-角色、角色-菜单、角色-部门的关联权限处理
 *
 * @author 芋道源码
 */
public interface PermissionService {

    /**
     * 获得登陆用户的部门数据权限
     *
     * @param userId 用户编号
     * @return 部门数据权限
     */
    DeptDataPermissionRespDTO getDeptDataPermission(Long userId);

    /**
     * 获得登陆用户的运单数据权限
     *
     * @param userId 用户编号
     * @return 部门数据权限
     */
    WaybillDataPermissionRespDTO getWaybillDataPermission(Long userId);
}

接口实现如下:

java 复制代码
package cn.iocoder.yudao.module.system.service.permission;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.biz.waybill.permission.dto.WaybillDataPermissionRespDTO;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
import cn.iocoder.yudao.module.system.service.dept.DeptService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Suppliers;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.*;
import java.util.function.Supplier;

import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;

/**
 * 权限 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Slf4j
public class PermissionServiceImpl implements PermissionService {

    @Resource
    private RoleMenuMapper roleMenuMapper;
    @Resource
    private UserRoleMapper userRoleMapper;

    @Resource
    private RoleService roleService;
    @Resource
    private MenuService menuService;
    @Resource
    private DeptService deptService;
    @Resource
    private AdminUserService userService;

    @Override
    @DataPermission(enable = false) // 关闭数据权限,不然就会出现递归获取数据权限的问题
    public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
        // 获得用户的角色
        List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);

        // 如果角色为空,则只能查看自己
        DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO();
        if (CollUtil.isEmpty(roles)) {
            result.setSelf(true);
            return result;
        }

        // 获得用户的部门编号的缓存,通过 Guava 的 Suppliers 惰性求值,即有且仅有第一次发起 DB 的查询
        Supplier<Long> userDeptId = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());
        // 遍历每个角色,计算
        for (RoleDO role : roles) {
            // 为空时,跳过
            if (role.getDataScope() == null) {
                continue;
            }
            // 情况一,ALL
            if (Objects.equals(role.getDataScope(), DataScopeEnum.ALL.getScope())) {
                result.setAll(true);
                continue;
            }
            // 情况二,DEPT_CUSTOM
            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_CUSTOM.getScope())) {
                CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
                // 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。
                // 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉
                CollUtil.addAll(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况三,DEPT_ONLY
            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
                CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况四,DEPT_DEPT_AND_CHILD
            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
                CollUtil.addAll(result.getDeptIds(), deptService.getChildDeptIdListFromCache(userDeptId.get()));
                // 添加本身部门编号
                CollUtil.addAll(result.getDeptIds(), userDeptId.get());
                continue;
            }
            // 情况五,SELF
            if (Objects.equals(role.getDataScope(), DataScopeEnum.SELF.getScope())) {
                result.setSelf(true);
                continue;
            }
            // 未知情况,error log 即可
            log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, toJsonString(result));
        }
        return result;
    }

    /**
     * 获得自身的代理对象,解决 AOP 生效问题
     *
     * @return 自己
     */
    private PermissionServiceImpl getSelf() {
        return SpringUtil.getBean(getClass());
    }

    @Override
    @DataPermission(enable = false) // 关闭数据权限,不然就会出现递归获取数据权限的问题
    public WaybillDataPermissionRespDTO getWaybillDataPermission(Long userId) {
        // 获得用户的角色
        List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);

        // 如果角色为空,则只能查看自己
        WaybillDataPermissionRespDTO result = new WaybillDataPermissionRespDTO();
        if (CollUtil.isEmpty(roles)) {
            result.setSelf(true);
            return result;
        }

        // 获得用户的部门编号的缓存,通过 Guava 的 Suppliers 惰性求值,即有且仅有第一次发起 DB 的查询
        AdminUserDO adminUserDO = userService.getUser(userId);
        //Supplier<Long> userTenantId = Suppliers.memoize(() -> adminUserDO.getTenantId());  //租户信息可以不要了,继承了BaseDO会拼接
        Supplier<Long> userEntId = Suppliers.memoize(() -> adminUserDO.getEntId());
        // 遍历每个角色,计算
        for (RoleDO role : roles) {
            // 为空时,跳过
            if (role.getDataScope() == null) {
                continue;
            }
            // 情况一,ALL
            if (Objects.equals(role.getDataScope(), DataScopeEnum.ALL.getScope())) {
                result.setAll(true);
                continue;
            }
            // 情况二,ENT_CUSTOM
            if (Objects.equals(role.getDataScope(), DataScopeEnum.ENT_CUSTOM.getScope())) {
                CollUtil.addAll(result.getEntIds(), role.getDataScopeEntIds());
                // 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。
                // 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉
                CollUtil.addAll(result.getEntIds(), userEntId.get());
                continue;
            }
            // 情况三,ENT_ONLY
            if (Objects.equals(role.getDataScope(), DataScopeEnum.ENT_ONLY.getScope())) {
                CollectionUtils.addIfNotNull(result.getEntIds(), userEntId.get());
                continue;
            }
            // 情况四,SELF
            if (Objects.equals(role.getDataScope(), DataScopeEnum.SELF.getScope())) {
                result.setSelf(true);
                continue;
            }
            // 未知情况,error log 即可
            log.error("[getWaybillDataPermission][LoginUser({}) role({}) 无法处理]", userId, toJsonString(result));
        }
        return result;
    }
}

3、仿写 DeptDataPermissionRuleCustomizer 和 DeptDataPermissionRule

接着仿照芋道的部门表写一个 DataPermissionRuleCustomizer 和 DataPermissionRule ,这里是 Waybill 运单表,写成了 WaybillDataPermissionRuleCustomizer 和 WaybillDataPermissionRule

WaybillDataPermissionRuleCustomizer 代码如下:

java 复制代码
package cn.iocoder.yudao.framework.datapermission.core.rule.waybill;

import cn.iocoder.yudao.framework.datapermission.core.rule.dept.DeptDataPermissionRule;

/**
 * {@link DeptDataPermissionRule} 的自定义配置接口
 *
 * @author: gan
 * @date: 2026-03-06 09:19
 */
@FunctionalInterface
public interface WaybillDataPermissionRuleCustomizer {

    /**
     * 自定义该权限规则
     * 1. 调用 {@link WaybillDataPermissionRule#addEntColumn(Class)} (Class, Long)} 方法,配置基于 ent_id 的过滤规则
     * 2. 调用 {@link WaybillDataPermissionRule#addUserColumn(Class, String)} 方法,配置基于 user_id 的过滤规则
     *
     * @param rule 权限规则
     */
    void customize(WaybillDataPermissionRule rule);
}

WaybillDataPermissionRuleCustomizer 的实现类代码如下,其中 WaybillDO 是我的业务表,里面包含要数据权限需要的字段 ent_id 和 receive_ent_id

java 复制代码
package cn.iocoder.yudao.module.waybill.framework.datapermission.config;

import cn.iocoder.yudao.framework.datapermission.core.rule.waybill.WaybillDataPermissionRuleCustomizer;
import cn.iocoder.yudao.module.waybill.dal.dataobject.waybill.WaybillDO;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * waybill 模块的数据权限 Configuration
 *
 * @author: gan
 * @date: 2026-03-06 09:25
 */
@Configuration(value = "waybillDataPermissionConfiguration", proxyBeanMethods = false)
public class DataPermissionConfiguration {

    @Bean
    public WaybillDataPermissionRuleCustomizer sysWaybillDataPermissionRuleCustomizer() {
        return rule -> {
            // waybill
            rule.addEntColumn(WaybillDO.class);
        };
    }
}

WaybillDataPermissionRule 代码如下:

java 复制代码
package cn.iocoder.yudao.framework.datapermission.core.rule.waybill;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
import cn.iocoder.yudao.framework.common.biz.waybill.permission.dto.WaybillDataPermissionRespDTO;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.NullValue;
import net.sf.jsqlparser.expression.operators.conditional.OrExpression;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.expression.operators.relational.InExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * 基于运单的DataPermissionRule
 *
 * @author: gan
 * @date: 2026-03-03 09:16
 */
@AllArgsConstructor
@Slf4j
@Component
public class WaybillDataPermissionRule implements DataPermissionRule {

    /**
     * LoginUser 的 Context 缓存 Key
     */
    protected static final String CONTEXT_KEY = WaybillDataPermissionRule.class.getSimpleName();

    /**
     * 基于运单的企业id字段配置
     * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
     *
     * key:表名
     * value:字段名
     */
    private final Map<String, Set<String>> entColumns = new HashMap<>();

    /**
     * 基于运单的租户id字段配置
     * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
     *
     * key:表名
     * value:字段名
     */
    private final Map<String, String> tenantColumns = new HashMap<>();

    /**
     * 基于用户的用户id字段配置
     * 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。
     *
     * key:表名
     * value:字段名
     */
    private final Map<String, String> userColumns = new HashMap<>();

    static final Expression EXPRESSION_NULL = new NullValue();

    private final PermissionCommonApi permissionApi;

    /**
     * 所有表名,是 {@link #entColumns} 、{@link #tenantColumns} 和 {@link #userColumns} 的合集
     */
    private final Set<String> TABLE_NAMES = new HashSet<>();

    @Override
    public Set<String> getTableNames() {
        return TABLE_NAMES;
    }

    @Override
    public Expression getExpression(String tableName, Alias tableAlias) {
        // 只有有登陆用户的情况下,才进行数据权限的处理
        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();
        if (loginUser == null) {
            return null;
        }

        // 获得数据权限
        WaybillDataPermissionRespDTO waybillDataPermission = loginUser.getContext(CONTEXT_KEY, WaybillDataPermissionRespDTO.class);
        // 从上下文中拿不到,则调用逻辑进行获取
        if (waybillDataPermission == null) {
            waybillDataPermission = permissionApi.getWaybillDataPermission(loginUser.getId());
            if (waybillDataPermission == null) {
                log.error("[getExpression][LoginUser({}) 获取数据权限为 null]", JsonUtils.toJsonString(loginUser));
                throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 未返回数据权限",
                        loginUser.getId(), tableName, tableAlias.getName()));
            }
            // 添加到上下文中,避免重复计算
            loginUser.setContext(CONTEXT_KEY, waybillDataPermission);
        }

        // 情况一,如果是 ALL 可查看全部,则无需拼接条件
        if (waybillDataPermission.getAll()) {
            return null;
        }

        // 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限
        if (CollUtil.isEmpty(waybillDataPermission.getEntIds())
                && Boolean.FALSE.equals(waybillDataPermission.getSelf())) {
            return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空
        }

        // 情况三,拼接 企业 和 User 的条件,最后组合
        Expression entExpression = buildEntIdExpression(tableName, tableAlias, waybillDataPermission.getEntIds());
        //Expression userExpression = buildUserExpression(tableName, tableAlias, waybillDataPermission.getSelf(), loginUser.getId());
        if (entExpression == null) {
            // TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据
            log.warn("[getExpression][LoginUser({}) Table({}/{}) WaybillDataPermissionRule({}) 构建的条件为空]",
                    JsonUtils.toJsonString(loginUser), tableName, tableAlias, JsonUtils.toJsonString(entExpression));
//            throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 构建的条件为空",
//                    loginUser.getId(), tableName, tableAlias.getName()));
            return EXPRESSION_NULL;
        }

        // 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (ent_id IN ? OR user_id = ?)
        List<Expression> condList = Arrays.asList(entExpression);
        return new ParenthesedExpressionList(buildMultiOrExpression(condList));
    }

    /**
     * 构建企业的 in 规则
     * @param tableName
     * @param tableAlias
     * @param entIds
     * @return
     */
    private Expression buildEntIdExpression(String tableName, Alias tableAlias, Set<Long> entIds) {
        // 如果不存在配置,则无需作为条件
        Set<String> columnNameSet = entColumns.get(tableName);
        if (CollUtil.isEmpty(columnNameSet)) {
            return null;
        }
        // 如果为空,则无条件
        if (CollUtil.isEmpty(entIds)) {
            return null;
        }

        // 拼接条件
        List<Expression> expressions = new ArrayList<>(columnNameSet.size());

        // Parenthesis 的目的,是提供 (1,2,3) 的 () 左右括号
        ParenthesedExpressionList entParenthesedExpressionList = new ParenthesedExpressionList(new ExpressionList<LongValue>(CollectionUtils.convertList(entIds, LongValue::new)));

        for (String columnName : columnNameSet) {
            Expression item = new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), entParenthesedExpressionList);

            expressions.add(item);
        }

        return buildMultiOrExpression(expressions);
    }

    /**
     * 构建用户的
     * @param tableName
     * @param tableAlias
     * @param userId
     * @return
     */
    private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) {
        // 如果不查看自己,则无需作为条件
        if (Boolean.FALSE.equals(self)) {
            return null;
        }
        String columnName = userColumns.get(tableName);
        if (StrUtil.isEmpty(columnName)) {
            return null;
        }
        // 拼接条件
        return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId));
    }

    /**
     * 通用工具方法:拼接任意数量的OR条件(支持2个及以上)
     * @param expressions 条件列表(Expression类型)
     * @return 拼接后的OR表达式
     */
    public static Expression buildMultiOrExpression(List<Expression> expressions) {
        // 边界校验:空列表/单条件直接返回
        if (expressions == null || expressions.isEmpty()) {
            return null;
        }
        if (expressions.size() == 1) {
            return expressions.get(0);
        }

        // 从第一个条件开始,逐个拼接OR
        Expression result = expressions.get(0);
        for (int i = 1; i < expressions.size(); i++) {
            result = new OrExpression(result, expressions.get(i));
        }
        return result;
    }

    // ==================== 添加配置 ====================

    public void addEntColumn(Class<? extends BaseDO> entityClass) {
        addEntColumn(entityClass, "ent_id");
        addEntColumn(entityClass, "receive_ent_id");
    }

    public void addEntColumn(Class<? extends BaseDO> entityClass, String columnName) {
        String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
        addEntColumn(tableName, columnName);
    }

    public void addEntColumn(String tableName, String columnName) {
        Set<String> columnNameSet = entColumns.get(tableName);
        if (CollUtil.isEmpty(columnNameSet)) {
            columnNameSet = new HashSet<>();
        }
        columnNameSet.add(columnName);
        entColumns.put(tableName, columnNameSet);
        TABLE_NAMES.add(tableName);
    }

    public void addTenantColumn(Class<? extends BaseDO> entityClass) {
        addTenantColumn(entityClass, "tenant_id");
        addTenantColumn(entityClass, "receive_tenant_id");
    }

    public void addTenantColumn(Class<? extends BaseDO> entityClass, String columnName) {
        String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
        addTenantColumn(tableName, columnName);
    }

    public void addTenantColumn(String tableName, String columnName) {
        tenantColumns.put(tableName, columnName);
        TABLE_NAMES.add(tableName);
    }

    public void addUserColumn(Class<? extends BaseDO> entityClass) {
        addUserColumn(entityClass, "user_id");
    }

    public void addUserColumn(Class<? extends BaseDO> entityClass, String columnName) {
        String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();
        addUserColumn(tableName, columnName);
    }

    public void addUserColumn(String tableName, String columnName) {
        userColumns.put(tableName, columnName);
        TABLE_NAMES.add(tableName);
    }
}

4、仿写 YudaoDeptDataPermissionAutoConfiguration

仿照芋道的 YudaoDeptDataPermissionAutoConfiguration 写一个 YudaoWaybillDataPermissionAutoConfiguration

YudaoWaybillDataPermissionAutoConfiguration 代码如下

java 复制代码
package cn.iocoder.yudao.framework.datapermission.config;

import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi;
import cn.iocoder.yudao.framework.datapermission.core.rule.waybill.WaybillDataPermissionRule;
import cn.iocoder.yudao.framework.datapermission.core.rule.waybill.WaybillDataPermissionRuleCustomizer;
import cn.iocoder.yudao.framework.security.core.LoginUser;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;

import java.util.List;

/**
 * 基于运单的数据权限 AutoConfiguration
 *
 * @author: gan
 * @date: 2026-03-06 09:42
 */
@AutoConfiguration
@ConditionalOnClass(LoginUser.class)
@ConditionalOnBean(value = {PermissionCommonApi.class, WaybillDataPermissionRuleCustomizer.class})
public class YudaoWaybillDataPermissionAutoConfiguration {

    @Bean
    public WaybillDataPermissionRule waybillDataPermissionRule(PermissionCommonApi permissionApi,
                                                            List<WaybillDataPermissionRuleCustomizer> customizers) {
        // 创建 WaybillDataPermissionRule 对象
        WaybillDataPermissionRule rule = new WaybillDataPermissionRule(permissionApi);
        // 补全表配置
        customizers.forEach(customizer -> customizer.customize(rule));
        return rule;
    }
}

5、增加一下引用配置

在 YudaoDeptDataPermissionAutoConfiguration 配置下面增加 4 中仿写的 YudaoDeptDataPermissionAutoConfiguration 完整路径

6、打开日志文件打印

这个看你的 mapper 是否在芋道原有的包下,如果在原有的包下则不需要修改,如果不在则需要仿照 system 在 yaml 文件中添加对应包名的的日志输出

7、配置数据权限

在要使用自定义的数据权限规则的方法中加上 DataPermission 注解

最后需要注意的是,自定义的数据权限规则要使用 mybatis plus 生成的 selectPage 方法才会有效,手写的 xml 是不会拼接的

8、测试

启动项目,调用对应接口

可以看到已经拼接了对应字段

相关推荐
Trouvaille ~2 小时前
【MySQL篇】表的约束:保证数据完整性
数据库·mysql·约束·数据完整性·实体完整性·域完整性·参照完整性
哆啦阿梦2 小时前
Java AI 应用工程师 - 完整技能清单
java·开发语言·人工智能
rchmin2 小时前
阿里Tair分布式锁与Redis分布式锁的实现区别
数据库·redis·分布式
VT LI2 小时前
Lua 源码执行流程全解析:词法分析、语法分析、字节码生成、虚拟机执行与垃圾回收
java·开发语言·lua
凤山老林4 小时前
04-Java JDK, JRE和JVM
java·开发语言·jvm
camellias_10 小时前
【无标题】
java·tomcat
咸鱼2.010 小时前
【java入门到放弃】需要背诵
java·开发语言
椰猫子10 小时前
Java:异常(exception)
java·开发语言
等....10 小时前
Minio使用
数据库