ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataColumn.java
@@ -3,9 +3,10 @@ import java.lang.annotation.*; /** * 数据权限 * * 数据权限注解,用于标记数据权限的占位符关键字和替换值 * <p> * 一个注解只能对应一个模板 * </p> * * @author Lion Li * @version 3.5.0 @@ -16,12 +17,16 @@ public @interface DataColumn { /** * 占位符关键字 * 数据权限模板的占位符关键字,默认为 "deptName" * * @return 占位符关键字数组 */ String[] key() default "deptName"; /** * 占位符替换值 * 数据权限模板的占位符替换值,默认为 "dept_id" * * @return 占位符替换值数组 */ String[] value() default "dept_id"; ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/annotation/DataPermission.java
@@ -3,7 +3,7 @@ import java.lang.annotation.*; /** * 数据权限组 * 数据权限组注解,用于标记数据权限配置数组 * * @author Lion Li * @version 3.5.0 @@ -13,6 +13,11 @@ @Documented public @interface DataPermission { /** * 数据权限配置数组,用于指定数据权限的占位符关键字和替换值 * * @return 数据权限配置数组 */ DataColumn[] value(); } ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/domain/BaseEntity.java
@@ -17,7 +17,6 @@ * * @author Lion Li */ @Data public class BaseEntity implements Serializable { ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/mapper/BaseMapperPlus.java
@@ -34,20 +34,38 @@ Log log = LogFactory.getLog(BaseMapperPlus.class); /** * 获取当前实例对象关联的泛型类型 V 的 Class 对象 * * @return 返回当前实例对象关联的泛型类型 V 的 Class 对象 */ default Class<V> currentVoClass() { return (Class<V>) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[1]; } /** * 获取当前实例对象关联的泛型类型 T 的 Class 对象 * * @return 返回当前实例对象关联的泛型类型 T 的 Class 对象 */ default Class<T> currentModelClass() { return (Class<T>) GenericTypeUtils.resolveTypeArguments(this.getClass(), BaseMapperPlus.class)[0]; } /** * 使用默认的查询条件查询并返回结果列表 * * @return 返回查询结果的列表 */ default List<T> selectList() { return this.selectList(new QueryWrapper<>()); } /** * 批量插入 * 批量插入实体对象集合 * * @param entityList 实体对象集合 * @return 插入操作是否成功的布尔值 */ default boolean insertBatch(Collection<T> entityList) { Db.saveBatch(entityList); @@ -56,7 +74,10 @@ } /** * 批量更新 * 批量根据ID更新实体对象集合 * * @param entityList 实体对象集合 * @return 更新操作是否成功的布尔值 */ default boolean updateBatchById(Collection<T> entityList) { Db.updateBatchById(entityList); @@ -65,7 +86,10 @@ } /** * 批量插入或更新 * 批量插入或更新实体对象集合 * * @param entityList 实体对象集合 * @return 插入或更新操作是否成功的布尔值 */ default boolean insertOrUpdateBatch(Collection<T> entityList) { Db.saveOrUpdateBatch(entityList); @@ -74,7 +98,11 @@ } /** * 批量插入(包含限制条数) * 批量插入实体对象集合并指定批处理大小 * * @param entityList 实体对象集合 * @param batchSize 批处理大小 * @return 插入操作是否成功的布尔值 */ default boolean insertBatch(Collection<T> entityList, int batchSize) { Db.saveBatch(entityList, batchSize); @@ -83,7 +111,11 @@ } /** * 批量更新(包含限制条数) * 批量根据ID更新实体对象集合并指定批处理大小 * * @param entityList 实体对象集合 * @param batchSize 批处理大小 * @return 更新操作是否成功的布尔值 */ default boolean updateBatchById(Collection<T> entityList, int batchSize) { Db.updateBatchById(entityList, batchSize); @@ -92,7 +124,11 @@ } /** * 批量插入或更新(包含限制条数) * 批量插入或更新实体对象集合并指定批处理大小 * * @param entityList 实体对象集合 * @param batchSize 批处理大小 * @return 插入或更新操作是否成功的布尔值 */ default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) { Db.saveOrUpdateBatch(entityList, batchSize); @@ -100,12 +136,23 @@ return true; } /** * 根据ID查询单个VO对象 * * @param id 主键ID * @return 查询到的单个VO对象 */ default V selectVoById(Serializable id) { return selectVoById(id, this.currentVoClass()); } /** * 根据 ID 查询 * 根据ID查询单个VO对象并将其转换为指定的VO类 * * @param id 主键ID * @param voClass 要转换的VO类的Class对象 * @param <C> VO类的类型 * @return 查询到的单个VO对象,经过转换为指定的VO类后返回 */ default <C> C selectVoById(Serializable id, Class<C> voClass) { T obj = this.selectById(id); @@ -115,12 +162,23 @@ return MapstructUtils.convert(obj, voClass); } /** * 根据ID集合批量查询VO对象列表 * * @param idList 主键ID集合 * @return 查询到的VO对象列表 */ default List<V> selectVoBatchIds(Collection<? extends Serializable> idList) { return selectVoBatchIds(idList, this.currentVoClass()); } /** * 查询(根据ID 批量查询) * 根据ID集合批量查询实体对象列表,并将其转换为指定的VO对象列表 * * @param idList 主键ID集合 * @param voClass 要转换的VO类的Class对象 * @param <C> VO类的类型 * @return 查询到的VO对象列表,经过转换为指定的VO类后返回 */ default <C> List<C> selectVoBatchIds(Collection<? extends Serializable> idList, Class<C> voClass) { List<T> list = this.selectBatchIds(idList); @@ -130,12 +188,23 @@ return MapstructUtils.convert(list, voClass); } /** * 根据查询条件Map查询VO对象列表 * * @param map 查询条件Map * @return 查询到的VO对象列表 */ default List<V> selectVoByMap(Map<String, Object> map) { return selectVoByMap(map, this.currentVoClass()); } /** * 查询(根据 columnMap 条件) * 根据查询条件Map查询实体对象列表,并将其转换为指定的VO对象列表 * * @param map 查询条件Map * @param voClass 要转换的VO类的Class对象 * @param <C> VO类的类型 * @return 查询到的VO对象列表,经过转换为指定的VO类后返回 */ default <C> List<C> selectVoByMap(Map<String, Object> map, Class<C> voClass) { List<T> list = this.selectByMap(map); @@ -145,23 +214,47 @@ return MapstructUtils.convert(list, voClass); } /** * 根据条件查询单个VO对象 * * @param wrapper 查询条件Wrapper * @return 查询到的单个VO对象 */ default V selectVoOne(Wrapper<T> wrapper) { return selectVoOne(wrapper, this.currentVoClass()); } /** * 根据条件查询单个VO对象,并根据需要决定是否抛出异常 * * @param wrapper 查询条件Wrapper * @param throwEx 是否抛出异常的标志 * @return 查询到的单个VO对象 */ default V selectVoOne(Wrapper<T> wrapper, boolean throwEx) { return selectVoOne(wrapper, this.currentVoClass(), throwEx); } /** * 根据 entity 条件,查询一条记录 * 根据条件查询单个VO对象,并指定返回的VO对象的类型 * * @param wrapper 查询条件Wrapper * @param voClass 返回的VO对象的Class对象 * @param <C> 返回的VO对象的类型 * @return 查询到的单个VO对象,经过类型转换为指定的VO类后返回 */ default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass) { return selectVoOne(wrapper, voClass, true); } /** * 根据 entity 条件,查询一条记录 * 根据条件查询单个实体对象,并将其转换为指定的VO对象 * * @param wrapper 查询条件Wrapper * @param voClass 要转换的VO类的Class对象 * @param throwEx 是否抛出异常的标志 * @param <C> VO类的类型 * @return 查询到的单个VO对象,经过转换为指定的VO类后返回 */ default <C> C selectVoOne(Wrapper<T> wrapper, Class<C> voClass, boolean throwEx) { T obj = this.selectOne(wrapper, throwEx); @@ -171,16 +264,32 @@ return MapstructUtils.convert(obj, voClass); } /** * 查询所有VO对象列表 * * @return 查询到的VO对象列表 */ default List<V> selectVoList() { return selectVoList(new QueryWrapper<>(), this.currentVoClass()); } /** * 根据条件查询VO对象列表 * * @param wrapper 查询条件Wrapper * @return 查询到的VO对象列表 */ default List<V> selectVoList(Wrapper<T> wrapper) { return selectVoList(wrapper, this.currentVoClass()); } /** * 根据 entity 条件,查询全部记录 * 根据条件查询实体对象列表,并将其转换为指定的VO对象列表 * * @param wrapper 查询条件Wrapper * @param voClass 要转换的VO类的Class对象 * @param <C> VO类的类型 * @return 查询到的VO对象列表,经过转换为指定的VO类后返回 */ default <C> List<C> selectVoList(Wrapper<T> wrapper, Class<C> voClass) { List<T> list = this.selectList(wrapper); @@ -190,15 +299,31 @@ return MapstructUtils.convert(list, voClass); } /** * 根据条件分页查询VO对象列表 * * @param page 分页信息 * @param wrapper 查询条件Wrapper * @return 查询到的VO对象分页列表 */ default <P extends IPage<V>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper) { return selectVoPage(page, wrapper, this.currentVoClass()); } /** * 分页查询VO * 根据条件分页查询实体对象列表,并将其转换为指定的VO对象分页列表 * * @param page 分页信息 * @param wrapper 查询条件Wrapper * @param voClass 要转换的VO类的Class对象 * @param <C> VO类的类型 * @param <P> VO对象分页列表的类型 * @return 查询到的VO对象分页列表,经过转换为指定的VO类后返回 */ default <C, P extends IPage<C>> P selectVoPage(IPage<T> page, Wrapper<T> wrapper, Class<C> voClass) { // 根据条件分页查询实体对象列表 List<T> list = this.selectList(page, wrapper); // 创建一个新的VO对象分页列表,并设置分页信息 IPage<C> voPage = new Page<>(page.getCurrent(), page.getSize(), page.getTotal()); if (CollUtil.isEmpty(list)) { return (P) voPage; @@ -207,6 +332,14 @@ return (P) voPage; } /** * 根据条件查询符合条件的对象,并将其转换为指定类型的对象列表 * * @param wrapper 查询条件Wrapper * @param mapper 转换函数,用于将查询到的对象转换为指定类型的对象 * @param <C> 要转换的对象的类型 * @return 查询到的符合条件的对象列表,经过转换为指定类型的对象后返回 */ default <C> List<C> selectObjs(Wrapper<T> wrapper, Function<? super Object, C> mapper) { return this.selectObjs(wrapper).stream().filter(Objects::nonNull).map(mapper).collect(Collectors.toList()); } ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/PageQuery.java
@@ -4,10 +4,10 @@ import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.metadata.OrderItem; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import lombok.Data; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.sql.SqlUtil; import lombok.Data; import java.io.Serial; import java.io.Serializable; @@ -19,7 +19,6 @@ * * @author Lion Li */ @Data public class PageQuery implements Serializable { @@ -56,6 +55,9 @@ */ public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE; /** * 构建分页对象 */ public <T> Page<T> build() { Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM); Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE); ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/core/page/TableDataInfo.java
@@ -14,7 +14,6 @@ * * @author Lion Li */ @Data @NoArgsConstructor public class TableDataInfo<T> implements Serializable { @@ -53,6 +52,9 @@ this.total = total; } /** * 根据分页对象构建表格分页数据对象 */ public static <T> TableDataInfo<T> build(IPage<T> page) { TableDataInfo<T> rspData = new TableDataInfo<>(); rspData.setCode(HttpStatus.HTTP_OK); @@ -62,6 +64,9 @@ return rspData; } /** * 根据数据列表构建表格分页数据对象 */ public static <T> TableDataInfo<T> build(List<T> list) { TableDataInfo<T> rspData = new TableDataInfo<>(); rspData.setCode(HttpStatus.HTTP_OK); @@ -71,6 +76,9 @@ return rspData; } /** * 构建表格分页数据对象 */ public static <T> TableDataInfo<T> build() { TableDataInfo<T> rspData = new TableDataInfo<>(); rspData.setCode(HttpStatus.HTTP_OK); ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataBaseType.java
@@ -1,8 +1,8 @@ package org.dromara.common.mybatis.enums; import org.dromara.common.core.utils.StringUtils; import lombok.AllArgsConstructor; import lombok.Getter; import org.dromara.common.core.utils.StringUtils; /** * 数据库类型 @@ -33,8 +33,17 @@ */ SQL_SERVER("Microsoft SQL Server"); /** * 数据库类型 */ private final String type; /** * 根据数据库产品名称查找对应的数据库类型 * * @param databaseProductName 数据库产品名称 * @return 对应的数据库类型枚举值,如果未找到则返回 null */ public static DataBaseType find(String databaseProductName) { if (StringUtils.isBlank(databaseProductName)) { return null; ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/enums/DataScopeType.java
@@ -1,19 +1,22 @@ package org.dromara.common.mybatis.enums; import org.dromara.common.core.utils.StringUtils; import lombok.AllArgsConstructor; import lombok.Getter; import org.dromara.common.core.domain.model.LoginUser; import org.dromara.common.core.utils.StringUtils; import org.dromara.common.mybatis.helper.DataPermissionHelper; /** * 数据权限类型 * 数据权限类型枚举 * <p> * 语法支持 spel 模板表达式 * <p> * 内置数据 user 当前用户 内容参考 LoginUser * 如需扩展数据 可使用 {@link DataPermissionHelper} 操作 * 内置服务 sdss 系统数据权限服务 内容参考 SysDataScopeService * 如需扩展更多自定义服务 可以参考 sdss 自行编写 * 支持使用 SpEL 模板表达式定义 SQL 查询条件 * 内置数据: * - {@code user}: 当前登录用户信息,参考 {@link LoginUser} * 内置服务: * - {@code sdss}: 系统数据权限服务,参考 {@link ISysDataScopeService} * 如需扩展数据,可以通过 {@link DataPermissionHelper} 进行操作 * 如需扩展服务,可以通过 {@link ISysDataScopeService} 自行编写 * </p> * * @author Lion Li * @version 3.5.0 @@ -29,36 +32,50 @@ /** * 自定数据权限 * 使用 SpEL 表达式:`#{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} )` * 如果不满足条件,则使用默认 SQL 表达式:`1 = 0` */ CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "), /** * 部门数据权限 * 使用 SpEL 表达式:`#{#deptName} = #{#user.deptId}` * 如果不满足条件,则使用默认 SQL 表达式:`1 = 0` */ DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "), /** * 部门及以下数据权限 * 使用 SpEL 表达式:`#{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )}` * 如果不满足条件,则使用默认 SQL 表达式:`1 = 0` */ DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "), /** * 仅本人数据权限 * 使用 SpEL 表达式:`#{#userName} = #{#user.userId}` * 如果不满足条件,则使用默认 SQL 表达式:`1 = 0` */ SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "); private final String code; /** * 语法 采用 spel 模板表达式 * SpEL 模板表达式,用于构建 SQL 查询条件 */ private final String sqlTemplate; /** * 不满足 sqlTemplate 则填充 * 如果不满足 {@code sqlTemplate} 的条件,则使用此默认 SQL 表达式 */ private final String elseSql; /** * 根据枚举代码查找对应的枚举值 * * @param code 枚举代码 * @return 对应的枚举值,如果未找到则返回 null */ public static DataScopeType findCode(String code) { if (StringUtils.isBlank(code)) { return null; ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/InjectionMetaObjectHandler.java
@@ -3,12 +3,12 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.http.HttpStatus; import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import org.dromara.common.core.domain.model.LoginUser; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.mybatis.core.domain.BaseEntity; import org.dromara.common.satoken.utils.LoginHelper; import lombok.extern.slf4j.Slf4j; import org.apache.ibatis.reflection.MetaObject; import java.util.Date; @@ -21,21 +21,28 @@ @Slf4j public class InjectionMetaObjectHandler implements MetaObjectHandler { /** * 插入填充方法,用于在插入数据时自动填充实体对象中的创建时间、更新时间、创建人、更新人等信息 * * @param metaObject 元对象,用于获取原始对象并进行填充 */ @Override public void insertFill(MetaObject metaObject) { try { if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) { // 获取当前时间作为创建时间和更新时间,如果创建时间不为空,则使用创建时间,否则使用当前时间 Date current = ObjectUtil.isNotNull(baseEntity.getCreateTime()) ? baseEntity.getCreateTime() : new Date(); baseEntity.setCreateTime(current); baseEntity.setUpdateTime(current); // 如果创建人为空,则填充当前登录用户的信息 if (ObjectUtil.isNull(baseEntity.getCreateBy())) { LoginUser loginUser = getLoginUser(); if (ObjectUtil.isNotNull(loginUser)) { Long userId = loginUser.getUserId(); // 当前已登录 且 创建人为空 则填充 // 填充创建人、更新人和创建部门信息 baseEntity.setCreateBy(userId); // 当前已登录 且 更新人为空 则填充 baseEntity.setUpdateBy(userId); baseEntity.setCreateDept(ObjectUtil.isNotNull(baseEntity.getCreateDept()) ? baseEntity.getCreateDept() : loginUser.getDeptId()); @@ -47,19 +54,24 @@ } } /** * 更新填充方法,用于在更新数据时自动填充实体对象中的更新时间和更新人信息 * * @param metaObject 元对象,用于获取原始对象并进行填充 */ @Override public void updateFill(MetaObject metaObject) { try { if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) { // 获取当前时间作为更新时间,无论原始对象中的更新时间是否为空都填充 Date current = new Date(); // 更新时间填充(不管为不为空) baseEntity.setUpdateTime(current); // 当前已登录 更新人填充(不管为不为空) // 获取当前登录用户的ID,并填充更新人信息 Long userId = LoginHelper.getUserId(); if (ObjectUtil.isNotNull(userId)) { baseEntity.setUpdateBy(userId); } } } catch (Exception e) { throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED); @@ -67,7 +79,9 @@ } /** * 获取登录用户名 * 获取当前登录用户信息 * * @return 当前登录用户的信息,如果用户未登录则返回 null */ private LoginUser getLoginUser() { LoginUser loginUser; ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/MybatisExceptionHandler.java
@@ -1,14 +1,13 @@ package org.dromara.common.mybatis.handler; import org.dromara.common.core.domain.R; import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.dromara.common.core.domain.R; import org.dromara.common.core.utils.StringUtils; import org.mybatis.spring.MyBatisSystemException; import org.springframework.dao.DuplicateKeyException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import jakarta.servlet.http.HttpServletRequest; /** * Mybatis异常处理器 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/handler/PlusDataPermissionHandler.java
@@ -68,13 +68,27 @@ */ private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory()); /** * 构造方法,扫描指定包下的 Mapper 类并初始化缓存 * * @param mapperPackage Mapper 类所在的包路径 */ public PlusDataPermissionHandler(String mapperPackage) { scanMapperClasses(mapperPackage); } /** * 获取数据过滤条件的 SQL 片段 * * @param where 原始的查询条件表达式 * @param mappedStatementId Mapper 方法的 ID * @param isSelect 是否为查询语句 * @return 数据过滤条件的 SQL 片段 */ public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) { // 获取数据权限配置 DataPermission dataPermission = getDataPermission(mappedStatementId); // 获取当前登录用户信息 LoginUser currentUser = DataPermissionHelper.getVariable("user"); if (ObjectUtil.isNull(currentUser)) { currentUser = LoginHelper.getLoginUser(); @@ -84,6 +98,7 @@ if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) { return where; } // 构造数据过滤条件的 SQL 片段 String dataFilterSql = buildDataFilter(dataPermission.value(), isSelect); if (StringUtils.isBlank(dataFilterSql)) { return where; @@ -103,7 +118,12 @@ } /** * 构造数据过滤sql * 构建数据过滤条件的 SQL 语句 * * @param dataColumns 数据权限注解中的列信息 * @param isSelect 标志当前操作是否为查询操作,查询操作和更新或删除操作在处理过滤条件时会有不同的处理方式 * @return 构建的数据过滤条件的 SQL 语句 * @throws ServiceException 如果角色的数据范围异常或者 key 与 value 的长度不匹配,则抛出 ServiceException 异常 */ private String buildDataFilter(DataColumn[] dataColumns, boolean isSelect) { // 更新或删除需满足所有条件 @@ -159,20 +179,29 @@ } /** * 通过 mapperPackage 设置的扫描包 扫描缓存有注解的方法与类 * 扫描指定包下的 Mapper 类,并查找其中带有特定注解的方法或类 * * @param mapperPackage Mapper 类所在的包路径 */ private void scanMapperClasses(String mapperPackage) { // 创建资源解析器和元数据读取工厂 PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory(); // 将 Mapper 包路径按分隔符拆分为数组 String[] packagePatternArray = StringUtils.splitPreserveAllTokens(mapperPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); String classpath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX; try { for (String packagePattern : packagePatternArray) { // 将包路径转换为资源路径 String path = ClassUtils.convertClassNameToResourcePath(packagePattern); // 获取指定路径下的所有 .class 文件资源 Resource[] resources = resolver.getResources(classpath + path + "/*.class"); for (Resource resource : resources) { // 获取资源的类元数据 ClassMetadata classMetadata = factory.getMetadataReader(resource).getClassMetadata(); // 获取资源对应的类对象 Class<?> clazz = Resources.classForName(classMetadata.getClassName()); // 查找类中的特定注解 findAnnotation(clazz); } } @@ -181,9 +210,13 @@ } } /** * 在指定的类中查找特定的注解 DataPermission,并将带有这个注解的方法或类存储到 dataPermissionCacheMap 中 * * @param clazz 要查找的类 */ private void findAnnotation(Class<?> clazz) { DataPermission dataPermission; // 获取方法注解 for (Method method : clazz.getMethods()) { if (method.isDefault() || method.isVarArgs()) { continue; @@ -194,17 +227,24 @@ dataPermissionCacheMap.put(mappedStatementId, dataPermission); } } // 获取类注解 if (AnnotationUtil.hasAnnotation(clazz, DataPermission.class)) { dataPermission = AnnotationUtil.getAnnotation(clazz, DataPermission.class); dataPermissionCacheMap.put(clazz.getName(), dataPermission); } } /** * 根据映射语句 ID 或类名获取对应的 DataPermission 注解对象 * * @param mapperId 映射语句 ID * @return DataPermission 注解对象,如果不存在则返回 null */ public DataPermission getDataPermission(String mapperId) { // 检查缓存中是否包含映射语句 ID 对应的 DataPermission 注解对象 if (dataPermissionCacheMap.containsKey(mapperId)) { return dataPermissionCacheMap.get(mapperId); } // 如果缓存中不包含映射语句 ID 对应的 DataPermission 注解对象,则尝试使用类名作为键查找 String clazzName = mapperId.substring(0, mapperId.lastIndexOf(".")); if (dataPermissionCacheMap.containsKey(clazzName)) { return dataPermissionCacheMap.get(clazzName); @@ -213,7 +253,10 @@ } /** * 是否无效 * 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象 * * @param mapperId 映射语句 ID * @return 如果找到对应的 DataPermission 注解对象,则返回 false;否则返回 true */ public boolean invalid(String mapperId) { return getDataPermission(mapperId) == null; ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataBaseHelper.java
@@ -2,11 +2,11 @@ import cn.hutool.core.convert.Convert; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; import lombok.AccessLevel; import lombok.NoArgsConstructor; import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.mybatis.enums.DataBaseType; import lombok.AccessLevel; import lombok.NoArgsConstructor; import javax.sql.DataSource; import java.sql.Connection; @@ -14,7 +14,6 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Set; /** * 数据库助手 ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/helper/DataPermissionHelper.java
@@ -24,17 +24,35 @@ private static final String DATA_PERMISSION_KEY = "data:permission"; /** * 从上下文中获取指定键的变量值,并将其转换为指定的类型 * * @param key 变量的键 * @param <T> 变量值的类型 * @return 指定键的变量值,如果不存在则返回 null */ public static <T> T getVariable(String key) { Map<String, Object> context = getContext(); return (T) context.get(key); } /** * 向上下文中设置指定键的变量值 * * @param key 要设置的变量的键 * @param value 要设置的变量值 */ public static void setVariable(String key, Object value) { Map<String, Object> context = getContext(); context.put(key, value); } /** * 获取数据权限上下文 * * @return 存储在SaStorage中的Map对象,用于存储数据权限相关的上下文信息 * @throws NullPointerException 如果数据权限上下文类型异常,则抛出NullPointerException */ public static Map<String, Object> getContext() { SaStorage saStorage = SaHolder.getStorage(); Object attribute = saStorage.get(DATA_PERMISSION_KEY); @@ -64,6 +82,7 @@ /** * 在忽略数据权限中执行 * <p>禁止在忽略数据权限中执行忽略数据权限</p> * * @param handle 处理执行方法 */ @@ -78,6 +97,7 @@ /** * 在忽略数据权限中执行 * <p>禁止在忽略数据权限中执行忽略数据权限</p> * * @param handle 处理执行方法 */ ruoyi-common/ruoyi-common-mybatis/src/main/java/org/dromara/common/mybatis/interceptor/PlusDataPermissionInterceptor.java
@@ -37,17 +37,33 @@ private final PlusDataPermissionHandler dataPermissionHandler; /** * 构造函数,初始化 PlusDataPermissionHandler 实例 * * @param mapperPackage 扫描的映射器包 */ public PlusDataPermissionInterceptor(String mapperPackage) { this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage); } /** * 在执行查询之前,检查并处理数据权限相关逻辑 * * @param executor MyBatis 执行器对象 * @param ms 映射语句对象 * @param parameter 方法参数 * @param rowBounds 分页对象 * @param resultHandler 结果处理器 * @param boundSql 绑定的 SQL 对象 * @throws SQLException 如果发生 SQL 异常 */ @Override public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { // 检查忽略注解 // 检查是否需要忽略数据权限处理 if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { return; } // 检查是否无效 无数据权限注解 // 检查是否缺少有效的数据权限注解 if (dataPermissionHandler.invalid(ms.getId())) { return; } @@ -56,16 +72,26 @@ mpBs.sql(parserSingle(mpBs.sql(), ms.getId())); } /** * 在准备 SQL 语句之前,检查并处理更新和删除操作的数据权限相关逻辑 * * @param sh MyBatis StatementHandler 对象 * @param connection 数据库连接对象 * @param transactionTimeout 事务超时时间 */ @Override public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) { PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh); MappedStatement ms = mpSh.mappedStatement(); // 获取 SQL 命令类型(增、删、改、查) SqlCommandType sct = ms.getSqlCommandType(); // 只处理更新和删除操作的 SQL 语句 if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) { if (InterceptorIgnoreHelper.willIgnoreDataPermission(ms.getId())) { return; } // 检查是否无效 无数据权限注解 // 检查是否缺少有效的数据权限注解 if (dataPermissionHandler.invalid(ms.getId())) { return; } @@ -74,6 +100,14 @@ } } /** * 处理 SELECT 查询语句中的 WHERE 条件 * * @param select SELECT 查询对象 * @param index 查询语句的索引 * @param sql 查询语句 * @param obj WHERE 条件参数 */ @Override protected void processSelect(Select select, int index, String sql, Object obj) { if (select instanceof PlainSelect) { @@ -84,6 +118,14 @@ } } /** * 处理 UPDATE 语句中的 WHERE 条件 * * @param update UPDATE 查询对象 * @param index 查询语句的索引 * @param sql 查询语句 * @param obj WHERE 条件参数 */ @Override protected void processUpdate(Update update, int index, String sql, Object obj) { Expression sqlSegment = dataPermissionHandler.getSqlSegment(update.getWhere(), (String) obj, false); @@ -92,6 +134,14 @@ } } /** * 处理 DELETE 语句中的 WHERE 条件 * * @param delete DELETE 查询对象 * @param index 查询语句的索引 * @param sql 查询语句 * @param obj WHERE 条件参数 */ @Override protected void processDelete(Delete delete, int index, String sql, Object obj) { Expression sqlSegment = dataPermissionHandler.getSqlSegment(delete.getWhere(), (String) obj, false); @@ -101,10 +151,10 @@ } /** * 设置 where 条件 * 设置 SELECT 语句的 WHERE 条件 * * @param plainSelect 查询对象 * @param mappedStatementId 执行方法id * @param plainSelect SELECT 查询对象 * @param mappedStatementId 映射语句的 ID */ protected void setWhere(PlainSelect plainSelect, String mappedStatementId) { Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true); @@ -113,6 +163,14 @@ } } /** * 构建表达式,用于处理表的数据权限 * * @param table 表对象 * @param where WHERE 条件表达式 * @param whereSegment WHERE 条件片段 * @return 构建的表达式 */ @Override public Expression buildTableExpression(Table table, Expression where, String whereSegment) { // 只有新版数据权限处理器才会执行到这里 ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysDataScopeServiceImpl.java
@@ -3,14 +3,14 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.convert.Convert; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.dromara.system.domain.SysDept; import org.dromara.common.mybatis.helper.DataBaseHelper; import lombok.RequiredArgsConstructor; import org.dromara.common.core.utils.StreamUtils; import org.dromara.common.mybatis.helper.DataBaseHelper; import org.dromara.system.domain.SysDept; import org.dromara.system.domain.SysRoleDept; import org.dromara.system.mapper.SysDeptMapper; import org.dromara.system.mapper.SysRoleDeptMapper; import org.dromara.system.service.ISysDataScopeService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import java.util.List; @@ -30,6 +30,12 @@ private final SysRoleDeptMapper roleDeptMapper; private final SysDeptMapper deptMapper; /** * 获取角色自定义权限 * * @param roleId 角色Id * @return 部门Id组 */ @Override public String getRoleCustom(Long roleId) { List<SysRoleDept> list = roleDeptMapper.selectList( @@ -42,6 +48,12 @@ return null; } /** * 获取部门及以下权限 * * @param deptId 部门Id * @return 部门Id组 */ @Override public String getDeptAndChild(Long deptId) { List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>()