init
This commit is contained in:
		| @ -0,0 +1,41 @@ | ||||
| package org.dromara.common.mybatis.annotation; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| /** | ||||
|  * 数据权限注解,用于标记数据权限的占位符关键字和替换值 | ||||
|  * <p> | ||||
|  * 一个注解只能对应一个模板 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  * @version 3.5.0 | ||||
|  */ | ||||
| @Target(ElementType.METHOD) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Documented | ||||
| public @interface DataColumn { | ||||
|  | ||||
|     /** | ||||
|      * 数据权限模板的占位符关键字,默认为 "deptName" | ||||
|      * | ||||
|      * @return 占位符关键字数组 | ||||
|      */ | ||||
|     String[] key() default "deptName"; | ||||
|  | ||||
|     /** | ||||
|      * 数据权限模板的占位符替换值,默认为 "dept_id" | ||||
|      * | ||||
|      * @return 占位符替换值数组 | ||||
|      */ | ||||
|     String[] value() default "dept_id"; | ||||
|  | ||||
|     /** | ||||
|      * 权限标识符 用于通过菜单权限标识符来获取数据权限 | ||||
|      * 拥有此标识符的角色 将不会拼接此角色的数据过滤sql | ||||
|      * | ||||
|      * @return 权限标识符 | ||||
|      */ | ||||
|     String permission() default ""; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,30 @@ | ||||
| package org.dromara.common.mybatis.annotation; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| /** | ||||
|  * 数据权限组注解,用于标记数据权限配置数组 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  * @version 3.5.0 | ||||
|  */ | ||||
| @Target({ElementType.METHOD, ElementType.TYPE}) | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Documented | ||||
| public @interface DataPermission { | ||||
|  | ||||
|     /** | ||||
|      * 数据权限配置数组,用于指定数据权限的占位符关键字和替换值 | ||||
|      * | ||||
|      * @return 数据权限配置数组 | ||||
|      */ | ||||
|     DataColumn[] value(); | ||||
|  | ||||
|     /** | ||||
|      * 权限拼接标识符(用于指定连接语句的sql符号) | ||||
|      * 如不填 默认 select 用 OR 其他语句用 AND | ||||
|      * 内容 OR 或者 AND | ||||
|      */ | ||||
|     String joinStr() default ""; | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,50 @@ | ||||
| package org.dromara.common.mybatis.aspect; | ||||
|  | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.aspectj.lang.JoinPoint; | ||||
| import org.aspectj.lang.annotation.AfterReturning; | ||||
| import org.aspectj.lang.annotation.AfterThrowing; | ||||
| import org.aspectj.lang.annotation.Aspect; | ||||
| import org.aspectj.lang.annotation.Before; | ||||
| import org.dromara.common.mybatis.annotation.DataPermission; | ||||
| import org.dromara.common.mybatis.helper.DataPermissionHelper; | ||||
|  | ||||
| /** | ||||
|  * 数据权限处理 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Slf4j | ||||
| @Aspect | ||||
| public class DataPermissionAspect { | ||||
|  | ||||
|     /** | ||||
|      * 处理请求前执行 | ||||
|      */ | ||||
|     @Before(value = "@annotation(dataPermission)") | ||||
|     public void doBefore(JoinPoint joinPoint, DataPermission dataPermission) { | ||||
|         DataPermissionHelper.setPermission(dataPermission); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 处理完请求后执行 | ||||
|      * | ||||
|      * @param joinPoint 切点 | ||||
|      */ | ||||
|     @AfterReturning(pointcut = "@annotation(dataPermission)") | ||||
|     public void doAfterReturning(JoinPoint joinPoint, DataPermission dataPermission) { | ||||
|         DataPermissionHelper.removePermission(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 拦截异常操作 | ||||
|      * | ||||
|      * @param joinPoint 切点 | ||||
|      * @param e         异常 | ||||
|      */ | ||||
|     @AfterThrowing(value = "@annotation(dataPermission)", throwing = "e") | ||||
|     public void doAfterThrowing(JoinPoint joinPoint, DataPermission dataPermission, Exception e) { | ||||
|         DataPermissionHelper.removePermission(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,149 @@ | ||||
| package org.dromara.common.mybatis.config; | ||||
|  | ||||
| import cn.hutool.core.net.NetUtil; | ||||
| import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; | ||||
| import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler; | ||||
| import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator; | ||||
| import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator; | ||||
| import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; | ||||
| import org.dromara.common.core.factory.YmlPropertySourceFactory; | ||||
| import org.dromara.common.core.utils.SpringUtils; | ||||
| import org.dromara.common.mybatis.aspect.DataPermissionAspect; | ||||
| import org.dromara.common.mybatis.handler.InjectionMetaObjectHandler; | ||||
| import org.dromara.common.mybatis.handler.MybatisExceptionHandler; | ||||
| import org.dromara.common.mybatis.handler.PlusPostInitTableInfoHandler; | ||||
| import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor; | ||||
| import org.dromara.common.mybatis.service.SysDataScopeService; | ||||
| import org.mybatis.spring.annotation.MapperScan; | ||||
| import org.springframework.beans.BeansException; | ||||
| import org.springframework.boot.autoconfigure.AutoConfiguration; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.PropertySource; | ||||
| import org.springframework.transaction.annotation.EnableTransactionManagement; | ||||
|  | ||||
| /** | ||||
|  * mybatis-plus配置类(下方注释有插件介绍) | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @AutoConfiguration | ||||
| @EnableTransactionManagement(proxyTargetClass = true) | ||||
| @MapperScan("${mybatis-plus.mapperPackage}") | ||||
| @PropertySource(value = "classpath:common-mybatis.yml", factory = YmlPropertySourceFactory.class) | ||||
| public class MybatisPlusConfiguration { | ||||
|  | ||||
|     @Bean | ||||
|     public MybatisPlusInterceptor mybatisPlusInterceptor() { | ||||
|         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); | ||||
|         // 多租户插件 必须放到第一位 | ||||
|         try { | ||||
|             TenantLineInnerInterceptor tenant = SpringUtils.getBean(TenantLineInnerInterceptor.class); | ||||
|             interceptor.addInnerInterceptor(tenant); | ||||
|         } catch (BeansException ignore) { | ||||
|         } | ||||
|         // 数据权限处理 | ||||
|         interceptor.addInnerInterceptor(dataPermissionInterceptor()); | ||||
|         // 分页插件 | ||||
|         interceptor.addInnerInterceptor(paginationInnerInterceptor()); | ||||
|         // 乐观锁插件 | ||||
|         interceptor.addInnerInterceptor(optimisticLockerInnerInterceptor()); | ||||
|         return interceptor; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 数据权限拦截器 | ||||
|      */ | ||||
|     public PlusDataPermissionInterceptor dataPermissionInterceptor() { | ||||
|         return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage")); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 数据权限切面处理器 | ||||
|      */ | ||||
|     @Bean | ||||
|     public DataPermissionAspect dataPermissionAspect() { | ||||
|         return new DataPermissionAspect(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 分页插件,自动识别数据库类型 | ||||
|      */ | ||||
|     public PaginationInnerInterceptor paginationInnerInterceptor() { | ||||
|         PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(); | ||||
|         // 分页合理化 | ||||
|         paginationInnerInterceptor.setOverflow(true); | ||||
|         return paginationInnerInterceptor; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 乐观锁插件 | ||||
|      */ | ||||
|     public OptimisticLockerInnerInterceptor optimisticLockerInnerInterceptor() { | ||||
|         return new OptimisticLockerInnerInterceptor(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 元对象字段填充控制器 | ||||
|      */ | ||||
|     @Bean | ||||
|     public MetaObjectHandler metaObjectHandler() { | ||||
|         return new InjectionMetaObjectHandler(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 使用网卡信息绑定雪花生成器 | ||||
|      * 防止集群雪花ID重复 | ||||
|      */ | ||||
|     @Bean | ||||
|     public IdentifierGenerator idGenerator() { | ||||
|         return new DefaultIdentifierGenerator(NetUtil.getLocalhost()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 异常处理器 | ||||
|      */ | ||||
|     @Bean | ||||
|     public MybatisExceptionHandler mybatisExceptionHandler() { | ||||
|         return new MybatisExceptionHandler(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 数据权限处理实现 | ||||
|      */ | ||||
|     @Bean("sdss") | ||||
|     public SysDataScopeService sysDataScopeService() { | ||||
|         return new SysDataScopeService(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 初始化表对象处理器 | ||||
|      */ | ||||
|     @Bean | ||||
|     public PostInitTableInfoHandler postInitTableInfoHandler() { | ||||
|         return new PlusPostInitTableInfoHandler(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * PaginationInnerInterceptor 分页插件,自动识别数据库类型 | ||||
|      * https://baomidou.com/pages/97710a/ | ||||
|      * OptimisticLockerInnerInterceptor 乐观锁插件 | ||||
|      * https://baomidou.com/pages/0d93c0/ | ||||
|      * MetaObjectHandler 元对象字段填充控制器 | ||||
|      * https://baomidou.com/pages/4c6bcf/ | ||||
|      * ISqlInjector sql注入器 | ||||
|      * https://baomidou.com/pages/42ea4a/ | ||||
|      * BlockAttackInnerInterceptor 如果是对全表的删除或更新操作,就会终止该操作 | ||||
|      * https://baomidou.com/pages/f9a237/ | ||||
|      * IllegalSQLInnerInterceptor sql性能规范插件(垃圾SQL拦截) | ||||
|      * IdentifierGenerator 自定义主键策略 | ||||
|      * https://baomidou.com/pages/568eb2/ | ||||
|      * TenantLineInnerInterceptor 多租户插件 | ||||
|      * https://baomidou.com/pages/aef2f2/ | ||||
|      * DynamicTableNameInnerInterceptor 动态表名插件 | ||||
|      * https://baomidou.com/pages/2a45ff/ | ||||
|      */ | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,70 @@ | ||||
| package org.dromara.common.mybatis.core.domain; | ||||
|  | ||||
| import com.baomidou.mybatisplus.annotation.FieldFill; | ||||
| import com.baomidou.mybatisplus.annotation.TableField; | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import com.fasterxml.jackson.annotation.JsonInclude; | ||||
| import lombok.Data; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.Date; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * Entity基类 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| public class BaseEntity implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 搜索值 | ||||
|      */ | ||||
|     @JsonIgnore | ||||
|     @TableField(exist = false) | ||||
|     private String searchValue; | ||||
|  | ||||
|     /** | ||||
|      * 创建部门 | ||||
|      */ | ||||
|     @TableField(fill = FieldFill.INSERT) | ||||
|     private Long createDept; | ||||
|  | ||||
|     /** | ||||
|      * 创建者 | ||||
|      */ | ||||
|     @TableField(fill = FieldFill.INSERT) | ||||
|     private Long createBy; | ||||
|  | ||||
|     /** | ||||
|      * 创建时间 | ||||
|      */ | ||||
|     @TableField(fill = FieldFill.INSERT) | ||||
|     private Date createTime; | ||||
|  | ||||
|     /** | ||||
|      * 更新者 | ||||
|      */ | ||||
|     @TableField(fill = FieldFill.INSERT_UPDATE) | ||||
|     private Long updateBy; | ||||
|  | ||||
|     /** | ||||
|      * 更新时间 | ||||
|      */ | ||||
|     @TableField(fill = FieldFill.INSERT_UPDATE) | ||||
|     private Date updateTime; | ||||
|  | ||||
|     /** | ||||
|      * 请求参数 | ||||
|      */ | ||||
|     @JsonInclude(JsonInclude.Include.NON_EMPTY) | ||||
|     @TableField(exist = false) | ||||
|     private Map<String, Object> params = new HashMap<>(); | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,343 @@ | ||||
| package org.dromara.common.mybatis.core.mapper; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import com.baomidou.mybatisplus.core.conditions.Wrapper; | ||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||
| import com.baomidou.mybatisplus.core.mapper.BaseMapper; | ||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | ||||
| import com.baomidou.mybatisplus.core.toolkit.ReflectionKit; | ||||
| import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||||
| import com.baomidou.mybatisplus.extension.toolkit.Db; | ||||
| import org.apache.ibatis.logging.Log; | ||||
| import org.apache.ibatis.logging.LogFactory; | ||||
| import org.dromara.common.core.utils.MapstructUtils; | ||||
|  | ||||
| import java.io.Serializable; | ||||
| import java.util.Collection; | ||||
| import java.util.List; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
| import java.util.function.Function; | ||||
| import java.util.stream.Collectors; | ||||
|  | ||||
| /** | ||||
|  * 自定义 Mapper 接口, 实现 自定义扩展 | ||||
|  * | ||||
|  * @param <T> table 泛型 | ||||
|  * @param <V> vo 泛型 | ||||
|  * @author Lion Li | ||||
|  * @since 2021-05-13 | ||||
|  */ | ||||
| @SuppressWarnings("unchecked") | ||||
| public interface BaseMapperPlus<T, V> extends BaseMapper<T> { | ||||
|  | ||||
|     Log log = LogFactory.getLog(BaseMapperPlus.class); | ||||
|  | ||||
|     /** | ||||
|      * 获取当前类的泛型类型 V 的 Class 对象 | ||||
|      * <p> | ||||
|      * 该方法使用反射机制从当前类(继承自 BaseMapperPlus 类)的泛型参数中获取第一个泛型类型 V 的 Class 对象 | ||||
|      * | ||||
|      * @return 当前类的泛型类型 V 的 Class 对象 | ||||
|      */ | ||||
|     default Class<V> currentVoClass() { | ||||
|         return (Class<V>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 1); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前类的泛型类型 T 的 Class 对象 | ||||
|      * <p> | ||||
|      * 该方法使用反射机制从当前类(继承自 BaseMapperPlus 类)的泛型参数中获取第一个泛型类型 T 的 Class 对象 | ||||
|      * | ||||
|      * @return 当前类的泛型类型 T 的 Class 对象 | ||||
|      */ | ||||
|     default Class<T> currentModelClass() { | ||||
|         return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseMapperPlus.class, 0); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 使用默认的查询条件查询并返回结果列表 | ||||
|      * | ||||
|      * @return 返回查询结果的列表 | ||||
|      */ | ||||
|     default List<T> selectList() { | ||||
|         return this.selectList(new QueryWrapper<>()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 批量插入实体对象集合 | ||||
|      * | ||||
|      * @param entityList 实体对象集合 | ||||
|      * @return 插入操作是否成功的布尔值 | ||||
|      */ | ||||
|     default boolean insertBatch(Collection<T> entityList) { | ||||
|         return Db.saveBatch(entityList); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 批量根据ID更新实体对象集合 | ||||
|      * | ||||
|      * @param entityList 实体对象集合 | ||||
|      * @return 更新操作是否成功的布尔值 | ||||
|      */ | ||||
|     default boolean updateBatchById(Collection<T> entityList) { | ||||
|         return Db.updateBatchById(entityList); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 批量插入或更新实体对象集合 | ||||
|      * | ||||
|      * @param entityList 实体对象集合 | ||||
|      * @return 插入或更新操作是否成功的布尔值 | ||||
|      */ | ||||
|     default boolean insertOrUpdateBatch(Collection<T> entityList) { | ||||
|         return Db.saveOrUpdateBatch(entityList); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 批量插入实体对象集合并指定批处理大小 | ||||
|      * | ||||
|      * @param entityList 实体对象集合 | ||||
|      * @param batchSize  批处理大小 | ||||
|      * @return 插入操作是否成功的布尔值 | ||||
|      */ | ||||
|     default boolean insertBatch(Collection<T> entityList, int batchSize) { | ||||
|         return Db.saveBatch(entityList, batchSize); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 批量根据ID更新实体对象集合并指定批处理大小 | ||||
|      * | ||||
|      * @param entityList 实体对象集合 | ||||
|      * @param batchSize  批处理大小 | ||||
|      * @return 更新操作是否成功的布尔值 | ||||
|      */ | ||||
|     default boolean updateBatchById(Collection<T> entityList, int batchSize) { | ||||
|         return Db.updateBatchById(entityList, batchSize); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 批量插入或更新实体对象集合并指定批处理大小 | ||||
|      * | ||||
|      * @param entityList 实体对象集合 | ||||
|      * @param batchSize  批处理大小 | ||||
|      * @return 插入或更新操作是否成功的布尔值 | ||||
|      */ | ||||
|     default boolean insertOrUpdateBatch(Collection<T> entityList, int batchSize) { | ||||
|         return Db.saveOrUpdateBatch(entityList, batchSize); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据ID查询单个VO对象 | ||||
|      * | ||||
|      * @param id 主键ID | ||||
|      * @return 查询到的单个VO对象 | ||||
|      */ | ||||
|     default V selectVoById(Serializable id) { | ||||
|         return selectVoById(id, this.currentVoClass()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据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); | ||||
|         if (ObjectUtil.isNull(obj)) { | ||||
|             return null; | ||||
|         } | ||||
|         return MapstructUtils.convert(obj, voClass); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据ID集合批量查询VO对象列表 | ||||
|      * | ||||
|      * @param idList 主键ID集合 | ||||
|      * @return 查询到的VO对象列表 | ||||
|      */ | ||||
|     default List<V> selectVoByIds(Collection<? extends Serializable> idList) { | ||||
|         return selectVoByIds(idList, this.currentVoClass()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据ID集合批量查询实体对象列表,并将其转换为指定的VO对象列表 | ||||
|      * | ||||
|      * @param idList  主键ID集合 | ||||
|      * @param voClass 要转换的VO类的Class对象 | ||||
|      * @param <C>     VO类的类型 | ||||
|      * @return 查询到的VO对象列表,经过转换为指定的VO类后返回 | ||||
|      */ | ||||
|     default <C> List<C> selectVoByIds(Collection<? extends Serializable> idList, Class<C> voClass) { | ||||
|         List<T> list = this.selectByIds(idList); | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             return CollUtil.newArrayList(); | ||||
|         } | ||||
|         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()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据查询条件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); | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             return CollUtil.newArrayList(); | ||||
|         } | ||||
|         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); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据条件查询单个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) { | ||||
|         T obj = this.selectOne(wrapper); | ||||
|         if (ObjectUtil.isNull(obj)) { | ||||
|             return null; | ||||
|         } | ||||
|         return MapstructUtils.convert(obj, voClass); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据条件查询单个实体对象,并将其转换为指定的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); | ||||
|         if (ObjectUtil.isNull(obj)) { | ||||
|             return null; | ||||
|         } | ||||
|         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()); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据条件查询实体对象列表,并将其转换为指定的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); | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             return CollUtil.newArrayList(); | ||||
|         } | ||||
|         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对象分页列表 | ||||
|      * | ||||
|      * @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; | ||||
|         } | ||||
|         voPage.setRecords(MapstructUtils.convert(list, voClass)); | ||||
|         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()); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,129 @@ | ||||
| package org.dromara.common.mybatis.core.page; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import com.baomidou.mybatisplus.core.metadata.OrderItem; | ||||
| import com.baomidou.mybatisplus.extension.plugins.pagination.Page; | ||||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.dromara.common.core.exception.ServiceException; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import org.dromara.common.core.utils.sql.SqlUtil; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 分页查询实体类 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class PageQuery implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 分页大小 | ||||
|      */ | ||||
|     private Integer pageSize; | ||||
|  | ||||
|     /** | ||||
|      * 当前页数 | ||||
|      */ | ||||
|     private Integer pageNum; | ||||
|  | ||||
|     /** | ||||
|      * 排序列 | ||||
|      */ | ||||
|     private String orderByColumn; | ||||
|  | ||||
|     /** | ||||
|      * 排序的方向desc或者asc | ||||
|      */ | ||||
|     private String isAsc; | ||||
|  | ||||
|     /** | ||||
|      * 当前记录起始索引 默认值 | ||||
|      */ | ||||
|     public static final int DEFAULT_PAGE_NUM = 1; | ||||
|  | ||||
|     /** | ||||
|      * 每页显示记录数 默认值 默认查全部 | ||||
|      */ | ||||
|     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); | ||||
|         if (pageNum <= 0) { | ||||
|             pageNum = DEFAULT_PAGE_NUM; | ||||
|         } | ||||
|         Page<T> page = new Page<>(pageNum, pageSize); | ||||
|         List<OrderItem> orderItems = buildOrderItem(); | ||||
|         if (CollUtil.isNotEmpty(orderItems)) { | ||||
|             page.addOrder(orderItems); | ||||
|         } | ||||
|         return page; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 构建排序 | ||||
|      * | ||||
|      * 支持的用法如下: | ||||
|      * {isAsc:"asc",orderByColumn:"id"} order by id asc | ||||
|      * {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc | ||||
|      * {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc | ||||
|      * {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc | ||||
|      */ | ||||
|     private List<OrderItem> buildOrderItem() { | ||||
|         if (StringUtils.isBlank(orderByColumn) || StringUtils.isBlank(isAsc)) { | ||||
|             return null; | ||||
|         } | ||||
|         String orderBy = SqlUtil.escapeOrderBySql(orderByColumn); | ||||
|         orderBy = StringUtils.toUnderScoreCase(orderBy); | ||||
|  | ||||
|         // 兼容前端排序类型 | ||||
|         isAsc = StringUtils.replaceEach(isAsc, new String[]{"ascending", "descending"}, new String[]{"asc", "desc"}); | ||||
|  | ||||
|         String[] orderByArr = orderBy.split(StringUtils.SEPARATOR); | ||||
|         String[] isAscArr = isAsc.split(StringUtils.SEPARATOR); | ||||
|         if (isAscArr.length != 1 && isAscArr.length != orderByArr.length) { | ||||
|             throw new ServiceException("排序参数有误"); | ||||
|         } | ||||
|  | ||||
|         List<OrderItem> list = new ArrayList<>(); | ||||
|         // 每个字段各自排序 | ||||
|         for (int i = 0; i < orderByArr.length; i++) { | ||||
|             String orderByStr = orderByArr[i]; | ||||
|             String isAscStr = isAscArr.length == 1 ? isAscArr[0] : isAscArr[i]; | ||||
|             if ("asc".equals(isAscStr)) { | ||||
|                 list.add(OrderItem.asc(orderByStr)); | ||||
|             } else if ("desc".equals(isAscStr)) { | ||||
|                 list.add(OrderItem.desc(orderByStr)); | ||||
|             } else { | ||||
|                 throw new ServiceException("排序参数有误"); | ||||
|             } | ||||
|         } | ||||
|         return list; | ||||
|     } | ||||
|  | ||||
|     @JsonIgnore | ||||
|     public Integer getFirstNum() { | ||||
|         return (pageNum - 1) * pageSize; | ||||
|     } | ||||
|  | ||||
|     public PageQuery(Integer pageSize, Integer pageNum) { | ||||
|         this.pageSize = pageSize; | ||||
|         this.pageNum = pageNum; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,105 @@ | ||||
| package org.dromara.common.mybatis.core.page; | ||||
|  | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.http.HttpStatus; | ||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | ||||
| import lombok.Data; | ||||
| import lombok.NoArgsConstructor; | ||||
|  | ||||
| import java.io.Serial; | ||||
| import java.io.Serializable; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 表格分页数据对象 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Data | ||||
| @NoArgsConstructor | ||||
| public class TableDataInfo<T> implements Serializable { | ||||
|  | ||||
|     @Serial | ||||
|     private static final long serialVersionUID = 1L; | ||||
|  | ||||
|     /** | ||||
|      * 总记录数 | ||||
|      */ | ||||
|     private long total; | ||||
|  | ||||
|     /** | ||||
|      * 列表数据 | ||||
|      */ | ||||
|     private List<T> rows; | ||||
|  | ||||
|     /** | ||||
|      * 消息状态码 | ||||
|      */ | ||||
|     private int code; | ||||
|  | ||||
|     /** | ||||
|      * 消息内容 | ||||
|      */ | ||||
|     private String msg; | ||||
|  | ||||
|     /** | ||||
|      * 分页 | ||||
|      * | ||||
|      * @param list  列表数据 | ||||
|      * @param total 总记录数 | ||||
|      */ | ||||
|     public TableDataInfo(List<T> list, long total) { | ||||
|         this.rows = list; | ||||
|         this.total = total; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据分页对象构建表格分页数据对象 | ||||
|      */ | ||||
|     public static <T> TableDataInfo<T> build(IPage<T> page) { | ||||
|         TableDataInfo<T> rspData = new TableDataInfo<>(); | ||||
|         rspData.setCode(HttpStatus.HTTP_OK); | ||||
|         rspData.setMsg("查询成功"); | ||||
|         rspData.setRows(page.getRecords()); | ||||
|         rspData.setTotal(page.getTotal()); | ||||
|         return rspData; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据数据列表构建表格分页数据对象 | ||||
|      */ | ||||
|     public static <T> TableDataInfo<T> build(List<T> list) { | ||||
|         TableDataInfo<T> rspData = new TableDataInfo<>(); | ||||
|         rspData.setCode(HttpStatus.HTTP_OK); | ||||
|         rspData.setMsg("查询成功"); | ||||
|         rspData.setRows(list); | ||||
|         rspData.setTotal(list.size()); | ||||
|         return rspData; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 构建表格分页数据对象 | ||||
|      */ | ||||
|     public static <T> TableDataInfo<T> build() { | ||||
|         TableDataInfo<T> rspData = new TableDataInfo<>(); | ||||
|         rspData.setCode(HttpStatus.HTTP_OK); | ||||
|         rspData.setMsg("查询成功"); | ||||
|         return rspData; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 根据原始数据列表和分页参数,构建表格分页数据对象(用于假分页) | ||||
|      * | ||||
|      * @param list 原始数据列表(全部数据) | ||||
|      * @param page 分页参数对象(包含当前页码、每页大小等) | ||||
|      * @return 构造好的分页结果 TableDataInfo<T> | ||||
|      */ | ||||
|     public static <T> TableDataInfo<T> build(List<T> list, IPage<T> page) { | ||||
|         if (CollUtil.isEmpty(list)) { | ||||
|             return TableDataInfo.build(); | ||||
|         } | ||||
|         List<T> pageList = CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), list); | ||||
|         return new TableDataInfo<>(pageList, list.size()); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,58 @@ | ||||
| package org.dromara.common.mybatis.enums; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
|  | ||||
| /** | ||||
|  * 数据库类型 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum DataBaseType { | ||||
|  | ||||
|     /** | ||||
|      * MySQL | ||||
|      */ | ||||
|     MY_SQL("MySQL"), | ||||
|  | ||||
|     /** | ||||
|      * Oracle | ||||
|      */ | ||||
|     ORACLE("Oracle"), | ||||
|  | ||||
|     /** | ||||
|      * PostgreSQL | ||||
|      */ | ||||
|     POSTGRE_SQL("PostgreSQL"), | ||||
|  | ||||
|     /** | ||||
|      * SQL Server | ||||
|      */ | ||||
|     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; | ||||
|         } | ||||
|         for (DataBaseType type : values()) { | ||||
|             if (type.getType().equals(databaseProductName)) { | ||||
|                 return type; | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,87 @@ | ||||
| package org.dromara.common.mybatis.enums; | ||||
|  | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.Getter; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import org.dromara.common.mybatis.helper.DataPermissionHelper; | ||||
| import org.dromara.system.api.model.LoginUser; | ||||
|  | ||||
| /** | ||||
|  * 数据权限类型枚举 | ||||
|  * <p> | ||||
|  * 支持使用 SpEL 模板表达式定义 SQL 查询条件 | ||||
|  * 内置数据: | ||||
|  * - {@code user}: 当前登录用户信息,参考 {@link LoginUser} | ||||
|  * 内置服务: | ||||
|  * - {@code sdss}: 系统数据权限服务,参考 SysDataScopeService | ||||
|  * 如需扩展数据,可以通过 {@link DataPermissionHelper} 进行操作 | ||||
|  * 如需扩展服务,可以通过 SysDataScopeService 自行编写 | ||||
|  * </p> | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  * @version 3.5.0 | ||||
|  */ | ||||
| @Getter | ||||
| @AllArgsConstructor | ||||
| public enum DataScopeType { | ||||
|  | ||||
|     /** | ||||
|      * 全部数据权限 | ||||
|      */ | ||||
|     ALL("1", "", ""), | ||||
|  | ||||
|     /** | ||||
|      * 自定数据权限 | ||||
|      */ | ||||
|     CUSTOM("2", " #{#deptName} IN ( #{@sdss.getRoleCustom( #user.roleId )} ) ", " 1 = 0 "), | ||||
|  | ||||
|     /** | ||||
|      * 部门数据权限 | ||||
|      */ | ||||
|     DEPT("3", " #{#deptName} = #{#user.deptId} ", " 1 = 0 "), | ||||
|  | ||||
|     /** | ||||
|      * 部门及以下数据权限 | ||||
|      */ | ||||
|     DEPT_AND_CHILD("4", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} )", " 1 = 0 "), | ||||
|  | ||||
|     /** | ||||
|      * 仅本人数据权限 | ||||
|      */ | ||||
|     SELF("5", " #{#userName} = #{#user.userId} ", " 1 = 0 "), | ||||
|  | ||||
|     /** | ||||
|      * 部门及以下或本人数据权限 | ||||
|      */ | ||||
|     DEPT_AND_CHILD_OR_SELF("6", " #{#deptName} IN ( #{@sdss.getDeptAndChild( #user.deptId )} ) OR #{#userName} = #{#user.userId} ", " 1 = 0 "); | ||||
|  | ||||
|     private final String code; | ||||
|  | ||||
|     /** | ||||
|      * SpEL 模板表达式,用于构建 SQL 条件 | ||||
|      */ | ||||
|     private final String sqlTemplate; | ||||
|  | ||||
|     /** | ||||
|      * 如果不满足 {@code sqlTemplate} 的条件,则使用此默认 SQL 表达式 | ||||
|      */ | ||||
|     private final String elseSql; | ||||
|  | ||||
|     /** | ||||
|      * 根据枚举代码查找对应的枚举值 | ||||
|      * | ||||
|      * @param code 枚举代码 | ||||
|      * @return 对应的枚举值,如果未找到则返回 null | ||||
|      */ | ||||
|     public static DataScopeType findCode(String code) { | ||||
|         if (StringUtils.isBlank(code)) { | ||||
|             return null; | ||||
|         } | ||||
|         for (DataScopeType type : values()) { | ||||
|             if (type.getCode().equals(code)) { | ||||
|                 return type; | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,28 @@ | ||||
| package org.dromara.common.mybatis.filter; | ||||
|  | ||||
| import org.dromara.common.mybatis.helper.DataPermissionHelper; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.apache.dubbo.common.constants.CommonConstants; | ||||
| import org.apache.dubbo.common.extension.Activate; | ||||
| import org.apache.dubbo.rpc.*; | ||||
|  | ||||
| import java.util.Map; | ||||
|  | ||||
| /** | ||||
|  * dubbo 数据权限参数传递 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Slf4j | ||||
| @Activate(group = {CommonConstants.CONSUMER}) | ||||
| public class DubboDataPermissionFilter implements Filter { | ||||
|  | ||||
|     @Override | ||||
|     public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException { | ||||
|         RpcServiceContext context = RpcContext.getServiceContext(); | ||||
|         Map<String, Object> dataPermissionContext = DataPermissionHelper.getContext(); | ||||
|         context.setObjectAttachment(DataPermissionHelper.DATA_PERMISSION_KEY, dataPermissionContext); | ||||
|         return invoker.invoke(invocation); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,112 @@ | ||||
| package org.dromara.common.mybatis.handler; | ||||
|  | ||||
| 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.exception.ServiceException; | ||||
| import org.dromara.common.core.utils.ObjectUtils; | ||||
| import org.dromara.common.mybatis.core.domain.BaseEntity; | ||||
| import org.dromara.common.satoken.utils.LoginHelper; | ||||
| import org.dromara.system.api.model.LoginUser; | ||||
|  | ||||
| import java.util.Date; | ||||
|  | ||||
| /** | ||||
|  * MP注入处理器 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Slf4j | ||||
| public class InjectionMetaObjectHandler implements MetaObjectHandler { | ||||
|  | ||||
|     /** | ||||
|      * 如果用户不存在默认注入-1代表无用户 | ||||
|      */ | ||||
|     private static final Long DEFAULT_USER_ID = -1L; | ||||
|  | ||||
|     /** | ||||
|      * 插入填充方法,用于在插入数据时自动填充实体对象中的创建时间、更新时间、创建人、更新人等信息 | ||||
|      * | ||||
|      * @param metaObject 元对象,用于获取原始对象并进行填充 | ||||
|      */ | ||||
|     @Override | ||||
|     public void insertFill(MetaObject metaObject) { | ||||
|         try { | ||||
|             if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) { | ||||
|                 // 获取当前时间作为创建时间和更新时间,如果创建时间不为空,则使用创建时间,否则使用当前时间 | ||||
|                 Date current = ObjectUtils.notNull(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(ObjectUtils.notNull(baseEntity.getCreateDept(), loginUser.getDeptId())); | ||||
|                     } else { | ||||
|                         // 填充创建人、更新人和创建部门信息 | ||||
|                         baseEntity.setCreateBy(DEFAULT_USER_ID); | ||||
|                         baseEntity.setUpdateBy(DEFAULT_USER_ID); | ||||
|                         baseEntity.setCreateDept(ObjectUtils.notNull(baseEntity.getCreateDept(), DEFAULT_USER_ID)); | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 Date date = new Date(); | ||||
|                 this.strictInsertFill(metaObject, "createTime", Date.class, date); | ||||
|                 this.strictInsertFill(metaObject, "updateTime", Date.class, date); | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 更新填充方法,用于在更新数据时自动填充实体对象中的更新时间和更新人信息 | ||||
|      * | ||||
|      * @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); | ||||
|                 } else { | ||||
|                     baseEntity.setUpdateBy(DEFAULT_USER_ID); | ||||
|                 } | ||||
|             } else { | ||||
|                 this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date()); | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             throw new ServiceException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前登录用户信息 | ||||
|      * | ||||
|      * @return 当前登录用户的信息,如果用户未登录则返回 null | ||||
|      */ | ||||
|     private LoginUser getLoginUser() { | ||||
|         LoginUser loginUser; | ||||
|         try { | ||||
|             loginUser = LoginHelper.getLoginUser(); | ||||
|         } catch (Exception e) { | ||||
|             return null; | ||||
|         } | ||||
|         return loginUser; | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,47 @@ | ||||
| package org.dromara.common.mybatis.handler; | ||||
|  | ||||
| import cn.hutool.http.HttpStatus; | ||||
| 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; | ||||
|  | ||||
| /** | ||||
|  * Mybatis异常处理器 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Slf4j | ||||
| @RestControllerAdvice | ||||
| public class MybatisExceptionHandler { | ||||
|  | ||||
|     /** | ||||
|      * 主键或UNIQUE索引,数据重复异常 | ||||
|      */ | ||||
|     @ExceptionHandler(DuplicateKeyException.class) | ||||
|     public R<Void> handleDuplicateKeyException(DuplicateKeyException e, HttpServletRequest request) { | ||||
|         String requestURI = request.getRequestURI(); | ||||
|         log.error("请求地址'{}',数据库中已存在记录'{}'", requestURI, e.getMessage()); | ||||
|         return R.fail(HttpStatus.HTTP_CONFLICT, "数据库中已存在该记录,请联系管理员确认"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Mybatis系统异常 通用处理 | ||||
|      */ | ||||
|     @ExceptionHandler(MyBatisSystemException.class) | ||||
|     public R<Void> handleCannotFindDataSourceException(MyBatisSystemException e, HttpServletRequest request) { | ||||
|         String requestURI = request.getRequestURI(); | ||||
|         String message = e.getMessage(); | ||||
|         if (StringUtils.contains(message, "CannotFindDataSourceException")) { | ||||
|             log.error("请求地址'{}', 未找到数据源", requestURI); | ||||
|             return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, "未找到数据源,请联系管理员确认"); | ||||
|         } | ||||
|         log.error("请求地址'{}', Mybatis系统异常", requestURI, e); | ||||
|         return R.fail(HttpStatus.HTTP_INTERNAL_ERROR, message); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,357 @@ | ||||
| package org.dromara.common.mybatis.handler; | ||||
|  | ||||
| import cn.hutool.core.annotation.AnnotationUtil; | ||||
| import cn.hutool.core.collection.CollUtil; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import lombok.AllArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import net.sf.jsqlparser.JSQLParserException; | ||||
| import net.sf.jsqlparser.expression.Expression; | ||||
| import net.sf.jsqlparser.expression.operators.conditional.AndExpression; | ||||
| import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; | ||||
| import net.sf.jsqlparser.parser.CCJSqlParserUtil; | ||||
| import org.apache.ibatis.io.Resources; | ||||
| import org.dromara.common.core.exception.ServiceException; | ||||
| import org.dromara.common.core.utils.SpringUtils; | ||||
| import org.dromara.common.core.utils.StreamUtils; | ||||
| import org.dromara.common.core.utils.StringUtils; | ||||
| import org.dromara.common.mybatis.annotation.DataColumn; | ||||
| import org.dromara.common.mybatis.annotation.DataPermission; | ||||
| import org.dromara.common.mybatis.enums.DataScopeType; | ||||
| import org.dromara.common.mybatis.helper.DataPermissionHelper; | ||||
| import org.dromara.common.satoken.utils.LoginHelper; | ||||
| import org.dromara.system.api.model.LoginUser; | ||||
| import org.dromara.system.api.model.RoleDTO; | ||||
| import org.springframework.context.ConfigurableApplicationContext; | ||||
| import org.springframework.context.expression.BeanFactoryResolver; | ||||
| import org.springframework.core.io.Resource; | ||||
| import org.springframework.core.io.support.PathMatchingResourcePatternResolver; | ||||
| import org.springframework.core.io.support.ResourcePatternResolver; | ||||
| import org.springframework.core.type.ClassMetadata; | ||||
| import org.springframework.core.type.classreading.CachingMetadataReaderFactory; | ||||
| import org.springframework.expression.*; | ||||
| import org.springframework.expression.common.TemplateParserContext; | ||||
| import org.springframework.expression.spel.standard.SpelExpressionParser; | ||||
| import org.springframework.expression.spel.support.StandardEvaluationContext; | ||||
| import org.springframework.util.ClassUtils; | ||||
|  | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.*; | ||||
| import java.util.concurrent.ConcurrentHashMap; | ||||
| import java.util.function.Function; | ||||
|  | ||||
| /** | ||||
|  * 数据权限过滤 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  * @version 3.5.0 | ||||
|  */ | ||||
| @Slf4j | ||||
| public class PlusDataPermissionHandler { | ||||
|  | ||||
|     /** | ||||
|      * 类名称与注解的映射关系缓存(由于aop无法拦截mybatis接口类上的注解 只能通过启动预扫描的方式进行) | ||||
|      */ | ||||
|     private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>(); | ||||
|  | ||||
|     /** | ||||
|      * spel 解析器 | ||||
|      */ | ||||
|     private final ExpressionParser parser = new SpelExpressionParser(); | ||||
|     private final ParserContext parserContext = new TemplateParserContext(); | ||||
|     /** | ||||
|      * bean解析器 用于处理 spel 表达式中对 bean 的调用 | ||||
|      */ | ||||
|     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) { | ||||
|         try { | ||||
|             // 获取数据权限配置 | ||||
|             DataPermission dataPermission = getDataPermission(mappedStatementId); | ||||
|             // 获取当前登录用户信息 | ||||
|             LoginUser currentUser = DataPermissionHelper.getVariable("user"); | ||||
|             if (ObjectUtil.isNull(currentUser)) { | ||||
|                 currentUser = LoginHelper.getLoginUser(); | ||||
|                 DataPermissionHelper.setVariable("user", currentUser); | ||||
|             } | ||||
|             // 如果是超级管理员或租户管理员,则不过滤数据 | ||||
|             if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) { | ||||
|                 return where; | ||||
|             } | ||||
|             // 构造数据过滤条件的 SQL 片段 | ||||
|             String dataFilterSql = buildDataFilter(dataPermission, isSelect); | ||||
|             if (StringUtils.isBlank(dataFilterSql)) { | ||||
|                 return where; | ||||
|             } | ||||
|             Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql); | ||||
|             // 数据权限使用单独的括号 防止与其他条件冲突 | ||||
|             ParenthesedExpressionList<Expression> parenthesis = new ParenthesedExpressionList<>(expression); | ||||
|             if (ObjectUtil.isNotNull(where)) { | ||||
|                 return new AndExpression(where, parenthesis); | ||||
|             } else { | ||||
|                 return parenthesis; | ||||
|             } | ||||
|         } catch (JSQLParserException e) { | ||||
|             throw new ServiceException("数据权限解析异常 => " + e.getMessage()); | ||||
|         } finally { | ||||
|             DataPermissionHelper.removePermission(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 构建数据过滤条件的 SQL 语句 | ||||
|      * | ||||
|      * @param dataPermission 数据权限注解 | ||||
|      * @param isSelect       标志当前操作是否为查询操作,查询操作和更新或删除操作在处理过滤条件时会有不同的处理方式 | ||||
|      * @return 构建的数据过滤条件的 SQL 语句 | ||||
|      * @throws ServiceException 如果角色的数据范围异常或者 key 与 value 的长度不匹配,则抛出 ServiceException 异常 | ||||
|      */ | ||||
|     private String buildDataFilter(DataPermission dataPermission, boolean isSelect) { | ||||
|         // 更新或删除需满足所有条件 | ||||
|         String joinStr = isSelect ? " OR " : " AND "; | ||||
|         if (StringUtils.isNotBlank(dataPermission.joinStr())) { | ||||
|             joinStr = " " + dataPermission.joinStr() + " "; | ||||
|         } | ||||
|         LoginUser user = DataPermissionHelper.getVariable("user"); | ||||
|         Object defaultValue = "-1"; | ||||
|         NullSafeStandardEvaluationContext context = new NullSafeStandardEvaluationContext(defaultValue); | ||||
|         context.addPropertyAccessor(new NullSafePropertyAccessor(context.getPropertyAccessors().get(0), defaultValue)); | ||||
|         context.setBeanResolver(beanResolver); | ||||
|         DataPermissionHelper.getContext().forEach(context::setVariable); | ||||
|         Set<String> conditions = new HashSet<>(); | ||||
|         // 优先设置变量 | ||||
|         List<String> keys = new ArrayList<>(); | ||||
|         Map<DataColumn, Boolean> ignoreMap = new HashMap<>(); | ||||
|         for (DataColumn dataColumn : dataPermission.value()) { | ||||
|             if (dataColumn.key().length != dataColumn.value().length) { | ||||
|                 throw new ServiceException("角色数据范围异常 => key与value长度不匹配"); | ||||
|             } | ||||
|             // 包含权限标识符 这直接跳过 | ||||
|             if (StringUtils.isNotBlank(dataColumn.permission()) && | ||||
|                 CollUtil.contains(user.getMenuPermission(), dataColumn.permission()) | ||||
|             ) { | ||||
|                 ignoreMap.put(dataColumn, Boolean.TRUE); | ||||
|                 continue; | ||||
|             } | ||||
|             // 设置注解变量 key 为表达式变量 value 为变量值 | ||||
|             for (int i = 0; i < dataColumn.key().length; i++) { | ||||
|                 context.setVariable(dataColumn.key()[i], dataColumn.value()[i]); | ||||
|             } | ||||
|             keys.addAll(Arrays.stream(dataColumn.key()).map(key -> "#" + key).toList()); | ||||
|         } | ||||
|  | ||||
|         for (RoleDTO role : user.getRoles()) { | ||||
|             user.setRoleId(role.getRoleId()); | ||||
|             // 获取角色权限泛型 | ||||
|             DataScopeType type = DataScopeType.findCode(role.getDataScope()); | ||||
|             if (ObjectUtil.isNull(type)) { | ||||
|                 throw new ServiceException("角色数据范围异常 => " + role.getDataScope()); | ||||
|             } | ||||
|             // 全部数据权限直接返回 | ||||
|             if (type == DataScopeType.ALL) { | ||||
|                 return StringUtils.EMPTY; | ||||
|             } | ||||
|             boolean isSuccess = false; | ||||
|             for (DataColumn dataColumn : dataPermission.value()) { | ||||
|                 // 包含权限标识符 这直接跳过 | ||||
|                 if (ignoreMap.containsKey(dataColumn)) { | ||||
|                     // 修复多角色与权限标识符共用问题 https://gitee.com/dromara/RuoYi-Vue-Plus/issues/IB4CS4 | ||||
|                     conditions.add(joinStr + " 1 = 1 "); | ||||
|                     isSuccess = true; | ||||
|                     continue; | ||||
|                 } | ||||
|                 // 不包含 key 变量 则不处理 | ||||
|                 if (!StringUtils.containsAny(type.getSqlTemplate(), keys.toArray(String[]::new))) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 // 当前注解不满足模板 不处理 | ||||
|                 if (!StringUtils.containsAny(type.getSqlTemplate(), dataColumn.key())) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 // 忽略数据权限 防止spel表达式内有其他sql查询导致死循环调用 | ||||
|                 String sql = DataPermissionHelper.ignore(() -> | ||||
|                     parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class) | ||||
|                 ); | ||||
|                 // 解析sql模板并填充 | ||||
|                 conditions.add(joinStr + sql); | ||||
|                 isSuccess = true; | ||||
|             } | ||||
|             // 未处理成功则填充兜底方案 | ||||
|             if (!isSuccess && StringUtils.isNotBlank(type.getElseSql())) { | ||||
|                 conditions.add(joinStr + type.getElseSql()); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (CollUtil.isNotEmpty(conditions)) { | ||||
|             String sql = StreamUtils.join(conditions, Function.identity(), ""); | ||||
|             return sql.substring(joinStr.length()); | ||||
|         } | ||||
|         return StringUtils.EMPTY; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 扫描指定包下的 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); | ||||
|                 } | ||||
|             } | ||||
|         } catch (Exception e) { | ||||
|             log.error("初始化数据安全缓存时出错:{}", e.getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 在指定的类中查找特定的注解 DataPermission,并将带有这个注解的方法或类存储到 dataPermissionCacheMap 中 | ||||
|      * | ||||
|      * @param clazz 要查找的类 | ||||
|      */ | ||||
|     private void findAnnotation(Class<?> clazz) { | ||||
|         DataPermission dataPermission; | ||||
|         for (Method method : clazz.getMethods()) { | ||||
|             if (method.isDefault() || method.isVarArgs()) { | ||||
|                 continue; | ||||
|             } | ||||
|             String mappedStatementId = clazz.getName() + "." + method.getName(); | ||||
|             if (AnnotationUtil.hasAnnotation(method, DataPermission.class)) { | ||||
|                 dataPermission = AnnotationUtil.getAnnotation(method, DataPermission.class); | ||||
|                 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 (DataPermissionHelper.getPermission() != null) { | ||||
|             return DataPermissionHelper.getPermission(); | ||||
|         } | ||||
|         // 检查缓存中是否包含映射语句 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); | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 检查给定的映射语句 ID 是否有效,即是否能够找到对应的 DataPermission 注解对象 | ||||
|      * | ||||
|      * @param mapperId 映射语句 ID | ||||
|      * @return 如果找到对应的 DataPermission 注解对象,则返回 false;否则返回 true | ||||
|      */ | ||||
|     public boolean invalid(String mapperId) { | ||||
|         return getDataPermission(mapperId) == null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 对所有null变量找不到的变量返回默认值 | ||||
|      */ | ||||
|     @AllArgsConstructor | ||||
|     private static class NullSafeStandardEvaluationContext extends StandardEvaluationContext { | ||||
|  | ||||
|         private final Object defaultValue; | ||||
|  | ||||
|         @Override | ||||
|         public Object lookupVariable(String name) { | ||||
|             Object obj = super.lookupVariable(name); | ||||
|             // 如果读取到的值是 null,则返回默认值 | ||||
|             if (obj == null) { | ||||
|                 return defaultValue; | ||||
|             } | ||||
|             return obj; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 对所有null变量找不到的变量返回默认值 委托模式 将不需要处理的方法委托给原处理器 | ||||
|      */ | ||||
|     @AllArgsConstructor | ||||
|     private static class NullSafePropertyAccessor implements PropertyAccessor { | ||||
|  | ||||
|         private final PropertyAccessor delegate; | ||||
|         private final Object defaultValue; | ||||
|  | ||||
|         @Override | ||||
|         public Class<?>[] getSpecificTargetClasses() { | ||||
|             return delegate.getSpecificTargetClasses(); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { | ||||
|             return delegate.canRead(context, target, name); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { | ||||
|             TypedValue value = delegate.read(context, target, name); | ||||
|             // 如果读取到的值是 null,则返回默认值 | ||||
|             if (value.getValue() == null) { | ||||
|                 return new TypedValue(defaultValue); | ||||
|             } | ||||
|             return value; | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { | ||||
|             return delegate.canWrite(context, target, name); | ||||
|         } | ||||
|  | ||||
|         @Override | ||||
|         public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException { | ||||
|             delegate.write(context, target, name, newValue); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,27 @@ | ||||
| package org.dromara.common.mybatis.handler; | ||||
|  | ||||
| import cn.hutool.core.convert.Convert; | ||||
| import com.baomidou.mybatisplus.core.handlers.PostInitTableInfoHandler; | ||||
| import com.baomidou.mybatisplus.core.metadata.TableInfo; | ||||
| import org.apache.ibatis.session.Configuration; | ||||
| import org.dromara.common.core.utils.SpringUtils; | ||||
| import org.dromara.common.core.utils.reflect.ReflectUtils; | ||||
|  | ||||
| /** | ||||
|  * 修改表信息初始化方式 | ||||
|  * 目前用于全局修改是否使用逻辑删除 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| public class PlusPostInitTableInfoHandler implements PostInitTableInfoHandler { | ||||
|  | ||||
|     @Override | ||||
|     public void postTableInfo(TableInfo tableInfo, Configuration configuration) { | ||||
|         String flag = SpringUtils.getProperty("mybatis-plus.enableLogicDelete", "true"); | ||||
|         // 只有关闭时 统一设置false 为true时mp自动判断不处理 | ||||
|         if (!Convert.toBool(flag)) { | ||||
|             ReflectUtils.setFieldValue(tableInfo, "withLogicDelete", false); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,81 @@ | ||||
| package org.dromara.common.mybatis.helper; | ||||
|  | ||||
| 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 javax.sql.DataSource; | ||||
| import java.sql.Connection; | ||||
| import java.sql.DatabaseMetaData; | ||||
| import java.sql.SQLException; | ||||
| import java.util.ArrayList; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 数据库助手 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| public class DataBaseHelper { | ||||
|  | ||||
|     private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class); | ||||
|  | ||||
|     /** | ||||
|      * 获取当前数据库类型 | ||||
|      */ | ||||
|     public static DataBaseType getDataBaseType() { | ||||
|         DataSource dataSource = DS.determineDataSource(); | ||||
|         try (Connection conn = dataSource.getConnection()) { | ||||
|             DatabaseMetaData metaData = conn.getMetaData(); | ||||
|             String databaseProductName = metaData.getDatabaseProductName(); | ||||
|             return DataBaseType.find(databaseProductName); | ||||
|         } catch (SQLException e) { | ||||
|             throw new ServiceException(e.getMessage()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     public static boolean isMySql() { | ||||
|         return DataBaseType.MY_SQL == getDataBaseType(); | ||||
|     } | ||||
|  | ||||
|     public static boolean isOracle() { | ||||
|         return DataBaseType.ORACLE == getDataBaseType(); | ||||
|     } | ||||
|  | ||||
|     public static boolean isPostgerSql() { | ||||
|         return DataBaseType.POSTGRE_SQL == getDataBaseType(); | ||||
|     } | ||||
|  | ||||
|     public static boolean isSqlServer() { | ||||
|         return DataBaseType.SQL_SERVER == getDataBaseType(); | ||||
|     } | ||||
|  | ||||
|     public static String findInSet(Object var1, String var2) { | ||||
|         DataBaseType dataBasyType = getDataBaseType(); | ||||
|         String var = Convert.toStr(var1); | ||||
|         if (dataBasyType == DataBaseType.SQL_SERVER) { | ||||
|             // charindex(',100,' , ',0,100,101,') <> 0 | ||||
|             return "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2); | ||||
|         } else if (dataBasyType == DataBaseType.POSTGRE_SQL) { | ||||
|             // (select strpos(',0,100,101,' , ',100,')) <> 0 | ||||
|             return "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var); | ||||
|         } else if (dataBasyType == DataBaseType.ORACLE) { | ||||
|             // instr(',0,100,101,' , ',100,') <> 0 | ||||
|             return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var); | ||||
|         } | ||||
|         // find_in_set('100' , '0,100,101') | ||||
|         return "find_in_set('%s' , %s) <> 0".formatted(var, var2); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取当前加载的数据库名 | ||||
|      */ | ||||
|     public static List<String> getDataSourceNameList() { | ||||
|         return new ArrayList<>(DS.getDataSources().keySet()); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,179 @@ | ||||
| package org.dromara.common.mybatis.helper; | ||||
|  | ||||
| import cn.dev33.satoken.context.SaHolder; | ||||
| import cn.dev33.satoken.context.model.SaStorage; | ||||
| import cn.hutool.core.collection.CollectionUtil; | ||||
| import cn.hutool.core.util.ObjectUtil; | ||||
| import com.baomidou.mybatisplus.core.plugins.IgnoreStrategy; | ||||
| import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; | ||||
| import lombok.AccessLevel; | ||||
| import lombok.NoArgsConstructor; | ||||
| import org.dromara.common.core.utils.reflect.ReflectUtils; | ||||
| import org.dromara.common.mybatis.annotation.DataPermission; | ||||
|  | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.Stack; | ||||
| import java.util.function.Supplier; | ||||
|  | ||||
| /** | ||||
|  * 数据权限助手 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  * @version 3.5.0 | ||||
|  */ | ||||
| @NoArgsConstructor(access = AccessLevel.PRIVATE) | ||||
| @SuppressWarnings("unchecked cast") | ||||
| public class DataPermissionHelper { | ||||
|  | ||||
|     public static final String DATA_PERMISSION_KEY = "data:permission"; | ||||
|  | ||||
|     private static final ThreadLocal<Stack<Integer>> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new); | ||||
|  | ||||
|     private static final ThreadLocal<DataPermission> PERMISSION_CACHE = new ThreadLocal<>(); | ||||
|  | ||||
|     /** | ||||
|      * 获取当前执行mapper权限注解 | ||||
|      * | ||||
|      * @return 返回当前执行mapper权限注解 | ||||
|      */ | ||||
|     public static DataPermission getPermission() { | ||||
|         return PERMISSION_CACHE.get(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 设置当前执行mapper权限注解 | ||||
|      * | ||||
|      * @param dataPermission   数据权限注解 | ||||
|      */ | ||||
|     public static void setPermission(DataPermission dataPermission) { | ||||
|         PERMISSION_CACHE.set(dataPermission); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 删除当前执行mapper权限注解 | ||||
|      */ | ||||
|     public static void removePermission() { | ||||
|         PERMISSION_CACHE.remove(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 从上下文中获取指定键的变量值,并将其转换为指定的类型 | ||||
|      * | ||||
|      * @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() { | ||||
|         Object attribute = new HashMap<>(); | ||||
|         if (SaHolder.getContext().isValid()) { | ||||
|             SaStorage saStorage = SaHolder.getStorage(); | ||||
|             attribute = saStorage.get(DATA_PERMISSION_KEY); | ||||
|             if (ObjectUtil.isNull(attribute)) { | ||||
|                 saStorage.set(DATA_PERMISSION_KEY, new HashMap<>()); | ||||
|                 attribute = saStorage.get(DATA_PERMISSION_KEY); | ||||
|             } | ||||
|         } | ||||
|         if (attribute instanceof Map map) { | ||||
|             return map; | ||||
|         } | ||||
|         throw new NullPointerException("data permission context type exception"); | ||||
|     } | ||||
|  | ||||
|     private static IgnoreStrategy getIgnoreStrategy() { | ||||
|         Object ignoreStrategyLocal = ReflectUtils.getStaticFieldValue(ReflectUtils.getField(InterceptorIgnoreHelper.class, "IGNORE_STRATEGY_LOCAL")); | ||||
|         if (ignoreStrategyLocal instanceof ThreadLocal<?> IGNORE_STRATEGY_LOCAL) { | ||||
|             if (IGNORE_STRATEGY_LOCAL.get() instanceof IgnoreStrategy ignoreStrategy) { | ||||
|                 return ignoreStrategy; | ||||
|             } | ||||
|         } | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 开启忽略数据权限(开启后需手动调用 {@link #disableIgnore()} 关闭) | ||||
|      */ | ||||
|     public static void enableIgnore() { | ||||
|         IgnoreStrategy ignoreStrategy = getIgnoreStrategy(); | ||||
|         if (ObjectUtil.isNull(ignoreStrategy)) { | ||||
|             InterceptorIgnoreHelper.handle(IgnoreStrategy.builder().dataPermission(true).build()); | ||||
|         } else { | ||||
|             ignoreStrategy.setDataPermission(true); | ||||
|         } | ||||
|         Stack<Integer> reentrantStack = REENTRANT_IGNORE.get(); | ||||
|         reentrantStack.push(reentrantStack.size() + 1); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 关闭忽略数据权限 | ||||
|      */ | ||||
|     public static void disableIgnore() { | ||||
|         IgnoreStrategy ignoreStrategy = getIgnoreStrategy(); | ||||
|         if (ObjectUtil.isNotNull(ignoreStrategy)) { | ||||
|             boolean noOtherIgnoreStrategy = !Boolean.TRUE.equals(ignoreStrategy.getDynamicTableName()) | ||||
|                 && !Boolean.TRUE.equals(ignoreStrategy.getBlockAttack()) | ||||
|                 && !Boolean.TRUE.equals(ignoreStrategy.getIllegalSql()) | ||||
|                 && !Boolean.TRUE.equals(ignoreStrategy.getTenantLine()) | ||||
|                 && CollectionUtil.isEmpty(ignoreStrategy.getOthers()); | ||||
|             Stack<Integer> reentrantStack = REENTRANT_IGNORE.get(); | ||||
|             boolean empty = reentrantStack.isEmpty() || reentrantStack.pop() == 1; | ||||
|             if (noOtherIgnoreStrategy && empty) { | ||||
|                 InterceptorIgnoreHelper.clearIgnoreStrategy(); | ||||
|             } else if (empty) { | ||||
|                 ignoreStrategy.setDataPermission(false); | ||||
|             } | ||||
|  | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 在忽略数据权限中执行 | ||||
|      * | ||||
|      * @param handle 处理执行方法 | ||||
|      */ | ||||
|     public static void ignore(Runnable handle) { | ||||
|         enableIgnore(); | ||||
|         try { | ||||
|             handle.run(); | ||||
|         } finally { | ||||
|             disableIgnore(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 在忽略数据权限中执行 | ||||
|      * | ||||
|      * @param handle 处理执行方法 | ||||
|      */ | ||||
|     public static <T> T ignore(Supplier<T> handle) { | ||||
|         enableIgnore(); | ||||
|         try { | ||||
|             return handle.get(); | ||||
|         } finally { | ||||
|             disableIgnore(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1,181 @@ | ||||
| package org.dromara.common.mybatis.interceptor; | ||||
|  | ||||
| import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper; | ||||
| import com.baomidou.mybatisplus.core.toolkit.PluginUtils; | ||||
| import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.BaseMultiTableInnerInterceptor; | ||||
| import com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import net.sf.jsqlparser.expression.Expression; | ||||
| import net.sf.jsqlparser.schema.Table; | ||||
| import net.sf.jsqlparser.statement.delete.Delete; | ||||
| import net.sf.jsqlparser.statement.select.PlainSelect; | ||||
| import net.sf.jsqlparser.statement.select.Select; | ||||
| import net.sf.jsqlparser.statement.select.SetOperationList; | ||||
| import net.sf.jsqlparser.statement.update.Update; | ||||
| import org.apache.ibatis.executor.Executor; | ||||
| import org.apache.ibatis.executor.statement.StatementHandler; | ||||
| import org.apache.ibatis.mapping.BoundSql; | ||||
| import org.apache.ibatis.mapping.MappedStatement; | ||||
| import org.apache.ibatis.mapping.SqlCommandType; | ||||
| import org.apache.ibatis.session.ResultHandler; | ||||
| import org.apache.ibatis.session.RowBounds; | ||||
| import org.dromara.common.mybatis.handler.PlusDataPermissionHandler; | ||||
|  | ||||
| import java.sql.Connection; | ||||
| import java.sql.SQLException; | ||||
| import java.util.List; | ||||
|  | ||||
| /** | ||||
|  * 数据权限拦截器 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  * @version 3.5.0 | ||||
|  */ | ||||
| @Slf4j | ||||
| public class PlusDataPermissionInterceptor extends BaseMultiTableInnerInterceptor implements InnerInterceptor { | ||||
|  | ||||
|     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; | ||||
|         } | ||||
|         // 解析 sql 分配对应方法 | ||||
|         PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql); | ||||
|         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; | ||||
|             } | ||||
|             PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql(); | ||||
|             mpBs.sql(parserMulti(mpBs.sql(), ms.getId())); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 处理 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) { | ||||
|             this.setWhere((PlainSelect) select, (String) obj); | ||||
|         } else if (select instanceof SetOperationList setOperationList) { | ||||
|             List<Select> selectBodyList = setOperationList.getSelects(); | ||||
|             selectBodyList.forEach(s -> this.setWhere((PlainSelect) s, (String) obj)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 处理 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); | ||||
|         if (null != sqlSegment) { | ||||
|             update.setWhere(sqlSegment); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 处理 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); | ||||
|         if (null != sqlSegment) { | ||||
|             delete.setWhere(sqlSegment); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 设置 SELECT 语句的 WHERE 条件 | ||||
|      * | ||||
|      * @param plainSelect       SELECT 查询对象 | ||||
|      * @param mappedStatementId 映射语句的 ID | ||||
|      */ | ||||
|     protected void setWhere(PlainSelect plainSelect, String mappedStatementId) { | ||||
|         Expression sqlSegment = dataPermissionHandler.getSqlSegment(plainSelect.getWhere(), mappedStatementId, true); | ||||
|         if (null != sqlSegment) { | ||||
|             plainSelect.setWhere(sqlSegment); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 构建表达式,用于处理表的数据权限 | ||||
|      * | ||||
|      * @param table        表对象 | ||||
|      * @param where        WHERE 条件表达式 | ||||
|      * @param whereSegment WHERE 条件片段 | ||||
|      * @return 构建的表达式 | ||||
|      */ | ||||
|     @Override | ||||
|     public Expression buildTableExpression(Table table, Expression where, String whereSegment) { | ||||
|         // 只有新版数据权限处理器才会执行到这里 | ||||
|         final MultiDataPermissionHandler handler = (MultiDataPermissionHandler) dataPermissionHandler; | ||||
|         return handler.getSqlSegment(table, where, whereSegment); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -0,0 +1,41 @@ | ||||
| package org.dromara.common.mybatis.service; | ||||
|  | ||||
| import org.apache.dubbo.config.annotation.DubboReference; | ||||
| import org.dromara.system.api.RemoteDataScopeService; | ||||
| import org.springframework.stereotype.Service; | ||||
|  | ||||
| /** | ||||
|  * 数据权限 实现 | ||||
|  * <p> | ||||
|  * 注意: 此Service内不允许调用标注`数据权限`注解的方法 | ||||
|  * 例如: deptMapper.selectList 此 selectList 方法标注了`数据权限`注解 会出现循环解析的问题 | ||||
|  * | ||||
|  * @author Lion Li | ||||
|  */ | ||||
| @Service("sdss") | ||||
| public class SysDataScopeService { | ||||
|  | ||||
|     @DubboReference | ||||
|     private RemoteDataScopeService remoteDataScopeService; | ||||
|  | ||||
|     /** | ||||
|      * 获取角色自定义权限语句 | ||||
|      * | ||||
|      * @param roleId 角色ID | ||||
|      * @return 返回角色的自定义权限语句,如果没有找到则返回 null | ||||
|      */ | ||||
|     public String getRoleCustom(Long roleId) { | ||||
|         return remoteDataScopeService.getRoleCustom(roleId); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 获取部门和下级权限语句 | ||||
|      * | ||||
|      * @param deptId 部门ID | ||||
|      * @return 返回部门及其下级的权限语句,如果没有找到则返回 null | ||||
|      */ | ||||
|     public String getDeptAndChild(Long deptId) { | ||||
|         return remoteDataScopeService.getDeptAndChild(deptId); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @ -0,0 +1 @@ | ||||
| dubboDataPermissionFilter=org.dromara.common.mybatis.filter.DubboDataPermissionFilter | ||||
| @ -0,0 +1 @@ | ||||
| org.dromara.common.mybatis.config.MybatisPlusConfiguration | ||||
| @ -0,0 +1,33 @@ | ||||
| # 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖 | ||||
| # MyBatisPlus配置 | ||||
| # https://baomidou.com/config/ | ||||
| mybatis-plus: | ||||
|   # 启动时是否检查 MyBatis XML 文件的存在,默认不检查 | ||||
|   checkConfigLocation: false | ||||
|   configuration: | ||||
|     # 自动驼峰命名规则(camel case)映射 | ||||
|     mapUnderscoreToCamelCase: true | ||||
|     # MyBatis 自动映射策略 | ||||
|     # NONE:不启用 PARTIAL:只对非嵌套 resultMap 自动映射 FULL:对所有 resultMap 自动映射 | ||||
|     autoMappingBehavior: FULL | ||||
|     # MyBatis 自动映射时未知列或未知属性处理策 | ||||
|     # NONE:不做处理 WARNING:打印相关警告 FAILING:抛出异常和详细信息 | ||||
|     autoMappingUnknownColumnBehavior: NONE | ||||
|     # 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl | ||||
|     # 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl | ||||
|     # 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl | ||||
|     logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl | ||||
|   global-config: | ||||
|     # 是否打印 Logo banner | ||||
|     banner: true | ||||
|     dbConfig: | ||||
|       # 主键类型 | ||||
|       # AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID | ||||
|       idType: ASSIGN_ID | ||||
|       # 逻辑已删除值(框架表均使用此值 禁止随意修改) | ||||
|       logicDeleteValue: 1 | ||||
|       # 逻辑未删除值 | ||||
|       logicNotDeleteValue: 0 | ||||
|       insertStrategy: NOT_NULL | ||||
|       updateStrategy: NOT_NULL | ||||
|       whereStrategy: NOT_NULL | ||||
| @ -0,0 +1,20 @@ | ||||
| # p6spy 性能分析插件配置文件 | ||||
| modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory | ||||
| # 自定义日志打印 | ||||
| logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger | ||||
| #日志输出到控制台 | ||||
| appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger | ||||
| # 使用日志系统记录 sql | ||||
| #appender=com.p6spy.engine.spy.appender.Slf4JLogger | ||||
| # 取消JDBC URL前缀 | ||||
| useprefix=true | ||||
| # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. | ||||
| excludecategories=info,debug,result,commit,resultset | ||||
| # 日期格式 | ||||
| dateformat=yyyy-MM-dd HH:mm:ss | ||||
| # SQL语句打印时间格式 | ||||
| databaseDialectTimestampFormat=yyyy-MM-dd HH:mm:ss | ||||
| # 是否过滤 Log | ||||
| filter=true | ||||
| # 过滤 Log 时所排除的 sql 关键字,以逗号分隔 | ||||
| exclude= | ||||
		Reference in New Issue
	
	Block a user