diff --git a/CHANGELOG.md b/CHANGELOG.md index 24ef2f625..e384f82ff 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,26 @@ # 🚀Changelog +------------------------------------------------------------------------------------------------------------- +# 5.8.35(2024-12-25) + +### 🐣新特性 +* 【poi 】 优化ExcelWriter中使用比较器writer的方法,只对第一条数据进行排序(pr#3807@Github) +* 【extra 】 优化Ftp.download,返回false抛出异常(issue#3805@Github) +* 【core 】 优化MAC地址正则(issue#IB95X4@Gitee) +* 【json 】 JSON的getByPath方法新增更为通用的指定出参类型重载(pr#3814@Github) +* 【core 】 DateUtil.parseUTC方法标记废弃,改名为parseISO8601(issue#IBB6I5@Gitee) +* 【core 】 添加EnumUtil#getBy(Class, Func1, Object)方法(pr#1283@Gitee) +* 【db 】 添加Entity.addCondition方法(issue#IBCDL2@Gitee) +* 【poi 】 添加StopReadException,定义sax读取时用户可手动终止(issue#3820@Github) + +### 🐞Bug修复 +* 【crypto 】 修复JWTSignerUtil.createSigner中algorithmId未转换问题(issue#3806@Github) +* 【core 】 修复DateUtil.rangeContains未重置问题(issue#IB8OFS@Gitee) +* 【cache 】 修复StampedCache类get方法并发问题(issue#IBCIQG@Gitee) +* 【cache 】 修复FIFOCache类使用StampedCache导致并发读的并发问题(issue#IBCIQG@Gitee) +* 【cache 】 废弃StampedCache,可能造成Map循环调用导致死锁(issue#IBDGBZ@Gitee) + ------------------------------------------------------------------------------------------------------------- # 5.8.34(2024-11-25) diff --git a/README-EN.md b/README-EN.md index a572e249c..80a055aef 100755 --- a/README-EN.md +++ b/README-EN.md @@ -36,6 +36,9 @@ github star + + gitcode star +


@@ -150,18 +153,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop: cn.hutool hutool-all - 5.8.34 + 5.8.35 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.8.34' +implementation 'cn.hutool:hutool-all:5.8.35' ``` ## 📥Download -- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.34/) +- [Maven Repo](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.35/) > 🔔️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. @@ -199,6 +202,7 @@ When submitting feedback, please indicate which JDK version, Hutool version, and - [Gitee issue](https://gitee.com/dromara/hutool/issues) - [Github issue](https://github.com/dromara/hutool/issues) +- [Gitcode issue](https://gitcode.com/dromara/hutool/issues) ### 🧬Principles of PR(pull request) diff --git a/README.md b/README.md index fbad57511..9034505b0 100755 --- a/README.md +++ b/README.md @@ -36,6 +36,9 @@ github star + + gitcode star +


