diff --git a/CHANGELOG.md b/CHANGELOG.md
index 30275df37..24ef2f625 100755
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,25 @@
# 🚀Changelog
+-------------------------------------------------------------------------------------------------------------
+# 5.8.34(2024-11-25)
+
+### 🐣新特性
+* 【http 】 增加Windows微信浏览器识别(issue#IB3SJF@Gitee)
+* 【core 】 ZipUtil.unzip增加编码容错(issue#I3UZ28@Gitee)
+* 【core 】 Calculator兼容`x`字符作为乘号(issue#3787@Github)
+* 【poi 】 Excel07SaxReader中,对于小数类型,增加精度判断(issue#IB0EJ9@Gitee)
+* 【extra 】 SpringUtil增加getBean重载(issue#3779@Github)
+* 【core 】 DataSizeUtil 新增format方法(issue#IB6UUX@Gitee)
+
+### 🐞Bug修复
+* 【core 】 修复DateUtil.rangeToList中step小于等于0时无限循环问题(issue#3783@Github)
+* 【cron 】 修复cron模块依赖log模块问题
+* 【extra 】 修复MailUtil发送html格式邮件无法正常展示图片问题(pr#1279@Gitee)
+* 【core 】 【可能的向下兼容问题】修复双引号转义符转义错误问题,修改规则后,对非闭合双引号字段的策略变更,如"aa,则被识别为aa(issue#IB5UQ8@Gitee)
+* 【extra 】 修复Sftp中传入Session重连时逻辑错误问题(issue#IB69U8@Gitee)
+* 【json 】 修复JSONUtil.toBean()中将JSON数组字符串转Map对象返回错误问题(issue#3795@Github)
+
-------------------------------------------------------------------------------------------------------------
# 5.8.33(2024-11-05)
diff --git a/README-EN.md b/README-EN.md
index 1e201f42e..e97a535e9 100755
--- a/README-EN.md
+++ b/README-EN.md
@@ -40,7 +40,7 @@
-
+
@@ -150,18 +150,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop:
cn.hutool
hutool-all
- 5.8.33
+ 5.8.34
```
### 🍐Gradle
```
-implementation 'cn.hutool:hutool-all:5.8.33'
+implementation 'cn.hutool:hutool-all:5.8.34'
```
## 📥Download
-- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.33/)
+- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.34/)
> 🔔️note:
> Hutool 5.x supports JDK8+ and is not tested on Android platforms, and cannot guarantee that all tool classes or tool methods are available.
diff --git a/README.md b/README.md
index 67b4a553c..4f0285353 100755
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@
-
+
@@ -143,20 +143,20 @@ Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu
cn.hutool
hutool-all
- 5.8.33
+ 5.8.34
```
### 🍐Gradle
```
-implementation 'cn.hutool:hutool-all:5.8.33'
+implementation 'cn.hutool:hutool-all:5.8.34'
```
### 📥下载jar
点击以下链接,下载`hutool-all-X.X.X.jar`即可:
-- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.33/)
+- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.34/)
> 🔔️注意
> Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。
diff --git a/bin/version.txt b/bin/version.txt
index ac78cf1a9..8cf2ade4e 100755
--- a/bin/version.txt
+++ b/bin/version.txt
@@ -1 +1 @@
-5.8.33
+5.8.34
diff --git a/docs/js/version.js b/docs/js/version.js
index 5af72462d..36dcf0ff4 100755
--- a/docs/js/version.js
+++ b/docs/js/version.js
@@ -1 +1 @@
-var version = '5.8.33'
\ No newline at end of file
+var version = '5.8.34'
\ No newline at end of file
diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml
index 8c23ef85e..16816432b 100755
--- a/hutool-all/pom.xml
+++ b/hutool-all/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-all
diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml
index 130b31323..3b59fff4d 100755
--- a/hutool-aop/pom.xml
+++ b/hutool-aop/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-aop
diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml
index 2d146dd50..e9174e95f 100755
--- a/hutool-bloomFilter/pom.xml
+++ b/hutool-bloomFilter/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-bloomFilter
diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml
index 2b047ad20..70bd043cd 100755
--- a/hutool-bom/pom.xml
+++ b/hutool-bom/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-bom
diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml
index d56159136..1c6fdd3e6 100755
--- a/hutool-cache/pom.xml
+++ b/hutool-cache/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-cache
diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java
index 7727a1fbd..2e90c9c4c 100755
--- a/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java
+++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/StampedCache.java
@@ -1,7 +1,6 @@
package cn.hutool.cache.impl;
import cn.hutool.core.collection.CopiedIter;
-import cn.hutool.core.thread.ThreadUtil;
import java.util.Iterator;
import java.util.concurrent.locks.StampedLock;
diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml
index d21ab174b..55c92332e 100755
--- a/hutool-captcha/pom.xml
+++ b/hutool-captcha/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-captcha
diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java b/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java
index 7f6cf2ed1..f528cf359 100755
--- a/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java
+++ b/hutool-captcha/src/main/java/cn/hutool/captcha/CircleCaptcha.java
@@ -4,7 +4,6 @@ import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.img.GraphicsUtil;
import cn.hutool.core.img.ImgUtil;
-import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import java.awt.*;
diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/GifCaptcha.java b/hutool-captcha/src/main/java/cn/hutool/captcha/GifCaptcha.java
index f6276967a..7da77496c 100755
--- a/hutool-captcha/src/main/java/cn/hutool/captcha/GifCaptcha.java
+++ b/hutool-captcha/src/main/java/cn/hutool/captcha/GifCaptcha.java
@@ -5,7 +5,6 @@ import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.img.ImgUtil;
import cn.hutool.core.img.gif.AnimatedGifEncoder;
-import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import java.awt.AlphaComposite;
diff --git a/hutool-captcha/src/main/java/cn/hutool/captcha/LineCaptcha.java b/hutool-captcha/src/main/java/cn/hutool/captcha/LineCaptcha.java
index 55be796cf..a4d556da4 100755
--- a/hutool-captcha/src/main/java/cn/hutool/captcha/LineCaptcha.java
+++ b/hutool-captcha/src/main/java/cn/hutool/captcha/LineCaptcha.java
@@ -4,7 +4,6 @@ import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.captcha.generator.RandomGenerator;
import cn.hutool.core.img.GraphicsUtil;
import cn.hutool.core.img.ImgUtil;
-import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.RandomUtil;
import java.awt.*;
diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml
index 4df697501..84c3e0d8f 100755
--- a/hutool-core/pom.xml
+++ b/hutool-core/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-core
diff --git a/hutool-core/src/main/java/cn/hutool/core/comparator/VersionComparator.java b/hutool-core/src/main/java/cn/hutool/core/comparator/VersionComparator.java
index 98df7a2d1..0cb62d4cf 100644
--- a/hutool-core/src/main/java/cn/hutool/core/comparator/VersionComparator.java
+++ b/hutool-core/src/main/java/cn/hutool/core/comparator/VersionComparator.java
@@ -1,13 +1,10 @@
package cn.hutool.core.comparator;
-import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Version;
import cn.hutool.core.util.*;
import java.io.Serializable;
import java.util.Comparator;
-import java.util.List;
-import java.util.regex.Pattern;
/**
* 版本比较器
diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/NumberWordFormatter.java b/hutool-core/src/main/java/cn/hutool/core/convert/NumberWordFormatter.java
index d86f21eec..cc9e6a4bb 100644
--- a/hutool-core/src/main/java/cn/hutool/core/convert/NumberWordFormatter.java
+++ b/hutool-core/src/main/java/cn/hutool/core/convert/NumberWordFormatter.java
@@ -1,6 +1,5 @@
package cn.hutool.core.convert;
-import cn.hutool.core.lang.Console;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/impl/MapConverter.java b/hutool-core/src/main/java/cn/hutool/core/convert/impl/MapConverter.java
index 72b4c32b7..f01a46c04 100644
--- a/hutool-core/src/main/java/cn/hutool/core/convert/impl/MapConverter.java
+++ b/hutool-core/src/main/java/cn/hutool/core/convert/impl/MapConverter.java
@@ -74,6 +74,11 @@ public class MapConverter extends AbstractConverter> {
}
convertMapToMap((Map) value, map);
} else if (BeanUtil.isBean(value.getClass())) {
+ if(value.getClass().getName().equals("cn.hutool.json.JSONArray")){
+ // issue#3795 增加JSONArray转Map错误检查
+ throw new UnsupportedOperationException(StrUtil.format("Unsupported {} to Map.", value.getClass().getName()));
+ }
+
map = BeanUtil.beanToMap(value);
// 二次转换,转换键值类型
map = convertInternal(map);
diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateRange.java b/hutool-core/src/main/java/cn/hutool/core/date/DateRange.java
index 0e91132a4..69183f3a9 100644
--- a/hutool-core/src/main/java/cn/hutool/core/date/DateRange.java
+++ b/hutool-core/src/main/java/cn/hutool/core/date/DateRange.java
@@ -48,6 +48,10 @@ public class DateRange extends Range {
*/
public DateRange(Date start, Date end, DateField unit, int step, boolean isIncludeStart, boolean isIncludeEnd) {
super(DateUtil.date(start), DateUtil.date(end), (current, end1, index) -> {
+ if(step <= 0){
+ // issue#3783
+ return null;
+ }
final DateTime dt = DateUtil.date(start).offsetNew(unit, (index + 1) * step);
if (dt.isAfter(end1)) {
return null;
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/unit/DataSizeUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/unit/DataSizeUtil.java
index 2835a8046..3962c5617 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/unit/DataSizeUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/unit/DataSizeUtil.java
@@ -1,5 +1,7 @@
package cn.hutool.core.io.unit;
+import cn.hutool.core.util.ArrayUtil;
+
import java.text.DecimalFormat;
/**
@@ -35,4 +37,20 @@ public class DataSizeUtil {
return new DecimalFormat("#,##0.##")
.format(size / Math.pow(1024, digitGroups)) + " " + DataUnit.UNIT_NAMES[digitGroups];
}
+
+ /**
+ * 根据单位,将文件大小转换为对应单位的大小
+ *
+ * @param size 文件大小
+ * @param fileDataUnit 单位
+ * @return 大小
+ * @since 5.8.34
+ */
+ public static String format(Long size, DataUnit fileDataUnit){
+ if (size <= 0) {
+ return "0";
+ }
+ int digitGroups = ArrayUtil.indexOf(DataUnit.UNIT_NAMES,fileDataUnit.getSuffix());
+ return new DecimalFormat("##0.##").format(size / Math.pow(1024, digitGroups)) + " " + DataUnit.UNIT_NAMES[digitGroups];
+ }
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/io/unit/DataUnit.java b/hutool-core/src/main/java/cn/hutool/core/io/unit/DataUnit.java
index 2163fc94e..f26ec8e0b 100644
--- a/hutool-core/src/main/java/cn/hutool/core/io/unit/DataUnit.java
+++ b/hutool-core/src/main/java/cn/hutool/core/io/unit/DataUnit.java
@@ -44,10 +44,12 @@ public enum DataUnit {
*/
TERABYTES("TB", DataSize.ofTerabytes(1));
+ /**
+ * 单位后缀
+ */
public static final String[] UNIT_NAMES = new String[]{"B", "KB", "MB", "GB", "TB", "PB", "EB"};
private final String suffix;
-
private final DataSize size;
@@ -56,6 +58,16 @@ public enum DataUnit {
this.size = size;
}
+ /**
+ * 单位后缀
+ *
+ * @return 单位后缀
+ * @since 5.8.34
+ */
+ public String getSuffix() {
+ return this.suffix;
+ }
+
DataSize size() {
return this.size;
}
@@ -76,5 +88,4 @@ public enum DataUnit {
}
throw new IllegalArgumentException("Unknown data unit suffix '" + suffix + "'");
}
-
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java
index 7e7f76d12..f44cf83d6 100755
--- a/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java
+++ b/hutool-core/src/main/java/cn/hutool/core/lang/Snowflake.java
@@ -2,7 +2,6 @@ package cn.hutool.core.lang;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.lang.id.IdConstants;
-import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
diff --git a/hutool-core/src/main/java/cn/hutool/core/math/Calculator.java b/hutool-core/src/main/java/cn/hutool/core/math/Calculator.java
index dbda0cfc5..ecaf93515 100644
--- a/hutool-core/src/main/java/cn/hutool/core/math/Calculator.java
+++ b/hutool-core/src/main/java/cn/hutool/core/math/Calculator.java
@@ -1,5 +1,6 @@
package cn.hutool.core.math;
+import cn.hutool.core.util.CharUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
@@ -192,6 +193,9 @@ public class Calculator {
arr[i] = '~';
}
}
+ } else if(CharUtil.equals(arr[i], 'x', true)){
+ // issue#3787 x转换为*
+ arr[i] = '*';
}
}
if (arr[0] == '~' && (arr.length > 1 && arr[1] == '(')) {
diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java
index f58a0a276..e9344822b 100755
--- a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java
+++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvParser.java
@@ -1,24 +1,33 @@
+/*
+ * Copyright (c) 2013-2024 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package cn.hutool.core.text.csv;
import cn.hutool.core.collection.ComputeIter;
import cn.hutool.core.io.IORuntimeException;
-import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.text.StrBuilder;
import cn.hutool.core.util.CharUtil;
-import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import java.io.Closeable;
import java.io.IOException;
import java.io.Reader;
import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.util.*;
/**
* CSV行解析器,参考:FastCSV
@@ -30,10 +39,8 @@ public final class CsvParser extends ComputeIter implements Closeable, S
private static final int DEFAULT_ROW_CAPACITY = 10;
- private final Reader reader;
private final CsvReadConfig config;
-
- private final Buffer buf = new Buffer(IoUtil.DEFAULT_LARGE_BUFFER_SIZE);
+ private final CsvTokener tokener;
/**
* 前一个特殊分界字符
*/
@@ -45,7 +52,7 @@ public final class CsvParser extends ComputeIter implements Closeable, S
/**
* 当前读取字段
*/
- private final StrBuilder currentField = new StrBuilder(512);
+ private final StringBuilder currentField = new StringBuilder(512);
/**
* 标题行
@@ -78,9 +85,9 @@ public final class CsvParser extends ComputeIter implements Closeable, S
* @param reader Reader
* @param config 配置,null则为默认配置
*/
- public CsvParser(final Reader reader, CsvReadConfig config) {
- this.reader = Objects.requireNonNull(reader, "reader must not be null");
- this.config = ObjectUtil.defaultIfNull(config, CsvReadConfig::defaultConfig);
+ public CsvParser(final Reader reader, final CsvReadConfig config) {
+ this.config = ObjUtil.defaultIfNull(config, CsvReadConfig::defaultConfig);
+ this.tokener = new CsvTokener(reader);
}
/**
@@ -90,13 +97,13 @@ public final class CsvParser extends ComputeIter implements Closeable, S
* @throws IllegalStateException 如果不解析头部或者没有调用nextRow()方法
*/
public List getHeader() {
- if (config.headerLineNo < 0) {
+ if (config.headerLineNo < 0) {
throw new IllegalStateException("No header available - header parsing is disabled");
}
if (lineNo < config.beginLineNo) {
throw new IllegalStateException("No header available - call nextRow() first");
}
- return header.fields;
+ return header.getRawList();
}
@Override
@@ -107,13 +114,13 @@ public final class CsvParser extends ComputeIter implements Closeable, S
/**
* 读取下一行数据
*
- * @return CsvRow
+ * @return CsvRow,{@code null}表示
* @throws IORuntimeException IO读取异常
*/
public CsvRow nextRow() throws IORuntimeException {
List currentFields;
int fieldCount;
- while (false == finished) {
+ while (!finished) {
currentFields = readLine();
fieldCount = currentFields.size();
if (fieldCount < 1) {
@@ -122,11 +129,11 @@ public final class CsvParser extends ComputeIter implements Closeable, S
}
// 读取范围校验
- if(lineNo < config.beginLineNo){
+ if (lineNo < config.beginLineNo) {
// 未达到读取起始行,继续
continue;
}
- if(lineNo > config.endLineNo){
+ if (lineNo > config.endLineNo) {
// 超出结束行,读取结束
break;
}
@@ -175,9 +182,9 @@ public final class CsvParser extends ComputeIter implements Closeable, S
String field = currentFields.get(i);
if (MapUtil.isNotEmpty(this.config.headerAlias)) {
// 自定义别名
- field = ObjectUtil.defaultIfNull(this.config.headerAlias.get(field), field);
+ field = ObjUtil.defaultIfNull(this.config.headerAlias.get(field), field);
}
- if (StrUtil.isNotEmpty(field) && false == localHeaderMap.containsKey(field)) {
+ if (StrUtil.isNotEmpty(field) && !localHeaderMap.containsKey(field)) {
localHeaderMap.put(field, i);
}
}
@@ -190,7 +197,7 @@ public final class CsvParser extends ComputeIter implements Closeable, S
* 空行是size为1的List,唯一元素是""
*
*
- * 行号要考虑注释行和引号包装的内容中的换行
+ * 行号要考虑注释行和引号包装的内容中的换行
*
*
* @return 一行数据
@@ -199,70 +206,67 @@ public final class CsvParser extends ComputeIter implements Closeable, S
private List readLine() throws IORuntimeException {
// 矫正行号
// 当一行内容包含多行数据时,记录首行行号,但是读取下一行时,需要把多行内容的行数加上
- if(inQuotesLineCount > 0){
+ if (inQuotesLineCount > 0) {
this.lineNo += this.inQuotesLineCount;
this.inQuotesLineCount = 0;
}
final List currentFields = new ArrayList<>(maxFieldCount > 0 ? maxFieldCount : DEFAULT_ROW_CAPACITY);
- final StrBuilder currentField = this.currentField;
- final Buffer buf = this.buf;
+ final StringBuilder currentField = this.currentField;
int preChar = this.preChar;//前一个特殊分界字符
- int copyLen = 0; //拷贝长度
boolean inComment = false;
+ int c;
while (true) {
- if (false == buf.hasRemaining()) {
- // 此Buffer读取结束,开始读取下一段
- if (copyLen > 0) {
- buf.appendTo(currentField, copyLen);
- // 此处无需mark,read方法会重置mark
- }
- if (buf.read(this.reader) < 0) {
- // CSV读取结束
- finished = true;
-
- if (currentField.hasContent() || preChar == config.fieldSeparator) {
- //剩余部分作为一个字段
- addField(currentFields, currentField.toStringAndReset());
+ c = tokener.next();
+ if(c < 0){
+ if (currentField.length() > 0 || preChar == config.fieldSeparator) {
+ if(this.inQuotes){
+ // 未闭合的文本包装,在末尾补充包装符
+ currentField.append(config.textDelimiter);
}
- break;
- }
- //重置
- copyLen = 0;
+ //剩余部分作为一个字段
+ addField(currentFields, currentField.toString());
+ currentField.setLength(0);
+ }
+ // 读取结束
+ this.finished = true;
+ break;
}
- final char c = buf.get();
-
// 注释行标记
- if(preChar < 0 || preChar == CharUtil.CR || preChar == CharUtil.LF){
+ if (preChar < 0 || preChar == CharUtil.CR || preChar == CharUtil.LF) {
// 判断行首字符为指定注释字符的注释开始,直到遇到换行符
// 行首分两种,1是preChar < 0表示文本开始,2是换行符后紧跟就是下一行的开始
// issue#IA8WE0 如果注释符出现在包装符内,被认为是普通字符
- if((false == inQuotes) && null != this.config.commentCharacter && c == this.config.commentCharacter){
+ if (!inQuotes && null != this.config.commentCharacter && c == this.config.commentCharacter) {
inComment = true;
}
}
// 注释行处理
- if(inComment){
+ if (inComment) {
if (c == CharUtil.CR || c == CharUtil.LF) {
// 注释行以换行符为结尾
lineNo++;
inComment = false;
}
// 跳过注释行中的任何字符
- buf.mark();
- preChar = c;
continue;
}
if (inQuotes) {
//引号内,作为内容,直到引号结束
if (c == config.textDelimiter) {
- // End of quoted text
- inQuotes = false;
+ // issue#IB5UQ8 文本包装符转义
+ final int next = tokener.next();
+ if(next != config.textDelimiter){
+ // 包装结束
+ inQuotes = false;
+ tokener.back();
+ }
+ // https://datatracker.ietf.org/doc/html/rfc4180#section-2 跳过转义符,只保留被转义的包装符
} else {
// 字段内容中新行
if (isLineEnd(c, preChar)) {
@@ -270,46 +274,34 @@ public final class CsvParser extends ComputeIter implements Closeable, S
}
}
// 普通字段字符
- copyLen++;
+ currentField.append((char)c);
} else {
// 非引号内
if (c == config.fieldSeparator) {
//一个字段结束
- if (copyLen > 0) {
- buf.appendTo(currentField, copyLen);
- copyLen = 0;
- }
- buf.mark();
- addField(currentFields, currentField.toStringAndReset());
+ addField(currentFields, currentField.toString());
+ currentField.setLength(0);
} else if (c == config.textDelimiter && isFieldBegin(preChar)) {
// 引号开始且出现在字段开头
inQuotes = true;
- copyLen++;
+ currentField.append((char)c);
} else if (c == CharUtil.CR) {
- // \r,直接结束
- if (copyLen > 0) {
- buf.appendTo(currentField, copyLen);
- }
- buf.mark();
- addField(currentFields, currentField.toStringAndReset());
+ // \r
+ addField(currentFields, currentField.toString());
+ currentField.setLength(0);
preChar = c;
break;
} else if (c == CharUtil.LF) {
// \n
if (preChar != CharUtil.CR) {
- if (copyLen > 0) {
- buf.appendTo(currentField, copyLen);
- }
- buf.mark();
- addField(currentFields, currentField.toStringAndReset());
+ addField(currentFields, currentField.toString());
+ currentField.setLength(0);
preChar = c;
break;
}
// 前一个字符是\r,已经处理过这个字段了,此处直接跳过
- buf.mark();
} else {
- // 普通字符
- copyLen++;
+ currentField.append((char)c);
}
}
@@ -325,7 +317,7 @@ public final class CsvParser extends ComputeIter implements Closeable, S
@Override
public void close() throws IOException {
- reader.close();
+ tokener.close();
}
/**
@@ -334,7 +326,7 @@ public final class CsvParser extends ComputeIter implements Closeable, S
* @param currentFields 当前的字段列表(即为行)
* @param field 字段
*/
- private void addField(List currentFields, String field) {
+ private void addField(final List currentFields, String field) {
final char textDelimiter = this.config.textDelimiter;
// 忽略多余引号后的换行符
@@ -342,12 +334,9 @@ public final class CsvParser extends ComputeIter implements Closeable, S
if(StrUtil.isWrap(field, textDelimiter)){
field = StrUtil.sub(field, 1, field.length() - 1);
- // https://datatracker.ietf.org/doc/html/rfc4180#section-2
- // 第七条规则,只有包装内的包装符需要转义
- field = StrUtil.replace(field, String.valueOf(textDelimiter) + textDelimiter, String.valueOf(textDelimiter));
}
- if(this.config.trimField){
+ if (this.config.trimField) {
// issue#I49M0C@Gitee
field = StrUtil.trim(field);
}
@@ -362,7 +351,7 @@ public final class CsvParser extends ComputeIter implements Closeable, S
* @return 是否结束
* @since 5.7.4
*/
- private boolean isLineEnd(char c, int preChar) {
+ private boolean isLineEnd(final int c, final int preChar) {
return (c == CharUtil.CR || c == CharUtil.LF) && preChar != CharUtil.CR;
}
@@ -383,89 +372,4 @@ public final class CsvParser extends ComputeIter implements Closeable, S
|| preChar == CharUtil.LF
|| preChar == CharUtil.CR;
}
-
- /**
- * 内部Buffer
- *
- * @author looly
- */
- private static class Buffer implements Serializable{
- private static final long serialVersionUID = 1L;
-
- final char[] buf;
-
- /**
- * 标记位置,用于读数据
- */
- private int mark;
- /**
- * 当前位置
- */
- private int position;
- /**
- * 读取的数据长度,一般小于buf.length,-1表示无数据
- */
- private int limit;
-
- Buffer(int capacity) {
- buf = new char[capacity];
- }
-
- /**
- * 是否还有未读数据
- *
- * @return 是否还有未读数据
- */
- public final boolean hasRemaining() {
- return position < limit;
- }
-
- /**
- * 读取到缓存
- * 全量读取,会重置Buffer中所有数据
- *
- * @param reader {@link Reader}
- */
- int read(Reader reader) {
- int length;
- try {
- length = reader.read(this.buf);
- } catch (IOException e) {
- throw new IORuntimeException(e);
- }
- this.mark = 0;
- this.position = 0;
- this.limit = length;
- return length;
- }
-
- /**
- * 先获取当前字符,再将当前位置后移一位
- * 此方法不检查是否到了数组末尾,请自行使用{@link #hasRemaining()}判断。
- *
- * @return 当前位置字符
- * @see #hasRemaining()
- */
- char get() {
- return this.buf[this.position++];
- }
-
- /**
- * 标记位置记为下次读取位置
- */
- void mark() {
- this.mark = this.position;
- }
-
- /**
- * 将数据追加到{@link StrBuilder},追加结束后需手动调用{@link #mark()} 重置读取位置
- *
- * @param builder {@link StrBuilder}
- * @param length 追加的长度
- * @see #mark()
- */
- void appendTo(StrBuilder builder, int length) {
- builder.append(this.buf, this.mark, length);
- }
- }
}
diff --git a/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvTokener.java b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvTokener.java
new file mode 100644
index 000000000..8e957e022
--- /dev/null
+++ b/hutool-core/src/main/java/cn/hutool/core/text/csv/CsvTokener.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2024 Hutool Team and hutool.cn
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package cn.hutool.core.text.csv;
+
+import cn.hutool.core.io.IORuntimeException;
+import cn.hutool.core.io.IoUtil;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * CSV解析器,用于解析CSV文件
+ *
+ * @author looly
+ * @since 5.8.0
+ */
+public class CsvTokener implements Closeable {
+
+ private final Reader raw;
+ /**
+ * 在Reader的位置(解析到第几个字符)
+ */
+ private long index;
+ /**
+ * 前一个字符
+ */
+ private int prev;
+ /**
+ * 是否使用前一个字符
+ */
+ private boolean usePrev;
+
+ /**
+ * 构造
+ *
+ * @param reader {@link Reader}
+ */
+ public CsvTokener(final Reader reader) {
+ this.raw = IoUtil.toBuffered(reader);
+ }
+
+ /**
+ * 读取下一个字符,并记录位置
+ *
+ * @return 下一个字符
+ */
+ public int next() {
+ if(this.usePrev){
+ this.usePrev = false;
+ }else{
+ try {
+ this.prev = this.raw.read();
+ } catch (final IOException e) {
+ throw new IORuntimeException(e);
+ }
+ }
+ this.index++;
+ return this.prev;
+ }
+
+ /**
+ * 将标记回退到第一个字符
+ *
+ * @throws IllegalStateException 当多次调用back时,抛出此异常
+ */
+ public void back() throws IllegalStateException {
+ if (this.usePrev || this.index <= 0) {
+ throw new IllegalStateException("Stepping back two steps is not supported");
+ }
+ this.index --;
+ this.usePrev = true;
+ }
+
+ /**
+ * 获取当前位置
+ *
+ * @return 位置
+ */
+ public long getIndex() {
+ return this.index;
+ }
+
+ @Override
+ public void close() throws IOException {
+ IoUtil.close(this.raw);
+ }
+}
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java
index 8d5317d52..4ca0f7885 100755
--- a/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/util/TypeUtil.java
@@ -1,6 +1,5 @@
package cn.hutool.core.util;
-import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.ParameterizedTypeImpl;
import cn.hutool.core.lang.reflect.ActualTypeMapperPool;
diff --git a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java
index d44059f60..2e0dad053 100755
--- a/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java
+++ b/hutool-core/src/main/java/cn/hutool/core/util/ZipUtil.java
@@ -34,10 +34,7 @@ import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.function.Consumer;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
+import java.util.zip.*;
/**
* 压缩工具类
@@ -65,6 +62,17 @@ public class ZipUtil {
try {
return new ZipFile(file, ObjectUtil.defaultIfNull(charset, CharsetUtil.CHARSET_UTF_8));
} catch (IOException e) {
+ // issue#I3UZ28 可能编码错误提示
+ if(e instanceof ZipException){
+ if(e.getMessage().contains("invalid CEN header")){
+ try {
+ // 尝试使用不同编码
+ return new ZipFile(file, CharsetUtil.CHARSET_UTF_8.equals(charset) ? CharsetUtil.CHARSET_GBK : CharsetUtil.CHARSET_UTF_8);
+ } catch (final IOException ex) {
+ throw new IORuntimeException(ex);
+ }
+ }
+ }
throw new IORuntimeException(e);
}
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateRangeTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateRangeTest.java
new file mode 100644
index 000000000..0a6404d26
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/date/DateRangeTest.java
@@ -0,0 +1,27 @@
+package cn.hutool.core.date;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Date;
+import java.util.List;
+
+public class DateRangeTest {
+ @Test
+ void issue3783Test() {
+ final Date start = DateUtil.parse("2024-01-01");
+ final Date end = DateUtil.parse("2024-02-01");
+ final List dateTimes = DateUtil.rangeToList(start, end, DateField.DAY_OF_MONTH, 0);
+ Assertions.assertEquals(1, dateTimes.size());
+ Assertions.assertEquals("2024-01-01 00:00:00", dateTimes.get(0).toString());
+ }
+
+ @Test
+ void issue3783Test2() {
+ final Date start = DateUtil.parse("2024-01-01");
+ final Date end = DateUtil.parse("2024-02-01");
+ final List dateTimes = DateUtil.rangeToList(start, end, DateField.DAY_OF_MONTH, -2);
+ Assertions.assertEquals(1, dateTimes.size());
+ Assertions.assertEquals("2024-01-01 00:00:00", dateTimes.get(0).toString());
+ }
+}
diff --git a/hutool-core/src/test/java/cn/hutool/core/date/Issue3798Test.java b/hutool-core/src/test/java/cn/hutool/core/date/Issue3798Test.java
new file mode 100644
index 000000000..b041fc3da
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/date/Issue3798Test.java
@@ -0,0 +1,22 @@
+package cn.hutool.core.date;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.TimeZone;
+
+public class Issue3798Test {
+ @Test
+ void parseTest() {
+ final String iso_datetime1 = "2000-01-01T12:00:00+08:00";
+ final DateTime parse1 = DateUtil.parse(iso_datetime1);
+ Assertions.assertEquals(TimeZone.getTimeZone("GMT+08:00"), parse1.getTimeZone());
+ Assertions.assertEquals("2000-01-01 12:00:00", parse1.toString());
+
+ // 伦敦时间(Greenwich Mean Time, GMT)和北京时间(China Standard Time, CST)之间的时差是8小时。北京时间比伦敦时间快8小时
+ final String iso_datetime2 = "2000-01-01T12:00:00+00:00";
+ final DateTime parse2 = DateUtil.parse(iso_datetime2);
+ Assertions.assertEquals(TimeZone.getTimeZone("GMT+00:00"), parse2.getTimeZone());
+ Assertions.assertEquals("2000-01-01 20:00:00", parse2.toString(TimeZone.getTimeZone("GMT+08:00")));
+ }
+}
diff --git a/hutool-core/src/test/java/cn/hutool/core/io/unit/DataSizeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/unit/DataSizeUtilTest.java
index fdf557539..388878eb9 100644
--- a/hutool-core/src/test/java/cn/hutool/core/io/unit/DataSizeUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/io/unit/DataSizeUtilTest.java
@@ -1,8 +1,9 @@
package cn.hutool.core.io.unit;
-import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
public class DataSizeUtilTest {
@Test
@@ -61,6 +62,18 @@ public class DataSizeUtilTest {
assertEquals("1 TB", format);
}
+ @Test
+ public void formatWithUnitTest(){
+ String format = DataSizeUtil.format(Long.MAX_VALUE, DataUnit.TERABYTES);
+ assertEquals("8388608 TB", format);
+
+ format = DataSizeUtil.format(1024L * 1024 * 1024 * 1024 * 1024, DataUnit.GIGABYTES);
+ assertEquals("1048576 GB", format);
+
+ format = DataSizeUtil.format(1024L * 1024 * 1024 * 1024, DataUnit.GIGABYTES);
+ assertEquals("1024 GB", format);
+ }
+
@Test
public void issueI88Z4ZTest() {
final String size = DataSizeUtil.format(10240000);
diff --git a/hutool-core/src/test/java/cn/hutool/core/math/CalculatorTest.java b/hutool-core/src/test/java/cn/hutool/core/math/CalculatorTest.java
index f1e1a8387..0e561e301 100644
--- a/hutool-core/src/test/java/cn/hutool/core/math/CalculatorTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/math/CalculatorTest.java
@@ -1,8 +1,9 @@
package cn.hutool.core.math;
-import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
public class CalculatorTest {
@Test
@@ -55,4 +56,14 @@ public class CalculatorTest {
final double calcValue = Calculator.conversion("(11+2)12");
assertEquals(156D, calcValue, 0.001);
}
+
+ @Test
+ void issue3787Test() {
+ final Calculator calculator1 = new Calculator();
+ double result = calculator1.calculate("0+50/100x(1/0.5)");
+ assertEquals(1D, result);
+
+ result = calculator1.calculate("0+50/100X(1/0.5)");
+ assertEquals(1D, result);
+ }
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java
index 9f75cc208..e44e49250 100755
--- a/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java
+++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/CsvUtilTest.java
@@ -7,7 +7,6 @@ import cn.hutool.core.lang.Console;
import cn.hutool.core.util.CharsetUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
-import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -17,6 +16,8 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
public class CsvUtilTest {
@Test
@@ -32,7 +33,8 @@ public class CsvUtilTest {
assertEquals("关注\"对象\"", row0.get(3));
assertEquals("年龄", row0.get(4));
assertEquals("", row0.get(5));
- assertEquals("\"", row0.get(6));
+ // 由于"""未闭合包装,因此末尾的换行符被当作包装内的内容,相当于:"""\n",转义后就是"\n
+ assertEquals("\"\n", row0.get(6));
}
@Test
@@ -46,7 +48,8 @@ public class CsvUtilTest {
assertEquals("关注\"对象\"", csvRow.get(3));
assertEquals("年龄", csvRow.get(4));
assertEquals("", csvRow.get(5));
- assertEquals("\"", csvRow.get(6));
+ // 由于"""未闭合包装,因此末尾的换行符被当作包装内的内容,相当于:"""\n",转义后就是"\n
+ assertEquals("\"\n", csvRow.get(6));
});
}
@@ -70,7 +73,8 @@ public class CsvUtilTest {
assertEquals("关注\"对象\"", row0.get(3));
assertEquals("年龄", row0.get(4));
assertEquals("", row0.get(5));
- assertEquals("\"", row0.get(6));
+ // 由于"""未闭合包装,因此末尾的换行符被当作包装内的内容,相当于:"""\n",转义后就是"\n
+ assertEquals("\"\n", row0.get(6));
}
@Test
@@ -84,7 +88,8 @@ public class CsvUtilTest {
assertEquals("关注\"对象\"", csvRow.get(3));
assertEquals("年龄", csvRow.get(4));
assertEquals("", csvRow.get(5));
- assertEquals("\"", csvRow.get(6));
+ // 由于"""未闭合包装,因此末尾的换行符被当作包装内的内容,相当于:"""\n",转义后就是"\n
+ assertEquals("\"\n", csvRow.get(6));
});
}
diff --git a/hutool-core/src/test/java/cn/hutool/core/text/csv/IssueIB5UQ8Test.java b/hutool-core/src/test/java/cn/hutool/core/text/csv/IssueIB5UQ8Test.java
new file mode 100644
index 000000000..daaf7ab38
--- /dev/null
+++ b/hutool-core/src/test/java/cn/hutool/core/text/csv/IssueIB5UQ8Test.java
@@ -0,0 +1,16 @@
+package cn.hutool.core.text.csv;
+
+import cn.hutool.core.lang.Console;
+import org.junit.jupiter.api.Test;
+
+import java.io.StringReader;
+
+public class IssueIB5UQ8Test {
+ @Test
+ void parseEscapeTest() {
+ String csv = "\"Consultancy, 10\"\",, food\"";
+ final CsvReader reader = CsvUtil.getReader(new StringReader(csv));
+ final String s = reader.read().getRow(0).get(0);
+ Console.log(s);
+ }
+}
diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml
index a31c34165..c945f9637 100755
--- a/hutool-cron/pom.xml
+++ b/hutool-cron/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-cron
@@ -26,6 +26,11 @@
hutool-core
${project.parent.version}
+
+ cn.hutool
+ hutool-log
+ ${project.parent.version}
+
cn.hutool
hutool-setting
diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml
index 6b1e2c6ed..33def367a 100755
--- a/hutool-crypto/pom.xml
+++ b/hutool-crypto/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-crypto
diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml
index 2bda60742..d885d9db0 100755
--- a/hutool-db/pom.xml
+++ b/hutool-db/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-db
diff --git a/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java b/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java
index c0c7906e6..ea9dfd952 100644
--- a/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java
+++ b/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java
@@ -2,10 +2,8 @@ package cn.hutool.db;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.PatternPool;
-import cn.hutool.core.lang.RegexPool;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
-import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.dialect.Dialect;
import cn.hutool.db.dialect.DialectFactory;
diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/DmDialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/DmDialect.java
index 111d0682a..06ac9e7ef 100644
--- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/DmDialect.java
+++ b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/DmDialect.java
@@ -3,7 +3,6 @@ package cn.hutool.db.dialect.impl;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Entity;
-import cn.hutool.db.Page;
import cn.hutool.db.StatementUtil;
import cn.hutool.db.dialect.DialectName;
import cn.hutool.db.sql.SqlBuilder;
diff --git a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java
index ffca39390..d94bf27c9 100644
--- a/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java
+++ b/hutool-db/src/main/java/cn/hutool/db/ds/pooled/PooledConnection.java
@@ -1,7 +1,6 @@
package cn.hutool.db.ds.pooled;
import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.DbRuntimeException;
import cn.hutool.db.DbUtil;
diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml
index 0fb5e6b44..992185a0e 100755
--- a/hutool-dfa/pom.xml
+++ b/hutool-dfa/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-dfa
diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml
index 26b110a5b..208f86684 100755
--- a/hutool-extra/pom.xml
+++ b/hutool-extra/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-extra
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java
index d3eec1df5..dd8fa1225 100755
--- a/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/compress/archiver/Archiver.java
@@ -1,7 +1,6 @@
package cn.hutool.extra.compress.archiver;
import cn.hutool.core.lang.Filter;
-import cn.hutool.core.util.StrUtil;
import java.io.Closeable;
import java.io.File;
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/JakartaMail.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/JakartaMail.java
index 8f73611ec..99a74280a 100644
--- a/hutool-extra/src/main/java/cn/hutool/extra/mail/JakartaMail.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/JakartaMail.java
@@ -268,6 +268,8 @@ public class JakartaMail implements Builder {
if (StrUtil.startWith(attachment.getContentType(), "image/")) {
// 图片附件,用于正文中引用图片
bodyPart.setContentID(nameEncoded);
+ // 图片附件设置内联,否则无法正常引用图片
+ bodyPart.setDisposition(MimeBodyPart.INLINE);
}
this.multipart.addBodyPart(bodyPart);
}
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java b/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java
index 2d71b2dcc..535300bf9 100644
--- a/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/mail/Mail.java
@@ -8,10 +8,7 @@ import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
-import javax.activation.DataHandler;
-import javax.activation.DataSource;
-import javax.activation.FileDataSource;
-import javax.activation.FileTypeMap;
+import javax.activation.*;
import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.Multipart;
@@ -273,6 +270,8 @@ public class Mail implements Builder {
if (StrUtil.startWith(attachment.getContentType(), "image/")) {
// 图片附件,用于正文中引用图片
bodyPart.setContentID(nameEncoded);
+ // 图片附件设置内联,否则无法正常引用图片
+ bodyPart.setDisposition(MimeBodyPart.INLINE);
}
this.multipart.addBodyPart(bodyPart);
}
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/TinyPinyinEngine.java b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/TinyPinyinEngine.java
index 2ecf0dd1c..0ac8b0c52 100644
--- a/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/TinyPinyinEngine.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/pinyin/engine/tinypinyin/TinyPinyinEngine.java
@@ -1,6 +1,5 @@
package cn.hutool.extra.pinyin.engine.tinypinyin;
-import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.pinyin.PinyinEngine;
import com.github.promeg.pinyinhelper.Pinyin;
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java
index abbdbda40..6a625dec8 100755
--- a/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/spring/SpringUtil.java
@@ -122,6 +122,19 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA
return getBeanFactory().getBean(clazz);
}
+ /**
+ * 通过class获取Bean
+ *
+ * @param Bean类型
+ * @param clazz Bean类
+ * @param args 创建bean需要的参数属性
+ * @return Bean对象
+ * @since 5.8.34
+ */
+ public static T getBean(Class clazz, Object... args) {
+ return getBeanFactory().getBean(clazz, args);
+ }
+
/**
* 通过name,以及Clazz返回指定的Bean
*
@@ -134,6 +147,18 @@ public class SpringUtil implements BeanFactoryPostProcessor, ApplicationContextA
return getBeanFactory().getBean(name, clazz);
}
+ /**
+ * 通过name,以及Clazz返回指定的Bean
+ *
+ * @param name Bean名称
+ * @param args 创建bean需要的参数属性
+ * @return Bean对象
+ * @since 5.8.34
+ */
+ public static Object getBean(String name, Object... args) {
+ return getBeanFactory().getBean(name, args);
+ }
+
/**
* 通过类型参考返回带泛型参数的Bean
*
diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java
index bbca18dd0..865f216d4 100755
--- a/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java
+++ b/hutool-extra/src/main/java/cn/hutool/extra/ssh/Sftp.java
@@ -8,13 +8,9 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.ftp.AbstractFtp;
import cn.hutool.extra.ftp.FtpConfig;
import cn.hutool.extra.ftp.FtpException;
-import com.jcraft.jsch.ChannelSftp;
+import com.jcraft.jsch.*;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.ChannelSftp.LsEntrySelector;
-import com.jcraft.jsch.Session;
-import com.jcraft.jsch.SftpATTRS;
-import com.jcraft.jsch.SftpException;
-import com.jcraft.jsch.SftpProgressMonitor;
import java.io.File;
import java.io.InputStream;
@@ -111,7 +107,7 @@ public class Sftp extends AbstractFtp {
* @since 4.1.14
*/
public Sftp(Session session, Charset charset) {
- super(FtpConfig.create().setCharset(charset));
+ super(FtpConfig.create().setCharset(charset).setHost(session.getHost()).setPort(session.getPort()));
init(session, charset);
}
@@ -172,6 +168,17 @@ public class Sftp extends AbstractFtp {
* @since 5.3.3
*/
public void init() {
+ // issue#IB69U8 如果用户传入Session对象,则不能使用配置初始化,而是尝试重新连接
+ if(StrUtil.isEmpty(this.ftpConfig.getHost()) && null != this.session){
+ try {
+ this.session.connect((int) this.ftpConfig.getConnectionTimeout());
+ } catch (JSchException e) {
+ throw new JschRuntimeException(e);
+ }
+ init(this.session, this.ftpConfig.getCharset());
+ return;
+ }
+
init(this.ftpConfig);
}
diff --git a/hutool-extra/src/test/java/cn/hutool/extra/mail/JakartaMailTest.java b/hutool-extra/src/test/java/cn/hutool/extra/mail/JakartaMailTest.java
index ec0824aac..20b419cff 100644
--- a/hutool-extra/src/test/java/cn/hutool/extra/mail/JakartaMailTest.java
+++ b/hutool-extra/src/test/java/cn/hutool/extra/mail/JakartaMailTest.java
@@ -38,6 +38,15 @@ public class JakartaMailTest {
JakartaMailUtil.sendHtml("hutool@foxmail.com", "测试", "邮件来自Hutool测试 ", map);
}
+ @Test
+ @Disabled
+ public void sendHtmlWithImageTest() {
+ Map map = new HashMap<>();
+ InputStream in = getClass().getClassLoader().getResourceAsStream("image/Dromara.png");
+ map.put("", in);
+ JakartaMailUtil.sendHtml("hutool@foxmail.com;li7hai26@outlook.com", "测试", "邮件来自Hutool测试 ", map);
+ }
+
@Test
@Disabled
public void sendHtmlTest() {
diff --git a/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java b/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java
index 9784b70af..3a29c9ec2 100644
--- a/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java
+++ b/hutool-extra/src/test/java/cn/hutool/extra/mail/MailTest.java
@@ -38,6 +38,15 @@ public class MailTest {
MailUtil.sendHtml("hutool@foxmail.com", "测试", "邮件来自Hutool测试 ", map);
}
+ @Test
+ @Disabled
+ public void sendHtmlWithImageTest() {
+ Map map = new HashMap<>();
+ InputStream in = getClass().getClassLoader().getResourceAsStream("image/Dromara.png");
+ map.put("", in);
+ MailUtil.sendHtml("hutool@foxmail.com;li7hai26@outlook.com", "测试", "邮件来自Hutool测试 ", map);
+ }
+
@Test
@Disabled
public void sendHtmlTest() {
diff --git a/hutool-extra/src/test/resources/image/Dromara.png b/hutool-extra/src/test/resources/image/Dromara.png
new file mode 100644
index 000000000..b5c594ff9
Binary files /dev/null and b/hutool-extra/src/test/resources/image/Dromara.png differ
diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml
index 18d5cb238..ece953121 100755
--- a/hutool-http/pom.xml
+++ b/hutool-http/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-http
diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java
index 45609ccb8..de88d1630 100755
--- a/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java
+++ b/hutool-http/src/main/java/cn/hutool/http/HttpUtil.java
@@ -5,7 +5,6 @@ import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.StreamProgress;
-import cn.hutool.core.lang.Console;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.RFC3986;
import cn.hutool.core.net.url.UrlQuery;
diff --git a/hutool-http/src/main/java/cn/hutool/http/useragent/Browser.java b/hutool-http/src/main/java/cn/hutool/http/useragent/Browser.java
index 3eef7862b..e6f4d7b9a 100755
--- a/hutool-http/src/main/java/cn/hutool/http/useragent/Browser.java
+++ b/hutool-http/src/main/java/cn/hutool/http/useragent/Browser.java
@@ -31,6 +31,8 @@ public class Browser extends UserAgentInfo {
// 部分特殊浏览器是基于安卓、Iphone等的,需要优先判断
// 企业微信 企业微信使用微信浏览器内核,会包含 MicroMessenger 所以要放在前面
new Browser("wxwork", "wxwork", "wxwork\\/([\\d\\w\\.\\-]+)"),
+ // issue#IB3SJF 微信电脑端
+ new Browser("WindowsWechat", "WindowsWechat", "MicroMessenger" + Other_Version),
// 微信
new Browser("MicroMessenger", "MicroMessenger", Other_Version),
// 微信小程序
diff --git a/hutool-http/src/test/java/cn/hutool/http/useragent/IssueIB3SJFTest.java b/hutool-http/src/test/java/cn/hutool/http/useragent/IssueIB3SJFTest.java
new file mode 100644
index 000000000..7af4c91e7
--- /dev/null
+++ b/hutool-http/src/test/java/cn/hutool/http/useragent/IssueIB3SJFTest.java
@@ -0,0 +1,16 @@
+package cn.hutool.http.useragent;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class IssueIB3SJFTest {
+ @Test
+ void isMobileTest() {
+ String str="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36 NetType/WIFI " +
+ "MicroMessenger/7.0.20.1781(0x6700143B) WindowsWechat(0x63090c11) XWEB/11275 Flue";
+ UserAgent ua = UserAgentUtil.parse(str);
+
+ Assertions.assertFalse(ua.isMobile());
+ Assertions.assertEquals("7.0.20.1781", ua.getBrowser().getVersion(str));
+ }
+}
diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml
index 92e4f3af8..fbf76976d 100755
--- a/hutool-json/pom.xml
+++ b/hutool-json/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-json
diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java
index 8e0a709bb..e7371f14f 100755
--- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java
+++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java
@@ -2,7 +2,6 @@ package cn.hutool.json;
import cn.hutool.core.bean.BeanPath;
import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.Filter;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.lang.mutable.Mutable;
diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue3790Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue3790Test.java
new file mode 100644
index 000000000..46132355c
--- /dev/null
+++ b/hutool-json/src/test/java/cn/hutool/json/Issue3790Test.java
@@ -0,0 +1,25 @@
+package cn.hutool.json;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+public class Issue3790Test {
+ @Test
+ void bigDecimalToStringTest() {
+ BigDecimal bigDecimal = new BigDecimal("0.01");
+ bigDecimal = bigDecimal.setScale(4, RoundingMode.HALF_UP);
+
+ Dto dto = new Dto();
+ dto.remain = bigDecimal;
+
+ final String jsonStr = JSONUtil.toJsonStr(dto, JSONConfig.create().setStripTrailingZeros(false));
+ Assertions.assertEquals("{\"remain\":0.0100}", jsonStr);
+ }
+
+ static class Dto {
+ public BigDecimal remain;
+ }
+}
diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue3795Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue3795Test.java
new file mode 100644
index 000000000..6e0ef606f
--- /dev/null
+++ b/hutool-json/src/test/java/cn/hutool/json/Issue3795Test.java
@@ -0,0 +1,17 @@
+package cn.hutool.json;
+
+import cn.hutool.core.lang.TypeReference;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+
+public class Issue3795Test {
+ @Test
+ void toBeanTest() {
+ String fieldMapping = "[{\"lable\":\"id\",\"value\":\"id\"},{\"lable\":\"name\",\"value\":\"name\"},{\"lable\":\"age\",\"value\":\"age\"}]";
+ Assertions.assertThrows(UnsupportedOperationException.class, ()->{
+ JSONUtil.toBean(fieldMapping, new TypeReference>() {}, false);
+ });
+ }
+}
diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml
index c0f0dd96c..6f5be6e0f 100755
--- a/hutool-jwt/pom.xml
+++ b/hutool-jwt/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-jwt
diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml
index ebda5848d..4cb957f25 100755
--- a/hutool-log/pom.xml
+++ b/hutool-log/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-log
diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml
index 631555a30..c7c0797c1 100755
--- a/hutool-poi/pom.xml
+++ b/hutool-poi/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-poi
diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java
index ff12045ef..66e5b176b 100644
--- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java
+++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/ExcelSaxUtil.java
@@ -5,6 +5,7 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.DependencyException;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.CharUtil;
+import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.poi.excel.ExcelDateUtil;
import cn.hutool.poi.excel.sax.handler.RowHandler;
@@ -264,7 +265,15 @@ public class ExcelSaxUtil {
if (StrUtil.isBlank(value)) {
return null;
}
- return getNumberValue(Double.parseDouble(value), numFmtString);
+
+ // issue#IB0EJ9 可能精度丢失,对含有小数的value判断并转为BigDecimal
+ final double number = Double.parseDouble(value);
+ if(StrUtil.contains(value, CharUtil.DOT) && !value.equals(Double.toString(number))){
+ // 精度丢失
+ return NumberUtil.toBigDecimal(value);
+ }
+
+ return getNumberValue(number, numFmtString);
}
/**
diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java
index a2734d819..5dbbeae8e 100644
--- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java
+++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/SheetDataSaxHandler.java
@@ -18,9 +18,10 @@ import java.util.List;
/**
* sheetData标签内容读取处理器
*
- *
- * <sheetData></sheetData>
- *
+ * {@code
+ *
+ * }
+ *
* @since 5.5.3
*/
public class SheetDataSaxHandler extends DefaultHandler {
@@ -62,7 +63,12 @@ public class SheetDataSaxHandler extends DefaultHandler {
// 存储每行的列元素
private List rowCellList = new ArrayList<>();
- public SheetDataSaxHandler(RowHandler rowHandler){
+ /**
+ * 构造
+ *
+ * @param rowHandler 行处理器
+ */
+ public SheetDataSaxHandler(RowHandler rowHandler) {
this.rowHandler = rowHandler;
}
@@ -156,7 +162,7 @@ public class SheetDataSaxHandler extends DefaultHandler {
lastFormula.append(ch, start, length);
break;
}
- } else{
+ } else {
// 按理说内容应该为"内容 ",但是某些特别的XML内容不在v或f标签中,此处做一些兼容
// issue#1303@Github
lastContent.append(ch, start, length);
@@ -292,8 +298,8 @@ public class SheetDataSaxHandler extends DefaultHandler {
// 单元格存储格式的索引,对应style.xml中的numFmts元素的子元素索引
final int numFmtIndex = xssfCellStyle.getDataFormat();
this.numFmtString = ObjectUtil.defaultIfNull(
- xssfCellStyle.getDataFormatString(),
- () -> BuiltinFormats.getBuiltinFormat(numFmtIndex));
+ xssfCellStyle.getDataFormatString(),
+ () -> BuiltinFormats.getBuiltinFormat(numFmtIndex));
if (CellDataType.NUMBER == this.cellDataType && ExcelSaxUtil.isDateFormat(numFmtIndex, numFmtString)) {
cellDataType = CellDataType.DATE;
}
diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/IssueIB0EJ9Test.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/IssueIB0EJ9Test.java
index dbc32f4e2..c5f82332e 100644
--- a/hutool-poi/src/test/java/cn/hutool/poi/excel/IssueIB0EJ9Test.java
+++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/IssueIB0EJ9Test.java
@@ -9,7 +9,7 @@ public class IssueIB0EJ9Test {
@Test
@Disabled
void saxReadTest() {
- ExcelUtil.readBySax(FileUtil.file("d:/test/bbb.xlsx"), "Sheet1",
+ ExcelUtil.readBySax(FileUtil.file("d:/test/数值型测试.xlsx"), "hcm工资表",
(sheetIndex, rowIndex, rowlist) -> Console.log("[{}] [{}] {}", sheetIndex, rowIndex, rowlist));
}
}
diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml
index d858f51be..92b0c66b2 100755
--- a/hutool-script/pom.xml
+++ b/hutool-script/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-script
diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml
index 45bdc1040..570ae9686 100755
--- a/hutool-setting/pom.xml
+++ b/hutool-setting/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-setting
diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml
index 5437b8189..36fbbb44e 100755
--- a/hutool-socket/pom.xml
+++ b/hutool-socket/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-socket
diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml
index 58377d7db..a34035627 100755
--- a/hutool-system/pom.xml
+++ b/hutool-system/pom.xml
@@ -9,7 +9,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool-system
diff --git a/pom.xml b/pom.xml
index 97472b007..268ebe5d9 100755
--- a/pom.xml
+++ b/pom.xml
@@ -8,7 +8,7 @@
cn.hutool
hutool-parent
- 5.8.33
+ 5.8.34-SNAPSHOT
hutool
Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。
https://github.com/dromara/hutool