Compare commits

..

10 Commits

Author SHA1 Message Date
Looly
23e3be4ec1 修复JschSessionPool并发问题(pr#4079@Github) 2025-09-16 21:05:59 +08:00
Golden Looly
ca32095739 Merge pull request #4079 from xxx-tea/v5-dev
Fix issue 4077 解决Jsch会话池并发删除问题
2025-09-16 21:03:43 +08:00
xxxtea
8844bcd5ce Fix issue 4077 解决Jsch会话池并发删除问题
#4077
2025-09-16 14:18:29 +08:00
Looly
6bc985b078 fix confict 2025-09-16 11:08:06 +08:00
Looly
235d914f87 Mail.buildContent改进,正文部分总在最前(issue#4072@Github)gts 2025-09-16 11:03:53 +08:00
elichow
f8ef09dbb6 Merge pull request #4076 from elichow/v5-dev
`Message`增加setter和构造方法(issue#ICXTP2@Gitee)
2025-09-15 11:13:44 +08:00
choweli
2f44ddb0f5 Message增加setter和构造方法(issue#ICXTP2@Gitee) 2025-09-15 11:12:44 +08:00
Looly
172bbff73e Mail.buildContent改进,正文部分总在最前(issue#4072@Github) 2025-09-13 16:33:31 +08:00
Looly
905fcbdfac 修复NamedSql.replaceVar关键字处理问题(issue#4062@Github) 2025-09-11 14:33:44 +08:00
Looly
cabeaebbd5 修复DialectRunner.count方法中,去除包含多字段order by子句的SQL语句时错误问题(issue#4066@Github) 2025-09-11 14:12:19 +08:00
12 changed files with 152 additions and 31 deletions

View File

@@ -2,7 +2,7 @@
# 🚀Changelog
-------------------------------------------------------------------------------------------------------------
# 5.8.41(2025-09-08)
# 5.8.41(2025-09-16)
### 🐣新特性
* 【core 】 增加`WeakKeyValueConcurrentMap`及其关联类,同时废弃`WeakConcurrentMap`并替换issue#4039@Github
@@ -15,6 +15,9 @@
* 【extra 】 `RedisDS`增加`getPool``getSetting`方法issue#ICVWDI@Gitee
* 【core 】 `NumberUtil.pow`增加重载支持指数自定义保留位数pr#4052@Github
* 【core 】 `NumberUtil.isPrimes`优化判断pr#4058@Github
* 【extra 】 `Mail.buildContent`改进正文部分总在最前issue#4072@Github
* 【core 】 `DataSizeUtil`改进,兼容`GiB`等单位名称issue#ICXXVF@Github
* 【ai 】 `Message`增加setter和构造方法issue#ICXTP2@Gitee
### 🐞Bug修复
* 【core 】 修复`ReflectUtil`中因class和Method关联导致的缓存无法回收问题issue#4039@Github
@@ -22,6 +25,9 @@
* 【core 】 修复`IoUtil``closeIfPosible`拼写错误,新建一个`closeIfPossible`方法原方法标记deprecatedissue#4047@Github
* 【http 】 修复`HttpRequest.sendRedirectIfPossible`未对308做判断问题。issue#4053@Github
* 【cron 】 修复`CronPatternUtil.nextDateAfter`当日为L时计算错误问题。issue#4056@Github
* 【db 】 修复`NamedSql.replaceVar`关键字处理问题issue#4062@Github
* 【db 】 修复`DialectRunner.count`方法中去除包含多字段order by子句的SQL语句时错误问题issue#4066@Github
* 【extra 】 修复`JschSessionPool`并发问题pr#4079@Github
-------------------------------------------------------------------------------------------------------------
# 5.8.40(2025-08-26)

View File

@@ -24,9 +24,15 @@ package cn.hutool.ai.core;
*/
public class Message {
//角色 注意如果设置系统消息请放在messages列表的第一位
private final String role;
private String role;
//内容
private final Object content;
private Object content;
/**
* 构造
*/
public Message() {
}
/**
* 构造
@@ -39,6 +45,15 @@ public class Message {
this.content = content;
}
/**
* 设置角色
*
* @param role 角色
*/
public void setRole(final String role) {
this.role = role;
}
/**
* 获取角色
*
@@ -56,4 +71,13 @@ public class Message {
public Object getContent() {
return content;
}
/**
* 设置内容
*
* @param content 内容
*/
public void setContent(final Object content) {
this.content = content;
}
}

View File

@@ -28,7 +28,7 @@ public final class DataSize implements Comparable<DataSize> {
/**
* The pattern for parsing.
*/
private static final Pattern PATTERN = Pattern.compile("^([+-]?\\d+(\\.\\d+)?)([a-zA-Z]{0,2})$");
private static final Pattern PATTERN = Pattern.compile("^([+-]?\\d+(\\.\\d+)?)([a-zA-Z]{0,3})$");
/**
* Bytes per Kilobyte(KB).

View File

@@ -1,5 +1,6 @@
package cn.hutool.core.io.unit;
import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.StrUtil;
/**
@@ -75,11 +76,16 @@ public enum DataUnit {
/**
* 通过后缀返回对应的 DataUnit
*
* @param suffix 单位后缀
* @return 匹配到的{@link DataUnit}
* @param suffix 单位后缀如KB、GB、GiB等
* @return 匹配到的{@code DataUnit}
* @throws IllegalArgumentException 后缀无法识别报错
*/
public static DataUnit fromSuffix(String suffix) {
// issue#ICXXVF 兼容KiB、MiB、GiB
if(StrUtil.length(suffix) == 3 && CharUtil.equals(suffix.charAt(1), 'i', true)){
suffix = new String(new char[]{suffix.charAt(0), suffix.charAt(2)});
}
for (DataUnit candidate : values()) {
// 支持类似于 3MB3M3m等写法
if (StrUtil.startWithIgnoreCase(candidate.suffix, suffix)) {

View File

@@ -1,5 +1,6 @@
package cn.hutool.core.io.unit;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -80,4 +81,10 @@ public class DataSizeUtilTest {
final long bytes = DataSize.parse(size).toBytes();
assertEquals(10244587, bytes);
}
@Test
void issueICXXVFTest(){
final long parse = DataSizeUtil.parse("279.40GiB");
Assertions.assertEquals(300003465625L, parse);
}
}

View File

@@ -1,7 +1,6 @@
package cn.hutool.db;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
@@ -9,18 +8,12 @@ import cn.hutool.db.dialect.Dialect;
import cn.hutool.db.dialect.DialectFactory;
import cn.hutool.db.handler.NumberHandler;
import cn.hutool.db.handler.RsHandler;
import cn.hutool.db.sql.Query;
import cn.hutool.db.sql.SqlBuilder;
import cn.hutool.db.sql.SqlExecutor;
import cn.hutool.db.sql.SqlUtil;
import cn.hutool.db.sql.Wrapper;
import cn.hutool.db.sql.*;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 提供基于方言的原始增删改查执行封装
@@ -275,14 +268,8 @@ public class DialectRunner implements Serializable {
public long count(Connection conn, SqlBuilder sqlBuilder) throws SQLException {
checkConn(conn);
String selectSql = sqlBuilder.build();
// 去除order by 子句
final Pattern pattern = PatternPool.get("(.*?)[\\s]order[\\s]by[\\s][^\\s]+\\s(asc|desc)?", Pattern.CASE_INSENSITIVE);
final Matcher matcher = pattern.matcher(selectSql);
if (matcher.matches()) {
selectSql = matcher.group(1);
}
final String selectSql = SqlUtil.removeOuterOrderBy(sqlBuilder.build());
return SqlExecutor.queryAndClosePs(dialect.psForCount(conn,
SqlBuilder.of(selectSql).addParams(sqlBuilder.getParamValueArray())),

View File

@@ -138,7 +138,7 @@ public class NamedSql {
if(paramMap.containsKey(nameStr)) {
// 有变量对应值值可以为null替换占位符为?变量值放入相应index位置
final Object paramValue = paramMap.get(nameStr);
if(ArrayUtil.isArray(paramValue) && StrUtil.containsIgnoreCase(sqlBuilder, "in")){
if(ArrayUtil.isArray(paramValue) && SqlUtil.isInClause(sqlBuilder)){
// 可能为select in (xxx)语句则拆分参数为多个参数变成in (?,?,?)
final int length = ArrayUtil.length(paramValue);
for (int i = 0; i < length; i++) {

View File

@@ -1,7 +1,9 @@
package cn.hutool.db.sql;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.PatternPool;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.DbRuntimeException;
import cn.hutool.db.Entity;
@@ -11,13 +13,10 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.nio.charset.Charset;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.RowId;
import java.sql.SQLException;
import java.sql.*;
import java.util.List;
import java.util.Map.Entry;
import java.util.regex.Pattern;
/**
* SQL相关工具类包括相关SQL语句拼接等
@@ -27,6 +26,15 @@ import java.util.Map.Entry;
*/
public class SqlUtil {
/**
* 创建SQL中的order by语句的正则
*/
private static final Pattern PATTERN_ORDER_BY = PatternPool.get("(.*)\\s+order\\s+by\\s+[^\\s]+", Pattern.CASE_INSENSITIVE);
/**
* SQL中的in语句部分的正则
*/
private static final Pattern PATTERN_IN_CLAUSE = PatternPool.get("\\s+in\\s+[(]", Pattern.CASE_INSENSITIVE);
/**
* 构件相等条件的where语句<br>
* 如果没有条件语句,泽返回空串,表示没有条件
@@ -252,4 +260,28 @@ public class SqlUtil {
public static java.sql.Timestamp toSqlTimestamp(java.util.Date date) {
return new java.sql.Timestamp(date.getTime());
}
/**
* 移除 SQL中的 ORDER BY 子句
*
* @param selectSql 原始 SQL
* @return 移除 ORDER BY 子句后的 SQL
* @since 5.8.41
*/
public static String removeOuterOrderBy(final String selectSql) {
// 去除order by 子句
return ReUtil.getGroup1(PATTERN_ORDER_BY, selectSql);
}
/**
* 判断当前上下文是否在 IN 子句中
* 通过检查变量前的SQL文本判断是否符合 IN 子句的模式
*
* @param sql 当前已构建的SQL
* @return 是否在 IN 子句中
* @since 5.8.41
*/
public static boolean isInClause(final CharSequence sql) {
return ReUtil.contains(PATTERN_IN_CLAUSE, sql);
}
}

View File

@@ -2,7 +2,6 @@ package cn.hutool.db;
import cn.hutool.core.map.MapUtil;
import cn.hutool.db.sql.NamedSql;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import java.sql.SQLException;
@@ -10,6 +9,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
public class NamedSqlTest {
@Test
@@ -100,4 +101,28 @@ public class NamedSqlTest {
query = Db.use().query(sql, new Object[]{paramMap});
assertEquals(1, query.size());
}
@Test
public void parseInTest2() {
// 测试表名包含"in"但不是IN子句的情况
final String sql = "select * from information where info_data = :info";
final HashMap<String, Object> paramMap = MapUtil.of("info", new int[]{10, 20});
final NamedSql namedSql = new NamedSql(sql, paramMap);
// sql语句不包含IN子句不会展开数组
assertEquals("select * from information where info_data = ?", namedSql.getSql());
assertArrayEquals(new int[]{10, 20}, (int[]) namedSql.getParams()[0]);
}
@Test
public void parseInTest3() {
// 测试字符串中包含"in"关键字但不是IN子句的情况
final String sql = "select * from user where comment = 'include in text' and id = :id";
final HashMap<String, Object> paramMap = MapUtil.of("id", new int[]{5, 6});
final NamedSql namedSql = new NamedSql(sql, paramMap);
// sql语句不包含IN子句不会展开数组
assertEquals("select * from user where comment = 'include in text' and id = ?", namedSql.getSql());
assertArrayEquals(new int[]{5, 6}, (int[]) namedSql.getParams()[0]);
}
}

View File

@@ -0,0 +1,31 @@
package cn.hutool.db.sql;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class Issue4066Test {
/**
* 基础测试:简单的 ORDER BY 语句
*/
@Test
public void removeOuterOrderByTest1() {
// 测试基本的ORDER BY移除
final String sql = "SELECT * FROM users ORDER BY name";
final String result = SqlUtil.removeOuterOrderBy(sql);
assertEquals("SELECT * FROM users", result);
}
/**
* 多字段 ORDER BY 测试:包含多个排序字段的复杂 ORDER BY语句
*/
@Test
public void removeOuterOrderByTest2() {
// 测试多字段ORDER BY移除
final String sql = "SELECT id, name, age FROM users WHERE status = 'active' ORDER BY name ASC, age DESC, created_date";
final String result = SqlUtil.removeOuterOrderBy(sql);
assertEquals("SELECT id, name, age FROM users WHERE status = 'active'", result);
}
}

View File

@@ -465,7 +465,7 @@ public class Mail implements Builder<MimeMessage> {
// 正文
final MimeBodyPart body = new MimeBodyPart();
body.setContent(content, StrUtil.format("text/{}; charset={}", isHtml ? "html" : "plain", charsetStr));
this.multipart.addBodyPart(body);
this.multipart.addBodyPart(body, 0);
return this.multipart;
}

View File

@@ -4,7 +4,6 @@ import cn.hutool.core.lang.SimpleCache;
import cn.hutool.core.util.StrUtil;
import com.jcraft.jsch.Session;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
@@ -14,12 +13,16 @@ import java.util.Map.Entry;
* @author looly
*/
public enum JschSessionPool {
/**
* 单例对象
*/
INSTANCE;
/**
* SSH会话池keyhostvalueSession对象
*/
private final SimpleCache<String, Session> cache = new SimpleCache<>(new HashMap<>());
private final SimpleCache<String, Session> cache = new SimpleCache<>();
/**
* 获取Session不存在返回null