@@ -143,20 +146,20 @@ Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu cn.hutool hutool-all - 5.8.34 + 5.8.35 ``` ### 🍐Gradle ``` -implementation 'cn.hutool:hutool-all:5.8.34' +implementation 'cn.hutool:hutool-all:5.8.35' ``` ### 📥下载jar 点击以下链接,下载`hutool-all-X.X.X.jar`即可: -- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.34/) +- [Maven中央库](https://repo1.maven.org/maven2/cn/hutool/hutool-all/5.8.35/) > 🔔️注意 > Hutool 5.x支持JDK8+,对Android平台没有测试,不能保证所有工具类或工具方法可用。 @@ -191,15 +194,16 @@ Hutool的源码分为两个分支,功能如下: - [Gitee issue](https://gitee.com/dromara/hutool/issues) - [Github issue](https://github.com/dromara/hutool/issues) +- [Gitcode issue](https://gitcode.com/dromara/hutool/issues) ### 🧬贡献代码的步骤 -1. 在Gitee或者Github上fork项目到自己的repo +1. 在Gitee或者Github/Gitcode上fork项目到自己的repo 2. 把fork过去的项目也就是你的项目clone到你的本地 3. 修改代码(记得一定要修改v5-dev分支) 4. commit后push到自己的库(v5-dev分支) -5. 登录Gitee或Github在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。 +5. 登录Gitee或Github/Gitcode在你首页可以看到一个 pull request 按钮,点击它,填写一些说明信息,然后提交即可。 6. 等待维护者合并 ### 📐PR遵照的原则 diff --git a/bin/push_dev.sh b/bin/push_dev.sh index 2e7f96936..5bca1c384 100755 --- a/bin/push_dev.sh +++ b/bin/push_dev.sh @@ -3,7 +3,11 @@ echo -e "\033[32mCheckout to v5-dev\033[0m" git checkout v5-dev -echo -e "\033[32mPush to origin v5-dev\033[0m" +echo -e "\033[32mPush to Github(origin) v5-dev\033[0m" git push origin v5-dev -echo -e "\033[32mPush to osc v5-dev\033[0m" + +echo -e "\033[32mPush to Gitee v5-dev\033[0m" git push osc v5-dev + +echo -e "\033[32mPush to Gitcode v5-dev\033[0m" +git push gitcode v5-dev diff --git a/bin/push_master.sh b/bin/push_master.sh index 729541c98..683910d49 100755 --- a/bin/push_master.sh +++ b/bin/push_master.sh @@ -6,7 +6,11 @@ git checkout v5-master echo -e "\033[32mMerge v5-dev branch\033[0m" git merge v5-dev -m 'Prepare release' -echo -e "\033[32mPush to origin v5-master\033[0m" +echo -e "\033[32mPush to Github(origin) v5-master\033[0m" git push origin v5-master -echo -e "\033[32mPush to osc v5-master\033[0m" + +echo -e "\033[32mPush to Gitee v5-master\033[0m" git push osc v5-master + +echo -e "\033[32mPush to Gitcode v5-master\033[0m" +git push gitcode v5-master diff --git a/bin/sync.sh b/bin/sync.sh index 804660f3b..393c03247 100644 --- a/bin/sync.sh +++ b/bin/sync.sh @@ -3,3 +3,4 @@ git checkout v5-dev git pull osc v5-dev git pull origin v5-dev +git pull gitcode v5-dev diff --git a/bin/version.txt b/bin/version.txt index 8cf2ade4e..8ce621ecf 100755 --- a/bin/version.txt +++ b/bin/version.txt @@ -1 +1 @@ -5.8.34 +5.8.35 diff --git a/docs/js/version.js b/docs/js/version.js index 36dcf0ff4..d677c5f76 100755 --- a/docs/js/version.js +++ b/docs/js/version.js @@ -1 +1 @@ -var version = '5.8.34' \ No newline at end of file +var version = '5.8.35' \ No newline at end of file diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index b173af04d..95a1e8296 100755 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index be66ef6a8..3b96ca4cd 100755 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index a05e42bc3..ff6d81e10 100755 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index b583cdc91..1b8b92bd4 100755 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index 0a43250b4..6e302bf30 100755 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-cache diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java index 071ac5067..68de47ef8 100755 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/FIFOCache.java @@ -16,7 +16,7 @@ import java.util.LinkedHashMap; * @param 值类型 * @author Looly */ -public class FIFOCache extends StampedCache { +public class FIFOCache extends ReentrantCache { private static final long serialVersionUID = 1L; /** diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java index b3bcdffc9..6ac8e6299 100755 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/LFUCache.java @@ -15,7 +15,7 @@ import java.util.Iterator; * @param 键类型 * @param 值类型 */ -public class LFUCache extends StampedCache { +public class LFUCache extends ReentrantCache { private static final long serialVersionUID = 1L; /** 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 2e90c9c4c..a3ca63a30 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 @@ -12,7 +12,9 @@ import java.util.concurrent.locks.StampedLock; * @param 值类型 * @author looly * @since 5.7.15 + * @deprecated Map使用StampedLock可能造成数据不一致甚至Map循环调用,此缓存废弃 */ +@Deprecated public abstract class StampedCache extends AbstractCache { private static final long serialVersionUID = 1L; @@ -88,7 +90,12 @@ public abstract class StampedCache extends AbstractCache { } /** - * 获取值 + * 获取值,使用乐观锁,但是此方法可能导致读取脏数据,但对于缓存业务可容忍。情况如下: + *
+	 *     1. 读取时无写入,不冲突,直接获取值
+	 *     2. 读取时无写入,但是乐观读时触发了并发异常,此时获取同步锁,获取新值
+	 *     4. 读取时有写入,此时获取同步锁,获取新值
+	 * 
* * @param key 键 * @param isUpdateLastAccess 是否更新最后修改时间 @@ -97,10 +104,24 @@ public abstract class StampedCache extends AbstractCache { */ private V get(K key, boolean isUpdateLastAccess, boolean isUpdateCount) { // 尝试读取缓存,使用乐观读锁 + CacheObj co = null; long stamp = lock.tryOptimisticRead(); - CacheObj co = getWithoutLock(key); - if (false == lock.validate(stamp)) { - // 有写线程修改了此对象,悲观读 + boolean isReadError = true; + if(lock.validate(stamp)){ + try{ + // 乐观读,可能读取脏数据,在缓存中可容忍,分两种情况 + // 1. 读取时无线程写入 + // 2. 读取时有线程写入,导致数据不一致,此时读取未更新的缓存值 + co = getWithoutLock(key); + isReadError = false; + } catch (final Exception ignore){ + // ignore + } + } + + if(isReadError){ + // 转换为悲观读 + // 原因可能为无锁读时触发并发异常,或者锁被占(正在写) stamp = lock.readLock(); try { co = getWithoutLock(key); diff --git a/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java b/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java index dfccd942e..3733a899c 100755 --- a/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java +++ b/hutool-cache/src/main/java/cn/hutool/cache/impl/TimedCache.java @@ -17,7 +17,7 @@ import java.util.concurrent.ScheduledFuture; * @param 键类型 * @param 值类型 */ -public class TimedCache extends StampedCache { +public class TimedCache extends ReentrantCache { private static final long serialVersionUID = 1L; /** 正在执行的定时任务 */ diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 18606bdbc..6ab907469 100755 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-captcha 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 7da77496c..d32c195d3 100755 --- a/hutool-captcha/src/main/java/cn/hutool/captcha/GifCaptcha.java +++ b/hutool-captcha/src/main/java/cn/hutool/captcha/GifCaptcha.java @@ -113,9 +113,7 @@ public class GifCaptcha extends AbstractCaptcha { * @return this */ public GifCaptcha setRepeat(int repeat) { - if (repeat >= 0) { - this.repeat = repeat; - } + this.repeat = Math.max(repeat, 0); return this; } diff --git a/hutool-captcha/src/test/java/cn/hutool/captcha/GifCaptchaUtilTest.java b/hutool-captcha/src/test/java/cn/hutool/captcha/GifCaptchaUtilTest.java new file mode 100644 index 000000000..2125aa64a --- /dev/null +++ b/hutool-captcha/src/test/java/cn/hutool/captcha/GifCaptchaUtilTest.java @@ -0,0 +1,116 @@ +package cn.hutool.captcha; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.*; + +public class GifCaptchaUtilTest { + + private GifCaptcha captcha; + + @BeforeEach + public void setUp() { + // 初始化 GifCaptcha 类的实例 + captcha = new GifCaptcha(200, 100, 4, 10); // width, height, codeCount, interfereCount + } + + // 使用反射调用私有方法 + private Object invokePrivateMethod(String methodName, Class[] parameterTypes, Object[] parameters) throws Exception { + Method method = GifCaptcha.class.getDeclaredMethod(methodName, parameterTypes); + method.setAccessible(true); // 允许访问私有方法 + return method.invoke(captcha, parameters); + } + + // 测试 setQuality() 方法 + @Test + public void testSetQuality() throws Exception { + captcha.setQuality(20); + // 通过反射获取 quality 字段的值并进行断言 + assertEquals(20, getPrivateField("quality"), "Quality 应该设置为 20"); + + captcha.setQuality(0); // 设置无效值,应该被设置为 1 + assertEquals(1, getPrivateField("quality"), "Quality 应该设置为 1,如果小于 1"); + } + + // 测试 setRepeat() 方法 + @Test + public void testSetRepeat() throws Exception { + captcha.setRepeat(5); + // 通过反射获取 repeat 字段的值并进行断言 + assertEquals(5, getPrivateField("repeat"), "Repeat 应该设置为 5"); + + captcha.setRepeat(-1); // 设置无效值,应该保持为 0 + assertEquals(0, getPrivateField("repeat"), "Repeat 应该设置为 0,如果设置了负值"); + } + + // 测试 setColorRange() 方法 + @Test + public void testSetColorRange() throws Exception { + captcha.setMinColor(100).setMaxColor(200); + // 通过反射获取 minColor 和 maxColor 字段的值并进行断言 + assertEquals(100, getPrivateField("minColor"), "Min color 应该设置为 100"); + assertEquals(200, getPrivateField("maxColor"), "Max color 应该设置为 200"); + } + + // 测试生成验证码图像的方法 createCode() + @Test + public void testCreateCode() throws Exception { + captcha.createCode(); + byte[] imageBytes = captcha.getImageBytes(); + + // 检查生成的图片字节是否不为 null 或空 + assertNotNull(imageBytes, "生成的图片字节不应该为 null"); + assertTrue(imageBytes.length > 0, "生成的图片字节不应该为空"); + + // 可选:你也可以通过解码图片字节,检查它是否是有效的 GIF 格式 + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write(imageBytes); + + // 解码图片检查它是否为有效的 GIF(假设你有库可以解码 GIF) + // ImageIO.read(new ByteArrayInputStream(imageBytes)); // 可以取消注释来检查它是否是有效的 GIF + } + + // 测试 graphicsImage() 方法 + @Test + public void testGraphicsImage() throws Exception { + char[] chars = new char[]{'A', 'B', 'C', 'D'}; + Color[] colors = new Color[]{ + Color.RED, Color.GREEN, Color.BLUE, Color.YELLOW + }; + + // 使用反射调用 private 方法 graphicsImage + Object result = invokePrivateMethod("graphicsImage", new Class[]{char[].class, Color[].class, char[].class, int.class}, new Object[]{chars, colors, chars, 0}); + + assertNotNull(result, "生成的图片不应该为 null"); + assertInstanceOf(BufferedImage.class, result, "返回的结果应该是 BufferedImage 类型"); + } + + // 测试 getRandomColor() 方法 + @Test + public void testRandomColor() throws Exception { + // 使用反射调用 private 方法 getRandomColor + Object result = invokePrivateMethod("getRandomColor", new Class[]{int.class, int.class}, new Object[]{0, 255}); + + assertNotNull(result, "生成的颜色不应该为 null"); + assertInstanceOf(Color.class, result, "返回的结果应该是 Color 类型"); + + Color color = (Color) result; + assertTrue(color.getRed() >= 0 && color.getRed() <= 255, "颜色的红色分量应该在 0 到 255 之间"); + assertTrue(color.getGreen() >= 0 && color.getGreen() <= 255, "颜色的绿色分量应该在 0 到 255 之间"); + assertTrue(color.getBlue() >= 0 && color.getBlue() <= 255, "颜色的蓝色分量应该在 0 到 255 之间"); + } + + // 辅助方法:通过反射获取私有字段的值 + private Object getPrivateField(String fieldName) throws NoSuchFieldException, IllegalAccessException { + Field field = GifCaptcha.class.getDeclaredField(fieldName); + field.setAccessible(true); // 允许访问私有字段 + return field.get(captcha); + } +} diff --git a/hutool-captcha/src/test/java/cn/hutool/captcha/ShearCaptchaTest.java b/hutool-captcha/src/test/java/cn/hutool/captcha/ShearCaptchaTest.java new file mode 100644 index 000000000..b75b46eb1 --- /dev/null +++ b/hutool-captcha/src/test/java/cn/hutool/captcha/ShearCaptchaTest.java @@ -0,0 +1,145 @@ +package cn.hutool.captcha; + +import cn.hutool.captcha.generator.RandomGenerator; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.*; + +public class ShearCaptchaTest { + + private ShearCaptcha captcha; + + @BeforeEach + public void setUp() { + // 初始化 ShearCaptcha 实例 + captcha = new ShearCaptcha(200, 100); + } + + // 测试构造函数和基本功能 + @Test + public void testConstructor() { + assertNotNull(captcha, "Captcha 实例应该被成功创建"); + } + + // 测试生成验证码图片的功能 + @Test + public void testCreateImage() { + String code = "ABCD"; + Image image = captcha.createImage(code); + assertNotNull(image, "验证码图片不应该为 null"); + assertInstanceOf(BufferedImage.class, image, "生成的图片应该是 BufferedImage 类型"); + + // 可选:进一步测试图像的内容 + BufferedImage bufferedImage = (BufferedImage) image; + assertEquals(200, bufferedImage.getWidth(), "图像宽度应该为 200"); + assertEquals(100, bufferedImage.getHeight(), "图像高度应该为 100"); + } + + // 测试绘制字符串的方法 + @Test + public void testDrawString() throws Exception { + String code = "ABCD"; + Method drawStringMethod = ShearCaptcha.class.getDeclaredMethod("drawString", Graphics2D.class, String.class); + drawStringMethod.setAccessible(true); + + Graphics2D g2d = (Graphics2D) new BufferedImage(200, 100, BufferedImage.TYPE_INT_ARGB).getGraphics(); + drawStringMethod.invoke(captcha, g2d, code); + + assertNotNull(g2d, "Graphics2D 对象不应该为 null"); + assertTrue(g2d.getRenderingHints().containsKey(RenderingHints.KEY_ANTIALIASING), "应该启用抗锯齿"); + } + + // 测试 shear() 方法 + @Test + public void testShear() throws Exception { + // 使用反射测试 shear 方法 + Method shearMethod = ShearCaptcha.class.getDeclaredMethod("shear", Graphics.class, int.class, int.class, Color.class); + shearMethod.setAccessible(true); + + Graphics g = new BufferedImage(200, 100, BufferedImage.TYPE_INT_ARGB).getGraphics(); + shearMethod.invoke(captcha, g, 200, 100, Color.WHITE); + + // 假设没有明显的错误输出,认为测试通过 + assertNotNull(g, "Graphics 对象不应该为 null"); + } + + // 测试 shearX() 方法 + @Test + public void testShearX() throws Exception { + // 使用反射测试 shearX 方法 + Method shearXMethod = ShearCaptcha.class.getDeclaredMethod("shearX", Graphics.class, int.class, int.class, Color.class); + shearXMethod.setAccessible(true); + + Graphics g = new BufferedImage(200, 100, BufferedImage.TYPE_INT_ARGB).getGraphics(); + shearXMethod.invoke(captcha, g, 200, 100, Color.RED); + + // 假设没有明显的错误输出,认为测试通过 + assertNotNull(g, "Graphics 对象不应该为 null"); + } + + // 测试 shearY() 方法 + @Test + public void testShearY() throws Exception { + // 使用反射测试 shearY 方法 + Method shearYMethod = ShearCaptcha.class.getDeclaredMethod("shearY", Graphics.class, int.class, int.class, Color.class); + shearYMethod.setAccessible(true); + + Graphics g = new BufferedImage(200, 100, BufferedImage.TYPE_INT_ARGB).getGraphics(); + shearYMethod.invoke(captcha, g, 200, 100, Color.BLUE); + + // 假设没有明显的错误输出,认为测试通过 + assertNotNull(g, "Graphics 对象不应该为 null"); + } + + // 测试 drawInterfere() 方法 + @Test + public void testDrawInterfere() throws Exception { + // 使用反射测试 drawInterfere 方法 + Method drawInterfereMethod = ShearCaptcha.class.getDeclaredMethod("drawInterfere", Graphics.class, int.class, int.class, int.class, int.class, int.class, Color.class); + drawInterfereMethod.setAccessible(true); + + Graphics g = new BufferedImage(200, 100, BufferedImage.TYPE_INT_ARGB).getGraphics(); + drawInterfereMethod.invoke(captcha, g, 0, 0, 200, 100, 4, Color.GREEN); + + // 假设没有明显的错误输出,认为测试通过 + assertNotNull(g, "Graphics 对象不应该为 null"); + } + + // 测试验证码生成时的干扰线 + @Test + public void testDrawInterfereLines() { + // 设置干扰线数量 + captcha = new ShearCaptcha(200, 100, 4); + Image image = captcha.createImage("ABCD"); + + // 检查图像内容,判断干扰线是否正确绘制 + assertNotNull(image, "生成的验证码图片不应该为空"); + } + + // 测试验证码的尺寸 + @Test + public void testCaptchaSize() { + captcha = new ShearCaptcha(300, 150); + + String code = "XYZ"; + Image image = captcha.createImage(code); + + BufferedImage bufferedImage = (BufferedImage) image; + assertEquals(300, bufferedImage.getWidth(), "图像宽度应该为 300"); + assertEquals(150, bufferedImage.getHeight(), "图像高度应该为 150"); + } + + // 测试生成随机验证码字符 + @Test + public void testRandomGenerator() { + RandomGenerator randomGenerator = new RandomGenerator(4); + String code = randomGenerator.generate(); + assertNotNull(code, "生成的验证码字符不应该为 null"); + assertEquals(4, code.length(), "验证码字符长度应该为 4"); + } +} diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 371ee4f92..bc1ab47d9 100755 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-core diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java index 53b44ad17..1dee902f2 100755 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java @@ -844,16 +844,37 @@ public class DateUtil extends CalendarUtil { * @param utcString UTC时间 * @return 日期对象 * @since 4.1.14 + * @deprecated 方法歧义,带T的日期并不一定是UTC时间,请使用 {@link #parseISO8601(String)} */ + @Deprecated public static DateTime parseUTC(String utcString) { - if (utcString == null) { + return parseISO8601(utcString); + } + + /** + * 解析ISO8601时间,格式:
+ *
    + *
  1. yyyy-MM-dd'T'HH:mm:ss'Z'
  2. + *
  3. yyyy-MM-dd'T'HH:mm:ss.SSS'Z'
  4. + *
  5. yyyy-MM-dd'T'HH:mm:ssZ
  6. + *
  7. yyyy-MM-dd'T'HH:mm:ss.SSSZ
  8. + *
  9. yyyy-MM-dd'T'HH:mm:ss+0800
  10. + *
  11. yyyy-MM-dd'T'HH:mm:ss+08:00
  12. + *
+ * + * @param iso8601String ISO8601时间 + * @return 日期对象 + * @since 5.8.34 + */ + public static DateTime parseISO8601(String iso8601String) { + if (iso8601String == null) { return null; } - final int length = utcString.length(); - if (StrUtil.contains(utcString, 'Z')) { + final int length = iso8601String.length(); + if (StrUtil.contains(iso8601String, 'Z')) { if (length == DatePattern.UTC_PATTERN.length() - 4) { // 格式类似:2018-09-13T05:34:31Z,-4表示减去4个单引号的长度 - return parse(utcString, DatePattern.UTC_FORMAT); + return parse(iso8601String, DatePattern.UTC_FORMAT); } final int patternLength = DatePattern.UTC_MS_PATTERN.length(); @@ -861,61 +882,61 @@ public class DateUtil extends CalendarUtil { // -4 ~ -6范围表示匹配毫秒1~3位的情况 if (length <= patternLength && length >= patternLength - 6) { // issue#I7H34N,支持最多6位毫秒 - return parse(utcString, DatePattern.UTC_MS_FORMAT); + return parse(iso8601String, DatePattern.UTC_MS_FORMAT); } - } else if (StrUtil.contains(utcString, '+')) { + } else if (StrUtil.contains(iso8601String, '+')) { // 去除类似2019-06-01T19:45:43 +08:00加号前的空格 - utcString = utcString.replace(" +", "+"); - final String zoneOffset = StrUtil.subAfter(utcString, '+', true); + iso8601String = iso8601String.replace(" +", "+"); + final String zoneOffset = StrUtil.subAfter(iso8601String, '+', true); if (StrUtil.isBlank(zoneOffset)) { - throw new DateException("Invalid format: [{}]", utcString); + throw new DateException("Invalid format: [{}]", iso8601String); } if (false == StrUtil.contains(zoneOffset, ':')) { // +0800转换为+08:00 - final String pre = StrUtil.subBefore(utcString, '+', true); - utcString = pre + "+" + zoneOffset.substring(0, 2) + ":" + "00"; + final String pre = StrUtil.subBefore(iso8601String, '+', true); + iso8601String = pre + "+" + zoneOffset.substring(0, 2) + ":" + "00"; } - if (StrUtil.contains(utcString, CharUtil.DOT)) { + if (StrUtil.contains(iso8601String, CharUtil.DOT)) { // 带毫秒,格式类似:2018-09-13T05:34:31.999+08:00 - utcString = normalizeMillSeconds(utcString, ".", "+"); - return parse(utcString, DatePattern.UTC_MS_WITH_XXX_OFFSET_FORMAT); + iso8601String = normalizeMillSeconds(iso8601String, ".", "+"); + return parse(iso8601String, DatePattern.UTC_MS_WITH_XXX_OFFSET_FORMAT); } else { // 格式类似:2018-09-13T05:34:31+08:00 - return parse(utcString, DatePattern.UTC_WITH_XXX_OFFSET_FORMAT); + return parse(iso8601String, DatePattern.UTC_WITH_XXX_OFFSET_FORMAT); } - } else if(ReUtil.contains("-\\d{2}:?00", utcString)){ + } else if(ReUtil.contains("-\\d{2}:?00", iso8601String)){ // Issue#2612,类似 2022-09-14T23:59:00-08:00 或者 2022-09-14T23:59:00-0800 // 去除类似2019-06-01T19:45:43 -08:00加号前的空格 - utcString = utcString.replace(" -", "-"); - if(':' != utcString.charAt(utcString.length() - 3)){ - utcString = utcString.substring(0, utcString.length() - 2) + ":00"; + iso8601String = iso8601String.replace(" -", "-"); + if(':' != iso8601String.charAt(iso8601String.length() - 3)){ + iso8601String = iso8601String.substring(0, iso8601String.length() - 2) + ":00"; } - if (StrUtil.contains(utcString, CharUtil.DOT)) { + if (StrUtil.contains(iso8601String, CharUtil.DOT)) { // 带毫秒,格式类似:2018-09-13T05:34:31.999-08:00 - utcString = normalizeMillSeconds(utcString, ".", "-"); - return new DateTime(utcString, DatePattern.UTC_MS_WITH_XXX_OFFSET_FORMAT); + iso8601String = normalizeMillSeconds(iso8601String, ".", "-"); + return new DateTime(iso8601String, DatePattern.UTC_MS_WITH_XXX_OFFSET_FORMAT); } else { // 格式类似:2018-09-13T05:34:31-08:00 - return new DateTime(utcString, DatePattern.UTC_WITH_XXX_OFFSET_FORMAT); + return new DateTime(iso8601String, DatePattern.UTC_WITH_XXX_OFFSET_FORMAT); } } else { if (length == DatePattern.UTC_SIMPLE_PATTERN.length() - 2) { // 格式类似:2018-09-13T05:34:31 - return parse(utcString, DatePattern.UTC_SIMPLE_FORMAT); + return parse(iso8601String, DatePattern.UTC_SIMPLE_FORMAT); } else if (length == DatePattern.UTC_SIMPLE_PATTERN.length() - 5) { // 格式类似:2018-09-13T05:34 - return parse(utcString + ":00", DatePattern.UTC_SIMPLE_FORMAT); - } else if (StrUtil.contains(utcString, CharUtil.DOT)) { + return parse(iso8601String + ":00", DatePattern.UTC_SIMPLE_FORMAT); + } else if (StrUtil.contains(iso8601String, CharUtil.DOT)) { // 可能为: 2021-03-17T06:31:33.99 - utcString = normalizeMillSeconds(utcString, ".", null); - return parse(utcString, DatePattern.UTC_SIMPLE_MS_FORMAT); + iso8601String = normalizeMillSeconds(iso8601String, ".", null); + return parse(iso8601String, DatePattern.UTC_SIMPLE_MS_FORMAT); } } // 没有更多匹配的时间格式 - throw new DateException("No format fit for date String [{}] !", utcString); + throw new DateException("No format fit for date String [{}] !", iso8601String); } /** @@ -1025,8 +1046,8 @@ public class DateUtil extends CalendarUtil { // Wed Aug 01 00:00:00 CST 2012 return parseRFC2822(dateStr); } else if (StrUtil.contains(dateStr, 'T')) { - // UTC时间 - return parseUTC(dateStr); + // ISO8601时间 + return parseISO8601(dateStr); } //标准日期格式(包括单个数字的日期时间) @@ -1975,8 +1996,8 @@ public class DateUtil extends CalendarUtil { * @since 5.7.21 */ public static List rangeContains(DateRange start, DateRange end) { - List startDateTimes = CollUtil.newArrayList((Iterable) start); - List endDateTimes = CollUtil.newArrayList((Iterable) end); + List startDateTimes = CollUtil.newArrayList((Iterable) start.reset()); + List endDateTimes = CollUtil.newArrayList((Iterable) end.reset()); return startDateTimes.stream().filter(endDateTimes::contains).collect(Collectors.toList()); } @@ -1990,8 +2011,8 @@ public class DateUtil extends CalendarUtil { * @since 5.7.21 */ public static List rangeNotContains(DateRange start, DateRange end) { - List startDateTimes = CollUtil.newArrayList((Iterable) start); - List endDateTimes = CollUtil.newArrayList((Iterable) end); + List startDateTimes = CollUtil.newArrayList((Iterable) start.reset()); + List endDateTimes = CollUtil.newArrayList((Iterable) end.reset()); return endDateTimes.stream().filter(item -> !startDateTimes.contains(item)).collect(Collectors.toList()); } diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java index 96d5b4457..664be9a46 100755 --- a/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/RegexPool.java @@ -132,7 +132,8 @@ public interface RegexPool { /** * MAC地址正则 */ - String MAC_ADDRESS = "((?:[a-fA-F0-9]{1,2}[:-]){5}[a-fA-F0-9]{1,2})|0x(\\d{12}).+ETHER"; + //String MAC_ADDRESS = "((?:[a-fA-F0-9]{1,2}[:-]){5}[a-fA-F0-9]{1,2})|0x(\\d{12}).+ETHER"; + String MAC_ADDRESS = "((?:[a-fA-F0-9]{1,2}[:-]){5}[a-fA-F0-9]{1,2})|((?:[a-fA-F0-9]{1,4}[.]){2}[a-fA-F0-9]{1,4})|[a-fA-F0-9]{12}|0x(\\d{12}).+ETHER"; /** * 16进制字符串 */ diff --git a/hutool-core/src/main/java/cn/hutool/core/util/EnumUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/EnumUtil.java index 3036cd762..2ab529a26 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/EnumUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/EnumUtil.java @@ -60,11 +60,11 @@ public class EnumUtil { * @since 5.1.6 */ public static > E getEnumAt(Class enumClass, int index) { - if(null == enumClass){ + if (null == enumClass) { return null; } final E[] enumConstants = enumClass.getEnumConstants(); - if(index < 0){ + if (index < 0) { index = enumConstants.length + index; } @@ -129,7 +129,7 @@ public class EnumUtil { */ @SuppressWarnings("unchecked") public static > E likeValueOf(Class enumClass, Object value) { - if(null == enumClass || null == value){ + if (null == enumClass || null == value) { return null; } if (value instanceof CharSequence) { @@ -161,7 +161,7 @@ public class EnumUtil { * @return name列表 */ public static List getNames(Class> clazz) { - if(null == clazz){ + if (null == clazz) { return null; } final Enum[] enums = clazz.getEnumConstants(); @@ -183,7 +183,7 @@ public class EnumUtil { * @return 字段值列表 */ public static List getFieldValues(Class> clazz, String fieldName) { - if(null == clazz || StrUtil.isBlank(fieldName)){ + if (null == clazz || StrUtil.isBlank(fieldName)) { return null; } final Enum[] enums = clazz.getEnumConstants(); @@ -210,7 +210,7 @@ public class EnumUtil { * @since 4.1.20 */ public static List getFieldNames(Class> clazz) { - if(null == clazz){ + if (null == clazz) { return null; } final List names = new ArrayList<>(); @@ -238,11 +238,62 @@ public class EnumUtil { * @since 5.8.0 */ public static > E getBy(Class enumClass, Predicate predicate) { - if(null == enumClass || null == predicate){ + return getBy(enumClass, predicate, null); + } + + /** + * 通过 某字段对应值 获取 枚举,获取不到时为 {@code defaultEnum} + * + * @param enumClass 枚举类 + * @param predicate 条件 + * @param defaultEnum 获取不到时的默认枚举值 + * @param 枚举类型 + * @return 对应枚举 ,获取不到时为 {@code defaultEnum} + */ + public static > E getBy(Class enumClass, Predicate predicate, E defaultEnum) { + if (null == enumClass || null == predicate) { return null; } return Arrays.stream(enumClass.getEnumConstants()) - .filter(predicate).findFirst().orElse(null); + .filter(predicate).findFirst().orElse(defaultEnum); + } + + /** + * 通过 某字段对应值 获取 枚举,获取不到时为 {@code null} + *

+ * {@link LambdaUtil#getRealClass(Func1)}} 是相对耗时的 + * 如果枚举值比较多,那么{@link EnumUtil#getBy(Func1, Object)} 方法 + * 大部分时间都是被{@link LambdaUtil#getRealClass(Func1)}}所消耗的 + *
+ * 如果可以在编码过程中可以提供对应的枚举类 该方法与枚举的{@code Enum.values()}方法是差不多的。 + * + * @param enumClass 枚举类, 为{@code null}返回{@code null} + * @param condition 条件字段,为{@code null}返回{@code null} + * @param value 条件字段值 + * @param 枚举类型 + * @param 字段类型 + * @return 对应枚举 ,获取不到时为 {@code null} + */ + public static , C> E getBy(Class enumClass, Func1 condition, C value) { + if (null == condition) { + return null; + } + return getBy(enumClass, constant -> ObjUtil.equals(condition.callWithRuntimeException(constant), value)); + } + + /** + * 通过 某字段对应值 获取 枚举,获取不到时为 {@code defaultEnum} + * + * @param enumClass 枚举类, 为{@code null}返回{@code null} + * @param condition 条件字段,为{@code null}返回{@code null} + * @param value 条件字段值 + * @param defaultEnum 获取不到时的默认枚举值 + * @param 枚举类型 + * @param 字段类型 + * @return 对应枚举 ,获取不到时为 {@code defaultEnum} + */ + public static , C> E getBy(Class enumClass, Func1 condition, C value, E defaultEnum) { + return ObjectUtil.defaultIfNull(getBy(enumClass, condition, value), defaultEnum); } /** @@ -259,10 +310,7 @@ public class EnumUtil { return null; } final Class implClass = LambdaUtil.getRealClass(condition); - return Arrays.stream(implClass.getEnumConstants()) - .filter(constant -> ObjUtil.equals(condition.callWithRuntimeException(constant), value)) - .findAny() - .orElse(null); + return getBy(implClass, condition, value); } /** @@ -293,7 +341,7 @@ public class EnumUtil { * @since 5.8.0 */ public static , F, C> F getFieldBy(Func1 field, Function condition, C value) { - if(null == field || null == condition){ + if (null == field || null == condition) { return null; } final Class implClass = LambdaUtil.getRealClass(field); @@ -316,7 +364,7 @@ public class EnumUtil { * @since 4.0.2 */ public static > LinkedHashMap getEnumMap(final Class enumClass) { - if(null == enumClass){ + if (null == enumClass) { return null; } final LinkedHashMap map = new LinkedHashMap<>(); @@ -335,7 +383,7 @@ public class EnumUtil { * @return 枚举名对应指定字段值的Map */ public static Map getNameFieldMap(Class> clazz, String fieldName) { - if(null == clazz || StrUtil.isBlank(fieldName)){ + if (null == clazz || StrUtil.isBlank(fieldName)) { return null; } final Enum[] enums = clazz.getEnumConstants(); @@ -359,7 +407,7 @@ public class EnumUtil { */ public static > boolean contains(final Class enumClass, String val) { final LinkedHashMap enumMap = getEnumMap(enumClass); - if(CollUtil.isEmpty(enumMap)){ + if (CollUtil.isEmpty(enumMap)) { return false; } return enumMap.containsKey(val); diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateBetweenTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateBetweenTest.java index afcdda170..86e723b2b 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateBetweenTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateBetweenTest.java @@ -1,12 +1,13 @@ package cn.hutool.core.date; import cn.hutool.core.date.BetweenFormatter.Level; -import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; import java.time.temporal.ChronoUnit; import java.util.Date; +import static org.junit.jupiter.api.Assertions.assertEquals; + public class DateBetweenTest { @Test @@ -36,6 +37,14 @@ public class DateBetweenTest { assertEquals(18, betweenYear); } + @Test + public void betweenYearTest3() { + Date start = DateUtil.parse("20170301"); + Date end = DateUtil.parse("2024-02-29 14:56:18"); + long betweenYear = new DateBetween(start, end).betweenYear(false); + assertEquals(6, betweenYear); + } + @Test public void betweenMonthTest() { Date start = DateUtil.parse("2017-02-01 12:23:46"); diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index 3a535a7c9..d8cd91198 100755 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -605,9 +605,9 @@ public class DateUtilTest { } @Test - public void parseUTCTest() { + public void parseISO8601Test() { String dateStr1 = "2018-09-13T05:34:31Z"; - DateTime dt = DateUtil.parseUTC(dateStr1); + DateTime dt = DateUtil.parseISO8601(dateStr1); // parse方法支持UTC格式测试 final DateTime dt2 = DateUtil.parse(dateStr1); @@ -622,12 +622,12 @@ public class DateUtilTest { assertEquals("2018-09-13 13:34:31", dateStr); dateStr1 = "2018-09-13T13:34:32+0800"; - dt = DateUtil.parseUTC(dateStr1); + dt = DateUtil.parseISO8601(dateStr1); dateStr = dt.toString(TimeZone.getTimeZone("GMT+8:00")); assertEquals("2018-09-13 13:34:32", dateStr); dateStr1 = "2018-09-13T13:34:33+08:00"; - dt = DateUtil.parseUTC(dateStr1); + dt = DateUtil.parseISO8601(dateStr1); dateStr = dt.toString(TimeZone.getTimeZone("GMT+8:00")); assertEquals("2018-09-13 13:34:33", dateStr); @@ -644,14 +644,14 @@ public class DateUtilTest { assertEquals("2018-09-13 13:34:35", dateStr); dateStr1 = "2018-09-13T13:34:36.999+0800"; - dt = DateUtil.parseUTC(dateStr1); + dt = DateUtil.parseISO8601(dateStr1); final SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DatePattern.NORM_DATETIME_MS_PATTERN); simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT+8:00")); dateStr = dt.toString(simpleDateFormat); assertEquals("2018-09-13 13:34:36.999", dateStr); dateStr1 = "2018-09-13T13:34:37.999+08:00"; - dt = DateUtil.parseUTC(dateStr1); + dt = DateUtil.parseISO8601(dateStr1); dateStr = dt.toString(simpleDateFormat); assertEquals("2018-09-13 13:34:37.999", dateStr); @@ -676,19 +676,19 @@ public class DateUtilTest { } @Test - public void parseUTCTest2() { + public void parseUTCTest() { // issue1503@Github // 检查不同毫秒长度都可以正常匹配 String utcTime = "2021-03-30T12:56:51.3Z"; - DateTime parse = DateUtil.parseUTC(utcTime); + DateTime parse = DateUtil.parseISO8601(utcTime); assertEquals("2021-03-30 12:56:51", parse.toString()); utcTime = "2021-03-30T12:56:51.34Z"; - parse = DateUtil.parseUTC(utcTime); + parse = DateUtil.parseISO8601(utcTime); assertEquals("2021-03-30 12:56:51", parse.toString()); utcTime = "2021-03-30T12:56:51.345Z"; - parse = DateUtil.parseUTC(utcTime); + parse = DateUtil.parseISO8601(utcTime); assertEquals("2021-03-30 12:56:51", parse.toString()); } @@ -994,7 +994,7 @@ public class DateUtilTest { @SuppressWarnings("ConstantConditions") @Test - public void parseISO8601Test() { + public void parseWithMilsTest() { final String dt = "2020-06-03 12:32:12,333"; final DateTime parse = DateUtil.parse(dt); assertEquals("2020-06-03 12:32:12", parse.toString()); diff --git a/hutool-core/src/test/java/cn/hutool/core/date/IssueIB8OFSTest.java b/hutool-core/src/test/java/cn/hutool/core/date/IssueIB8OFSTest.java new file mode 100644 index 000000000..1d6921523 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/date/IssueIB8OFSTest.java @@ -0,0 +1,25 @@ +package cn.hutool.core.date; + +import cn.hutool.core.lang.Console; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class IssueIB8OFSTest { + @Test + void rangeTest() { + DateRange startRange = DateUtil.range( + DateUtil.parse("2017-01-01"), + DateUtil.parse("2017-01-31"), DateField.DAY_OF_YEAR); + DateRange endRange = DateUtil.range( + DateUtil.parse("2017-01-31"), + DateUtil.parse("2017-02-02"), DateField.DAY_OF_YEAR); + + List dateTimes = DateUtil.rangeContains(startRange, endRange); + Assertions.assertEquals(1, dateTimes.size()); + + List dateNotTimes = DateUtil.rangeNotContains(startRange, endRange); + Assertions.assertEquals(2, dateNotTimes.size()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/date/IssueIB9NPUTest.java b/hutool-core/src/test/java/cn/hutool/core/date/IssueIB9NPUTest.java new file mode 100644 index 000000000..447f5b1aa --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/date/IssueIB9NPUTest.java @@ -0,0 +1,12 @@ +package cn.hutool.core.date; + +import org.junit.jupiter.api.Test; + +import java.text.SimpleDateFormat; + +public class IssueIB9NPUTest { + @Test + void parseTest() { + DateUtil.parse("202409032400", new SimpleDateFormat("yyyyMMddHHmm")); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/date/IssueIBB6I5Test.java b/hutool-core/src/test/java/cn/hutool/core/date/IssueIBB6I5Test.java new file mode 100644 index 000000000..718f37a9f --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/date/IssueIBB6I5Test.java @@ -0,0 +1,25 @@ +package cn.hutool.core.date; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.time.ZoneId; +import java.util.TimeZone; + +public class IssueIBB6I5Test { + @Test + void parseISO8601Test() { + DateTime date = DateUtil.parseISO8601("2024-12-13T08:02:27Z"); + TimeZone timeZone = TimeZone.getTimeZone(ZoneId.of("Asia/Shanghai")); + date.setTimeZone(timeZone); + Assertions.assertEquals("2024-12-13 16:02:27", date.toString()); + } + + @Test + void parseISO8601Test2() { + DateTime date = DateUtil.parseISO8601("2024-12-13T08:02:27"); + TimeZone timeZone = TimeZone.getTimeZone(ZoneId.of("Asia/Shanghai")); + date.setTimeZone(timeZone); + Assertions.assertEquals("2024-12-13 08:02:27", date.toString()); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/Issue3809Test.java b/hutool-core/src/test/java/cn/hutool/core/util/Issue3809Test.java new file mode 100644 index 000000000..ac667387b --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/util/Issue3809Test.java @@ -0,0 +1,16 @@ +package cn.hutool.core.util; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class Issue3809Test { + @Test + void roundStrTest() { + Assertions.assertEquals("9999999999999999.99", NumberUtil.roundStr("9999999999999999.99", 2)); //输出结果不符合方法声明返回值规则 + Assertions.assertEquals("11111111111111119.00", NumberUtil.roundStr("11111111111111119.00", 2)); + Assertions.assertEquals("7999999999999999.99", NumberUtil.roundStr("7999999999999999.99", 2)); //输出结果不符合方法声明返回值规则 + Assertions.assertEquals("699999999991999.92", NumberUtil.roundStr("699999999991999.92", 2)); //输出结果不符合方法声明返回值规则 + Assertions.assertEquals("10.92", NumberUtil.roundStr("10.92", 2)); + Assertions.assertEquals("10.99", NumberUtil.roundStr("10.99", 2)); + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/IssueIB95X4Test.java b/hutool-core/src/test/java/cn/hutool/core/util/IssueIB95X4Test.java new file mode 100644 index 000000000..49b281229 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/util/IssueIB95X4Test.java @@ -0,0 +1,16 @@ +package cn.hutool.core.util; + +import cn.hutool.core.lang.PatternPool; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class IssueIB95X4Test { + + @Test + void isMacTest() { + Assertions.assertTrue(ReUtil.isMatch(PatternPool.MAC_ADDRESS, "ab1c.2d3e.f468")); + Assertions.assertTrue(ReUtil.isMatch(PatternPool.MAC_ADDRESS, "ab:1c:2d:3e:f4:68")); + Assertions.assertTrue(ReUtil.isMatch(PatternPool.MAC_ADDRESS, "ab-1c-2d-3e-f4-68")); + Assertions.assertTrue(ReUtil.isMatch(PatternPool.MAC_ADDRESS, "ab1c2d3ef468")); + } +} diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index 2f36a22ad..78263c982 100755 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 4ee1a3991..557797fb6 100755 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 224d04a89..ee443457a 100755 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-db diff --git a/hutool-db/src/main/java/cn/hutool/db/Entity.java b/hutool-db/src/main/java/cn/hutool/db/Entity.java index cd807c359..a66ad8060 100755 --- a/hutool-db/src/main/java/cn/hutool/db/Entity.java +++ b/hutool-db/src/main/java/cn/hutool/db/Entity.java @@ -8,6 +8,7 @@ import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.db.sql.Condition; import cn.hutool.db.sql.SqlUtil; import java.nio.charset.Charset; @@ -266,6 +267,17 @@ public class Entity extends Dict { } // -------------------------------------------------------------------- Put and Set start + /** + * 添加条件 + * + * @param condition 条件 + * @return this + * @since 5.8.34 + */ + public Entity addCondition(final Condition condition) { + return set(condition.getField(), condition); + } + @Override public Entity set(String field, Object value) { return (Entity) super.set(field, value); diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index d619fd4dd..5927c802f 100755 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 83c88d98c..564ebc22d 100755 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-extra diff --git a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java index 9bdc9fac2..96fbd275e 100755 --- a/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java +++ b/hutool-extra/src/main/java/cn/hutool/extra/ftp/Ftp.java @@ -708,9 +708,11 @@ public class Ftp extends AbstractFtp { if (null != fileNameCharset) { fileName = new String(fileName.getBytes(fileNameCharset), StandardCharsets.ISO_8859_1); } + + boolean isSuccess; try { client.setFileType(FTPClient.BINARY_FILE_TYPE); - client.retrieveFile(fileName, out); + isSuccess = client.retrieveFile(fileName, out); } catch (IOException e) { throw new IORuntimeException(e); } finally { @@ -718,6 +720,10 @@ public class Ftp extends AbstractFtp { cd(pwd); } } + + if(false == isSuccess){ + throw new FtpException("retrieveFile return false"); + } } /** 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 865f216d4..412509098 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 @@ -169,7 +169,7 @@ public class Sftp extends AbstractFtp { */ public void init() { // issue#IB69U8 如果用户传入Session对象,则不能使用配置初始化,而是尝试重新连接 - if(StrUtil.isEmpty(this.ftpConfig.getHost()) && null != this.session){ + if(null != this.session){ try { this.session.connect((int) this.ftpConfig.getConnectionTimeout()); } catch (JSchException e) { diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index 403dae32a..1662ffb7f 100755 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-http diff --git a/hutool-http/src/test/java/cn/hutool/http/IssueIB7REWTest.java b/hutool-http/src/test/java/cn/hutool/http/IssueIB7REWTest.java new file mode 100644 index 000000000..58db5ce38 --- /dev/null +++ b/hutool-http/src/test/java/cn/hutool/http/IssueIB7REWTest.java @@ -0,0 +1,15 @@ +package cn.hutool.http; + +import cn.hutool.core.lang.Console; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +public class IssueIB7REWTest { + @Test + @Disabled + void getTest() { + System.setProperty("jdk.tls.namedCurves", "secp256r1,secp384r1,secp521r1"); + final String s = HttpUtil.get("https://ebssec.boc.cn/"); + Console.log(s); + } +} diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index c1dee152d..281a1fc5f 100755 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-json diff --git a/hutool-json/src/main/java/cn/hutool/json/JSON.java b/hutool-json/src/main/java/cn/hutool/json/JSON.java index 4932f1e8a..9a5972cc9 100755 --- a/hutool-json/src/main/java/cn/hutool/json/JSON.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSON.java @@ -97,6 +97,32 @@ public interface JSON extends Cloneable, Serializable, IJSONTypeConverter { */ T getByPath(String expression, Class resultType); + /** + * 通过表达式获取JSON中嵌套的对象
+ *

    + *
  1. .表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值
  2. + *
  3. []表达式,可以获取集合等对象中对应index的值
  4. + *
+ *

+ * 表达式栗子: + * + *

+	 * persion
+	 * persion.name
+	 * persons[3]
+	 * person.friends[5].name
+	 * 
+ *

+ * 获取表达式对应值后转换为对应类型的值 + * + * @param expression 表达式 + * @param targetType 返回值类型 + * @return 对象 + * @see BeanPath#get(Object) + * @since 5.8.34 + */ + T getByPath(String expression, TypeReference targetType); + /** * 格式化打印JSON,缩进为4个空格 * 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 e7371f14f..636da5da7 100755 --- a/hutool-json/src/main/java/cn/hutool/json/JSONArray.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONArray.java @@ -3,6 +3,7 @@ package cn.hutool.json; import cn.hutool.core.bean.BeanPath; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Filter; +import cn.hutool.core.lang.TypeReference; import cn.hutool.core.lang.Validator; import cn.hutool.core.lang.mutable.Mutable; import cn.hutool.core.lang.mutable.MutableObj; @@ -13,6 +14,7 @@ import cn.hutool.json.serialize.JSONWriter; import java.io.StringWriter; import java.io.Writer; +import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -195,7 +197,7 @@ public class JSONArray implements JSON, JSONGetter, List, Rando */ public String join(String separator) throws JSONException { return StrJoiner.of(separator) - .append(this, InternalJSONUtil::valueToString).toString(); + .append(this, InternalJSONUtil::valueToString).toString(); } @Override @@ -218,6 +220,11 @@ public class JSONArray implements JSON, JSONGetter, List, Rando return JSONConverter.jsonConvert(resultType, getByPath(expression), getConfig()); } + @Override + public T getByPath(String expression, TypeReference targetType) { + return JSONConverter.jsonConvert(targetType, getByPath(expression), getConfig()); + } + @Override public void putByPath(String expression, Object value) { BeanPath.create(expression).set(this, value); diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index 327be1be0..d09a6b067 100755 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -3,6 +3,7 @@ package cn.hutool.json; import cn.hutool.core.bean.BeanPath; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.lang.Filter; +import cn.hutool.core.lang.TypeReference; import cn.hutool.core.lang.mutable.MutablePair; import cn.hutool.core.map.CaseInsensitiveMap; import cn.hutool.core.map.MapUtil; @@ -14,6 +15,7 @@ import cn.hutool.json.serialize.JSONWriter; import java.io.StringWriter; import java.io.Writer; +import java.lang.reflect.Type; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; @@ -161,8 +163,8 @@ public class JSONObject extends MapWrapper implements JSON, JSON @Deprecated public JSONObject(Object source, boolean ignoreNullValue, boolean isOrder) { this(source, JSONConfig.create()// - .setIgnoreCase((source instanceof CaseInsensitiveMap))// - .setIgnoreNullValue(ignoreNullValue) + .setIgnoreCase((source instanceof CaseInsensitiveMap))// + .setIgnoreNullValue(ignoreNullValue) ); } @@ -320,6 +322,11 @@ public class JSONObject extends MapWrapper implements JSON, JSON return JSONConverter.jsonConvert(resultType, getByPath(expression), getConfig()); } + @Override + public T getByPath(String expression, TypeReference targetType) { + return JSONConverter.jsonConvert(targetType, getByPath(expression), getConfig()); + } + @Override public void putByPath(String expression, Object value) { BeanPath.create(expression).set(this, value); @@ -561,7 +568,7 @@ public class JSONObject extends MapWrapper implements JSON, JSON */ public Writer write(Writer writer, int indentFactor, int indent, Filter> filter) throws JSONException { final JSONWriter jsonWriter = JSONWriter.of(writer, indentFactor, indent, config) - .beginObj(); + .beginObj(); this.forEach((key, value) -> jsonWriter.writeField(new MutablePair<>(key, value), filter)); jsonWriter.end(); // 此处不关闭Writer,考虑writer后续还需要填内容 diff --git a/hutool-json/src/test/java/cn/hutool/json/IssueIB9MH0Test.java b/hutool-json/src/test/java/cn/hutool/json/IssueIB9MH0Test.java new file mode 100644 index 000000000..cfc431da7 --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/IssueIB9MH0Test.java @@ -0,0 +1,38 @@ +package cn.hutool.json; + +import cn.hutool.json.serialize.GlobalSerializeMapping; +import cn.hutool.json.serialize.JSONObjectSerializer; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class IssueIB9MH0Test { + + @Test + void parseTest() { + GlobalSerializeMapping.put(TabTypeEnum.class, (JSONObjectSerializer) (json, bean) -> json.set("code", bean.getCode()) + .set("title", bean.getTitle())); + final JSON parse = JSONUtil.parse(TabTypeEnum._01); + Assertions.assertEquals("{\"code\":\"tab_people_home\",\"title\":\"首页\"}", parse.toString()); + } + + public enum TabTypeEnum { + _01("tab_people_home","首页"), + _02("tab_people_hospital","医院"); + + private String code; + private String title; + + TabTypeEnum(String code, String title) { + this.code = code; + this.title = title; + } + + public String getCode() { + return code; + } + + public String getTitle() { + return title; + } + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONPathTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONPathTest.java index 472def851..437f012cb 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONPathTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONPathTest.java @@ -1,8 +1,12 @@ package cn.hutool.json; import static org.junit.jupiter.api.Assertions.*; + +import cn.hutool.core.lang.TypeReference; import org.junit.jupiter.api.Test; +import java.util.List; + /** * JSON路径单元测试 * @@ -27,4 +31,23 @@ public class JSONPathTest { Long accountId = JSONUtil.getByPath(json, "$.accountId", 0L); assertEquals(111L, accountId.longValue()); } + + @Test + public void getByPathTest3(){ + String str = "[{'accountId':1},{'accountId':2},{'accountId':3}]"; + JSON json = JSONUtil.parse(str); + // 返回指定泛型的对象 List + List accountIds = json.getByPath("$.accountId", new TypeReference>() { + }); + assertNotNull(accountIds); + assertArrayEquals(new Long[]{1L, 2L, 3L}, accountIds.toArray()); + + str = "{\"accountInfos\": [{\"accountId\":1},{\"accountId\":2},{\"accountId\":3}]}"; + json = JSONUtil.parse(str); + // 返回指定泛型的对象 List + accountIds = json.getByPath("$.accountInfos.accountId", new TypeReference>() { + }); + assertNotNull(accountIds); + assertArrayEquals(new Long[]{1L, 2L, 3L}, accountIds.toArray()); + } } diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 2394d8da7..2f1fc0078 100755 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-jwt diff --git a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSignerUtil.java b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSignerUtil.java index 9829b7daf..9586680f7 100755 --- a/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSignerUtil.java +++ b/hutool-jwt/src/main/java/cn/hutool/jwt/signers/JWTSignerUtil.java @@ -278,7 +278,7 @@ public class JWTSignerUtil { if (key instanceof PrivateKey || key instanceof PublicKey) { // issue3205@Github if(ReUtil.isMatch("ES\\d{3}", algorithmId)){ - return new EllipticCurveJWTSigner(algorithmId, key); + return new EllipticCurveJWTSigner(AlgorithmUtil.getAlgorithm(algorithmId), key); } return new AsymmetricJWTSigner(AlgorithmUtil.getAlgorithm(algorithmId), key); diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index ceed84258..f42c075b5 100755 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index bb99766ae..3166aafcb 100755 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-poi diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java index e101516b8..ee8fe7c8a 100755 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelWriter.java @@ -865,11 +865,20 @@ public class ExcelWriter extends ExcelBase { boolean isFirstRow = true; Map map; for (Object obj : data) { - if (obj instanceof Map) { - map = new TreeMap<>(comparator); - map.putAll((Map) obj); + // 只第一行使用比较器排序 + if (isFirstRow) { + if (obj instanceof Map) { + map = new TreeMap<>(comparator); + map.putAll((Map) obj); + } else { + map = BeanUtil.beanToMap(obj, new TreeMap<>(comparator), false, false); + } } else { - map = BeanUtil.beanToMap(obj, new TreeMap<>(comparator), false, false); + if (obj instanceof Map) { + map = (Map) obj; + } else { + map = BeanUtil.beanToMap(obj, new HashMap<>(), false, false); + } } writeRow(map, isFirstRow); if (isFirstRow) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java index a24882c9d..72c10ee7e 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel03SaxReader.java @@ -147,6 +147,8 @@ public class Excel03SaxReader implements HSSFListener, ExcelSaxReader createSaxReader(boolean isXlsx, RowHandler rowHandler) { return isXlsx - ? new Excel07SaxReader(rowHandler) - : new Excel03SaxReader(rowHandler); + ? new Excel07SaxReader(rowHandler) + : new Excel03SaxReader(rowHandler); } /** @@ -184,6 +184,8 @@ public class ExcelSaxUtil { throw new IORuntimeException(e); } catch (SAXException e) { throw new POIException(e); + } catch (final StopReadException e) { + // issue#3820 跳过,用户抛出此异常,表示强制结束读取 } } @@ -268,7 +270,7 @@ public class ExcelSaxUtil { // issue#IB0EJ9 可能精度丢失,对含有小数的value判断并转为BigDecimal final double number = Double.parseDouble(value); - if(StrUtil.contains(value, CharUtil.DOT) && !value.equals(Double.toString(number))){ + if (StrUtil.contains(value, CharUtil.DOT) && !value.equals(Double.toString(number))) { // 精度丢失 return NumberUtil.toBigDecimal(value); } diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/StopReadException.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/StopReadException.java new file mode 100644 index 000000000..22d56c4b9 --- /dev/null +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/StopReadException.java @@ -0,0 +1,33 @@ +package cn.hutool.poi.excel.sax; + +import cn.hutool.poi.exceptions.POIException; + +/** + * 读取结束异常,用于标记读取结束
+ * Sax方式读取时,如果用户在RowHandler中抛出此异常,表示读取结束,此时不再读取其他数据 + * + * @author Looly + * @since 5.8.35 + */ +public class StopReadException extends POIException { + private static final long serialVersionUID = 1L; + + /** + * 构造 + * + */ + public StopReadException() { + this("Stop read by user."); + } + + /** + * 构造 + * + * @param message 消息 + */ + public StopReadException(final String message) { + super(message); + // 去除堆栈 + setStackTrace(new StackTraceElement[0]); + } +} diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelSaxReadTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelSaxReadTest.java index b54379de2..2f52c1a0e 100644 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelSaxReadTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelSaxReadTest.java @@ -8,6 +8,7 @@ import cn.hutool.core.lang.Console; import cn.hutool.core.util.StrUtil; import cn.hutool.poi.excel.cell.FormulaCellValue; import cn.hutool.poi.excel.sax.Excel03SaxReader; +import cn.hutool.poi.excel.sax.StopReadException; import cn.hutool.poi.excel.sax.handler.RowHandler; import cn.hutool.poi.exceptions.POIException; import org.apache.poi.ss.usermodel.CellStyle; @@ -33,6 +34,24 @@ public class ExcelSaxReadTest { ExcelUtil.readBySax("aaa.xlsx", 0, createRowHandler()); } + @Test + void readEndByExceptionTest(){ + ExcelUtil.readBySax("aaa.xlsx", 0, (sheetIndex, rowIndex, rowList) -> { + if (rowIndex == 1) { + throw new StopReadException(); + } + }); + } + + @Test + void readEndByException03Test(){ + ExcelUtil.readBySax("aaa.xls", 0, (sheetIndex, rowIndex, rowList) -> { + if (rowIndex == 1) { + throw new StopReadException(); + } + }); + } + @Test public void excel07ByNameTest() { // 工具化快速读取 diff --git a/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelWriteTest.java b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelWriteTest.java index c0a03fee9..29ae68dd1 100755 --- a/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelWriteTest.java +++ b/hutool-poi/src/test/java/cn/hutool/poi/excel/ExcelWriteTest.java @@ -2,6 +2,7 @@ package cn.hutool.poi.excel; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.comparator.IndexedComparator; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Console; @@ -904,4 +905,75 @@ public class ExcelWriteTest { } } + @Test + @Disabled + public void writeWithComparatorTest() { + // 生成测试数据, 10w行50列 + List> dataList = new ArrayList<>(); + for (int i = 1; i <= 100000; i++) { + Map map = new HashMap<>(); + map.put("test11", "test11_" + i); + map.put("test12", "test12_" + i); + map.put("test13", "test13_" + i); + map.put("test14", "test14_" + i); + map.put("test15", "test15_" + i); + map.put("test16", "test16_" + i); + map.put("test17", "test17_" + i); + map.put("test18", "test18_" + i); + map.put("test19", "test19_" + i); + map.put("test1", "test1_" + i); + map.put("test2", "test2_" + i); + map.put("test3", "test3_" + i); + map.put("test4", "test4_" + i); + map.put("test5", "test5_" + i); + map.put("test6", "test6_" + i); + map.put("test7", "test7_" + i); + map.put("test8", "test8_" + i); + map.put("test9", "test9_" + i); + map.put("test10", "test10_" + i); + map.put("test20", "test20_" + i); + map.put("test21", "test21_" + i); + map.put("test22", "test22_" + i); + map.put("test23", "test23_" + i); + map.put("test24", "test24_" + i); + map.put("test25", "test25_" + i); + map.put("test26", "test26_" + i); + map.put("test27", "test27_" + i); + map.put("test28", "test28_" + i); + map.put("test29", "test29_" + i); + map.put("test30", "test30_" + i); + map.put("test41", "test41_" + i); + map.put("test42", "test42_" + i); + map.put("test43", "test43_" + i); + map.put("test44", "test44_" + i); + map.put("test45", "test45_" + i); + map.put("test46", "test46_" + i); + map.put("test47", "test47_" + i); + map.put("test48", "test48_" + i); + map.put("test49", "test49_" + i); + map.put("test50", "test50_" + i); + map.put("test31", "test31_" + i); + map.put("test32", "test32_" + i); + map.put("test33", "test33_" + i); + map.put("test34", "test34_" + i); + map.put("test35", "test35_" + i); + map.put("test36", "test36_" + i); + map.put("test37", "test37_" + i); + map.put("test38", "test38_" + i); + map.put("test39", "test39_" + i); + map.put("test40", "test40_" + i); + dataList.add(map); + } + // 使用比较器写出 + try (BigExcelWriter excelWriter = ExcelUtil.getBigWriter("d:/writeWithComparatorTest.xlsx")) { + excelWriter.write(dataList, new IndexedComparator<>( + "test1", "test2", "test3", "test4", "test5", "test6", "test7", "test8", "test9", "test10", + "test11", "test12", "test13", "test14", "test15", "test16", "test17", "test18", "test19", "test20", + "test21", "test22", "test23", "test24", "test25", "test26", "test27", "test28", "test29", "test30", + "test31", "test32", "test33", "test34", "test35", "test36", "test37", "test38", "test39", "test40", + "test41", "test42", "test43", "test44", "test45", "test46", "test47", "test48", "test49", "test50" + )); + } + } + } diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 55eb70228..e7147b9fc 100755 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 9a59405e5..8529bb999 100755 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 3736f335b..18a5e972e 100755 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index 0ffc730cc..09b69acbc 100755 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool-system diff --git a/pom.xml b/pom.xml index 093213727..582dc12b4 100755 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.8.34 + 5.8.35 hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool