Merge remote-tracking branch 'origin/dev' into warm-flow-future

# Conflicts:
#	ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysPostServiceImpl.java
#	ruoyi-modules/ruoyi-system/src/main/java/org/dromara/system/service/impl/SysRoleServiceImpl.java
#	ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/service/impl/WorkflowServiceImpl.java
This commit is contained in:
AprilWind 2024-11-07 15:54:02 +08:00
commit b04c9bee21
49 changed files with 488 additions and 458 deletions

View File

@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> <configuration default="false" name="ruoyi-monitor-admin" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile"> <deployment type="dockerfile">
<settings> <settings>
<option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.2.2" /> <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.2.3" />
<option name="buildOnly" value="true" /> <option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" /> <option name="sourceFilePath" value="ruoyi-extend/ruoyi-monitor-admin/Dockerfile" />
</settings> </settings>

View File

@ -2,11 +2,11 @@
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="演示机"> <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="演示机">
<deployment type="dockerfile"> <deployment type="dockerfile">
<settings> <settings>
<option name="imageTag" value="ruoyi/ruoyi-server:5.2.2" /> <option name="imageTag" value="ruoyi/ruoyi-server:5.2.3" />
<option name="buildOnly" value="true" /> <option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-admin/Dockerfile" /> <option name="sourceFilePath" value="ruoyi-admin/Dockerfile" />
</settings> </settings>
</deployment> </deployment>
<method v="2" /> <method v="2" />
</configuration> </configuration>
</component> </component>

View File

@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> <configuration default="false" name="ruoyi-snailjob-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile"> <deployment type="dockerfile">
<settings> <settings>
<option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.2.2" /> <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.2.3" />
<option name="buildOnly" value="true" /> <option name="buildOnly" value="true" />
<option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" /> <option name="sourceFilePath" value="ruoyi-extend/ruoyi-snailjob-server/Dockerfile" />
</settings> </settings>

View File

@ -6,11 +6,12 @@
[![码云Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Vue-Plus) [![码云Gitee](https://gitee.com/dromara/RuoYi-Vue-Plus/badge/star.svg?theme=blue)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Vue-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Vue-Plus) [![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Vue-Plus.svg?style=social&label=Stars)](https://github.com/dromara/RuoYi-Vue-Plus)
[![Star](https://gitcode.com/dromara/RuoYi-Vue-Plus/star/badge.svg)](https://gitcode.com/dromara/RuoYi-Vue-Plus)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/master/LICENSE)
[![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus) [![使用IntelliJ IDEA开发维护](https://img.shields.io/badge/IntelliJ%20IDEA-提供支持-blue.svg)](https://www.jetbrains.com/?from=RuoYi-Vue-Plus)
<br> <br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.2.2-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus) [![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.2.3-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.2-blue.svg)]() [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.3-blue.svg)]()
[![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]() [![JDK-17](https://img.shields.io/badge/JDK-17-green.svg)]()
[![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]() [![JDK-21](https://img.shields.io/badge/JDK-21-green.svg)]()

20
pom.xml
View File

@ -13,8 +13,8 @@
<description>RuoYi-Vue-Plus多租户管理系统</description> <description>RuoYi-Vue-Plus多租户管理系统</description>
<properties> <properties>
<revision>5.2.2</revision> <revision>5.2.3</revision>
<spring-boot.version>3.2.10</spring-boot.version> <spring-boot.version>3.3.5</spring-boot.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version> <java.version>17</java.version>
@ -24,15 +24,15 @@
<easyexcel.version>4.0.3</easyexcel.version> <easyexcel.version>4.0.3</easyexcel.version>
<velocity.version>2.3</velocity.version> <velocity.version>2.3</velocity.version>
<satoken.version>1.39.0</satoken.version> <satoken.version>1.39.0</satoken.version>
<mybatis-plus.version>3.5.8</mybatis-plus.version> <mybatis-plus.version>3.5.9</mybatis-plus.version>
<p6spy.version>3.9.1</p6spy.version> <p6spy.version>3.9.1</p6spy.version>
<hutool.version>5.8.31</hutool.version> <hutool.version>5.8.31</hutool.version>
<spring-boot-admin.version>3.2.3</spring-boot-admin.version> <spring-boot-admin.version>3.3.4</spring-boot-admin.version>
<redisson.version>3.36.0</redisson.version> <redisson.version>3.37.0</redisson.version>
<lock4j.version>2.2.7</lock4j.version> <lock4j.version>2.2.7</lock4j.version>
<dynamic-ds.version>4.3.1</dynamic-ds.version> <dynamic-ds.version>4.3.1</dynamic-ds.version>
<snailjob.version>1.1.2</snailjob.version> <snailjob.version>1.1.2</snailjob.version>
<mapstruct-plus.version>1.4.4</mapstruct-plus.version> <mapstruct-plus.version>1.4.5</mapstruct-plus.version>
<mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version> <mapstruct-plus.lombok.version>0.2.0</mapstruct-plus.lombok.version>
<lombok.version>1.18.34</lombok.version> <lombok.version>1.18.34</lombok.version>
<bouncycastle.version>1.76</bouncycastle.version> <bouncycastle.version>1.76</bouncycastle.version>
@ -48,7 +48,7 @@
<!-- 限制框架中的fastjson版本 --> <!-- 限制框架中的fastjson版本 -->
<fastjson.version>1.2.83</fastjson.version> <fastjson.version>1.2.83</fastjson.version>
<!-- 面向运行时的D-ORM依赖 --> <!-- 面向运行时的D-ORM依赖 -->
<anyline.version>8.7.2-20240930</anyline.version> <anyline.version>8.7.2-20241022</anyline.version>
<!--工作流配置--> <!--工作流配置-->
<warm-flow.version>1.3.0</warm-flow.version> <warm-flow.version>1.3.0</warm-flow.version>
@ -226,6 +226,12 @@
<version>${mybatis-plus.version}</version> <version>${mybatis-plus.version}</version>
</dependency> </dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-annotation</artifactId> <artifactId>mybatis-plus-annotation</artifactId>

View File

@ -84,11 +84,11 @@ public class SysRegisterService {
String captcha = RedisUtils.getCacheObject(verifyKey); String captcha = RedisUtils.getCacheObject(verifyKey);
RedisUtils.deleteObject(verifyKey); RedisUtils.deleteObject(verifyKey);
if (captcha == null) { if (captcha == null) {
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.expire")); recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire"));
throw new CaptchaExpireException(); throw new CaptchaExpireException();
} }
if (!code.equalsIgnoreCase(captcha)) { if (!code.equalsIgnoreCase(captcha)) {
recordLogininfor(tenantId, username, Constants.REGISTER, MessageUtils.message("user.jcaptcha.error")); recordLogininfor(tenantId, username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error"));
throw new CaptchaException(); throw new CaptchaException();
} }
} }

View File

@ -110,15 +110,12 @@ sa-token:
security: security:
# 排除路径 # 排除路径
excludes: excludes:
# 静态资源
- /*.html - /*.html
- /**/*.html - /**/*.html
- /**/*.css - /**/*.css
- /**/*.js - /**/*.js
# 公共路径
- /favicon.ico - /favicon.ico
- /error - /error
# swagger 文档配置
- /*/api-docs - /*/api-docs
- /*/api-docs/** - /*/api-docs/**
# warm-flow-ui # warm-flow-ui

View File

@ -14,7 +14,7 @@
</description> </description>
<properties> <properties>
<revision>5.2.2</revision> <revision>5.2.3</revision>
</properties> </properties>
<dependencyManagement> <dependencyManagement>

View File

@ -43,6 +43,23 @@ public class TreeBuildUtils extends TreeUtil {
return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser); return TreeUtil.build(list, k, DEFAULT_CONFIG, nodeParser);
} }
/**
* 构建树形结构
*
* @param <T> 输入节点的类型
* @param <K> 节点ID的类型
* @param parentId 顶级节点
* @param list 节点列表其中包含了要构建树形结构的所有节点
* @param nodeParser 解析器用于将输入节点转换为树节点
* @return 构建好的树形结构列表
*/
public static <T, K> List<Tree<K>> build(List<T> list, K parentId, NodeParser<T, K> nodeParser) {
if (CollUtil.isEmpty(list)) {
return CollUtil.newArrayList();
}
return TreeUtil.build(list, parentId, DEFAULT_CONFIG, nodeParser);
}
/** /**
* 获取节点列表中所有节点的叶子节点 * 获取节点列表中所有节点的叶子节点
* *

View File

@ -55,6 +55,7 @@ public class ExcelDownHandler implements SheetWriteHandler {
* 下拉可选项 * 下拉可选项
*/ */
private final List<DropDownOptions> dropDownOptions; private final List<DropDownOptions> dropDownOptions;
private final DictService dictService;
/** /**
* 当前单选进度 * 当前单选进度
*/ */
@ -63,7 +64,6 @@ public class ExcelDownHandler implements SheetWriteHandler {
* 当前联动选择进度 * 当前联动选择进度
*/ */
private int currentLinkedOptionsSheetIndex; private int currentLinkedOptionsSheetIndex;
private final DictService dictService;
public ExcelDownHandler(List<DropDownOptions> options) { public ExcelDownHandler(List<DropDownOptions> options) {
this.dropDownOptions = options; this.dropDownOptions = options;
@ -139,8 +139,8 @@ public class ExcelDownHandler implements SheetWriteHandler {
} else if (everyOptions.getOptions().size() > 10) { } else if (everyOptions.getOptions().size() > 10) {
// 当一级选项参数个数大于10使用额外表的形式 // 当一级选项参数个数大于10使用额外表的形式
dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions()); dropDownWithSheet(helper, workbook, sheet, everyOptions.getIndex(), everyOptions.getOptions());
} else if (everyOptions.getOptions().size() != 0) { } else {
// 当一级选项个数不为空使用默认形式 // 否则使用默认形式
dropDownWithSimple(helper, sheet, everyOptions.getIndex(), everyOptions.getOptions()); dropDownWithSimple(helper, sheet, everyOptions.getIndex(), everyOptions.getOptions());
} }
}); });
@ -171,10 +171,24 @@ public class ExcelDownHandler implements SheetWriteHandler {
Sheet linkedOptionsDataSheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(linkedOptionsSheetName)); Sheet linkedOptionsDataSheet = workbook.createSheet(WorkbookUtil.createSafeSheetName(linkedOptionsSheetName));
// 将下拉表隐藏 // 将下拉表隐藏
workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true); workbook.setSheetHidden(workbook.getSheetIndex(linkedOptionsDataSheet), true);
// 完善横向的一级选项数据 // 选项数据
List<String> firstOptions = options.getOptions(); List<String> firstOptions = options.getOptions();
Map<String, List<String>> secoundOptionsMap = options.getNextOptions(); Map<String, List<String>> secoundOptionsMap = options.getNextOptions();
// 采用按行填充数据的方式避免EasyExcel出现数据无法写入的问题
// Attempting to write a row in the range that is already written to disk
// 使用ArrayList记载数据防止乱序
List<String> columnNames = new ArrayList<>();
// 写入第一行即第一级的数据
Row firstRow = linkedOptionsDataSheet.createRow(0);
for (int columnIndex = 0; columnIndex < firstOptions.size(); columnIndex++) {
String columnName = firstOptions.get(columnIndex);
firstRow.createCell(columnIndex)
.setCellValue(columnName);
columnNames.add(columnName);
}
// 创建名称管理器 // 创建名称管理器
Name name = workbook.createName(); Name name = workbook.createName();
// 设置名称管理器的别名 // 设置名称管理器的别名
@ -190,28 +204,12 @@ public class ExcelDownHandler implements SheetWriteHandler {
// 设置数据校验为序列模式引用的是名称管理器中的别名 // 设置数据校验为序列模式引用的是名称管理器中的别名
this.markOptionsToSheet(helper, sheet, options.getIndex(), helper.createFormulaListConstraint(linkedOptionsSheetName)); this.markOptionsToSheet(helper, sheet, options.getIndex(), helper.createFormulaListConstraint(linkedOptionsSheetName));
for (int columIndex = 0; columIndex < firstOptions.size(); columIndex++) { // 创建二级选项的名称管理器
// 先提取主表中一级下拉的列名 for (int columIndex = 0; columIndex < columnNames.size(); columIndex++) {
// 列名
String firstOptionsColumnName = getExcelColumnName(columIndex); String firstOptionsColumnName = getExcelColumnName(columIndex);
// 一次循环是每一个一级选项 // 对应的一级值
int finalI = columIndex; String thisFirstOptionsValue = columnNames.get(columIndex);
// 本次循环的一级选项值
String thisFirstOptionsValue = firstOptions.get(columIndex);
// 创建第一行的数据
Optional.ofNullable(linkedOptionsDataSheet.getRow(0))
// 如果不存在则创建第一行
.orElseGet(() -> linkedOptionsDataSheet.createRow(finalI))
// 第一行当前列
.createCell(columIndex)
// 设置值为当前一级选项值
.setCellValue(thisFirstOptionsValue);
// 第二行开始设置第二级别选项参数
List<String> secondOptions = secoundOptionsMap.get(thisFirstOptionsValue);
if (CollUtil.isEmpty(secondOptions)) {
// 必须保证至少有一个关联选项否则将导致Excel解析错误
secondOptions = Collections.singletonList("暂无_0");
}
// 以该一级选项值创建子名称管理器 // 以该一级选项值创建子名称管理器
Name sonName = workbook.createName(); Name sonName = workbook.createName();
@ -222,7 +220,9 @@ public class ExcelDownHandler implements SheetWriteHandler {
linkedOptionsSheetName, linkedOptionsSheetName,
firstOptionsColumnName, firstOptionsColumnName,
firstOptionsColumnName, firstOptionsColumnName,
secondOptions.size() + 1 // 二级选项存在则设置为(选项个数+1)否则设置为2行
Math.max(Optional.ofNullable(secoundOptionsMap.get(thisFirstOptionsValue))
.orElseGet(ArrayList::new).size(), 1) + 1
); );
// 设置名称管理器的引用位置 // 设置名称管理器的引用位置
sonName.setRefersToFormula(sonFunction); sonName.setRefersToFormula(sonFunction);
@ -235,25 +235,51 @@ public class ExcelDownHandler implements SheetWriteHandler {
// 二级只能主表每一行的每一列添加二级校验 // 二级只能主表每一行的每一列添加二级校验
markLinkedOptionsToSheet(helper, sheet, i, options.getNextIndex(), helper.createFormulaListConstraint(secondOptionsFunction)); markLinkedOptionsToSheet(helper, sheet, i, options.getNextIndex(), helper.createFormulaListConstraint(secondOptionsFunction));
} }
}
for (int rowIndex = 0; rowIndex < secondOptions.size(); rowIndex++) { // 将二级数据处理为按行区分
// 从第二行开始填充二级选项 Map<Integer, List<String>> columnValueMap = new HashMap<>();
int finalRowIndex = rowIndex + 1; int currentRow = 1;
int finalColumIndex = columIndex; while (currentRow >= 0) {
boolean flag = false;
Row row = Optional.ofNullable(linkedOptionsDataSheet.getRow(finalRowIndex)) List<String> rowData = new ArrayList<>();
// 没有则创建 for (String columnName : columnNames) {
.orElseGet(() -> linkedOptionsDataSheet.createRow(finalRowIndex)); List<String> data = secoundOptionsMap.get(columnName);
Optional if (CollUtil.isEmpty(data)) {
// 在本级一级选项所在的列 // 添加空字符串填充位置
.ofNullable(row.getCell(finalColumIndex)) rowData.add(" ");
// 不存在则创建 continue;
.orElseGet(() -> row.createCell(finalColumIndex)) }
// 设置二级选项值 // 取第一个
.setCellValue(secondOptions.get(rowIndex)); String str = data.get(0);
rowData.add(str);
// 通过移除的方式避免重复
data.remove(0);
// 设置可以继续
flag = true;
}
columnValueMap.put(currentRow, rowData);
// 可以继续则增加行数否则置为负数跳出循环
if (flag) {
currentRow++;
} else {
currentRow = -1;
} }
} }
// 填充第二级选项数据
columnValueMap.forEach((rowIndex, rowValues) -> {
Row row = linkedOptionsDataSheet.createRow(rowIndex);
for (int columnIndex = 0; columnIndex < rowValues.size(); columnIndex++) {
String rowValue = rowValues.get(columnIndex);
// 填充位置的部分不渲染
if (StrUtil.isNotBlank(rowValue)) {
row.createCell(columnIndex)
.setCellValue(rowValue);
}
}
});
currentLinkedOptionsSheetIndex++; currentLinkedOptionsSheetIndex++;
} }

View File

@ -37,6 +37,11 @@
<artifactId>mybatis-plus-spring-boot3-starter</artifactId> <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
</dependency>
<!-- sql性能分析插件 --> <!-- sql性能分析插件 -->
<dependency> <dependency>
<groupId>p6spy</groupId> <groupId>p6spy</groupId>

View File

@ -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();
}
}

View File

