Compare commits

...

1 Commits

Author SHA1 Message Date
疯狂的狮子Li 8136a0191a !860 发布 5.6.2 版本 依赖升级
* 🦁🦁🦁发布 5.6.2 版本 依赖升级
* update springboot 3.5.14 => 3.5.15(springboot 3.X最后一个版本)
* update 优化 防止工作流保存被xss过滤
* update README.md
* add 新增 赞助商
* fix 修复 多次驳回无法锁定审批人问题
* update 优化 findInSet 方法 增加参数校验防止注入
* fix 修复 前端输入性CVE漏洞 禁止使用代码生成到本地路径
* !848 fix: 用户管理导入用户时校验账号最小长度
2026-06-17 05:00:28 +00:00
16 changed files with 69 additions and 108 deletions
+1 -1
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.6.1" /> <option name="imageTag" value="ruoyi/ruoyi-monitor-admin:5.6.2" />
<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>
+1 -1
View File
@@ -2,7 +2,7 @@
<configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker"> <configuration default="false" name="ruoyi-server" type="docker-deploy" factoryName="dockerfile" server-name="Docker">
<deployment type="dockerfile"> <deployment type="dockerfile">
<settings> <settings>
<option name="imageTag" value="ruoyi/ruoyi-server:5.6.1" /> <option name="imageTag" value="ruoyi/ruoyi-server:5.6.2" />
<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>
+1 -1
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.6.1" /> <option name="imageTag" value="ruoyi/ruoyi-snailjob-server:5.6.2" />
<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>
+3 -3
View File
@@ -5,11 +5,11 @@
## 平台简介 ## 平台简介
[![码云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?label=Github%20Stars)](https://github.com/dromara/RuoYi-Vue-Plus) [![GitHub](https://img.shields.io/github/stars/dromara/RuoYi-Vue-Plus?style=social&label=Github%20Stars)](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) [![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/5.X/LICENSE) [![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/LICENSE)
<br> <br>
[![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.6.1-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus) [![RuoYi-Vue-Plus](https://img.shields.io/badge/RuoYi_Vue_Plus-5.6.2-success.svg)](https://gitee.com/dromara/RuoYi-Vue-Plus)
[![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5-blue.svg)]() [![Spring Boot](https://img.shields.io/badge/Spring%20Boot-3.5-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)]()
@@ -33,11 +33,11 @@
MaxKey 业界领先单点登录产品 - https://gitee.com/dromara/MaxKey <br> MaxKey 业界领先单点登录产品 - https://gitee.com/dromara/MaxKey <br>
CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br> CCFlow 驰聘低代码-流程-表单 - https://gitee.com/opencc/RuoYi-JFlow <br>
数舵科技 软件定制开发APP小程序等 - https://www.shuduokeji.com/ <br> 数舵科技 软件定制开发APP小程序等 - https://www.shuduokeji.com/ <br>
引迈信息 软件开发平台 - https://www.jnpfsoft.com/index.html?from=plus-doc <br>
Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br> Mall4J 高质量Java商城系统 - https://www.mall4j.com/cn/?statId=11 <br>
aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br> aizuda flowlong 工作流 - https://gitee.com/aizuda/flowlong <br>
Ruoyi-Plus-Uniapp - https://ruoyi.plus <br> Ruoyi-Plus-Uniapp - https://ruoyi.plus <br>
Topiam IAM/IDaaS身份管理平台 - https://www.topiam.cn/ <br> Topiam IAM/IDaaS身份管理平台 - https://www.topiam.cn/ <br>
稳定低价的大模型中转站 - https://aicodelink.top/register?aff=pHeG <br>
[如何成为赞助商 加群联系作者详谈 每日PV2500-3000 IP1700-2500](https://plus-doc.dromara.org/#/common/add_group) [如何成为赞助商 加群联系作者详谈 每日PV2500-3000 IP1700-2500](https://plus-doc.dromara.org/#/common/add_group)
+2 -2
View File
@@ -13,8 +13,8 @@
<description>Dromara RuoYi-Vue-Plus多租户管理系统</description> <description>Dromara RuoYi-Vue-Plus多租户管理系统</description>
<properties> <properties>
<revision>5.6.1</revision> <revision>5.6.2</revision>
<spring-boot.version>3.5.14</spring-boot.version> <spring-boot.version>3.5.15</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>
@@ -215,6 +215,7 @@ xss:
# 排除链接 # 排除链接
excludeUrls: excludeUrls:
- /system/notice - /system/notice
- /warm-flow/save-json
--- # 分布式锁 lock4j 全局配置 --- # 分布式锁 lock4j 全局配置
lock4j: lock4j:
+1 -1
View File
@@ -14,7 +14,7 @@
</description> </description>
<properties> <properties>
<revision>5.6.1</revision> <revision>5.6.2</revision>
</properties> </properties>
<dependencyManagement> <dependencyManagement>
@@ -1,5 +1,6 @@
package org.dromara.common.core.utils.sql; package org.dromara.common.core.utils.sql;
import cn.hutool.core.exceptions.UtilException;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.common.core.utils.StringUtils; import org.dromara.common.core.utils.StringUtils;
@@ -7,7 +8,7 @@ import org.dromara.common.core.utils.StringUtils;
/** /**
* sql操作工具类 * sql操作工具类
* *
* @author ruoyi * @author Lion Li
*/ */
@NoArgsConstructor(access = AccessLevel.PRIVATE) @NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SqlUtil { public class SqlUtil {
@@ -15,7 +16,7 @@ public class SqlUtil {
/** /**
* 定义常用的 sql关键字 * 定义常用的 sql关键字
*/ */
public static String SQL_REGEX = "\u000B|and |extractvalue|updatexml|sleep|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()"; public static final String SQL_REGEX = "\u000B|%0A|and |extractvalue|updatexml|sleep|information_schema|exec |insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |or |union |like |+|/*|user()";
/** /**
* 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序) * 仅支持字母、数字、下划线、空格、逗号、小数点(支持多个字段排序)
@@ -46,11 +47,21 @@ public class SqlUtil {
if (StringUtils.isEmpty(value)) { if (StringUtils.isEmpty(value)) {
return; return;
} }
// ==================== 核心增强:自动转义单引号 ====================
// 不抛异常、不破坏业务、不改变原方法行为、自动防注入
if (value.contains("'")) {
throw new UtilException("请求参数包含非法字符【'】,已禁止执行");
}
// ==================== 原有逻辑不变 ====================
String normalizedValue = value.replaceAll("\\p{Z}|\\s", "");
String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|"); String[] sqlKeywords = StringUtils.split(SQL_REGEX, "\\|");
for (String sqlKeyword : sqlKeywords) { for (String sqlKeyword : sqlKeywords) {
if (StringUtils.indexOfIgnoreCase(value, sqlKeyword) > -1) { if (StringUtils.indexOf(normalizedValue, sqlKeyword) > -1) {
throw new IllegalArgumentException("参数存在SQL注入风险"); throw new UtilException("请求参数包含敏感关键词'" + sqlKeyword + "',可能存在安全风险");
} }
} }
} }
} }
@@ -2,10 +2,12 @@ package org.dromara.common.mybatis.helper;
import cn.hutool.core.convert.Convert; import cn.hutool.core.convert.Convert;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import lombok.AccessLevel; import lombok.AccessLevel;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import org.dromara.common.core.exception.ServiceException; import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.sql.SqlUtil;
import org.dromara.common.mybatis.enums.DataBaseType; import org.dromara.common.mybatis.enums.DataBaseType;
import javax.sql.DataSource; import javax.sql.DataSource;
@@ -14,6 +16,8 @@ import java.sql.DatabaseMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/** /**
* 数据库助手 * 数据库助手
@@ -24,6 +28,7 @@ import java.util.List;
public class DataBaseHelper { public class DataBaseHelper {
private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class); private static final DynamicRoutingDataSource DS = SpringUtils.getBean(DynamicRoutingDataSource.class);
private static final Map<String, DataBaseType> DB_TYPE_CACHE = new ConcurrentHashMap<>();
/** /**
* 获取当前数据源对应的数据库类型 * 获取当前数据源对应的数据库类型
@@ -37,13 +42,17 @@ public class DataBaseHelper {
*/ */
public static DataBaseType getDataBaseType() { public static DataBaseType getDataBaseType() {
DataSource dataSource = DS.determineDataSource(); DataSource dataSource = DS.determineDataSource();
try (Connection conn = dataSource.getConnection()) { String dsKey = DynamicDataSourceContextHolder.peek();
DatabaseMetaData metaData = conn.getMetaData(); final String key = dsKey != null ? dsKey : "primary";
String databaseProductName = metaData.getDatabaseProductName(); return DB_TYPE_CACHE.computeIfAbsent(key, k -> {
return DataBaseType.find(databaseProductName); try (Connection conn = dataSource.getConnection()) {
} catch (SQLException e) { DatabaseMetaData metaData = conn.getMetaData();
throw new RuntimeException("获取数据库类型失败", e); String databaseProductName = metaData.getDatabaseProductName();
} return DataBaseType.find(databaseProductName);
} catch (SQLException e) {
throw new RuntimeException("获取数据库类型失败", e);
}
});
} }
/** /**
@@ -55,13 +64,15 @@ public class DataBaseHelper {
*/ */
public static DataBaseType getDataBaseType(String dsName) { public static DataBaseType getDataBaseType(String dsName) {
DataSource dataSource = DS.getDataSource(dsName); DataSource dataSource = DS.getDataSource(dsName);
try (Connection conn = dataSource.getConnection()) { return DB_TYPE_CACHE.computeIfAbsent(dsName, k -> {
DatabaseMetaData metaData = conn.getMetaData(); try (Connection conn = dataSource.getConnection()) {
String databaseProductName = metaData.getDatabaseProductName(); DatabaseMetaData metaData = conn.getMetaData();
return DataBaseType.find(databaseProductName); String databaseProductName = metaData.getDatabaseProductName();
} catch (SQLException e) { return DataBaseType.find(databaseProductName);
throw new RuntimeException("获取数据库类型失败", e); } catch (SQLException e) {
} throw new RuntimeException("获取数据库类型失败", e);
}
});
} }
/** /**
@@ -79,6 +90,8 @@ public class DataBaseHelper {
*/ */
public static String findInSet(Object var1, String var2) { public static String findInSet(Object var1, String var2) {
String var = Convert.toStr(var1); String var = Convert.toStr(var1);
SqlUtil.filterKeyword(var);
SqlUtil.filterKeyword(var2);
return switch (getDataBaseType()) { return switch (getDataBaseType()) {
// instr(',0,100,101,' , ',100,') <> 0 // instr(',0,100,101,' , ',100,') <> 0
case ORACLE -> "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var); case ORACLE -> "instr(','||%s||',' , ',%s,') <> 0".formatted(var2, var);
@@ -92,7 +105,9 @@ public class DataBaseHelper {
} }
/** /**
* 获取当前加载的数据库名 * 获取当前注册的数据源名称列表。
*
* @return 数据源名称列表
*/ */
public static List<String> getDataSourceNameList() { public static List<String> getDataSourceNameList() {
return new ArrayList<>(DS.getDataSources().keySet()); return new ArrayList<>(DS.getDataSources().keySet());
@@ -157,19 +157,6 @@ public class GenController extends BaseController {
genCode(response, data); genCode(response, data);
} }
/**
* 生成代码(自定义路径)
*
* @param tableId 表ID
*/
@SaCheckPermission("tool:gen:code")
@Log(title = "代码生成", businessType = BusinessType.GENCODE)
@GetMapping("/genCode/{tableId}")
public R<Void> genCode(@PathVariable("tableId") Long tableId) {
genTableService.generatorCode(tableId);
return R.ok();
}
/** /**
* 同步数据库 * 同步数据库
* *
@@ -101,12 +101,12 @@ public class GenTable extends BaseEntity {
private String functionAuthor; private String functionAuthor;
/** /**
* 生成代码方式(0zip压缩包 1自定义路径 * 生成代码方式(固定为0zip压缩包)
*/ */
private String genType; private String genType;
/** /**
* 生成路径(不填默认项目路径 * 生成路径(兼容历史字段
*/ */
@TableField(updateStrategy = FieldStrategy.NOT_EMPTY) @TableField(updateStrategy = FieldStrategy.NOT_EMPTY)
private String genPath; private String genPath;
@@ -23,7 +23,6 @@ import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.SpringUtils; import org.dromara.common.core.utils.SpringUtils;
import org.dromara.common.core.utils.StreamUtils; 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.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;
@@ -40,7 +39,6 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@@ -222,6 +220,8 @@ public class GenTableServiceImpl implements IGenTableService {
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Override @Override
public void updateGenTable(GenTable genTable) { public void updateGenTable(GenTable genTable) {
genTable.setGenType("0");
genTable.setGenPath("/");
String options = JsonUtils.toJsonString(genTable.getParams()); String options = JsonUtils.toJsonString(genTable.getParams());
genTable.setOptions(options); genTable.setOptions(options);
int row = baseMapper.updateById(genTable); int row = baseMapper.updateById(genTable);
@@ -357,40 +357,6 @@ public class GenTableServiceImpl implements IGenTableService {
return outputStream.toByteArray(); return outputStream.toByteArray();
} }
/**
* 生成代码(自定义路径)
*
* @param tableId 表名称
*/
@Override
public void generatorCode(Long tableId) {
// 查询表信息
GenTable table = baseMapper.selectGenTableById(tableId);
// 设置主键列信息
setPkColumn(table);
VelocityInitializer.initVelocity();
VelocityContext context = VelocityUtils.prepareContext(table);
// 获取模板列表
List<String> templates = VelocityUtils.getTemplateList(table.getTplCategory(), table.getDataName());
for (String template : templates) {
if (!StringUtils.containsAny(template, "sql.vm", "api.ts.vm", "types.ts.vm", "index.vue.vm", "index-tree.vue.vm")) {
// 渲染模板
StringWriter sw = new StringWriter();
Template tpl = Velocity.getTemplate(template, Constants.UTF8);
tpl.merge(context, sw);
try {
String path = getGenPath(table, template);
FileUtils.writeUtf8String(sw.toString(), path);
} catch (Exception e) {
throw new ServiceException("渲染模板失败,表名:" + table.getTableName());
}
}
}
}
/** /**
* 同步数据库 * 同步数据库
* *
@@ -560,19 +526,5 @@ public class GenTableServiceImpl implements IGenTableService {
} }
} }
/**
* 获取代码生成地址
*
* @param table 业务表信息
* @param template 模板文件路径
* @return 生成地址
*/
public static String getGenPath(GenTable table, String template) {
String genPath = table.getGenPath();
if (StringUtils.equals(genPath, "/")) {
return System.getProperty("user.dir") + File.separator + "src" + File.separator + VelocityUtils.getFileName(template, table);
}
return genPath + File.separator + VelocityUtils.getFileName(template, table);
}
} }
@@ -110,13 +110,6 @@ public interface IGenTableService {
*/ */
byte[] downloadCode(Long tableId); byte[] downloadCode(Long tableId);
/**
* 生成代码(自定义路径)
*
* @param tableId 表名称
*/
void generatorCode(Long tableId);
/** /**
* 同步数据库 * 同步数据库
* *
@@ -39,7 +39,7 @@ public class SysUserBo extends BaseEntity {
*/ */
@Xss(message = "用户账号不能包含脚本字符") @Xss(message = "用户账号不能包含脚本字符")
@NotBlank(message = "用户账号不能为空") @NotBlank(message = "用户账号不能为空")
@Size(min = 0, max = 30, message = "用户账号长度不能超过{max}个字符") @Size(min = 2, max = 30, message = "用户账号长度必须在{min}到{max}个字符之间")
private String userName; private String userName;
/** /**
@@ -159,9 +159,11 @@ public class WorkflowGlobalListener implements GlobalListener {
String[] userIdArray = userIds.split(StringUtils.SEPARATOR); String[] userIdArray = userIds.split(StringUtils.SEPARATOR);
if (userIdArray.length > 0) { if (userIdArray.length > 0) {
flowTask.setPermissionList(List.of(userIdArray)); flowTask.setPermissionList(List.of(userIdArray));
// 移除已处理的状态变量 if (TaskStatusEnum.PASS.getStatus().equals(taskStatus)) {
variable.remove(nodeKey); // 办理指定人变量只消费一次;驳回指定人变量需要保留给后续重复驳回。
FlowEngine.insService().removeVariables(flowTask.getInstanceId(), nodeKey); variable.remove(nodeKey);
FlowEngine.insService().removeVariables(flowTask.getInstanceId(), nodeKey);
}
} }
} }
+4 -4
View File
@@ -99,7 +99,7 @@ services:
network_mode: "host" network_mode: "host"
ruoyi-server1: ruoyi-server1:
image: ruoyi/ruoyi-server:5.6.1 image: ruoyi/ruoyi-server:5.6.2
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.6.1 image: ruoyi/ruoyi-server:5.6.2
container_name: ruoyi-server2 container_name: ruoyi-server2
environment: environment:
# 时区上海 # 时区上海
@@ -131,7 +131,7 @@ services:
network_mode: "host" network_mode: "host"
ruoyi-monitor-admin: ruoyi-monitor-admin:
image: ruoyi/ruoyi-monitor-admin:5.6.1 image: ruoyi/ruoyi-monitor-admin:5.6.2
container_name: ruoyi-monitor-admin container_name: ruoyi-monitor-admin
environment: environment:
# 时区上海 # 时区上海
@@ -143,7 +143,7 @@ services:
network_mode: "host" network_mode: "host"
ruoyi-snailjob-server: ruoyi-snailjob-server:
image: ruoyi/ruoyi-snailjob-server:5.6.1 image: ruoyi/ruoyi-snailjob-server:5.6.2
container_name: ruoyi-snailjob-server container_name: ruoyi-snailjob-server
environment: environment:
# 时区上海 # 时区上海