在芋道自定义数据权限

在芋道自定义数据权限

一、前言

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

芋道版本

二、查询文档

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

三、过程

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、测试

启动项目,调用对应接口

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

相关推荐
hh真是个慢性子1 分钟前
GaussDB Inside 2.23.01.280 集中式一主一备安装
数据库·database·gaussdb·国产数据库·高斯
沉静的小伙5 分钟前
Spring事务
java·数据库·spring
大侠锅锅9 分钟前
第 3 篇:节点自动化纳管——从裸机到可管理的 7 步安装流水线
数据库·自动化·边缘计算·iot
apihz13 分钟前
随机驾考题目(C 照科一 / 科四 2000+ 题)免费API调用教程
android·java·c语言·开发语言·网络协议·tcp/ip
笃行35019 分钟前
踩坑实战 | MySQL/PostgreSQL 迁移必踩:LEFT JOIN 莫名丢数据,KES 外连接消除避坑指南
数据库
摇滚侠24 分钟前
Redis7 零基础到进阶 Linux 离线安装 Redis 09-10
数据库·redis·缓存
AIGS00128 分钟前
企业AI落地:Agent OS 治理框架实践
java·大数据·人工智能·机器学习·人工智能ai大模型应用
赵渝强老师1 小时前
【赵渝强老师】达梦数据库的回滚数据
数据库·达梦数据库·国产数据库
从此以后自律1 小时前
SQL 语句从基础到高级
数据库·oracle
IvorySQL1 小时前
PG 日报|多款 PostgreSQL 工具新版本集中发布
数据库·postgresql