@ -10,6 +10,7 @@ import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerIntercept
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.dromara.common.core.factory.YmlPropertySourceFactory; import org.dromara.common.core.factory.YmlPropertySourceFactory;
import org.dromara.common.core.utils.SpringUtils; 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.InjectionMetaObjectHandler;
import org.dromara.common.mybatis.handler.MybatisExceptionHandler; import org.dromara.common.mybatis.handler.MybatisExceptionHandler;
import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor; import org.dromara.common.mybatis.interceptor.PlusDataPermissionInterceptor;
@ -51,7 +52,15 @@ public class MybatisPlusConfig {
* 数据权限拦截器 * 数据权限拦截器
*/ */
public PlusDataPermissionInterceptor dataPermissionInterceptor() { public PlusDataPermissionInterceptor dataPermissionInterceptor() {
return new PlusDataPermissionInterceptor(SpringUtils.getProperty("mybatis-plus.mapperPackage")); return new PlusDataPermissionInterceptor();
}
/**
* 数据权限切面处理器
*/
@Bean
public DataPermissionAspect dataPermissionAspect() {
return new DataPermissionAspect();
} }
/** /**

View File

@ -1,6 +1,5 @@
package org.dromara.common.mybatis.handler; package org.dromara.common.mybatis.handler;
import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -9,7 +8,6 @@ import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.operators.conditional.AndExpression; import net.sf.jsqlparser.expression.operators.conditional.AndExpression;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
import net.sf.jsqlparser.parser.CCJSqlParserUtil; import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import org.apache.ibatis.io.Resources;
import org.dromara.common.core.domain.dto.RoleDTO; import org.dromara.common.core.domain.dto.RoleDTO;
import org.dromara.common.core.domain.model.LoginUser; import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
@ -21,27 +19,17 @@ import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.enums.DataScopeType; import org.dromara.common.mybatis.enums.DataScopeType;
import org.dromara.common.mybatis.helper.DataPermissionHelper; import org.dromara.common.mybatis.helper.DataPermissionHelper;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.expression.BeanFactoryResolver; 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.BeanResolver; import org.springframework.expression.BeanResolver;
import org.springframework.expression.ExpressionParser; import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext; import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ClassUtils;
import java.lang.reflect.Method;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function; import java.util.function.Function;
/** /**
@ -53,11 +41,6 @@ import java.util.function.Function;
@Slf4j @Slf4j
public class PlusDataPermissionHandler { public class PlusDataPermissionHandler {
/**
* 方法或类(名称) 注解的映射关系缓存
*/
private final Map<String, DataPermission> dataPermissionCacheMap = new ConcurrentHashMap<>();
/** /**
* spel 解析器 * spel 解析器
*/ */
@ -68,15 +51,6 @@ public class PlusDataPermissionHandler {
*/ */
private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory()); private final BeanResolver beanResolver = new BeanFactoryResolver(SpringUtils.getBeanFactory());
/**
* 构造方法扫描指定包下的 Mapper 类并初始化缓存
*
* @param mapperPackage Mapper 类所在的包路径
*/
public PlusDataPermissionHandler(String mapperPackage) {
scanMapperClasses(mapperPackage);
}
/** /**
* 获取数据过滤条件的 SQL 片段 * 获取数据过滤条件的 SQL 片段
* *
@ -86,24 +60,24 @@ public class PlusDataPermissionHandler {
* @return 数据过滤条件的 SQL 片段 * @return 数据过滤条件的 SQL 片段
*/ */
public Expression getSqlSegment(Expression where, String mappedStatementId, boolean isSelect) { 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();
DataPermissionHelper.setVariable("user", currentUser);
}
// 如果是超级管理员或租户管理员则不过滤数据
if (LoginHelper.isSuperAdmin() || LoginHelper.isTenantAdmin()) {
return where;
}
// 构造数据过滤条件的 SQL 片段
String dataFilterSql = buildDataFilter(dataPermission, isSelect);
if (StringUtils.isBlank(dataFilterSql)) {
return where;
}
try { try {
// 获取数据权限配置
DataPermission dataPermission = DataPermissionHelper.getPermission();
// 获取当前登录用户信息
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); Expression expression = CCJSqlParserUtil.parseExpression(dataFilterSql);
// 数据权限使用单独的括号 防止与其他条件冲突 // 数据权限使用单独的括号 防止与其他条件冲突
ParenthesedExpressionList<Expression> parenthesis = new ParenthesedExpressionList<>(expression); ParenthesedExpressionList<Expression> parenthesis = new ParenthesedExpressionList<>(expression);
@ -114,6 +88,8 @@ public class PlusDataPermissionHandler {
} }
} catch (JSQLParserException e) { } catch (JSQLParserException e) {
throw new ServiceException("数据权限解析异常 => " + e.getMessage()); throw new ServiceException("数据权限解析异常 => " + e.getMessage());
} finally {
DataPermissionHelper.removePermission();
} }
} }
@ -170,8 +146,11 @@ public class PlusDataPermissionHandler {
context.setVariable(dataColumn.key()[i], dataColumn.value()[i]); context.setVariable(dataColumn.key()[i], dataColumn.value()[i]);
} }
// 忽略数据权限 防止spel表达式内有其他sql查询导致死循环调用
String sql = DataPermissionHelper.ignore(() ->
parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class)
);
// 解析sql模板并填充 // 解析sql模板并填充
String sql = parser.parseExpression(type.getSqlTemplate(), parserContext).getValue(context, String.class);
conditions.add(joinStr + sql); conditions.add(joinStr + sql);
isSuccess = true; isSuccess = true;
} }
@ -188,87 +167,12 @@ public class PlusDataPermissionHandler {
return ""; return "";
} }
/**
* 扫描指定包下的 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 (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 注解对象 * 检查给定的映射语句 ID 是否有效即是否能够找到对应的 DataPermission 注解对象
* *
* @param mapperId 映射语句 ID
* @return 如果找到对应的 DataPermission 注解对象则返回 false否则返回 true * @return 如果找到对应的 DataPermission 注解对象则返回 false否则返回 true
*/ */
public boolean invalid(String mapperId) { public boolean invalid() {
return getDataPermission(mapperId) == null; return DataPermissionHelper.getPermission() == null;
} }
} }

View File

@ -62,8 +62,8 @@ public class DataBaseHelper {
// charindex(',100,' , ',0,100,101,') <> 0 // charindex(',100,' , ',0,100,101,') <> 0
return "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2); return "charindex(',%s,' , ','+%s+',') <> 0".formatted(var, var2);
} else if (dataBasyType == DataBaseType.POSTGRE_SQL) { } else if (dataBasyType == DataBaseType.POSTGRE_SQL) {
// (select position(',100,' in ',0,100,101,')) <> 0 // (select strpos(',0,100,101,' , ',100,')) <> 0
return "(select position(',%s,' in ','||%s||',')) <> 0".formatted(var, var2); return "(select strpos(','||%s||',' , ',%s,')) <> 0".formatted(var2, var);
} else if (dataBasyType == DataBaseType.ORACLE) { } else if (dataBasyType == DataBaseType.ORACLE) {
// instr(',0,100,101,' , ',100,') <> 0 // instr(',0,100,101,' , ',100,') <> 0
return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var); return "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var);

View File

@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.reflect.ReflectUtils; import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.mybatis.annotation.DataPermission;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -29,6 +30,33 @@ public class DataPermissionHelper {
private static final ThreadLocal<Stack<Integer>> REENTRANT_IGNORE = ThreadLocal.withInitial(Stack::new); 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();
}
/** /**
* 从上下文中获取指定键的变量值并将其转换为指定的类型 * 从上下文中获取指定键的变量值并将其转换为指定的类型
* *

View File

@ -39,11 +39,9 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
/** /**
* 构造函数初始化 PlusDataPermissionHandler 实例 * 构造函数初始化 PlusDataPermissionHandler 实例
*
* @param mapperPackage 扫描的映射器包
*/ */
public PlusDataPermissionInterceptor(String mapperPackage) { public PlusDataPermissionInterceptor() {
this.dataPermissionHandler = new PlusDataPermissionHandler(mapperPackage); this.dataPermissionHandler = new PlusDataPermissionHandler();
} }
/** /**
@ -64,7 +62,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
return; return;
} }
// 检查是否缺少有效的数据权限注解 // 检查是否缺少有效的数据权限注解
if (dataPermissionHandler.invalid(ms.getId())) { if (dataPermissionHandler.invalid()) {
return; return;
} }
// 解析 sql 分配对应方法 // 解析 sql 分配对应方法
@ -92,7 +90,7 @@ public class PlusDataPermissionInterceptor extends BaseMultiTableInnerIntercepto
return; return;
} }
// 检查是否缺少有效的数据权限注解 // 检查是否缺少有效的数据权限注解
if (dataPermissionHandler.invalid(ms.getId())) { if (dataPermissionHandler.invalid()) {
return; return;
} }
PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql(); PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();

View File

@ -9,7 +9,6 @@ import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.oss.constant.OssConstant; import org.dromara.common.oss.constant.OssConstant;
import org.dromara.common.oss.entity.UploadResult; import org.dromara.common.oss.entity.UploadResult;
import org.dromara.common.oss.enumd.AccessPolicyType; import org.dromara.common.oss.enumd.AccessPolicyType;
import org.dromara.common.oss.enumd.PolicyType;
import org.dromara.common.oss.exception.OssException; import org.dromara.common.oss.exception.OssException;
import org.dromara.common.oss.properties.OssProperties; import org.dromara.common.oss.properties.OssProperties;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
@ -22,8 +21,6 @@ import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.S3Configuration; import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration; import software.amazon.awssdk.services.s3.crt.S3CrtHttpConfiguration;
import software.amazon.awssdk.services.s3.model.GetObjectResponse; import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.NoSuchBucketException;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.presigner.S3Presigner; import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.transfer.s3.S3TransferManager; import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.*; import software.amazon.awssdk.transfer.s3.model.*;
@ -83,10 +80,10 @@ public class OssClient {
StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create( StaticCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey())); AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey()));
//使用对象存储服务时要求明确配置访问样式路径样式或虚拟托管样式需要启用路径样式访问 // MinIO 使用 HTTPS 限制使用域名访问站点填域名需要启用路径样式访问
boolean isStyle = true; boolean isStyle = !StringUtils.containsAny(properties.getEndpoint(), OssConstant.CLOUD_SERVICE);
//创建AWS基于 CRT S3 客户端 // 创建AWS基于 CRT S3 客户端
this.client = S3AsyncClient.crtBuilder() this.client = S3AsyncClient.crtBuilder()
.credentialsProvider(credentialsProvider) .credentialsProvider(credentialsProvider)
.endpointOverride(URI.create(getEndpoint())) .endpointOverride(URI.create(getEndpoint()))
@ -115,8 +112,6 @@ public class OssClient {
.serviceConfiguration(config) .serviceConfiguration(config)
.build(); .build();
// 创建存储桶
createBucket();
} catch (Exception e) { } catch (Exception e) {
if (e instanceof OssException) { if (e instanceof OssException) {
throw e; throw e;
@ -125,43 +120,6 @@ public class OssClient {
} }
} }
/**
* 同步创建存储桶
* 如果存储桶不存在会进行创建如果存储桶存在不执行任何操作
*
* @throws OssException 当创建存储桶时发生异常时抛出
*/
public void createBucket() {
String bucketName = properties.getBucketName();
try {
// 尝试获取存储桶的信息
client.headBucket(
x -> x.bucket(bucketName)
.build())
.join();
} catch (Exception ex) {
if (ex.getCause() instanceof NoSuchBucketException) {
try {
// 存储桶不存在尝试创建存储桶
client.createBucket(
x -> x.bucket(bucketName))
.join();
// 设置存储桶的访问策略Bucket Policy
client.putBucketPolicy(
x -> x.bucket(bucketName)
.policy(getPolicy(bucketName, getAccessPolicy().getPolicyType())))
.join();
} catch (S3Exception e) {
// 存储桶创建或策略设置失败
throw new OssException("创建Bucket失败, 请核对配置信息:[" + e.getMessage() + "]");
}
} else {
throw new OssException("判断Bucket是否存在失败请核对配置信息:[" + ex.getMessage() + "]");
}
}
}
/** /**
* 上传文件到 Amazon S3并返回上传结果 * 上传文件到 Amazon S3并返回上传结果
* *
@ -529,77 +487,4 @@ public class OssClient {
return AccessPolicyType.getByType(properties.getAccessPolicy()); return AccessPolicyType.getByType(properties.getAccessPolicy());
} }
/**
* 生成 AWS S3 存储桶访问策略
*
* @param bucketName 存储桶
* @param policyType 桶策略类型
* @return 符合 AWS S3 存储桶访问策略格式的字符串
*/
private static String getPolicy(String bucketName, PolicyType policyType) {
String policy = switch (policyType) {
case WRITE -> """
{
"Version": "2012-10-17",
"Statement": []
}
""";
case READ_WRITE -> """
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetBucketLocation",
"s3:ListBucket",
"s3:ListBucketMultipartUploads"
],
"Resource": "arn:aws:s3:::bucketName"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:AbortMultipartUpload",
"s3:DeleteObject",
"s3:GetObject",
"s3:ListMultipartUploadParts",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::bucketName/*"
}
]
}
""";
case READ -> """
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetBucketLocation"],
"Resource": "arn:aws:s3:::bucketName"
},
{
"Effect": "Deny",
"Principal": "*",
"Action": ["s3:ListBucket"],
"Resource": "arn:aws:s3:::bucketName"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::bucketName/*"
}
]
}
""";
};
return policy.replaceAll("bucketName", bucketName);
}
} }

