mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-04-19 03:01:48 +08:00
Prepare release
This commit is contained in:
commit
ced8bf817a
20
CHANGELOG.md
20
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)
|
||||
|
||||
|
10
README-EN.md
10
README-EN.md
@ -36,6 +36,9 @@
|
||||
<a target="_blank" href='https://github.com/dromara/hutool'>
|
||||
<img src="https://img.shields.io/github/stars/dromara/hutool.svg?style=social" alt="github star"/>
|
||||
</a>
|
||||
<a target="_blank" href='https://gitcode.com/dromara/hutool'>
|
||||
<img src="https://gitcode.com/dromara/hutool/star/badge.svg" alt="gitcode star"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
@ -150,18 +153,18 @@ We provide the T-Shirt and Sweater with Hutool Logo, please visit the shop:
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐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)
|
||||
|
||||
|
14
README.md
14
README.md
@ -36,6 +36,9 @@
|
||||
<a target="_blank" href='https://github.com/dromara/hutool'>
|
||||
<img src="https://img.shields.io/github/stars/dromara/hutool.svg?style=social" alt="github star"/>
|
||||
</a>
|
||||
<a target="_blank" href='https://gitcode.com/dromara/hutool'>
|
||||
<img src="https://gitcode.com/dromara/hutool/star/badge.svg" alt="gitcode star"/>
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br/>
|
||||
@ -143,20 +146,20 @@ Hutool = Hu + tool,是原公司项目底层代码剥离后的开源库,“Hu
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### 🍐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遵照的原则
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -3,3 +3,4 @@
|
||||
git checkout v5-dev
|
||||
git pull osc v5-dev
|
||||
git pull origin v5-dev
|
||||
git pull gitcode v5-dev
|
||||
|
@ -1 +1 @@
|
||||
5.8.34
|
||||
5.8.35
|
||||
|
@ -1 +1 @@
|
||||
var version = '5.8.34'
|
||||
var version = '5.8.35'
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-all</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-aop</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bloomFilter</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-bom</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cache</artifactId>
|
||||
|
@ -16,7 +16,7 @@ import java.util.LinkedHashMap;
|
||||
* @param <V> 值类型
|
||||
* @author Looly
|
||||
*/
|
||||
public class FIFOCache<K, V> extends StampedCache<K, V> {
|
||||
public class FIFOCache<K, V> extends ReentrantCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
|
@ -15,7 +15,7 @@ import java.util.Iterator;
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class LFUCache<K, V> extends StampedCache<K, V> {
|
||||
public class LFUCache<K, V> extends ReentrantCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
|
@ -12,7 +12,9 @@ import java.util.concurrent.locks.StampedLock;
|
||||
* @param <V> 值类型
|
||||
* @author looly
|
||||
* @since 5.7.15
|
||||
* @deprecated Map使用StampedLock可能造成数据不一致甚至Map循环调用,此缓存废弃
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class StampedCache<K, V> extends AbstractCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@ -88,7 +90,12 @@ public abstract class StampedCache<K, V> extends AbstractCache<K, V> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取值
|
||||
* 获取值,使用乐观锁,但是此方法可能导致读取脏数据,但对于缓存业务可容忍。情况如下:
|
||||
* <pre>
|
||||
* 1. 读取时无写入,不冲突,直接获取值
|
||||
* 2. 读取时无写入,但是乐观读时触发了并发异常,此时获取同步锁,获取新值
|
||||
* 4. 读取时有写入,此时获取同步锁,获取新值
|
||||
* </pre>
|
||||
*
|
||||
* @param key 键
|
||||
* @param isUpdateLastAccess 是否更新最后修改时间
|
||||
@ -97,10 +104,24 @@ public abstract class StampedCache<K, V> extends AbstractCache<K, V> {
|
||||
*/
|
||||
private V get(K key, boolean isUpdateLastAccess, boolean isUpdateCount) {
|
||||
// 尝试读取缓存,使用乐观读锁
|
||||
CacheObj<K, V> co = null;
|
||||
long stamp = lock.tryOptimisticRead();
|
||||
CacheObj<K, V> 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);
|
||||
|
@ -17,7 +17,7 @@ import java.util.concurrent.ScheduledFuture;
|
||||
* @param <K> 键类型
|
||||
* @param <V> 值类型
|
||||
*/
|
||||
public class TimedCache<K, V> extends StampedCache<K, V> {
|
||||
public class TimedCache<K, V> extends ReentrantCache<K, V> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/** 正在执行的定时任务 */
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-captcha</artifactId>
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-core</artifactId>
|
||||
|
@ -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时间,格式:<br>
|
||||
* <ol>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ss'Z'</li>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ss.SSS'Z'</li>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ssZ</li>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ss.SSSZ</li>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ss+0800</li>
|
||||
* <li>yyyy-MM-dd'T'HH:mm:ss+08:00</li>
|
||||
* </ol>
|
||||
*
|
||||
* @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<DateTime> rangeContains(DateRange start, DateRange end) {
|
||||
List<DateTime> startDateTimes = CollUtil.newArrayList((Iterable<DateTime>) start);
|
||||
List<DateTime> endDateTimes = CollUtil.newArrayList((Iterable<DateTime>) end);
|
||||
List<DateTime> startDateTimes = CollUtil.newArrayList((Iterable<DateTime>) start.reset());
|
||||
List<DateTime> endDateTimes = CollUtil.newArrayList((Iterable<DateTime>) 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<DateTime> rangeNotContains(DateRange start, DateRange end) {
|
||||
List<DateTime> startDateTimes = CollUtil.newArrayList((Iterable<DateTime>) start);
|
||||
List<DateTime> endDateTimes = CollUtil.newArrayList((Iterable<DateTime>) end);
|
||||
List<DateTime> startDateTimes = CollUtil.newArrayList((Iterable<DateTime>) start.reset());
|
||||
List<DateTime> endDateTimes = CollUtil.newArrayList((Iterable<DateTime>) end.reset());
|
||||
return endDateTimes.stream().filter(item -> !startDateTimes.contains(item)).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -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进制字符串
|
||||
*/
|
||||
|
@ -60,11 +60,11 @@ public class EnumUtil {
|
||||
* @since 5.1.6
|
||||
*/
|
||||
public static <E extends Enum<E>> E getEnumAt(Class<E> 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 extends Enum<E>> E likeValueOf(Class<E> 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<String> getNames(Class<? extends Enum<?>> 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<Object> getFieldValues(Class<? extends Enum<?>> 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<String> getFieldNames(Class<? extends Enum<?>> clazz) {
|
||||
if(null == clazz){
|
||||
if (null == clazz) {
|
||||
return null;
|
||||
}
|
||||
final List<String> names = new ArrayList<>();
|
||||
@ -238,11 +238,62 @@ public class EnumUtil {
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public static <E extends Enum<E>> E getBy(Class<E> enumClass, Predicate<? super E> predicate) {
|
||||
if(null == enumClass || null == predicate){
|
||||
return getBy(enumClass, predicate, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过 某字段对应值 获取 枚举,获取不到时为 {@code defaultEnum}
|
||||
*
|
||||
* @param enumClass 枚举类
|
||||
* @param predicate 条件
|
||||
* @param defaultEnum 获取不到时的默认枚举值
|
||||
* @param <E> 枚举类型
|
||||
* @return 对应枚举 ,获取不到时为 {@code defaultEnum}
|
||||
*/
|
||||
public static <E extends Enum<E>> E getBy(Class<E> enumClass, Predicate<? super E> 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}
|
||||
* <p>
|
||||
* {@link LambdaUtil#getRealClass(Func1)}} 是相对耗时的
|
||||
* 如果枚举值比较多,那么{@link EnumUtil#getBy(Func1, Object)} 方法
|
||||
* 大部分时间都是被{@link LambdaUtil#getRealClass(Func1)}}所消耗的
|
||||
* <br>
|
||||
* 如果可以在编码过程中可以提供对应的枚举类 该方法与枚举的{@code Enum.values()}方法是差不多的。
|
||||
*
|
||||
* @param enumClass 枚举类, 为{@code null}返回{@code null}
|
||||
* @param condition 条件字段,为{@code null}返回{@code null}
|
||||
* @param value 条件字段值
|
||||
* @param <E> 枚举类型
|
||||
* @param <C> 字段类型
|
||||
* @return 对应枚举 ,获取不到时为 {@code null}
|
||||
*/
|
||||
public static <E extends Enum<E>, C> E getBy(Class<E> enumClass, Func1<E, C> 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 <E> 枚举类型
|
||||
* @param <C> 字段类型
|
||||
* @return 对应枚举 ,获取不到时为 {@code defaultEnum}
|
||||
*/
|
||||
public static <E extends Enum<E>, C> E getBy(Class<E> enumClass, Func1<E, C> condition, C value, E defaultEnum) {
|
||||
return ObjectUtil.defaultIfNull(getBy(enumClass, condition, value), defaultEnum);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -259,10 +310,7 @@ public class EnumUtil {
|
||||
return null;
|
||||
}
|
||||
final Class<E> 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 <E extends Enum<E>, F, C> F getFieldBy(Func1<E, F> field, Function<E, C> condition, C value) {
|
||||
if(null == field || null == condition){
|
||||
if (null == field || null == condition) {
|
||||
return null;
|
||||
}
|
||||
final Class<E> implClass = LambdaUtil.getRealClass(field);
|
||||
@ -316,7 +364,7 @@ public class EnumUtil {
|
||||
* @since 4.0.2
|
||||
*/
|
||||
public static <E extends Enum<E>> LinkedHashMap<String, E> getEnumMap(final Class<E> enumClass) {
|
||||
if(null == enumClass){
|
||||
if (null == enumClass) {
|
||||
return null;
|
||||
}
|
||||
final LinkedHashMap<String, E> map = new LinkedHashMap<>();
|
||||
@ -335,7 +383,7 @@ public class EnumUtil {
|
||||
* @return 枚举名对应指定字段值的Map
|
||||
*/
|
||||
public static Map<String, Object> getNameFieldMap(Class<? extends Enum<?>> 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 <E extends Enum<E>> boolean contains(final Class<E> enumClass, String val) {
|
||||
final LinkedHashMap<String, E> enumMap = getEnumMap(enumClass);
|
||||
if(CollUtil.isEmpty(enumMap)){
|
||||
if (CollUtil.isEmpty(enumMap)) {
|
||||
return false;
|
||||
}
|
||||
return enumMap.containsKey(val);
|
||||
|
@ -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");
|
||||
|
@ -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());
|
||||
|
@ -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<DateTime> dateTimes = DateUtil.rangeContains(startRange, endRange);
|
||||
Assertions.assertEquals(1, dateTimes.size());
|
||||
|
||||
List<DateTime> dateNotTimes = DateUtil.rangeNotContains(startRange, endRange);
|
||||
Assertions.assertEquals(2, dateNotTimes.size());
|
||||
}
|
||||
}
|
@ -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"));
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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"));
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-cron</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-crypto</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-db</artifactId>
|
||||
|
@ -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);
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-dfa</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-extra</artifactId>
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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) {
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-http</artifactId>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-json</artifactId>
|
||||
|
@ -97,6 +97,32 @@ public interface JSON extends Cloneable, Serializable, IJSONTypeConverter {
|
||||
*/
|
||||
<T> T getByPath(String expression, Class<T> resultType);
|
||||
|
||||
/**
|
||||
* 通过表达式获取JSON中嵌套的对象<br>
|
||||
* <ol>
|
||||
* <li>.表达式,可以获取Bean对象中的属性(字段)值或者Map中key对应的值</li>
|
||||
* <li>[]表达式,可以获取集合等对象中对应index的值</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* 表达式栗子:
|
||||
*
|
||||
* <pre>
|
||||
* persion
|
||||
* persion.name
|
||||
* persons[3]
|
||||
* person.friends[5].name
|
||||
* </pre>
|
||||
* <p>
|
||||
* 获取表达式对应值后转换为对应类型的值
|
||||
*
|
||||
* @param expression 表达式
|
||||
* @param targetType 返回值类型
|
||||
* @return 对象
|
||||
* @see BeanPath#get(Object)
|
||||
* @since 5.8.34
|
||||
*/
|
||||
<T> T getByPath(String expression, TypeReference<T> targetType);
|
||||
|
||||
/**
|
||||
* 格式化打印JSON,缩进为4个空格
|
||||
*
|
||||
|
@ -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<Integer>, List<Object>, 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<Integer>, List<Object>, Rando
|
||||
return JSONConverter.jsonConvert(resultType, getByPath(expression), getConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getByPath(String expression, TypeReference<T> targetType) {
|
||||
return JSONConverter.jsonConvert(targetType, getByPath(expression), getConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putByPath(String expression, Object value) {
|
||||
BeanPath.create(expression).set(this, value);
|
||||
|
@ -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<String, Object> 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<String, Object> implements JSON, JSON
|
||||
return JSONConverter.jsonConvert(resultType, getByPath(expression), getConfig());
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T getByPath(String expression, TypeReference<T> 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<String, Object> implements JSON, JSON
|
||||
*/
|
||||
public Writer write(Writer writer, int indentFactor, int indent, Filter<MutablePair<Object, Object>> 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后续还需要填内容
|
||||
|
@ -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<TabTypeEnum>) (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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Long>
|
||||
List<Long> accountIds = json.getByPath("$.accountId", new TypeReference<List<Long>>() {
|
||||
});
|
||||
assertNotNull(accountIds);
|
||||
assertArrayEquals(new Long[]{1L, 2L, 3L}, accountIds.toArray());
|
||||
|
||||
str = "{\"accountInfos\": [{\"accountId\":1},{\"accountId\":2},{\"accountId\":3}]}";
|
||||
json = JSONUtil.parse(str);
|
||||
// 返回指定泛型的对象 List<Long>
|
||||
accountIds = json.getByPath("$.accountInfos.accountId", new TypeReference<List<Long>>() {
|
||||
});
|
||||
assertNotNull(accountIds);
|
||||
assertArrayEquals(new Long[]{1L, 2L, 3L}, accountIds.toArray());
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-jwt</artifactId>
|
||||
|
@ -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);
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-log</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-poi</artifactId>
|
||||
|
@ -865,11 +865,20 @@ public class ExcelWriter extends ExcelBase<ExcelWriter> {
|
||||
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) {
|
||||
|
@ -147,6 +147,8 @@ public class Excel03SaxReader implements HSSFListener, ExcelSaxReader<Excel03Sax
|
||||
factory.processWorkbookEvents(request, fs);
|
||||
} catch (IOException e) {
|
||||
throw new POIException(e);
|
||||
} catch (final StopReadException e) {
|
||||
// issue#3820 跳过,用户抛出此异常,表示强制结束读取
|
||||
} finally {
|
||||
IoUtil.close(fs);
|
||||
}
|
||||
|
@ -47,8 +47,8 @@ public class ExcelSaxUtil {
|
||||
*/
|
||||
public static 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);
|
||||
}
|
||||
|
@ -0,0 +1,33 @@
|
||||
package cn.hutool.poi.excel.sax;
|
||||
|
||||
import cn.hutool.poi.exceptions.POIException;
|
||||
|
||||
/**
|
||||
* 读取结束异常,用于标记读取结束<br>
|
||||
* 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]);
|
||||
}
|
||||
}
|
@ -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() {
|
||||
// 工具化快速读取
|
||||
|
@ -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<Map<String, Object>> dataList = new ArrayList<>();
|
||||
for (int i = 1; i <= 100000; i++) {
|
||||
Map<String, Object> 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"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-script</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-setting</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-socket</artifactId>
|
||||
|
@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hutool-system</artifactId>
|
||||
|
2
pom.xml
2
pom.xml
@ -8,7 +8,7 @@
|
||||
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-parent</artifactId>
|
||||
<version>5.8.34</version>
|
||||
<version>5.8.35</version>
|
||||
<name>hutool</name>
|
||||
<description>Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。</description>
|
||||
<url>https://github.com/dromara/hutool</url>
|
||||
|
Loading…
x
Reference in New Issue
Block a user