java每日精进 5.07【框架之数据权限】
数据权限,实现指定用户可以操作指定范围的数据。
数据权限不支持指定用户只能查看数据的某些字段;
权限可以分成三类:功能权限、数据权限、字段权限。
目前可以使用数据脱敏实现一定程度的字段权限控制;
1.数据权限实现步骤
1.1插件原理
DataPermissionInterceptor 的工作原理与租户插件类似,它会在 SQL 执行前拦截 SQL 语句,并根据用户权限动态添加权限相关的 SQL 片段。这样,只有用户有权限访问的数据才会被查询出来。
数据权限插件 | MyBatis-Plus (baomidou.com)
1.2SQL片段组装的核心逻辑代码
JSQLParser 是一个开源的 SQL 解析库,可方便地解析和修改 SQL 语句。它是插件实现权限逻辑的关键工具,MyBatis-Plus 的数据权限依托于 JSQLParser 的解析能力。
以下示例展示如何使用 JSQLParser 来修改 SQL
:
// 示例 SQL
String sql = "SELECT * FROM user WHERE status = 'active'";
Expression expression;try {expression = CCJSqlParserUtil.parseCondExpression("status = 'inactive'");PlainSelect select = (PlainSelect) ((Select) CCJSqlParserUtil.parse(sql)).getSelectBody();select.setWhere(expression);System.out.println(select); // 输出:SELECT * FROM user WHERE status = 'inactive'
} catch (JSQLParserException e) {e.printStackTrace();
}
自定义 MultiDataPermissionHandler
,实现特定业务逻辑:
/*** 基于 {@link DataPermissionRule} 的数据权限处理器** 它的底层,是基于 MyBatis Plus 的 <a href="https://baomidou.com/plugins/data-permission/">数据权限插件</a>* 核心原理:它会在 SQL 执行前拦截 SQL 语句,并根据用户权限动态添加权限相关的 SQL 片段。这样,只有用户有权限访问的数据才会被查询出来*/
@RequiredArgsConstructor
public class DataPermissionRuleHandler implements MultiDataPermissionHandler {//ruleFactory 是一个“规则工厂”,它是一个工具,专门用来提供“权限规则”。private final DataPermissionRuleFactory ruleFactory;//Table table:表示你要查的表;//Expression where:表示你原来的查询条件(比如 WHERE id = 1)//String mappedStatementId:一个字符串,表示你正在调用的查询方法@Overridepublic Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {// 获得 Mapper 对应的数据权限的规则List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(mappedStatementId);if (CollUtil.isEmpty(rules)) {return null;}// 生成条件,allExpression,用来存放最终的权限条件Expression allExpression = null;for (DataPermissionRule rule : rules) {// 判断表名是否匹配,规则是否管理当前的表String tableName = MyBatisUtils.getTableName(table);if (!rule.getTableNames().contains(tableName)) {continue;}// 单条规则的条件,对于这个表(tableName),它的限制条件是什么Expression oneExpress = rule.getExpression(tableName, table.getAlias());if (oneExpress == null) {continue;}// 拼接到 allExpression 中allExpression = allExpression == null ? oneExpress: new AndExpression(allExpression, oneExpress);}return allExpression;}
}
整体流程(用图书馆的例子)
- 你想借书(查数据库),比如查 SELECT * FROM user。
- 图书管理员(DataPermissionRuleHandler)先拦住你,问:“你用的是哪个借书方法?”(mappedStatementId)。
- 管理员去问规则工厂(ruleFactory):“这个借书方法有哪些权限规则?”(rules)。
- 如果没有规则,管理员说:“随便借!”(返回 null)。
- 如果有规则(比如“只能借 dept_id = 1 的书”),管理员检查你借的书是不是这个部门的(tableName)。
- 如果是,管理员生成一个限制条件(u.dept_id = 1),并把所有条件用 AND 连起来。
- 最后,管理员把限制条件加到你的借书请求里(SELECT * FROM user WHERE dept_id = 1),确保你只能借到有权限的书。
2.整体代码实现流程
-
2.1Spring 应用启动:
- Spring 容器启动时,处理 @AutoConfiguration 和 @Configuration 类。
- YudaoDataPermissionAutoConfiguration 创建核心 Bean:
- DataPermissionRuleFactory:管理所有 DataPermissionRule 实例。
- DataPermissionRuleHandler:通过 DataPermissionInterceptor 集成到 MyBatis Plus 拦截器链。
- DataPermissionAnnotationAdvisor:支持基于注解的权限控制。
- YudaoDeptDataPermissionAutoConfiguration(在满足条件时)创建 DeptDataPermissionRule Bean,并通过 DeptDataPermissionRuleCustomizer 配置部门相关规则。
- 具体每个模块的规则就像DataPermissionConfiguration 定义 DeptDataPermissionRuleCustomizer,为 DeptDataPermissionRule 配置特定表和字段(如 AdminUserDO、DeptDO)。
-
/*** 数据权限的自动配置类* DataPermissionRuleFactory:管理所有 DataPermissionRule 实例。* DataPermissionRuleHandler:通过 DataPermissionInterceptor 集成到 MyBatis Plus 拦截器链* DataPermissionAnnotationAdvisor:支持基于注解的权限控制*/ @AutoConfiguration public class YudaoDataPermissionAutoConfiguration {/*** 创建数据权限规则的工厂,管理所有数据权限规则的集合* @param rules* @return*/@Beanpublic DataPermissionRuleFactory dataPermissionRuleFactory(List<DataPermissionRule> rules) {return new DataPermissionRuleFactoryImpl(rules);}/*** 处理数据权限规则的核心处理器* @param interceptor* @param ruleFactory* @return*/@Beanpublic DataPermissionRuleHandler dataPermissionRuleHandler(MybatisPlusInterceptor interceptor,DataPermissionRuleFactory ruleFactory) {// 创建 DataPermissionInterceptor 拦截器DataPermissionRuleHandler handler = new DataPermissionRuleHandler(ruleFactory);DataPermissionInterceptor inner = new DataPermissionInterceptor(handler);// 添加到 interceptor 中// 需要加在首个,主要是为了在分页插件前面。这个是 MyBatis Plus 的规定MyBatisUtils.addInterceptor(interceptor, inner, 0);return handler;}/*** 提供基于注解的数据权限控制* @return*/@Beanpublic DataPermissionAnnotationAdvisor dataPermissionAnnotationAdvisor() {return new DataPermissionAnnotationAdvisor();}}
/*** 基于部门的数据权限 AutoConfiguration* YudaoDeptDataPermissionAutoConfiguration(在满足条件时)创建 DeptDataPermissionRule Bean,* 并通过 DeptDataPermissionRuleCustomizer 配置部门相关规则。*/ @AutoConfiguration @ConditionalOnClass(LoginUser.class) @ConditionalOnBean(value = {PermissionApi.class, DeptDataPermissionRuleCustomizer.class}) public class YudaoDeptDataPermissionAutoConfiguration {/*** 创建一个 DeptDataPermissionRule 对象,并将 permissionApi 作为参数传递给其构造函数。* 遍历 customizers 列表,调用每个 DeptDataPermissionRuleCustomizer 的 customize 方法* 对 rule 对象进行自定义配置。* 返回配置好的 DeptDataPermissionRule 对象。* @param permissionApi* @param customizers* @return*/@Beanpublic DeptDataPermissionRule deptDataPermissionRule(PermissionApi permissionApi,List<DeptDataPermissionRuleCustomizer> customizers) {// 创建 DeptDataPermissionRule 对象DeptDataPermissionRule rule = new DeptDataPermissionRule(permissionApi);// 补全表配置customizers.forEach(customizer -> customizer.customize(rule));return rule;}}
/*** system 模块的数据权限 Configuration*/ @Configuration(proxyBeanMethods = false) public class DataPermissionConfiguration {@Beanpublic DeptDataPermissionRuleCustomizer sysDeptDataPermissionRuleCustomizer() {return rule -> {// deptrule.addDeptColumn(AdminUserDO.class);rule.addDeptColumn(DeptDO.class, "id");// userrule.addUserColumn(AdminUserDO.class, "id");};}}
-
2.2发起 MyBatis Plus 查询:
- 通过 MyBatis Mapper 方法(如 selectList)发起数据库查询。
- 生成查询的 mappedStatementId,例如 com.example.UserMapper.selectList。
-
2.3MyBatis Plus 拦截器链处理:
- MyBatis Plus 通过拦截器链处理查询。
- DataPermissionInterceptor(在 YudaoDataPermissionAutoConfiguration 中添加到 MybatisPlusInterceptor)在链的早期(位置 0,优先于分页插件)被调用。
- DataPermissionInterceptor 委托给 DataPermissionRuleHandler 的 getSqlSegment 方法。
-
/*** 基于 {@link DataPermissionRule} 的数据权限处理器** 它的底层,是基于 MyBatis Plus 的 <a href="https://baomidou.com/plugins/data-permission/">数据权限插件</a>* 核心原理:它会在 SQL 执行前拦截 SQL 语句,并根据用户权限动态添加权限相关的 SQL 片段。这样,只有用户有权限访问的数据才会被查询出来*/ @RequiredArgsConstructor public class DataPermissionRuleHandler implements MultiDataPermissionHandler {//ruleFactory 是一个“规则工厂”,它是一个工具,专门用来提供“权限规则”。private final DataPermissionRuleFactory ruleFactory;//Table table:表示你要查的表;//Expression where:表示你原来的查询条件(比如 WHERE id = 1)//String mappedStatementId:一个字符串,表示你正在调用的查询方法@Overridepublic Expression getSqlSegment(Table table, Expression where, String mappedStatementId) {// 获得 Mapper 对应的数据权限的规则List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(mappedStatementId);if (CollUtil.isEmpty(rules)) {return null;}// 生成条件,allExpression,用来存放最终的权限条件Expression allExpression = null;for (DataPermissionRule rule : rules) {// 判断表名是否匹配,规则是否管理当前的表String tableName = MyBatisUtils.getTableName(table);if (!rule.getTableNames().contains(tableName)) {continue;}// 单条规则的条件,对于这个表(tableName),它的限制条件是什么Expression oneExpress = rule.getExpression(tableName, table.getAlias());if (oneExpress == null) {continue;}// 拼接到 allExpression 中allExpression = allExpression == null ? oneExpress: new AndExpression(allExpression, oneExpress);}return allExpression;} }
-
2.4DataPermissionRuleHandler 处理:
- DataPermissionRuleHandler 从 DataPermissionRuleFactory 获取适用于当前查询的 DataPermissionRule 列表(通过 getDataPermissionRule(mappedStatementId))。
- DataPermissionRuleFactoryImpl 根据 DataPermissionContextHolder 过滤规则:
- 无上下文:返回所有规则。
- 有上下文:根据 enable()、includeRules 或 excludeRules 过滤。
- 对每个适用规则(如 DeptDataPermissionRule),检查是否适用于查询的表(tableName)。
- 如果适用,调用规则的 getExpression 方法生成 SQL 条件(Expression)。
-
/*** 默认的 DataPermissionRuleFactoryImpl 实现类* 支持通过 {@link DataPermissionContextHolder} 过滤数据权限*/ @RequiredArgsConstructor public class DataPermissionRuleFactoryImpl implements DataPermissionRuleFactory {/*** 数据权限规则数组*/private final List<DataPermissionRule> rules;@Overridepublic List<DataPermissionRule> getDataPermissionRules() {return rules;}@Override // mappedStatementId 参数,暂时没有用。以后,可以基于 mappedStatementId + DataPermission 进行缓存public List<DataPermissionRule> getDataPermissionRule(String mappedStatementId) {// 1. 无数据权限if (CollUtil.isEmpty(rules)) {return Collections.emptyList();}// 2. 未配置,则默认开启DataPermission dataPermission = DataPermissionContextHolder.get();if (dataPermission == null) {return rules;}// 3. 已配置,但禁用if (!dataPermission.enable()) {return Collections.emptyList();}// 4. 已配置,只选择部分规则if (ArrayUtil.isNotEmpty(dataPermission.includeRules())) {return rules.stream().filter(rule -> ArrayUtil.contains(dataPermission.includeRules(), rule.getClass())).collect(Collectors.toList()); // 一般规则不会太多,所以不采用 HashSet 查询}// 5. 已配置,只排除部分规则if (ArrayUtil.isNotEmpty(dataPermission.excludeRules())) {return rules.stream().filter(rule -> !ArrayUtil.contains(dataPermission.excludeRules(), rule.getClass())).collect(Collectors.toList()); // 一般规则不会太多,所以不采用 HashSet 查询}// 6. 已配置,全部规则return rules;}}
-
2.5DeptDataPermissionRule 处理:
- DeptDataPermissionRule 检查是否存在登录用户(LoginUser)并验证用户是否为管理员(UserTypeEnum.ADMIN)。
- 从用户上下文或通过 PermissionApi.getDeptDataPermission 获取部门数据权限(DeptDataPermissionRespDTO)。
- 根据权限设置(all、deptIds、self)构建 SQL 条件:
- buildDeptExpression:若 deptIds 不为空且表配置了 dept_id 字段,生成 WHERE dept_id IN (...)。
- buildUserExpression:若 self 为 true 且表配置了 user_id 字段,生成 WHERE user_id = ?。
- 使用 OR 组合条件或返回单一条件。
- 将生成的 Expression 转换为 SQL 片段(如 dept_id IN (1, 2) OR user_id = 100)。
-
/*** 基于部门的 {@link DataPermissionRule} 数据权限规则实现** 注意,使用 DeptDataPermissionRule 时,需要保证表中有 dept_id 部门编号的字段,可自定义。** 实际业务场景下,会存在一个经典的问题?当用户修改部门时,冗余的 dept_id 是否需要修改?* 1. 一般情况下,dept_id 不进行修改,则会导致用户看不到之前的数据。【moyun-server 采用该方案】* 2. 部分情况下,希望该用户还是能看到之前的数据,则有两种方式解决:【需要你改造该 DeptDataPermissionRule 的实现代码】* 1)编写洗数据的脚本,将 dept_id 修改成新部门的编号;【建议】* 最终过滤条件是 WHERE dept_id = ?* 2)洗数据的话,可能涉及的数据量较大,也可以采用 user_id 进行过滤的方式,此时需要获取到 dept_id 对应的所有 user_id 用户编号;* 最终过滤条件是 WHERE user_id IN (?, ?, ? ...)* 3)想要保证原 dept_id 和 user_id 都可以看的到,此时使用 dept_id 和 user_id 一起过滤;* 最终过滤条件是 WHERE dept_id = ? OR user_id IN (?, ?, ? ...)*/ @AllArgsConstructor @Slf4j public class DeptDataPermissionRule implements DataPermissionRule {/*** LoginUser 的 Context 缓存 Key*/protected static final String CONTEXT_KEY = DeptDataPermissionRule.class.getSimpleName();private static final String DEPT_COLUMN_NAME = "dept_id";private static final String USER_COLUMN_NAME = "user_id";static final Expression EXPRESSION_NULL = new NullValue();private final PermissionApi permissionApi;/*** 基于部门的表字段配置* 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义** key:表名* value:字段名*/private final Map<String, String> deptColumns = new HashMap<>();/*** 基于用户的表字段配置* 一般情况下,每个表的部门编号字段是 dept_id,通过该配置自定义。** key:表名* value:字段名*/private final Map<String, String> userColumns = new HashMap<>();/*** 所有表名,是 {@link #deptColumns} 和 {@link #userColumns} 的合集*/private final Set<String> TABLE_NAMES = new HashSet<>();@Overridepublic Set<String> getTableNames() {return TABLE_NAMES;}@Overridepublic Expression getExpression(String tableName, Alias tableAlias) {// 只有有登陆用户的情况下,才进行数据权限的处理LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();if (loginUser == null) {return null;}// 只有管理员类型的用户,才进行数据权限的处理if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) {return null;}// 获得数据权限DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);// 从上下文中拿不到,则调用逻辑进行获取if (deptDataPermission == null) {deptDataPermission = permissionApi.getDeptDataPermission(loginUser.getId());if (deptDataPermission == 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, deptDataPermission);}// 情况一,如果是 ALL 可查看全部,则无需拼接条件if (deptDataPermission.getAll()) {return null;}// 情况二,即不能查看部门,又不能查看自己,则说明 100% 无权限if (CollUtil.isEmpty(deptDataPermission.getDeptIds())&& Boolean.FALSE.equals(deptDataPermission.getSelf())) {return new EqualsTo(null, null); // WHERE null = null,可以保证返回的数据为空}// 情况三,拼接 Dept 和 User 的条件,最后组合Expression deptExpression = buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());if (deptExpression == null && userExpression == null) {// TODO 芋艿:获得不到条件的时候,暂时不抛出异常,而是不返回数据log.warn("[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]",JsonUtils.toJsonString(loginUser), tableName, tableAlias, JsonUtils.toJsonString(deptDataPermission)); // throw new NullPointerException(String.format("LoginUser(%d) Table(%s/%s) 构建的条件为空", // loginUser.getId(), tableName, tableAlias.getName()));return EXPRESSION_NULL;}if (deptExpression == null) {return userExpression;}if (userExpression == null) {return deptExpression;}// 目前,如果有指定部门 + 可查看自己,采用 OR 条件。即,WHERE (dept_id IN ? OR user_id = ?)return new ParenthesedExpressionList(new OrExpression(deptExpression, userExpression));}private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {// 如果不存在配置,则无需作为条件String columnName = deptColumns.get(tableName);if (StrUtil.isEmpty(columnName)) {return null;}// 如果为空,则无条件if (CollUtil.isEmpty(deptIds)) {return null;}// 拼接条件return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),// Parenthesis 的目的,是提供 (1,2,3) 的 () 左右括号new ParenthesedExpressionList(new ExpressionList<LongValue>(CollectionUtils.convertList(deptIds, LongValue::new))));}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));}// ==================== 添加配置 ====================public void addDeptColumn(Class<? extends BaseDO> entityClass) {addDeptColumn(entityClass, DEPT_COLUMN_NAME);}public void addDeptColumn(Class<? extends BaseDO> entityClass, String columnName) {String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();addDeptColumn(tableName, columnName);}public void addDeptColumn(String tableName, String columnName) {deptColumns.put(tableName, columnName);TABLE_NAMES.add(tableName);}public void addUserColumn(Class<? extends BaseDO> entityClass) {addUserColumn(entityClass, USER_COLUMN_NAME);}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);}}
-
2.6SQL 修改与执行:
- DataPermissionInterceptor 将 SQL 片段追加到原始查询的 WHERE 子句。
- MyBatis Plus 继续通过其他拦截器(如分页)处理修改后的 SQL。
- 最终 SQL 执行,数据库返回仅限于用户有权限的数据。
-
2.7示例场景
假设用户(ID: 100,管理员类型)查询用户列表(AdminUserDO),配置如下:查询通过 UserMapper.selectList(new QueryWrapper<>()) 执行。用户权限:deptIds = [1, 2],self = true。AdminUserDO 在 DataPermissionConfiguration 中配置了 dept_id 和 user_id 字段。
分步执行:1. 查询发起:
Mapper 方法:com.example.UserMapper.selectList。
原始 SQL:SELECT * FROM admin_user。
2. 拦截器调用:
DataPermissionInterceptor 拦截查询,调用 DataPermissionRuleHandler.getSqlSegment。
3.规则获取:
DataPermissionRuleHandler 调用 DataPermissionRuleFactoryImpl.getDataPermissionRule("com.example.UserMapper.selectList")。
返回 [DeptDataPermissionRule](假设无上下文过滤)。
4. 表匹配:
表名:admin_user。
DeptDataPermissionRule.getTableNames() 包含 admin_user,规则适用。
5. 条件生成:
调用 DeptDataPermissionRule.getExpression("admin_user", alias)。
用户已登录(ID: 100,管理员)。
获取 DeptDataPermissionRespDTO:{ all: false, deptIds: [1, 2], self: true }。
buildDeptExpression:deptColumns 映射 admin_user 到 dept_id,生成 dept_id IN (1, 2)。
buildUserExpression:userColumns 映射 admin_user 到 user_id,self = true,生成 user_id = 100。
组合:(dept_id IN (1, 2) OR user_id = 100)。
6. SQL 修改:
修改后 SQL:SELECT * FROM admin_user WHERE (dept_id IN (1, 2) OR user_id = 100)。
7. 执行:
数据库执行修改后的 SQL,仅返回部门 1 或 2 的用户,或 user_id = 100 的用户。
每个类功能详解
以下是每个类的功能描述以及它们之间的联系。
3.解释类
3.1类详解
- YudaoDataPermissionAutoConfiguration:
- 目的:配置数据权限核心组件。
- 功能:
- 创建 DataPermissionRuleFactory,管理所有 DataPermissionRule。
- 创建 DataPermissionRuleHandler,通过 DataPermissionInterceptor 集成到 MyBatis Plus。
- 提供 DataPermissionAnnotationAdvisor,支持注解驱动的权限控制。
- 关键 Bean:
- dataPermissionRuleFactory:返回 DataPermissionRuleFactoryImpl。
- dataPermissionRuleHandler:配置 MyBatis Plus 拦截器链。
- dataPermissionAnnotationAdvisor:启用注解权限。
- YudaoDeptDataPermissionAutoConfiguration:
- 目的:配置基于部门的数据权限。
- 功能:
- 在 LoginUser、PermissionApi 和 DeptDataPermissionRuleCustomizer 存在时激活。
- 创建 DeptDataPermissionRule,通过 DeptDataPermissionRuleCustomizer 进行定制。
- 关键 Bean:
- deptDataPermissionRule:初始化并配置部门规则。
- DataPermissionRuleHandler:
- 目的:应用数据权限规则的核心处理器。
- 功能:
- 从 DataPermissionRuleFactory 获取适用规则。
- 遍历规则,检查表适用性,生成 SQL 条件。
- 使用 AND 组合多条规则的条件。
- 关键方法:
- getSqlSegment:为给定表和查询生成 SQL 片段。
- DataPermissionRuleFactoryImpl:
- 目的:管理和过滤数据权限规则。
- 功能:
- 存储 DataPermissionRule 列表。
- 根据 DataPermissionContextHolder 过滤规则(enable、includeRules、excludeRules)。
- 关键方法:
- getDataPermissionRules:返回所有规则。
- getDataPermissionRule:为特定查询过滤规则。
- DeptDataPermissionRule:
- 目的:实现基于部门的数据权限逻辑。
- 功能:
- 配置表的 dept_id 和 user_id 字段。
- 根据用户权限(deptIds、self)生成 SQL 条件。
- 支持动态字段映射(deptColumns、userColumns)。
- 关键方法:
- getTableNames:返回受控表。
- getExpression:为表构建 SQL 条件。
- buildDeptExpression / buildUserExpression:生成部门/用户条件。
- 配置方法(如 addDeptColumn、addUserColumn)。
- DeptDataPermissionRuleCustomizer:
- 目的:为 DeptDataPermissionRule 提供定制接口。
- 功能:
- 允许外部配置表-字段映射。
- 在 DataPermissionConfiguration 中实现,为特定表(如 AdminUserDO)设置规则。
- DataPermissionConfiguration:
- 目的:为系统模块提供特定配置。
- 功能:
- 定义 DeptDataPermissionRuleCustomizer,为 DeptDataPermissionRule 配置 AdminUserDO 和 DeptDO 的字段映射。
3.2类之间的关系
- YudaoDataPermissionAutoConfiguration 是入口,搭建核心框架,创建 DataPermissionRuleFactory、DataPermissionRuleHandler 和注解支持。
- YudaoDeptDataPermissionAutoConfiguration 扩展框架,添加部门特定规则,依赖 DataPermissionRuleFactory 注册 DeptDataPermissionRule。
- DataPermissionRuleHandler 是运行时处理器,连接 DataPermissionRuleFactory(提供规则)和 MyBatis Plus(通过 DataPermissionInterceptor)。
- DataPermissionRuleFactoryImpl 作为规则仓库,管理包括 DeptDataPermissionRule 在内的规则,支持动态过滤。
- DeptDataPermissionRule 是具体规则实现,由 DeptDataPermissionRuleCustomizer 配置,被 DataPermissionRuleHandler 使用。
- DeptDataPermissionRuleCustomizer(通过 DataPermissionConfiguration)为 DeptDataPermissionRule 提供表特定配置。
- PermissionApi 和 LoginUser 是外部依赖,提供用户权限数据和上下文。
3.3类形成分层架构:
- 配置层:YudaoDataPermissionAutoConfiguration、YudaoDeptDataPermissionAutoConfiguration、DataPermissionConfiguration。
- 核心逻辑层:DataPermissionRuleHandler、DataPermissionRuleFactoryImpl。
- 规则层:DeptDataPermissionRule。
- 定制层:DeptDataPermissionRuleCustomizer。
相关文章:
java每日精进 5.07【框架之数据权限】
数据权限,实现指定用户可以操作指定范围的数据。 数据权限不支持指定用户只能查看数据的某些字段; 权限可以分成三类:功能权限、数据权限、字段权限。 目前可以使用数据脱敏实现一定程度的字段权限控制; 1.数据权限实现步骤 1.1插…...
2:点云处理—3D相机开发
1.包含相机库目录 1.include D:\中科\Ainstec3DViewer\AinstecCamSDK\AinstecCamSDK_CPP\include 2.lib D:\中科\Ainstec3DViewer\AinstecCamSDK\AinstecCamSDK_CPP\lib\win 3.release D:\中科\Ainstec3DViewer\AinstecCamSDK\AinstecCamSDK_CPP\bin 4.示例程序 D:\中科\A…...
MySQL 中的 MVCC 是什么?
MySQL 中的 MVCC(Multi-Version Concurrency Control,多版本并发控制) 是一种用于实现高并发读写操作的机制,它通过维护数据的多个版本来解决读写冲突,从而在保证事务隔离性的同时,减少锁的使用,…...
JC/T 2187-2013 铝波纹芯复合铝板检测
铝波纹芯复合铝板是指以铝波纹板为芯材,双面粘接铝板的复合板材,如下图所示,铝波纹芯复合板具有轻质,环保,吸音,隔热,防火等性能,被广泛应用于机场,医院,体育…...
数字乡村综合管理与服务平台软件需求规格说明文档
数字乡村综合管理与服务平台 软件需求规格文档 文件版本:1.3 版本 文件状态:修改完成 文件作者:7组全体成员 完成日期:2025年5月7日 文章目录 a. 引言a.1 目的a.2 文档约定a.3 预期读者与涉众分析a.4 产品的范围a.5 参考文献…...
Linux常用命令33——sudo授权普通用户执行管理员命令
在使用Linux或macOS日常开发中,熟悉一些基本的命令有助于提高工作效率,sudo命令来自英文词组super user do的缩写,中文译为“超级用户才能干的事”,其功能是授权普通用户执行管理员命令。使用su命令变更用户身份虽然好用ÿ…...
Redis--哈希类型
目录 一、Hash 哈希 1.2 常用命令 1.2.1 HSET 1.2.2 HGET 1.2.3 HEXISTS 1.2.4 HDEL 1.2.5 HKEYS 1.2.6 HVALS 1.2.7 HGETALL 1.2.8 HMGET 1.2.9 HLEN 1.2.10 HSETNX 1.2.11 HINCRBY 1.2.12 HINCRBYFLOAT 1.3 内部编码 一、Hash 哈希 几乎所有的主流编程语言都提…...
数据结构——排序(万字解说)初阶数据结构完
目录 1.排序 2.实现常见的排序算法 2.1 直接插入排序 编辑 2.2 希尔排序 2.3 直接选择排序 2.4 堆排序 2.5 冒泡排序 2.6 快速排序 2.6.1 递归版本 2.6.1.1 hoare版本 2.6.1.2 挖坑法 2.6.1.3 lomuto前后指针 2.6.1.4 时间复杂度 2.6.2 非递归版本 2.7 归并排序…...
东方泵业,室外消火栓泵 2#故障灯亮,报警生响
东方泵业,室外消火栓泵 2#故障, 图纸上显示有一个热继电器,过热了,然后它不会自动复位,需要手动复位,手动点一下那个蓝色的按钮,然后警报就解除了...
vue3:十二、图形看板- 基础准备+首行列表项展示
文章主要实现了看板页面的搭建;将看板页面加入左侧菜单;首行列表项的实现 一、效果展示 展示四个数据列表,四个列表颜色各不相同,列表左侧有颜色边线(同标题颜色、图标颜色一致);展示的数字有一个从0到当前数据逐渐增长的一个动画效果 二、图形看板的准备工作 1、创建视…...
基于开源链动2+1模式AI智能名片S2B2C商城小程序的分销价格管控机制研究
摘要:本文聚焦开源链动21模式AI智能名片S2B2C商城小程序在分销体系中的价格管控机制,通过解析其技术架构与商业模式,揭示平台如何通过"去中心化裂变中心化管控"双轨机制实现价格统一。研究显示,该模式通过区块链存证技术…...
指定Docker镜像源,使用阿里云加速异常解决
yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo异常贴图 yum-config-manager:找不到命令 因为系统默认没有安装这个命令,这个命令在yum-utils 包里,可以通过命令yum -y install yum-util…...
java基础-数组
1.数组的声明和初始化: (1)静态初始化: import java.util.Arrays;public class Main {public static void main(String[] args) {int[] ids {1,2,3,4,5};System.out.println(Arrays.toString(ids));} } (2…...
CSS手动布局
CSS支持单独设置某个元素的布局,最主要的属性是 position ,它有以下几个值: static静态位置(默认值)。元素采用正常流布局,此时元素的位置偏移属性( top 、 right 、 bottom 、 left 和 z-inde…...
什么是智能合约?区块链上的自动化契约
智能合约是运行在区块链上的计算机程序或交易协议。与传统的纸质合同不同,智能合约将合同条款直接编码到程序中,并在满足预设条件时自动执行。它们旨在实现无需第三方介入的可信交易,具有自动化、透明、不可篡改和高效等特点。 智能合约的起…...
webRtc之指定摄像头设备绿屏问题
摘要:最近发现,在使用navigator.mediaDevices.getUserMedia({ deviceId: ‘xxx’}),指定设备的时候,video播放总是绿屏,发现关闭浏览器硬件加速不会出现,但显然这不是一个最好的方案; 播放后张这样 修复后 上代码 指定…...
正则表达式非捕获分组?:
一个使用 Java 正则表达式的具体例子,展示了 (ab) 和 (?:ab) 的不同: 示例 1:使用 (ab)(捕获分组) import java.util.regex.*; public class RegexExample { public static void main(String[] args) { …...
Linux系统Shell脚本之shell数组、正则表达式、及AWK
目录 一.shell数组 1.数组分类 2.定义数组的方法 二.正则表达式 1. 元字符 2.表示次数 3.位置锚定 4.分组 5.扩展正则表达式 三.文本三剑客之AWK 1.awk 2.使用格式 3、处理动作 4.选项 5.处理模式 6.awk常见的内置变量 7.if条件判断 一.shell数组 1.数组分类 …...
在 ESP-IDF 中使用 .a 静态库调用
1. 准备静态库文件 将你的 .a 文件(如 libmylib.a)放置在工程目录中,推荐放在 components 子目录下: your_project/ ├── CMakeLists.txt ├── main/ └── components/└── my_lib/├── include/ # 头文件│ …...
大疆无人机“指点飞行模式”(TapFly)
在大疆无人机的功能中,“指点飞行模式”(TapFly)是一种通过点击屏幕目标点,让无人机自动规划路径并飞向指定位置的智能飞行模式。用户无需手动操控摇杆,只需在 App 地图或实时画面上点击目标位置&…...
力扣 : 781. 森林中的兔子
781. 森林中的兔子 - 力扣(LeetCode) 同一个数字的可以分为一组 , 3就是有3个人和我自己相同 也就是4个人,所以相同的数字可以分为 / (num1) 向上取整 class Solution { public:int numRabbits(vector<int>& answer…...
LVS中的DR模式,直接路由模式
DR模式工作原理介绍 请求经过调度器,响应由real server 直接响应给客户端。 如上图所示,real server想要正常访问互联网,后端的real server的网关就得写网络中真实的网关。 DR模式的核心要素:【重点】 1.请求经过调度器&…...
iTwin 数据报表(只是简单的原型不代表实现)
大概想法是 前端从schema和class中选中感兴趣的property内容生成ecsql语句传递给后端后端解析ecsql并提供公开接口给各个分析工具,如excel,poewerBI等(Odata或者直接选择来自网站)再由分析工具做进一步的处 还未想好的点 如何存…...
【无标题】如何在sheel中运行Spark
启动hdfs集群,打开hadoop100:9870,在wcinput目录下上传一个包含很多个单词的文本文件。 启动之后在spark-shell中写代码。 // 读取文件,得到RDD val rdd1 sc.textFile("hdfs://hadoop100:8020/wcinput/words.txt") // 将单词进行…...
Spark 处理过程转换:算子与行动算子详解
在大数据处理领域,Apache Spark 凭借其强大的分布式计算能力脱颖而出,成为处理海量数据的利器。而 Spark 的核心处理过程,主要通过转换算子和行动算子来实现。本文将深入探讨 Spark 中的转换算子和行动算子,帮助读者更好地理解和应…...
Docker编排工具---Compose的概述及使用
目录 一、Compose工具的概述 二、Compose的常用命令 1、列出容器 2、查看访问日志 3、输出绑定的公共端口 4、重新构建服务 5、启动服务 6、停止服务 7、删除已停止服务的容器 8、创建和启动容器 9、在运行的容器中执行命令 10、指定一个服务启动容器的个数 11、其…...
Matlab实现绘制任意自由曲线
Matlab实现绘制任意自由曲线,实现Photoshop中的钢笔路径功能,用光顺连接的B样条/贝塞尔曲线实现,鼠标点击生成控制点,拖动形成任意曲线。 可描绘多路径,也可旋转、平移、缩放。经调试可用。 ByangtiaoSculpt/Byangti…...
如何保证Kafka生产者的消息顺序性? (单分区内有序,需确保同一Key的消息发送到同一分区)
Kafka 生产者消息顺序性保障方案 1. 核心实现原理 消息顺序性保障公式: 同一 Key → 同一 Partition → 严格顺序写入2. 关键配置参数 Properties props new Properties(); props.put("acks", "all"); // 确保消息持久化 props.put("ma…...
[D1,2] 贪心刷题
文章目录 摆动序列最大子数组合买卖股票跳跃游戏跳跃2 摆动序列 不像是贪心,只要抓住摆动这个点,前一个上升,那下一个就要下降,记录上一次的状态为1的话,那下一次就要更新为-1,如果上一次为1,这…...
springboot使用阿里云OSS实现文件上传
在Spring Boot中集成阿里云OSS(对象存储服务)可以通过以下步骤实现: 添加Maven依赖 在pom.xml中添加阿里云OSS SDK依赖: <dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss<…...
nginx之proxy_redirect应用
一、功能说明 proxy_redirect 是 Nginx 反向代理中用于修改后端返回的响应头中 Location 和 Refresh 字段的核心指令,主要解决以下问题:协议/地址透传错误:当后端返回的 Location 包含内部 IP、HTTP 协议或非标准端口时,需修正为…...
FAISS(Facebook AI Similarity Search)
First steps with Faiss for k-nearest neighbor search in large search spaces - Davide’s GitHub pages FAISS(Facebook AI Similarity Search)是由Meta(原Facebook)AI团队开发的高效相似性搜索库,主要用于处理大规…...
创建虚拟服务时实现持久连接。
在调度器中配置虚拟服务,实现持久性连接,解决会话保持问题。 -p 【timeout】 -p 300 这5分钟之内调度器会把来自同一个客户端的请求转发到同一个后端服务器。【不管使用的调度算法是什么。】【称为持久性连接。】 作用:将客户端一段时间…...
RabbitMQ中Exchange交换器的类型
在RabbitMQ中,Exchange(交换器)是消息路由的核心组件,它接收生产者发送的消息,并根据不同的规则将消息转发到一个或多个队列。 RabbitMQ主要支持以下几种类型的交换器: 1. Direct Exchange(直连…...
JavaSE核心知识点01基础语法01-05(字符串)
🤟致敬读者 🟩感谢阅读🟦笑口常开🟪生日快乐⬛早点睡觉 📘博主相关 🟧博主信息🟨博客首页🟫专栏推荐🟥活动信息 文章目录 JavaSE核心知识点01基础语法01-05࿰…...
Vue 项目中二维码生成功能全解析
Vue 项目中二维码生成功能全解析 在信息快速传递的时代,二维码以其简洁高效的特点,成为数据交互的重要媒介。无论是用于支付、信息分享,还是活动参与,二维码都扮演着关键角色。在 Vue 项目开发中,如何实现二维码生成功…...
【AWS+Wordpress】将本地 WordPress 网站部署到AWS
前言 自学笔记,解决问题为主,亲测有效,欢迎补充。 本地开发机:macOS(Sequoia 15.0.1) 服务器:AWS EC2(Amazon Linux 2023) 目标:从本地迁移 WordPress 到云…...
性能优化-初识(C++)
性能优化-初识 一、内联与优化(Inlining and Optimization)什么是内联(inline)?使用方式:适用场景:注意事项: 二、缓存友好设计(Cache-Friendly Design)原理简…...
[人机交互]交互设计过程
*一.设计 1.1什么是设计 设计是一项创新活动,旨在为用户提供可用的产品 –交互设计是“设计交互式产品、以支持人们的生活和工作” 1.2设计包含的四个活动 – 识别用户的需要( needs )并建立需求( requirements ) …...
密码学基石:哈希、对称/非对称加密与HTTPS实践详解
密码学是现代信息安全的基石,它提供了一系列强大的数学工具和技术,用于保护数据的机密性、完整性和真实性,并确保通信双方的身份可被认证。在纷繁复杂的网络世界中,无论是安全的网页浏览 (HTTPS)、安全的软件更新、还是用户密码的…...
WebRTC通信原理与流程
1、服务器与协议相关 1.1 STUN服务器 图1.1.1 STUN服务器在通信中的位置图 1.1.1 STUN服务简介 STUN(Session Traversal Utilities for NAT,NAT会话穿越应用程序)是一种网络协议,它允许位于NAT(或多重 NAT)…...
ChromaDB调用BGE模型的两种实践方式
ChromaDB调用BGE模型 前言1.chromadb调用BGE模型api2.调用本地模型 前言 在语义搜索、知识库构建等场景中,文本向量化(Embedding)是核心技术环节。作为一款开源的向量数据库,ChromaDB允许开发者通过自定义嵌入函数灵活对接各类模…...
解构与重构:自动化测试框架的进阶认知之旅
目录 一、自动化测试的介绍 (一)自动化测试的起源与发展 (二)自动化测试的定义与目标 (三)自动化测试的适用场景 二、什么是自动化测试框架 (一)自动化测试框架的定义 &#x…...
如何巧妙解决 Too many connections 报错?
1. 背景 在日常的 MySQL 运维中,难免会出现参数设置不合理,导致 MySQL 在使用过程中出现各种各样的问题。 今天,我们就来讲解一下 MySQL 运维中一种常见的问题:最大连接数设置不合理,一旦到了业务高峰期就会出现连接…...
【卡特兰数】不同的二叉搜索树
文章目录 96. 不同的二叉搜索树解法一:动态规划状态表示状态转移方程初始化遍历顺序返回值💥解法二:卡特兰数96. 不同的二叉搜索树 96. 不同的二叉搜索树 给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉…...
《饶议科学》阅读笔记
《饶议科学》 《偷窃的生物学机制:(有些)小偷有药可治》阅读笔记 核心内容:探讨偷窃狂(kleptomania)的生物学机制及相关研究。具体要点 偷窃狂的特征:患者不可抑制地反复偷窃个人不需要、与金钱…...
ShardingJdbc-公共表
ShardingJdbc-公共表 公共表 公共表属于系统中数据量小,变动少,但是却高频联合查询的表,参数表,字典表等属于此类型。可以将此类表在每个数据库中存储一份,所有更新操作将同时发送到所有分库执行。 案例 建立库 shar…...
低成本监控IPC模组概述
1、低成本sigmastar ssc335\ssc377摄像机方案,配合AI边缘计算终端即插即用,差异化AI训练及样 本采集 2、支持200万、500万H265\H264视频编码,支持网络Rtsp,Rtmp,Onvif,web,GB28181,tf卡本地录像, 视频平台接入等...
携手高校科研团队,共建TWS耳机芯片技术新生态
TWS(真无线立体声)蓝牙耳机已成为人们生活中不可或缺的一部分。而在这背后,有一家名为华芯邦的公司,其专注于TWS蓝牙仓耳机芯片的研发,并不断取得令人瞩目的突破。 一、芯片领域的实力玩家 华芯邦作为一家在芯片行业崭…...
动态规划-91.解码方法-力扣(LeetCode)
一、题目解析 将对应字符转化为数字,我们知道有的大写字母范围是在[1,9],剩下的则是[10,26],这个对应关系使我们解题的关键。 二、算法原理 1.状态表示 dp[i]表示:以i位置为结尾时,解码方法总…...