View File

@ -17,17 +17,17 @@ public enum AccessPolicyType {
/** /**
* private * private
*/ */
PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE, PolicyType.WRITE), PRIVATE("0", BucketCannedACL.PRIVATE, ObjectCannedACL.PRIVATE),
/** /**
* public * public
*/ */
PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE, PolicyType.READ_WRITE), PUBLIC("1", BucketCannedACL.PUBLIC_READ_WRITE, ObjectCannedACL.PUBLIC_READ_WRITE),
/** /**
* custom * custom
*/ */
CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ, PolicyType.READ); CUSTOM("2", BucketCannedACL.PUBLIC_READ, ObjectCannedACL.PUBLIC_READ);
/** /**
* 权限类型数据库值 * 权限类型数据库值
@ -44,11 +44,6 @@ public enum AccessPolicyType {
*/ */
private final ObjectCannedACL objectCannedACL; private final ObjectCannedACL objectCannedACL;
/**
* 桶策略类型
*/
private final PolicyType policyType;
public static AccessPolicyType getByType(String type) { public static AccessPolicyType getByType(String type) {
for (AccessPolicyType value : values()) { for (AccessPolicyType value : values()) {
if (value.getType().equals(type)) { if (value.getType().equals(type)) {

View File

@ -1,35 +0,0 @@
package org.dromara.common.oss.enumd;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* minio策略配置
*
* @author Lion Li
*/
@Getter
@AllArgsConstructor
public enum PolicyType {
/**
* 只读
*/
READ("read-only"),
/**
* 只写
*/
WRITE("write-only"),
/**
* 读写
*/
READ_WRITE("read-write");
/**
* 类型
*/
private final String type;
}

View File

@ -38,4 +38,10 @@ public @interface RateLimiter {
* 提示消息 支持国际化 格式为 {code} * 提示消息 支持国际化 格式为 {code}
*/ */
String message() default "{rate.limiter.message}"; String message() default "{rate.limiter.message}";
/**
* 限流策略超时时间 默认一天(策略存活时间 会清除已存在的策略数据)
*/
int timeout() default 86400;
} }

View File

@ -54,13 +54,14 @@ public class RateLimiterAspect {
public void doBefore(JoinPoint point, RateLimiter rateLimiter) { public void doBefore(JoinPoint point, RateLimiter rateLimiter) {
int time = rateLimiter.time(); int time = rateLimiter.time();
int count = rateLimiter.count(); int count = rateLimiter.count();
int timeout = rateLimiter.timeout();
try { try {
String combineKey = getCombineKey(rateLimiter, point); String combineKey = getCombineKey(rateLimiter, point);
RateType rateType = RateType.OVERALL; RateType rateType = RateType.OVERALL;
if (rateLimiter.limitType() == LimitType.CLUSTER) { if (rateLimiter.limitType() == LimitType.CLUSTER) {
rateType = RateType.PER_CLIENT; rateType = RateType.PER_CLIENT;
} }
long number = RedisUtils.rateLimiter(combineKey, rateType, count, time); long number = RedisUtils.rateLimiter(combineKey, rateType, count, time, timeout);
if (number == -1) { if (number == -1) {
String message = rateLimiter.message(); String message = rateLimiter.message();
if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) { if (StringUtils.startsWith(message, "{") && StringUtils.endsWith(message, "}")) {

View File

@ -4,6 +4,7 @@ import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.redisson.api.*; import org.redisson.api.*;
import org.redisson.api.options.KeysScanOptions;
import java.time.Duration; import java.time.Duration;
import java.util.Collection; import java.util.Collection;
@ -36,8 +37,22 @@ public class RedisUtils {
* @return -1 表示失败 * @return -1 表示失败
*/ */
public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) { public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
return rateLimiter(key, rateType, rate, rateInterval, 0);
}
/**
* 限流
*
* @param key 限流key
* @param rateType 限流类型
* @param rate 速率
* @param rateInterval 速率间隔
* @param timeout 超时时间
* @return -1 表示失败
*/
public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval, int timeout) {
RRateLimiter rateLimiter = CLIENT.getRateLimiter(key); RRateLimiter rateLimiter = CLIENT.getRateLimiter(key);
rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS); rateLimiter.trySetRate(rateType, rate, Duration.ofSeconds(rateInterval), Duration.ofSeconds(timeout));
if (rateLimiter.tryAcquire()) { if (rateLimiter.tryAcquire()) {
return rateLimiter.availablePermits(); return rateLimiter.availablePermits();
} else { } else {
@ -518,13 +533,34 @@ public class RedisUtils {
/** /**
* 获得缓存的基本对象列表(全局匹配忽略租户 自行拼接租户id) * 获得缓存的基本对象列表(全局匹配忽略租户 自行拼接租户id)
* * <P>
* limit-设置扫描的限制数量(默认为0,查询全部)
* pattern-设置键的匹配模式(默认为null)
* chunkSize-设置每次扫描的块大小(默认为0,本方法设置为1000)
* type-设置键的类型(默认为null,查询全部类型)
* </P>
* @see KeysScanOptions
* @param pattern 字符串前缀 * @param pattern 字符串前缀
* @return 对象列表 * @return 对象列表
*/ */
public static Collection<String> keys(final String pattern) { public static Collection<String> keys(final String pattern) {
Stream<String> stream = CLIENT.getKeys().getKeysStreamByPattern(pattern); return keys(KeysScanOptions.defaults().pattern(pattern).chunkSize(1000));
return stream.collect(Collectors.toList()); }
/**
* 通过扫描参数获取缓存的基本对象列表
* @param keysScanOptions 扫描参数
* <P>
* limit-设置扫描的限制数量(默认为0,查询全部)
* pattern-设置键的匹配模式(默认为null)
* chunkSize-设置每次扫描的块大小(默认为0)
* type-设置键的类型(默认为null,查询全部类型)
* </P>
* @see KeysScanOptions
*/
public static Collection<String> keys(final KeysScanOptions keysScanOptions) {
Stream<String> keysStream = CLIENT.getKeys().getKeysStream(keysScanOptions);
return keysStream.collect(Collectors.toList());
} }
/** /**

View File

@ -66,7 +66,7 @@ public class PlusSmsDao implements SmsDao {
*/ */
@Override @Override
public void clean() { public void clean() {
RedisUtils.deleteObject(GlobalConstants.GLOBAL_REDIS_KEY + "sms:"); RedisUtils.deleteKeys(GlobalConstants.GLOBAL_REDIS_KEY + "sms:*");
} }
} }

View File

@ -4,7 +4,6 @@ import cn.dev33.satoken.dao.SaTokenDao;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
import org.dromara.common.core.utils.reflect.ReflectUtils; import org.dromara.common.core.utils.reflect.ReflectUtils;
import org.dromara.common.mybatis.config.MybatisPlusConfig;
import org.dromara.common.redis.config.RedisConfig; import org.dromara.common.redis.config.RedisConfig;
import org.dromara.common.redis.config.properties.RedissonProperties; import org.dromara.common.redis.config.properties.RedissonProperties;
import org.dromara.common.tenant.core.TenantSaTokenDao; import org.dromara.common.tenant.core.TenantSaTokenDao;
@ -16,7 +15,7 @@ import org.redisson.config.ClusterServersConfig;
import org.redisson.config.SingleServerConfig; import org.redisson.config.SingleServerConfig;
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer; import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
@ -33,8 +32,8 @@ import org.springframework.context.annotation.Primary;
@ConditionalOnProperty(value = "tenant.enable", havingValue = "true") @ConditionalOnProperty(value = "tenant.enable", havingValue = "true")
public class TenantConfig { public class TenantConfig {
@ConditionalOnBean(MybatisPlusConfig.class) @ConditionalOnClass(TenantLineInnerInterceptor.class)
@AutoConfiguration(after = {MybatisPlusConfig.class}) @AutoConfiguration
static class MybatisPlusConfiguration { static class MybatisPlusConfiguration {
/** /**

View File

@ -27,8 +27,12 @@ public class TenantKeyPrefixHandler extends KeyPrefixHandler {
if (StringUtils.isBlank(name)) { if (StringUtils.isBlank(name)) {
return null; return null;
} }
if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) { try {
return super.map(name); if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
return super.map(name);
}
} catch (NoClassDefFoundError ignore) {
// 有些服务不需要mp导致类不存在 忽略即可
} }
if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) { if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
return super.map(name); return super.map(name);
@ -54,8 +58,12 @@ public class TenantKeyPrefixHandler extends KeyPrefixHandler {
if (StringUtils.isBlank(unmap)) { if (StringUtils.isBlank(unmap)) {
return null; return null;
} }
if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) { try {
return super.unmap(name); if (InterceptorIgnoreHelper.willIgnoreTenantLine("")) {
return super.unmap(name);
}
} catch (NoClassDefFoundError ignore) {
// 有些服务不需要mp导致类不存在 忽略即可
} }
if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) { if (StringUtils.contains(name, GlobalConstants.GLOBAL_REDIS_KEY)) {
return super.unmap(name); return super.unmap(name);

View File

@ -39,7 +39,7 @@ public class TranslationHandler extends JsonSerializer<Object> implements Contex
if (ObjectUtil.isNotNull(trans)) { if (ObjectUtil.isNotNull(trans)) {
// 如果映射字段不为空 则取映射字段的值 // 如果映射字段不为空 则取映射字段的值
if (StringUtils.isNotBlank(translation.mapper())) { if (StringUtils.isNotBlank(translation.mapper())) {
value = ReflectUtils.invokeGetter(gen.getCurrentValue(), translation.mapper()); value = ReflectUtils.invokeGetter(gen.currentValue(), translation.mapper());
} }
// 如果为 null 直接写出 // 如果为 null 直接写出
if (ObjectUtil.isNull(value)) { if (ObjectUtil.isNull(value)) {

View File

@ -19,23 +19,21 @@ import org.springframework.context.annotation.Bean;
@EnableConfigurationProperties(XssProperties.class) @EnableConfigurationProperties(XssProperties.class)
public class FilterConfig { public class FilterConfig {
@SuppressWarnings({"rawtypes", "unchecked"})
@Bean @Bean
@ConditionalOnProperty(value = "xss.enabled", havingValue = "true") @ConditionalOnProperty(value = "xss.enabled", havingValue = "true")
public FilterRegistrationBean xssFilterRegistration(XssProperties xssProperties) { public FilterRegistrationBean<XssFilter> xssFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean(); FilterRegistrationBean<XssFilter> registration = new FilterRegistrationBean<>();
registration.setDispatcherTypes(DispatcherType.REQUEST); registration.setDispatcherTypes(DispatcherType.REQUEST);
registration.setFilter(new XssFilter()); registration.setFilter(new XssFilter());
registration.addUrlPatterns("/*"); registration.addUrlPatterns("/*");
registration.setName("xssFilter"); registration.setName("xssFilter");
registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE); registration.setOrder(FilterRegistrationBean.HIGHEST_PRECEDENCE + 1);
return registration; return registration;
} }
@SuppressWarnings({"rawtypes", "unchecked"})
@Bean @Bean
public FilterRegistrationBean someFilterRegistration() { public FilterRegistrationBean<RepeatableFilter> someFilterRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean(); FilterRegistrationBean<RepeatableFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new RepeatableFilter()); registration.setFilter(new RepeatableFilter());
registration.addUrlPatterns("/*"); registration.addUrlPatterns("/*");
registration.setName("repeatableFilter"); registration.setName("repeatableFilter");

View File

@ -14,6 +14,7 @@ import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Map;
/** /**
* XSS过滤处理 * XSS过滤处理
@ -28,6 +29,33 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
super(request); super(request);
} }
@Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value != null) {
return HtmlUtil.cleanHtmlTag(value).trim();
}
return value;
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> valueMap = super.getParameterMap();
for (Map.Entry<String, String[]> entry : valueMap.entrySet()) {
String[] values = entry.getValue();
if (values != null) {
int length = values.length;
String[] escapseValues = new String[length];
for (int i = 0; i < length; i++) {
// 防xss攻击和过滤前后空格
escapseValues[i] = HtmlUtil.cleanHtmlTag(values[i]).trim();
}
valueMap.put(entry.getKey(), escapseValues);
}
}
return valueMap;
}
@Override @Override
public String[] getParameterValues(String name) { public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name); String[] values = super.getParameterValues(name);
@ -40,7 +68,7 @@ public class XssHttpServletRequestWrapper extends HttpServletRequestWrapper {
} }
return escapseValues; return escapseValues;
} }
return super.getParameterValues(name); return values;
} }
@Override @Override

View File

@ -2,6 +2,7 @@ package org.dromara.common.web.interceptor;
import cn.hutool.core.io.IoUtil; import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil; import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -64,9 +65,11 @@ public class PlusWebInvokeTimeInterceptor implements HandlerInterceptor {
@Override @Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
StopWatch stopWatch = KEY_CACHE.get(); StopWatch stopWatch = KEY_CACHE.get();
stopWatch.stop(); if (ObjectUtil.isNotNull(stopWatch)) {
log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime()); stopWatch.stop();
KEY_CACHE.remove(); log.info("[PLUS]结束请求 => URL[{}],耗时:[{}]毫秒", request.getMethod() + " " + request.getRequestURI(), stopWatch.getTime());
KEY_CACHE.remove();
}
} }
/** /**

View File

@ -34,14 +34,18 @@ public interface TestDemoMapper extends BaseMapperPlus<TestDemo, TestDemoVo> {
@DataColumn(key = "deptName", value = "dept_id"), @DataColumn(key = "deptName", value = "dept_id"),
@DataColumn(key = "userName", value = "user_id") @DataColumn(key = "userName", value = "user_id")
}) })
List<TestDemo> selectList(IPage<TestDemo> page, @Param(Constants.WRAPPER) Wrapper<TestDemo> queryWrapper); default <P extends IPage<TestDemoVo>> P selectVoPage(IPage<TestDemo> page, Wrapper<TestDemo> wrapper) {
return selectVoPage(page, wrapper, this.currentVoClass());
}
@Override @Override
@DataPermission({ @DataPermission({
@DataColumn(key = "deptName", value = "dept_id"), @DataColumn(key = "deptName", value = "dept_id"),
@DataColumn(key = "userName", value = "user_id") @DataColumn(key = "userName", value = "user_id")
}) })
List<TestDemo> selectList(@Param(Constants.WRAPPER) Wrapper<TestDemo> queryWrapper); default List<TestDemoVo> selectVoList(Wrapper<TestDemo> wrapper) {
return selectVoList(wrapper, this.currentVoClass());
}
@Override @Override
@DataPermission(value = { @DataPermission(value = {

View File

@ -1,5 +1,6 @@
package org.dromara.demo.service.impl; package org.dromara.demo.service.impl;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import lombok.Data; import lombok.Data;
@ -121,8 +122,9 @@ public class ExportExcelServiceImpl implements IExportExcelService {
List<DemoCityData> provinceList = new ArrayList<>(); List<DemoCityData> provinceList = new ArrayList<>();
// 实际业务中一般采用数据库读取的形式这里直接拼接创建 // 实际业务中一般采用数据库读取的形式这里直接拼接创建
provinceList.add(new DemoCityData(0, null, "安徽省")); provinceList.add(new DemoCityData(0, null, "P100000"));
provinceList.add(new DemoCityData(1, null, "江苏省")); provinceList.add(new DemoCityData(1, null, "P200000"));
provinceList.add(new DemoCityData(2, null, "P300000"));
return provinceList; return provinceList;
} }
@ -137,11 +139,11 @@ public class ExportExcelServiceImpl implements IExportExcelService {
List<DemoCityData> cityList = new ArrayList<>(); List<DemoCityData> cityList = new ArrayList<>();
// 实际业务中一般采用数据库读取的形式这里直接拼接创建 // 实际业务中一般采用数据库读取的形式这里直接拼接创建
cityList.add(new DemoCityData(0, 0, "合肥市")); cityList.add(new DemoCityData(0, 0, "C110000"));
cityList.add(new DemoCityData(1, 0, "芜湖市")); cityList.add(new DemoCityData(1, 0, "C120000"));
cityList.add(new DemoCityData(2, 1, "南京市")); cityList.add(new DemoCityData(2, 1, "C210000"));
cityList.add(new DemoCityData(3, 1, "无锡市")); cityList.add(new DemoCityData(3, 1, "C220000"));
cityList.add(new DemoCityData(4, 1, "徐州市")); cityList.add(new DemoCityData(4, 1, "C230000"));
selectParentData(provinceList, cityList); selectParentData(provinceList, cityList);
@ -157,17 +159,29 @@ public class ExportExcelServiceImpl implements IExportExcelService {
private List<DemoCityData> getAreaList(List<DemoCityData> cityList) { private List<DemoCityData> getAreaList(List<DemoCityData> cityList) {
List<DemoCityData> areaList = new ArrayList<>(); List<DemoCityData> areaList = new ArrayList<>();
int minCount = 500;
int maxCount = 10000;
// 实际业务中一般采用数据库读取的形式这里直接拼接创建 // 实际业务中一般采用数据库读取的形式这里直接拼接创建
areaList.add(new DemoCityData(0, 0, "瑶海区")); for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
areaList.add(new DemoCityData(1, 0, "庐江区")); areaList.add(new DemoCityData(areaList.size(), 0, String.format("A11%04d", i)));
areaList.add(new DemoCityData(2, 1, "南宁县")); }
areaList.add(new DemoCityData(3, 1, "镜湖区"));
areaList.add(new DemoCityData(4, 2, "玄武区")); for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
areaList.add(new DemoCityData(5, 2, "秦淮区")); areaList.add(new DemoCityData(areaList.size(), 1, String.format("A12%04d", i)));
areaList.add(new DemoCityData(6, 3, "宜兴市")); }
areaList.add(new DemoCityData(7, 3, "新吴区"));
areaList.add(new DemoCityData(8, 4, "鼓楼区")); for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
areaList.add(new DemoCityData(9, 4, "丰县")); areaList.add(new DemoCityData(areaList.size(), 2, String.format("A21%04d", i)));
}
for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
areaList.add(new DemoCityData(areaList.size(), 3, String.format("A22%04d", i)));
}
for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
areaList.add(new DemoCityData(areaList.size(), 4, String.format("A23%04d", i)));
}
selectParentData(cityList, areaList); selectParentData(cityList, areaList);

View File

@ -29,7 +29,6 @@ import org.dromara.common.core.utils.file.FileUtils;
import org.dromara.common.json.utils.JsonUtils; import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.generator.constant.GenConstants; import org.dromara.generator.constant.GenConstants;
import org.dromara.generator.domain.GenTable; import org.dromara.generator.domain.GenTable;
import org.dromara.generator.domain.GenTableColumn; import org.dromara.generator.domain.GenTableColumn;
@ -106,7 +105,8 @@ public class GenTableServiceImpl implements IGenTableService {
.like(StringUtils.isNotBlank(genTable.getTableName()), "lower(table_name)", StringUtils.lowerCase(genTable.getTableName())) .like(StringUtils.isNotBlank(genTable.getTableName()), "lower(table_name)", StringUtils.lowerCase(genTable.getTableName()))
.like(StringUtils.isNotBlank(genTable.getTableComment()), "lower(table_comment)", StringUtils.lowerCase(genTable.getTableComment())) .like(StringUtils.isNotBlank(genTable.getTableComment()), "lower(table_comment)", StringUtils.lowerCase(genTable.getTableComment()))
.between(params.get("beginTime") != null && params.get("endTime") != null, .between(params.get("beginTime") != null && params.get("endTime") != null,
"create_time", params.get("beginTime"), params.get("endTime")); "create_time", params.get("beginTime"), params.get("endTime"))
.orderByAsc("table_id");
return wrapper; return wrapper;
} }
@ -269,11 +269,10 @@ public class GenTableServiceImpl implements IGenTableService {
@DSTransactional @DSTransactional
@Override @Override
public void importGenTable(List<GenTable> tableList, String dataName) { public void importGenTable(List<GenTable> tableList, String dataName) {
Long operId = LoginHelper.getUserId();
try { try {
for (GenTable table : tableList) { for (GenTable table : tableList) {
String tableName = table.getTableName(); String tableName = table.getTableName();
GenUtils.initTable(table, operId); GenUtils.initTable(table);
table.setDataName(dataName); table.setDataName(dataName);
int row = baseMapper.insert(table); int row = baseMapper.insert(table);
if (row > 0) { if (row > 0) {

View File

@ -22,14 +22,13 @@ public class GenUtils {
/** /**
* 初始化表信息 * 初始化表信息
*/ */
public static void initTable(GenTable genTable, Long operId) { public static void initTable(GenTable genTable) {
genTable.setClassName(convertClassName(genTable.getTableName())); genTable.setClassName(convertClassName(genTable.getTableName()));
genTable.setPackageName(GenConfig.getPackageName()); genTable.setPackageName(GenConfig.getPackageName());
genTable.setModuleName(getModuleName(GenConfig.getPackageName())); genTable.setModuleName(getModuleName(GenConfig.getPackageName()));
genTable.setBusinessName(getBusinessName(genTable.getTableName())); genTable.setBusinessName(getBusinessName(genTable.getTableName()));
genTable.setFunctionName(replaceText(genTable.getTableComment())); genTable.setFunctionName(replaceText(genTable.getTableComment()));
genTable.setFunctionAuthor(GenConfig.getAuthor()); genTable.setFunctionAuthor(GenConfig.getAuthor());
genTable.setCreateBy(operId);
} }
/** /**

View File

@ -128,13 +128,9 @@ public class SysUserController extends BaseController {
@SaCheckPermission("system:user:query") @SaCheckPermission("system:user:query")
@GetMapping(value = {"/", "/{userId}"}) @GetMapping(value = {"/", "/{userId}"})
public R<SysUserInfoVo> getInfo(@PathVariable(value = "userId", required = false) Long userId) { public R<SysUserInfoVo> getInfo(@PathVariable(value = "userId", required = false) Long userId) {
userService.checkUserDataScope(userId);
SysUserInfoVo userInfoVo = new SysUserInfoVo(); SysUserInfoVo userInfoVo = new SysUserInfoVo();
SysRoleBo roleBo = new SysRoleBo();
roleBo.setStatus(UserConstants.ROLE_NORMAL);
List<SysRoleVo> roles = roleService.selectRoleList(roleBo);
userInfoVo.setRoles(LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isSuperAdmin()));
if (ObjectUtil.isNotNull(userId)) { if (ObjectUtil.isNotNull(userId)) {
userService.checkUserDataScope(userId);
SysUserVo sysUser = userService.selectUserById(userId); SysUserVo sysUser = userService.selectUserById(userId);
userInfoVo.setUser(sysUser); userInfoVo.setUser(sysUser);
userInfoVo.setRoleIds(roleService.selectRoleListByUserId(userId)); userInfoVo.setRoleIds(roleService.selectRoleListByUserId(userId));
@ -146,6 +142,10 @@ public class SysUserController extends BaseController {
userInfoVo.setPostIds(postService.selectPostListByUserId(userId)); userInfoVo.setPostIds(postService.selectPostListByUserId(userId));
} }
} }
SysRoleBo roleBo = new SysRoleBo();
roleBo.setStatus(UserConstants.ROLE_NORMAL);
List<SysRoleVo> roles = roleService.selectRoleList(roleBo);
userInfoVo.setRoles(LoginHelper.isSuperAdmin(userId) ? roles : StreamUtils.filter(roles, r -> !r.isSuperAdmin()));
return R.ok(userInfoVo); return R.ok(userInfoVo);
} }

View File

@ -1,12 +1,14 @@
package org.dromara.system.mapper; package org.dromara.system.mapper;
import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants; import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.dromara.common.mybatis.annotation.DataColumn; import org.dromara.common.mybatis.annotation.DataColumn;
import org.dromara.common.mybatis.annotation.DataPermission; import org.dromara.common.mybatis.annotation.DataPermission;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus; import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.system.domain.SysDept; import org.dromara.system.domain.SysDept;
import org.dromara.system.domain.vo.SysDeptVo; import org.dromara.system.domain.vo.SysDeptVo;
@ -46,6 +48,12 @@ public interface SysDeptMapper extends BaseMapperPlus<SysDept, SysDeptVo> {
}) })
long countDeptById(Long deptId); long countDeptById(Long deptId);
default List<SysDept> selectListByParentId(Long parentId) {
return this.selectList(new LambdaQueryWrapper<SysDept>()
.select(SysDept::getDeptId)
.apply(DataBaseHelper.findInSet(parentId, "ancestors")));
}
/** /**
* 根据角色ID查询部门树信息 * 根据角色ID查询部门树信息
* *

View File

@ -197,4 +197,6 @@ public interface ISysRoleService {
void cleanOnlineUserByRole(Long roleId); void cleanOnlineUserByRole(Long roleId);
void cleanOnlineUser(List<Long> userIds);
} }

View File

@ -6,7 +6,6 @@ import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.dromara.common.core.utils.StreamUtils; 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.SysDept;
import org.dromara.system.domain.SysRoleDept; import org.dromara.system.domain.SysRoleDept;
import org.dromara.system.mapper.SysDeptMapper; import org.dromara.system.mapper.SysDeptMapper;
@ -63,9 +62,7 @@ public class SysDataScopeServiceImpl implements ISysDataScopeService {
if (ObjectUtil.isNull(deptId)) { if (ObjectUtil.isNull(deptId)) {
return "-1"; return "-1";
} }
List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>() List<SysDept> deptList = deptMapper.selectListByParentId(deptId);
.select(SysDept::getDeptId)
.apply(DataBaseHelper.findInSet(deptId, "ancestors")));
List<Long> ids = StreamUtils.toList(deptList, SysDept::getDeptId); List<Long> ids = StreamUtils.toList(deptList, SysDept::getDeptId);
ids.add(deptId); ids.add(deptId);
if (CollUtil.isNotEmpty(ids)) { if (CollUtil.isNotEmpty(ids)) {

View File

@ -20,6 +20,7 @@ import org.dromara.common.core.service.DeptService;
import org.dromara.common.core.utils.MapstructUtils; import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.TreeBuildUtils; import org.dromara.common.core.utils.TreeBuildUtils;
import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.helper.DataBaseHelper; import org.dromara.common.mybatis.helper.DataBaseHelper;
@ -109,11 +110,22 @@ public class SysDeptServiceImpl implements ISysDeptService, DeptService {
if (CollUtil.isEmpty(depts)) { if (CollUtil.isEmpty(depts)) {
return CollUtil.newArrayList(); return CollUtil.newArrayList();
} }
return TreeBuildUtils.build(depts, (dept, tree) -> // 获取当前列表中每一个节点的parentId然后在列表中查找是否有id与其parentId对应若无对应则表明此时节点列表中该节点在当前列表中属于顶级节点
tree.setId(dept.getDeptId()) List<Tree<Long>> treeList = CollUtil.newArrayList();
.setParentId(dept.getParentId()) for (SysDeptVo d : depts) {
.setName(dept.getDeptName()) Long parentId = d.getParentId();
.setWeight(dept.getOrderNum())); SysDeptVo sysDeptVo = StreamUtils.findFirst(depts, it -> it.getDeptId().longValue() == parentId);
if (ObjectUtil.isNull(sysDeptVo)) {
List<Tree<Long>> trees = TreeBuildUtils.build(depts, parentId, (dept, tree) ->
tree.setId(dept.getDeptId())
.setParentId(dept.getParentId())
.setName(dept.getDeptName())
.setWeight(dept.getOrderNum()));
Tree<Long> tree = trees.stream().filter(it -> it.getId().longValue() == d.getDeptId()).findFirst().get();
treeList.add(tree);
}
}
return treeList;
} }
/** /**
@ -328,11 +340,14 @@ public class SysDeptServiceImpl implements ISysDeptService, DeptService {
public int updateDept(SysDeptBo bo) { public int updateDept(SysDeptBo bo) {
SysDept dept = MapstructUtils.convert(bo, SysDept.class); SysDept dept = MapstructUtils.convert(bo, SysDept.class);
SysDept oldDept = baseMapper.selectById(dept.getDeptId()); SysDept oldDept = baseMapper.selectById(dept.getDeptId());
if (ObjectUtil.isNull(oldDept)) {
throw new ServiceException("部门不存在,无法修改");
}
if (!oldDept.getParentId().equals(dept.getParentId())) { if (!oldDept.getParentId().equals(dept.getParentId())) {
// 如果是新父部门 则校验是否具有新父部门权限 避免越权 // 如果是新父部门 则校验是否具有新父部门权限 避免越权
this.checkDeptDataScope(dept.getParentId()); this.checkDeptDataScope(dept.getParentId());
SysDept newParentDept = baseMapper.selectById(dept.getParentId()); SysDept newParentDept = baseMapper.selectById(dept.getParentId());
if (ObjectUtil.isNotNull(newParentDept) && ObjectUtil.isNotNull(oldDept)) { if (ObjectUtil.isNotNull(newParentDept)) {
String newAncestors = newParentDept.getAncestors() + StringUtils.SEPARATOR + newParentDept.getDeptId(); String newAncestors = newParentDept.getAncestors() + StringUtils.SEPARATOR + newParentDept.getDeptId();
String oldAncestors = oldDept.getAncestors(); String oldAncestors = oldDept.getAncestors();
dept.setAncestors(newAncestors); dept.setAncestors(newAncestors);

View File

@ -16,7 +16,6 @@ import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.system.domain.SysDept; import org.dromara.system.domain.SysDept;
import org.dromara.system.domain.SysPost; import org.dromara.system.domain.SysPost;
import org.dromara.system.domain.SysUserPost; import org.dromara.system.domain.SysUserPost;
@ -82,12 +81,8 @@ public class SysPostServiceImpl implements ISysPostService, PostService {
} else if (ObjectUtil.isNotNull(bo.getBelongDeptId())) { } else if (ObjectUtil.isNotNull(bo.getBelongDeptId())) {
//部门树搜索 //部门树搜索
wrapper.and(x -> { wrapper.and(x -> {
List<Long> deptIds = deptMapper.selectList(new LambdaQueryWrapper<SysDept>() List<SysDept> deptList = deptMapper.selectListByParentId(bo.getBelongDeptId());
.select(SysDept::getDeptId) List<Long> deptIds = StreamUtils.toList(deptList, SysDept::getDeptId);
.apply(DataBaseHelper.findInSet(bo.getBelongDeptId(), "ancestors")))
.stream()
.map(SysDept::getDeptId)
.collect(Collectors.toList());
deptIds.add(bo.getBelongDeptId()); deptIds.add(bo.getBelongDeptId());
x.in(SysPost::getDeptId, deptIds); x.in(SysPost::getDeptId, deptIds);
}); });

View File

@ -438,7 +438,7 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService {
.eq(SysUserRole::getRoleId, userRole.getRoleId()) .eq(SysUserRole::getRoleId, userRole.getRoleId())
.eq(SysUserRole::getUserId, userRole.getUserId())); .eq(SysUserRole::getUserId, userRole.getUserId()));
if (rows > 0) { if (rows > 0) {
cleanOnlineUserByRole(userRole.getRoleId()); cleanOnlineUser(List.of(userRole.getUserId()));
} }
return rows; return rows;
} }
@ -452,11 +452,12 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService {
*/ */
@Override @Override
public int deleteAuthUsers(Long roleId, Long[] userIds) { public int deleteAuthUsers(Long roleId, Long[] userIds) {
List<Long> ids = List.of(userIds);
int rows = userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>() int rows = userRoleMapper.delete(new LambdaQueryWrapper<SysUserRole>()
.eq(SysUserRole::getRoleId, roleId) .eq(SysUserRole::getRoleId, roleId)
.in(SysUserRole::getUserId, Arrays.asList(userIds))); .in(SysUserRole::getUserId, ids));
if (rows > 0) { if (rows > 0) {
cleanOnlineUserByRole(roleId); cleanOnlineUser(ids);
} }
return rows; return rows;
} }
@ -472,7 +473,8 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService {
public int insertAuthUsers(Long roleId, Long[] userIds) { public int insertAuthUsers(Long roleId, Long[] userIds) {
// 新增用户与角色管理 // 新增用户与角色管理
int rows = 1; int rows = 1;
List<SysUserRole> list = StreamUtils.toList(List.of(userIds), userId -> { List<Long> ids = List.of(userIds);
List<SysUserRole> list = StreamUtils.toList(ids, userId -> {
SysUserRole ur = new SysUserRole(); SysUserRole ur = new SysUserRole();
ur.setUserId(userId); ur.setUserId(userId);
ur.setRoleId(roleId); ur.setRoleId(roleId);
@ -482,7 +484,7 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService {
rows = userRoleMapper.insertBatch(list) ? list.size() : 0; rows = userRoleMapper.insertBatch(list) ? list.size() : 0;
} }
if (rows > 0) { if (rows > 0) {
cleanOnlineUserByRole(roleId); cleanOnlineUser(ids);
} }
return rows; return rows;
} }
@ -506,6 +508,9 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService {
return; return;
} }
LoginUser loginUser = LoginHelper.getLoginUser(token); LoginUser loginUser = LoginHelper.getLoginUser(token);
if (ObjectUtil.isNull(loginUser) || CollUtil.isEmpty(loginUser.getRoles())) {
return;
}
if (loginUser.getRoles().stream().anyMatch(r -> r.getRoleId().equals(roleId))) { if (loginUser.getRoles().stream().anyMatch(r -> r.getRoleId().equals(roleId))) {
try { try {
StpUtil.logoutByTokenValue(token); StpUtil.logoutByTokenValue(token);
@ -515,6 +520,32 @@ public class SysRoleServiceImpl implements ISysRoleService, RoleService {
}); });
} }
@Override
public void cleanOnlineUser(List<Long> userIds) {
List<String> keys = StpUtil.searchTokenValue("", 0, -1, false);
if (CollUtil.isEmpty(keys)) {
return;
}
// 角色关联的在线用户量过大会导致redis阻塞卡顿 谨慎操作
keys.parallelStream().forEach(key -> {
String token = StringUtils.substringAfterLast(key, ":");
// 如果已经过期则跳过
if (StpUtil.stpLogic.getTokenActiveTimeoutByToken(token) < -1) {
return;
}
LoginUser loginUser = LoginHelper.getLoginUser(token);
if (ObjectUtil.isNull(loginUser)) {
return;
}
if (userIds.contains(loginUser.getUserId())) {
try {
StpUtil.logoutByTokenValue(token);
} catch (NotLoginException ignored) {
}
}
});
}
/** /**
* 查询角色并返回任务指派的列表支持分页 * 查询角色并返回任务指派的列表支持分页
* *

View File

@ -1,6 +1,7 @@
package org.dromara.system.service.impl; package org.dromara.system.service.impl;
import cn.dev33.satoken.secure.BCrypt; import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
@ -203,7 +204,7 @@ public class SysTenantServiceImpl implements ISysTenantService {
String numbers = RandomUtil.randomNumbers(6); String numbers = RandomUtil.randomNumbers(6);
// 判断是否存在如果存在则重新生成 // 判断是否存在如果存在则重新生成
if (tenantIds.contains(numbers)) { if (tenantIds.contains(numbers)) {
generateTenantId(tenantIds); return generateTenantId(tenantIds);
} }
return numbers; return numbers;
} }
@ -413,33 +414,36 @@ public class SysTenantServiceImpl implements ISysTenantService {
Map<String, SysDictData> map = StreamUtils.toIdentityMap(dataListTenant, SysDictData::getDictValue); Map<String, SysDictData> map = StreamUtils.toIdentityMap(dataListTenant, SysDictData::getDictValue);
for (SysDictData dictData : dataList) { for (SysDictData dictData : dataList) {
if (!map.containsKey(dictData.getDictValue())) { if (!map.containsKey(dictData.getDictValue())) {
SysDictData data = BeanUtil.toBean(dictData, SysDictData.class);
// 设置字典编码为 null // 设置字典编码为 null
dictData.setDictCode(null); data.setDictCode(null);
dictData.setTenantId(tenantId); data.setTenantId(tenantId);
dictData.setCreateTime(null); data.setCreateTime(null);
dictData.setUpdateTime(null); data.setUpdateTime(null);
set.add(tenantId); set.add(tenantId);
saveDataList.add(dictData); saveDataList.add(data);
} }
} }
} else { } else {
dictType.setDictId(null); SysDictType type = BeanUtil.toBean(dictType, SysDictType.class);
dictType.setTenantId(tenantId); type.setDictId(null);
dictType.setCreateTime(null); type.setTenantId(tenantId);
dictType.setUpdateTime(null); type.setCreateTime(null);
type.setUpdateTime(null);
set.add(tenantId); set.add(tenantId);
saveTypeList.add(dictType); saveTypeList.add(type);
if (CollUtil.isNotEmpty(dataList)) { if (CollUtil.isNotEmpty(dataList)) {
// 筛选出 dictType 对应的 data // 筛选出 dictType 对应的 data
for (SysDictData dictData : dataList) { for (SysDictData dictData : dataList) {
SysDictData data = BeanUtil.toBean(dictData, SysDictData.class);
// 设置字典编码为 null // 设置字典编码为 null
dictData.setDictCode(null); data.setDictCode(null);
dictData.setTenantId(tenantId); data.setTenantId(tenantId);
dictData.setCreateTime(null); data.setCreateTime(null);
dictData.setUpdateTime(null); data.setUpdateTime(null);
set.add(tenantId); set.add(tenantId);
saveDataList.add(data);
} }
saveDataList.addAll(dataList);
} }
} }
} }

View File

@ -26,7 +26,6 @@ import org.dromara.common.core.utils.StreamUtils;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.PageQuery;
import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.common.satoken.utils.LoginHelper; import org.dromara.common.satoken.utils.LoginHelper;
import org.dromara.system.domain.*; import org.dromara.system.domain.*;
import org.dromara.system.domain.bo.SysUserBo; import org.dromara.system.domain.bo.SysUserBo;
@ -88,9 +87,7 @@ public class SysUserServiceImpl implements ISysUserService, UserService {
.between(params.get("beginTime") != null && params.get("endTime") != null, .between(params.get("beginTime") != null && params.get("endTime") != null,
"u.create_time", params.get("beginTime"), params.get("endTime")) "u.create_time", params.get("beginTime"), params.get("endTime"))
.and(ObjectUtil.isNotNull(user.getDeptId()), w -> { .and(ObjectUtil.isNotNull(user.getDeptId()), w -> {
List<SysDept> deptList = deptMapper.selectList(new LambdaQueryWrapper<SysDept>() List<SysDept> deptList = deptMapper.selectListByParentId(user.getDeptId());
.select(SysDept::getDeptId)
.apply(DataBaseHelper.findInSet(user.getDeptId(), "ancestors")));
List<Long> ids = StreamUtils.toList(deptList, SysDept::getDeptId); List<Long> ids = StreamUtils.toList(deptList, SysDept::getDeptId);
ids.add(user.getDeptId()); ids.add(user.getDeptId());
w.in("u.dept_id", ids); w.in("u.dept_id", ids);

View File

@ -96,7 +96,7 @@ public class WfDefinitionConfigServiceImpl implements IWfDefinitionConfigService
baseMapper.delete(new LambdaQueryWrapper<WfDefinitionConfig>().eq(WfDefinitionConfig::getTableName, bo.getTableName())); baseMapper.delete(new LambdaQueryWrapper<WfDefinitionConfig>().eq(WfDefinitionConfig::getTableName, bo.getTableName()));
add.setTableName(add.getTableName().toLowerCase()); add.setTableName(add.getTableName().toLowerCase());
boolean flag = baseMapper.insertOrUpdate(add); boolean flag = baseMapper.insertOrUpdate(add);
if (baseMapper.insertOrUpdate(add)) { if (flag) {
bo.setId(add.getId()); bo.setId(add.getId());
} }
return flag; return flag;

View File

@ -100,7 +100,7 @@ services:
network_mode: "host" network_mode: "host"
ruoyi-server1: ruoyi-server1:
image: ruoyi/ruoyi-server:5.2.2 image: ruoyi/ruoyi-server:5.2.3
container_name: ruoyi-server1 container_name: ruoyi-server1
environment: environment:
# 时区上海 # 时区上海
@ -115,7 +115,7 @@ services:
network_mode: "host" network_mode: "host"
ruoyi-server2: ruoyi-server2:
image: ruoyi/ruoyi-server:5.2.2 image: ruoyi/ruoyi-server:5.2.3
container_name: ruoyi-server2 container_name: ruoyi-server2
environment: environment:
# 时区上海 # 时区上海
@ -130,7 +130,7 @@ services:
network_mode: "host" network_mode: "host"
ruoyi-monitor-admin: ruoyi-monitor-admin:
image: ruoyi/ruoyi-monitor-admin:5.2.2 image: ruoyi/ruoyi-monitor-admin:5.2.3
container_name: ruoyi-monitor-admin container_name: ruoyi-monitor-admin
environment: environment:
# 时区上海 # 时区上海
@ -142,7 +142,7 @@ services:
network_mode: "host" network_mode: "host"
ruoyi-snailjob-server: ruoyi-snailjob-server:
image: ruoyi/ruoyi-snailjob-server:5.2.2 image: ruoyi/ruoyi-snailjob-server:5.2.3
container_name: ruoyi-snailjob-server container_name: ruoyi-snailjob-server
environment: environment:
# 时区上海 # 时区上海

View File

@ -5,7 +5,7 @@ create table sys_social
( (
id number(20) not null, id number(20) not null,
user_id number(20) not null, user_id number(20) not null,
tenant_id varchar2(20) default null, tenant_id varchar2(20) default '000000',
auth_id varchar2(255) not null, auth_id varchar2(255) not null,
source varchar2(255) not null, source varchar2(255) not null,
open_id varchar2(255) default null, open_id varchar2(255) default null,

View File

@ -5,7 +5,7 @@ create table sys_social
( (
id int8 not null, id int8 not null,
user_id int8 not null, user_id int8 not null,
tenant_id varchar(20) default null::varchar, tenant_id varchar(20) default '000000'::varchar,
auth_id varchar(255) not null, auth_id varchar(255) not null,
source varchar(255) not null, source varchar(255) not null,
open_id varchar(255) default null::varchar, open_id varchar(255) default null::varchar,

View File

@ -5,7 +5,7 @@ create table sys_social
( (
id bigint not null comment '主键', id bigint not null comment '主键',
user_id bigint not null comment '用户ID', user_id bigint not null comment '用户ID',
tenant_id varchar(20) default null comment '租户id', tenant_id varchar(20) default '000000' comment '租户id',
auth_id varchar(255) not null comment '平台+平台唯一id', auth_id varchar(255) not null comment '平台+平台唯一id',
source varchar(255) not null comment '用户来源', source varchar(255) not null comment '用户来源',
open_id varchar(255) default null comment '平台编号唯一id', open_id varchar(255) default null comment '平台编号唯一id',

View File

@ -2,7 +2,7 @@ create table sys_social
( (
id bigint NOT NULL, id bigint NOT NULL,
user_id bigint NOT NULL, user_id bigint NOT NULL,
tenant_id nvarchar(20) NULL, tenant_id nvarchar(20) DEFAULT ('000000') NULL,
auth_id nvarchar(255) NOT NULL, auth_id nvarchar(255) NOT NULL,
source nvarchar(255) NOT NULL, source nvarchar(255) NOT NULL,
open_id nvarchar(255) NULL, open_id nvarchar(255) NULL,